[
  {
    "path": ".agents/skills/writing-docs/SKILL.md",
    "content": "---\nname: writing-docs\ndescription: Doc conventions (impersonal style, formatting, Vitest in examples, structure, linking). Use when writing, editing, or reviewing guides, tutorials, or READMEs.\n---\n\n# Writing Documentation\n\nApply when writing, editing, or reviewing docs.\n\n## Voice and style\n\n- **Impersonal:** No \"you\", \"your\", or second-person imperatives.\n- **Statements over imperatives:** Prefer \"Tests should not spy\" over \"Avoid spying\"; passive or descriptive phrasing.\n- **Titles:** Gerunds, not imperatives (e.g. \"Testing the store\" not \"Test the store\").\n- **Tone:** Direct and minimal; every sentence adds information.\n\n## Formatting\n\n- **Dashes:** Space-hyphen-space (`-`), not em dash (`—`).\n- **Paragraphs:** Merge single-sentence paragraphs when it helps readability.\n\n## Code examples\n\n- **Examples:** Simple, self-contained (e.g. Counter, Books). Short snippets; shorten or link out long async flows unless the topic is async.\n\n## Structure and navigation\n\n- **Section titles:** Accurate and scannable.\n- **Links over repetition:** Link to other guides; explain only what is specific to this page.\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// For format details, see https://aka.ms/devcontainer.json.\n{\n  \"name\": \"Platform\",\n  \"image\": \"mcr.microsoft.com/devcontainers/typescript-node:22-bullseye\",\n\n  // Features to add to the dev container. More info: https://containers.dev/features.\n  \"features\": {\n    \"ghcr.io/devcontainers/features/github-cli:1\": {},\n    \"ghcr.io/devcontainers/features/sshd:1\": {}\n  },\n\n  // Use 'forwardPorts' to make a list of ports inside the container available locally.\n  // \"forwardPorts\": [],\n\n  // Use 'postCreateCommand' to run commands after the container is created.\n  \"postCreateCommand\": \"npm i pnpm -g && pnpm install\",\n  \"waitFor\": \"postCreateCommand\",\n  \"onCreateCommand\": \"sudo cp .devcontainer/welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt\",\n\n  // Configure tool-specific properties.\n  \"customizations\": {\n    // Configure properties specific to VS Code.\n    \"vscode\": {\n      \"settings\": {\n        \"[typescript]\": {\n          \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n          \"editor.formatOnSave\": true\n        },\n        \"[md]\": {\n          \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n          \"editor.formatOnSave\": true\n        },\n        \"[json]\": {\n          \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n          \"editor.formatOnSave\": true\n        }\n      },\n      // Add the IDs of extensions you want installed when the container is created.\n      \"extensions\": [\"esbenp.prettier-vscode\", \"Angular.ng-template\"]\n    }\n  }\n}\n"
  },
  {
    "path": ".devcontainer/welcome-message.txt",
    "content": "👋 Welcome to \"Ngrx Platform\" in GitHub Codespaces!\n\n🛠️  Your environment is fully setup with all the required software.\n\n🔍 To explore VS Code to its fullest, search using the Command Palette (Cmd/Ctrl + Shift + P or F1).\n\n📝 Edit away, run your app as usual, and we'll automatically make it available for you to access.\n\n"
  },
  {
    "path": ".dockerignore",
    "content": "node_modules\nnpm-debug.log\nDockerfile*\ndocker-compose*\n.dockerignore\n.git\n.gitignore\n"
  },
  {
    "path": ".editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org/\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [ngrx, brandonroberts] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: ngrx\nko_fi: timdeschryver # Replace with a single Ko-fi username\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\ncustom: https://ko-fi.com/markostanimirovic # Replace with a single custom sponsorship URL\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "content": "name: Bug Report\ndescription: Report a bug or regression in functionality\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        ❤️ ngrx? Please consider sponsoring us: 👉  [sponsor](https://github.com/sponsors/ngrx)\n        Please search GitHub for a similar issue or PR before submitting a new issue.\n        If you need real-time help, join us on [Discord](https://discord.com/invite/ngrx).\n\n  - type: dropdown\n    id: affected-packages\n    attributes:\n      label: Which @ngrx/* package(s) are the source of the bug?\n      options:\n        - component-store\n        - component\n        - data\n        - effects\n        - entity\n        - eslint-plugin\n        - operators\n        - router-store\n        - schematics\n        - signals\n        - store-devtools\n        - store\n      multiple: true\n    validations:\n      required: true\n\n  - type: textarea\n    id: reproduction\n    attributes:\n      label: Minimal reproduction of the bug/regression with instructions\n      description: Use the [NgRx Global Store StackBlitz example](https://stackblitz.com/edit/ngrx-seed) or [NgRx Signal Store StackBlitz example](https://stackblitz.com/github/ngrx/signal-store-starter?file=src%2Fmain.ts) to create a reproduction.\n      placeholder: If the bug/regression does not include a reproduction via StackBlitz or GitHub repo, your issue may be closed without resolution.\n    validations:\n      required: true\n\n  - type: textarea\n    id: expected_behavior\n    attributes:\n      label: Expected behavior\n      description: Describe what the expected behavior would be.\n    validations:\n      required: true\n\n  - type: textarea\n    id: version\n    attributes:\n      label: Versions of NgRx, Angular, Node, affected browser(s) and operating system(s)\n      placeholder: |\n        NgRx:\n        Angular:\n        Node:\n        Browser(s):\n        Operating system(s):\n    validations:\n      required: true\n\n  - type: textarea\n    id: other\n    attributes:\n      label: Other information\n\n  - type: checkboxes\n    id: assistance\n    attributes:\n      label: I would be willing to submit a PR to fix this issue\n      description: Assistance is provided if you need help submitting a pull request\n      options:\n        - label: 'Yes'\n        - label: 'No'\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\r\ncontact_links:\r\n  - name: Support Request\r\n    url: https://github.com/ngrx/platform/blob/main/CONTRIBUTING.md#questions-and-requests-for-support\r\n    about: Questions and requests for support\r\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation-report.yml",
    "content": "name: Documentation Report\ndescription: Report missing or inaccurate documentation\nlabels: ['Comp: Docs']\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        ❤️ ngrx? Please consider sponsoring us: 👉  [sponsor](https://github.com/sponsors/ngrx)\n        Please search GitHub for a similar issue or PR before submitting a new issue.\n        If you need real-time help, join us on [Discord](https://discord.com/invite/ngrx).\n\n  - type: textarea\n    id: information\n    attributes:\n      label: Information\n    validations:\n      required: true\n\n  - type: input\n    id: url\n    attributes:\n      label: Documentation page\n      description: Add the documentation URL\n\n  - type: checkboxes\n    id: assistance\n    attributes:\n      label: I would be willing to submit a PR to fix this issue\n      description: Assistance is provided if you need help submitting a pull request\n      options:\n        - label: 'Yes'\n        - label: 'No'\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yml",
    "content": "name: Feature Request\ndescription: Submit a Request For Consideration\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        ❤️ ngrx? Please consider sponsoring us: 👉  [sponsor](https://github.com/sponsors/ngrx)\n        Please search GitHub for a similar issue or PR before submitting a new issue.\n        If you need real-time help, join us on [Discord](https://discord.com/invite/ngrx).\n\n  - type: dropdown\n    id: affected-packages\n    attributes:\n      label: Which @ngrx/* package(s) are relevant/related to the feature request?\n      options:\n        - component-store\n        - component\n        - data\n        - effects\n        - entity\n        - eslint-plugin\n        - operators\n        - router-store\n        - schematics\n        - signals\n        - store-devtools\n        - store\n      multiple: true\n    validations:\n      required: true\n\n  - type: textarea\n    id: information\n    attributes:\n      label: Information\n      description: Tell us what you want to be added to NgRx\n    validations:\n      required: true\n\n  - type: textarea\n    id: alternatives\n    attributes:\n      label: Describe any alternatives/workarounds you're currently using\n\n  - type: checkboxes\n    id: assistance\n    attributes:\n      label: I would be willing to submit a PR to fix this issue\n      description: Assistance is provided if you need help submitting a pull request\n      options:\n        - label: 'Yes'\n        - label: 'No'\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "## PR Checklist\n\nPlease check if your PR fulfills the following requirements:\n\n- [ ] The commit message follows our guidelines: https://github.com/ngrx/platform/blob/main/CONTRIBUTING.md#commit\n- [ ] Tests for the changes have been added (for bug fixes / features)\n- [ ] Documentation has been added / updated (for bug fixes / features)\n\n## PR Type\n\nWhat kind of change does this PR introduce?\n\n<!-- Please check the one that applies to this PR using \"x\". -->\n\n```\n[ ] Bugfix\n[ ] Feature\n[ ] Code style update (formatting, local variables)\n[ ] Refactoring (no functional changes, no api changes)\n[ ] Build related changes\n[ ] CI related changes\n[ ] Documentation content changes\n[ ] Other... Please describe:\n```\n\n## What is the current behavior?\n\n<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->\n\nCloses #\n\n## What is the new behavior?\n\n## Does this PR introduce a breaking change?\n\n```\n[ ] Yes\n[ ] No\n```\n\n<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->\n\n## Other information\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request: {}\n\npermissions:\n  actions: read\n  contents: read\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.number || github.sha }}\n  cancel-in-progress: true\n\njobs:\n  install:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n\n  lint-affected:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm exec nx affected -t lint\n\n  test-affected:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm exec nx affected -t test --skip-nx-cache --parallel=3\n\n  e2e-affected:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - run: pnpm exec cypress install\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm exec nx affected -t e2e --exclude=www --parallel=1\n\n  build-affected:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm exec nx affected -t build --exclude=www --skip-nx-cache --parallel=3\n\n  schematics-core-check:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm run copy:schematics\n      - run: pnpm run schematics:check\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: main\n\non:\n  push:\n    branches:\n      - main\n\npermissions:\n  actions: read\n  contents: read\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.number || github.sha }}\n  cancel-in-progress: true\n\njobs:\n  install:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n\n  lint:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm exec nx run-many -t lint\n\n  test:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm exec nx run-many -t test --skip-nx-cache --parallel=3\n\n  e2e:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - run: pnpm exec cypress install\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm exec nx run-many -t e2e --exclude=www --parallel=1\n\n  schematics-core-check:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm run copy:schematics\n      - run: pnpm run schematics:check\n\n  build:\n    runs-on: ubuntu-latest\n    needs: install\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - run: pnpm install --frozen-lockfile --prefer-offline\n      - uses: nrwl/nx-set-shas@v4\n      - run: pnpm exec nx run-many -t build --exclude=www --skip-nx-cache --parallel=3\n      - uses: actions/upload-artifact@v4\n        with:\n          name: artifact-modules\n          path: dist/modules\n\n  deploy:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: .node-version\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - uses: actions/download-artifact@v4\n        with:\n          name: artifact-modules\n          path: dist\n      # TODO: Add deployment steps here\n      # - run: pnpm run deploy:builds\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\npermissions:\n  contents: read\n\non:\n  workflow_dispatch:\n    inputs:\n      release_version:\n        description: 'Version'\n        required: true\n      release_tag:\n        description: 'Tag'\n        required: true\n        default: 'latest'\n        type: choice\n        options:\n          - latest\n          - next\n      dry_run:\n        description: 'Dry Run'\n        required: true\n        default: true\n        type: boolean\n\njobs:\n  version:\n    name: Version and Publish\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write # to be able to publish a GitHub release\n      id-token: write # provenance\n    steps:\n      - name: Generate bot app token\n        id: generate_token\n        uses: actions/create-github-app-token@v1\n        with:\n          app-id: ${{ secrets.NGRX_APP_ID }}\n          private-key: ${{ secrets.NGRX_APP_PRIVATE_KEY }}\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          token: ${{ steps.generate_token.outputs.token }}\n          fetch-depth: 0\n          persist-credentials: false\n      - name: Setup Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version-file: .node-version\n      - run: npm install --global corepack@0.31.0\n      - run: corepack enable\n      - run: pnpm --version\n      - name: Install dependencies\n        uses: actions/setup-node@v3\n        with:\n          cache: 'pnpm'\n          cache-dependency-path: '**/pnpm-lock.yaml'\n      - name: Install\n        run: pnpm install --frozen-lockfile --prefer-offline\n      - name: Update Versions\n        run: pnpm run update:versions ${{ inputs.release_version }}\n      - name: Tag Release\n        env:\n          GIT_AUTHOR_EMAIL: \"${{ secrets.RELEASE_EMAIL }}\"\n          GIT_COMMITTER_EMAIL: \"${{ secrets.RELEASE_EMAIL }}\"\n          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}\n        run: |\n          git config --global user.email ${{ secrets.RELEASE_EMAIL }}\n          git config --global user.name \"${{ secrets.RELEASE_NAME }}\"\n          git status\n          git commit -am \"chore: release ${{ inputs.release_version }}\"\n          git tag ${{ inputs.release_version }}\n          git push https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/ngrx/platform.git --follow-tags $(${{ inputs.dry_run == true }} && echo '--dry-run' || echo '')\n      - name: Build Packages\n        run: pnpm run build --exclude=standalone-app,example-app,www --skip-nx-cache\n      - name: Release\n        env:\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n          DRY_RUN: ${{ inputs.dry_run }}\n          RELEASE_TAG: ${{ inputs.release_tag }}\n          RELEASE_VERSION: ${{ inputs.release_version }}\n        run: npx tsx ./build/publish-release.ts\n"
  },
  {
    "path": ".gitignore",
    "content": "/.angular/cache\n# Logs\nlogs\n*.log\n.nyc\n.nyc_output\n.history\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# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Users Environment Variables\n.lock-wscript\n\n# OS generated files #\n.DS_Store\nehthumbs.db\nIcon?\nThumbs.db\n\n# Node Files #\nnode_modules\n/bower_components\n\n# Typing TSD #\n/src/typings/tsd/\n/typings/\n/tsd_typings/\n\n# Dist #\n/dist\n/public/__build__/\n/src/*/__build__/\n__build__/**\n.webpack.json\n\n#doc\n/doc\n\n# IDE #\n.idea/\n*.iml\n*.swp\n!/typings/custom.d.ts\n.vscode/\n.vim/\n\n# Build Artifacts #\nrelease\ndist\n/node_modules/\nlerna-debug.log\n/lib/\nngfactory\noutput\n*.ngsummary.json\n*.ngfactory.ts\ntmp\n\nexample-dist/\n\n*.tgz\nmodules/*/schematics-core/testing\n!modules/schematics-core/testing\n\n.angular\nmigrations.json\n.env\n.nx/**\n\nvite.config.*.timestamp*\nvitest.config.*.timestamp*\n\n# www reference files\nprojects/www/src/app/reference/**/*.json\nprojects/www/src/app/examples/**/*.txt\n.nx/cache\n.nx/workspace-data\n.cursor/rules/nx-rules.mdc\n.github/instructions/nx.instructions.md\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".node-version",
    "content": "22.16.0\n"
  },
  {
    "path": ".nxignore",
    "content": "*.spec.ts\n**/testing/**\nmodules/BUILD\nmodules/license-banner.txt\nmodules/README.md"
  },
  {
    "path": ".prettierignore",
    "content": "/dist\n/modules/schematics/src/*/files/*\n/modules/**/schematics/**/files/*\n/tmp\npackage-lock.json\npackage.json\nyarn.lock\n\n/.nx/cache\n/.nx/workspace-data\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<a name=\"21.0.1\"></a>\n\n## [21.0.1](https://github.com/ngrx/platform/compare/21.0.0...21.0.1) (2025-12-22)\n\n### Bug Fixes\n\n- **schematics:** restore CommonJS module output for Node.js ([#5051](https://github.com/ngrx/platform/issues/5051)) ([7819972](https://github.com/ngrx/platform/commit/7819972)), closes [#5050](https://github.com/ngrx/platform/issues/5050)\n\n<a name=\"21.0.0\"></a>\n\n# [21.0.0](https://github.com/ngrx/platform/compare/21.0.0-rc.0...21.0.0) (2025-12-18)\n\n<a name=\"21.0.0-rc.0\"></a>\n\n# [21.0.0-rc.0](https://github.com/ngrx/platform/compare/21.0.0-beta.0...21.0.0-rc.0) (2025-12-16)\n\n### Bug Fixes\n\n- **eslint-plugin:** add type-checked configs to legacy config ([#5038](https://github.com/ngrx/platform/issues/5038)) ([faf6491](https://github.com/ngrx/platform/commit/faf6491))\n\n### Features\n\n- **eslint-plugin:** extend NonRecord type checks in state rules ([#5045](https://github.com/ngrx/platform/issues/5045)) ([bab4d12](https://github.com/ngrx/platform/commit/bab4d12)), closes [#4615](https://github.com/ngrx/platform/issues/4615)\n\n<a name=\"21.0.0-beta.0\"></a>\n\n# [21.0.0-beta.0](https://github.com/ngrx/platform/compare/20.0.1...21.0.0-beta.0) (2025-12-04)\n\n### Bug Fixes\n\n- **eslint-plugin:** only lint NgRx selectors in prefix-selectors-with-select ([#4995](https://github.com/ngrx/platform/issues/4995)) ([568dbe3](https://github.com/ngrx/platform/commit/568dbe3)), closes [#4447](https://github.com/ngrx/platform/issues/4447)\n- **signals:** drop `assertInInjectionContext` in production ([#4954](https://github.com/ngrx/platform/issues/4954)) ([37e6fa1](https://github.com/ngrx/platform/commit/37e6fa1))\n- **signals:** drop `assertUniqueStoreMembers` in production ([#4953](https://github.com/ngrx/platform/issues/4953)) ([b4edd95](https://github.com/ngrx/platform/commit/b4edd95))\n- **signals:** remove symbol descriptions from prod bundle ([#4979](https://github.com/ngrx/platform/issues/4979)) ([05cfb03](https://github.com/ngrx/platform/commit/05cfb03))\n\n### Features\n\n- **eslint-plugin:** enhance prefix-selectors-with-select to handle destructuring ([#4926](https://github.com/ngrx/platform/issues/4926)) ([bc89544](https://github.com/ngrx/platform/commit/bc89544))\n- **eslint-plugin:** split required typechecking configs ([#5002](https://github.com/ngrx/platform/issues/5002)) ([c1f4fc5](https://github.com/ngrx/platform/commit/c1f4fc5))\n- **signals:** add ability to provide SignalStore at the platform level ([#4964](https://github.com/ngrx/platform/issues/4964)) ([835014b](https://github.com/ngrx/platform/commit/835014b)), closes [#4963](https://github.com/ngrx/platform/issues/4963)\n- **signals:** add EntityChanges type to public API ([#5014](https://github.com/ngrx/platform/issues/5014)) ([76e4dc6](https://github.com/ngrx/platform/commit/76e4dc6)), closes [#5012](https://github.com/ngrx/platform/issues/5012)\n- **signals:** add migration schematic to rename withEffects to withEventHandlers ([#5032](https://github.com/ngrx/platform/issues/5032)) ([d6a2c56](https://github.com/ngrx/platform/commit/d6a2c56)), closes [#5010](https://github.com/ngrx/platform/issues/5010)\n- **signals:** allow computation fn to be provided to `signalMethod` and `rxMethod` ([#4996](https://github.com/ngrx/platform/issues/4996)) ([eea69d7](https://github.com/ngrx/platform/commit/eea69d7)), closes [#4986](https://github.com/ngrx/platform/issues/4986)\n- **signals:** allow returning an array of observables from withEffects ([#5008](https://github.com/ngrx/platform/issues/5008)) ([8994d92](https://github.com/ngrx/platform/commit/8994d92))\n- **signals:** provide Dispatcher and Events at the platform level ([#4978](https://github.com/ngrx/platform/issues/4978)) ([0722ddb](https://github.com/ngrx/platform/commit/0722ddb))\n- **signals:** provide support for scoped events ([#4997](https://github.com/ngrx/platform/issues/4997)) ([c719d19](https://github.com/ngrx/platform/commit/c719d19)), closes [#4776](https://github.com/ngrx/platform/issues/4776)\n- **signals:** rename withEffects to withEventHandlers ([#5009](https://github.com/ngrx/platform/issues/5009)) ([3aa64ae](https://github.com/ngrx/platform/commit/3aa64ae))\n\n### BREAKING CHANGES\n\n- **signals:** The withEffects feature from @ngrx/signals/events plugin is renamed to withEventHandlers.\n\nBEFORE:\n\n```ts\nimport { withEffects } from '@ngrx/signals/events';\n\nexport const CounterStore = signalStore(\n  withState({ count: 0 }),\n  withEffects((store, events = inject(Events)) => ({\n    logCount$: events\n      .on(increment)\n      .pipe(tap(() => console.log(store.count()))),\n  }))\n);\n```\n\nAFTER:\n\n```ts\nimport { withEventHandlers } from '@ngrx/signals/events';\n\nexport const CounterStore = signalStore(\n  withState({ count: 0 }),\n  withEventHandlers((store, events = inject(Events)) => ({\n    logCount$: events\n      .on(increment)\n      .pipe(tap(() => console.log(store.count()))),\n  }))\n);\n```\n\n<a name=\"20.1.0\"></a>\n\n# [20.1.0](https://github.com/ngrx/platform/compare/20.0.1...20.1.0) (2025-10-22)\n\n### Bug Fixes\n\n- **signals:** drop `assertInInjectionContext` in production ([#4954](https://github.com/ngrx/platform/issues/4954)) ([37e6fa1](https://github.com/ngrx/platform/commit/37e6fa1))\n- **signals:** drop `assertUniqueStoreMembers` in production ([#4953](https://github.com/ngrx/platform/issues/4953)) ([b4edd95](https://github.com/ngrx/platform/commit/b4edd95))\n\n### Features\n\n- **eslint-plugin:** enhance prefix-selectors-with-select to handle destructuring ([#4926](https://github.com/ngrx/platform/issues/4926)) ([bc89544](https://github.com/ngrx/platform/commit/bc89544))\n- **signals:** add ability to provide SignalStore at the platform level ([#4964](https://github.com/ngrx/platform/issues/4964)) ([835014b](https://github.com/ngrx/platform/commit/835014b)), closes [#4963](https://github.com/ngrx/platform/issues/4963)\n- **signals:** provide Dispatcher and Events at the platform level ([#4978](https://github.com/ngrx/platform/issues/4978)) ([0722ddb](https://github.com/ngrx/platform/commit/0722ddb))\n\n<a name=\"20.0.1\"></a>\n\n## [20.0.1](https://github.com/ngrx/platform/compare/20.0.0...20.0.1) (2025-08-25)\n\n### Bug Fixes\n\n- **component:** use setTimeout instead of requestAnimationFrame in SSR mode ([#4912](https://github.com/ngrx/platform/issues/4912)) ([3ae98d7](https://github.com/ngrx/platform/commit/3ae98d7)), closes [#4902](https://github.com/ngrx/platform/issues/4902)\n- **eslint-plugin:** report on-function-explicit-return-type for call expressions ([#4904](https://github.com/ngrx/platform/issues/4904)) ([c8e1352](https://github.com/ngrx/platform/commit/c8e1352)), closes [#4901](https://github.com/ngrx/platform/issues/4901)\n- **signals:** add unique member check to `withLinkedState` ([#4932](https://github.com/ngrx/platform/issues/4932)) ([c5a5a27](https://github.com/ngrx/platform/commit/c5a5a27)), closes [#4931](https://github.com/ngrx/platform/issues/4931)\n- **www:** Make Guide Pages Responsive ([#4900](https://github.com/ngrx/platform/issues/4900)) ([4c921e3](https://github.com/ngrx/platform/commit/4c921e3))\n- **www:** use the stackblitz examples ([#4895](https://github.com/ngrx/platform/issues/4895)) ([e7eabd4](https://github.com/ngrx/platform/commit/e7eabd4))\n\n<a name=\"20.0.0\"></a>\n\n# [20.0.0](https://github.com/ngrx/platform/compare/20.0.0-rc.0...20.0.0) (2025-07-28)\n\n### Bug Fixes\n\n- **www:** Make API Pages Responsive ([#4885](https://github.com/ngrx/platform/issues/4885)) ([7f09858](https://github.com/ngrx/platform/commit/7f09858)), closes [#4816](https://github.com/ngrx/platform/issues/4816)\n\n<a name=\"20.0.0-rc.0\"></a>\n\n# [20.0.0-rc.0](https://github.com/ngrx/platform/compare/20.0.0-beta.0...20.0.0-rc.0) (2025-07-14)\n\n### Bug Fixes\n\n- **signals:** allow generic template literals as state keys ([#4871](https://github.com/ngrx/platform/issues/4871)) ([9fefc77](https://github.com/ngrx/platform/commit/9fefc77)), closes [#4638](https://github.com/ngrx/platform/issues/4638)\n- **signals:** allow lazy initialization of DeepSignal ([#4866](https://github.com/ngrx/platform/issues/4866)) ([cb1a2ba](https://github.com/ngrx/platform/commit/cb1a2ba)), closes [#4749](https://github.com/ngrx/platform/issues/4749)\n- **signals:** do not create deep signals for empty objects and unknown records with symbol keys ([#4880](https://github.com/ngrx/platform/issues/4880)) ([bae9f18](https://github.com/ngrx/platform/commit/bae9f18))\n- **signals:** remove internal Signal type ([#4867](https://github.com/ngrx/platform/issues/4867)) ([4a4a5db](https://github.com/ngrx/platform/commit/4a4a5db))\n\n### Features\n\n- **operators:** add migration for deprecated tapResponse signature ([#4858](https://github.com/ngrx/platform/issues/4858)) ([551ceb4](https://github.com/ngrx/platform/commit/551ceb4))\n- **signals:** add `withLinkedState()` ([#4818](https://github.com/ngrx/platform/issues/4818)) ([4bb7fdd](https://github.com/ngrx/platform/commit/4bb7fdd))\n- **signals:** allow access to methods in `withComputed` ([#4864](https://github.com/ngrx/platform/issues/4864)) ([e11c23f](https://github.com/ngrx/platform/commit/e11c23f)), closes [#4846](https://github.com/ngrx/platform/issues/4846)\n- **signals:** disallow user-defined signals in withState and signalState ([#4879](https://github.com/ngrx/platform/issues/4879)) ([306ed5a](https://github.com/ngrx/platform/commit/306ed5a))\n\n<a name=\"20.0.0-beta.0\"></a>\n\n# [20.0.0-beta.0](https://github.com/ngrx/platform/compare/19.2.1...20.0.0-beta.0) (2025-06-30)\n\n### Bug Fixes\n\n- **signals:** handle events in the dispatched order ([#4857](https://github.com/ngrx/platform/issues/4857)) ([fa50f43](https://github.com/ngrx/platform/commit/fa50f43)), closes [#4852](https://github.com/ngrx/platform/issues/4852)\n- **www:** Add padding to code snippets ([#4812](https://github.com/ngrx/platform/issues/4812)) ([9e942db](https://github.com/ngrx/platform/commit/9e942db)), closes [#4811](https://github.com/ngrx/platform/issues/4811)\n- **www:** add styles for video ([#4851](https://github.com/ngrx/platform/issues/4851)) ([85680a0](https://github.com/ngrx/platform/commit/85680a0))\n- **www:** fix color-scheme and combine duplicate html declarations ([#4855](https://github.com/ngrx/platform/issues/4855)) ([f9b2565](https://github.com/ngrx/platform/commit/f9b2565))\n- **www:** remove duplicate scrollbar ([#4829](https://github.com/ngrx/platform/issues/4829)) ([f0f1f2a](https://github.com/ngrx/platform/commit/f0f1f2a)), closes [#4828](https://github.com/ngrx/platform/issues/4828)\n- **www:** remove horizontal scrollbar ([#4808](https://github.com/ngrx/platform/issues/4808)) ([2639f67](https://github.com/ngrx/platform/commit/2639f67))\n\n### build\n\n- update to Angular 20 ([#4778](https://github.com/ngrx/platform/issues/4778)) ([8a4ecd9](https://github.com/ngrx/platform/commit/8a4ecd9))\n\n### Features\n\n- **effects:** remove act operator ([#4839](https://github.com/ngrx/platform/issues/4839)) ([9a83f1d](https://github.com/ngrx/platform/commit/9a83f1d))\n- **entity:** strengthen typing of getInitialState ([#4819](https://github.com/ngrx/platform/issues/4819)) ([bfb21c2](https://github.com/ngrx/platform/commit/bfb21c2)), closes [#4422](https://github.com/ngrx/platform/issues/4422)\n- **eslint-plugin:** add new rule enforce type call ([#4809](https://github.com/ngrx/platform/issues/4809)) ([9b82e67](https://github.com/ngrx/platform/commit/9b82e67)), closes [#4797](https://github.com/ngrx/platform/issues/4797)\n- **operators:** deprecate `tapResponse` signature with a sequence of callbacks ([#4844](https://github.com/ngrx/platform/issues/4844)) ([9a16813](https://github.com/ngrx/platform/commit/9a16813)), closes [#4840](https://github.com/ngrx/platform/issues/4840)\n- **signals:** allow user-defined signals in `withState` and `signalState` by splitting `STATE_SOURCE` ([#4795](https://github.com/ngrx/platform/issues/4795)) ([521a2a6](https://github.com/ngrx/platform/commit/521a2a6))\n- **signals:** enhance `withComputed` to accept computation functions ([#4822](https://github.com/ngrx/platform/issues/4822)) ([c8b15dd](https://github.com/ngrx/platform/commit/c8b15dd)), closes [#4782](https://github.com/ngrx/platform/issues/4782)\n- **www:** add sidebar for mobile view and make home page responsive ([#4813](https://github.com/ngrx/platform/issues/4813)) ([4397bfb](https://github.com/ngrx/platform/commit/4397bfb)), closes [#4807](https://github.com/ngrx/platform/issues/4807)\n\n### BREAKING CHANGES\n\n- **signals:** The internal `STATE_SOURCE` is no longer represented as a single `WritableSignal` holding the entire state object. Instead, each top-level state property becomes its own `WritableSignal` or remains as-is if a `WritableSignal` is provided as a state property.\n\nBEFORE:\n\n1. The initial state object reference is preserved:\n\nconst initialState = { ngrx: 'rocks' };\n\n// signalState:\nconst state = signalState(initialState);\nstate() === initialState; // true\n\n// withState:\nconst Store = signalStore(withState(initialState));\nconst store = new Store();\ngetState(store) === initialState; // true\n\n2. Top-level `WritableSignal`s are wrapped with `Signal`s:\n\n// signalState:\nconst state = signalState({ ngrx: signal('rocks') });\nstate.ngrx // type: Signal<WritableSignal<string>>\n\n// withState:\nconst Store = signalStore(withState({ ngrx: signal('rocks') }));\nconst store = new Store();\nstore.ngrx // type: Signal<WritableSignal<string>>\n\n3. Root state properties can be added dynamically:\n\n// signalState:\nconst state = signalState<Record<string, string>>({});\nconsole.log(state()); // {}\n\npatchState(state, { ngrx: 'rocks' });\nconsole.log(state()); // { ngrx: 'rocks' }\n\n// withState:\nconst Store = signalStore(\n{ protectedState: false },\nwithState<Record<string, string>>({})\n);\nconst store = new Store();\nconsole.log(getState(store)); // {}\n\npatchState(store, { ngrx: 'rocks' });\nconsole.log(getState(store)); // { ngrx: 'rocks' }\n\nAFTER:\n\n1. The initial state object reference is not preserved:\n\nconst initialState = { ngrx: 'rocks' };\n\n// signalState:\nconst state = signalState(initialState);\nstate() === initialState; // false\n\n// withState:\nconst Store = signalStore(withState(initialState));\nconst store = new Store();\ngetState(store) === initialState; // false\n\n2. Top-level `WritableSignal`s are not wrapped with `Signal`s:\n\n// signalState:\nconst state = signalState({ ngrx: signal('rocks') });\nstate.ngrx // type: Signal<string>\n\n// withState:\nconst Store = signalStore(withState({ ngrx: signal('rocks') }));\nconst store = new Store();\nstore.ngrx // type: Signal<string>\n\n3. Root state properties can not be added dynamically:\n\n// signalState:\nconst state = signalState<Record<string, string>>({});\nconsole.log(state()); // {}\n\npatchState(state, { ngrx: 'rocks' });\nconsole.log(state()); // {}\n\n// withState:\nconst Store = signalStore(\n{ protectedState: false },\nwithState<Record<string, string>>({})\n);\nconst store = new Store();\nconsole.log(getState(store)); // {}\n\npatchState(store, { ngrx: 'rocks' });\nconsole.log(getState(store)); // {}\n\nCo-authored-by: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com>\nCo-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>\nCo-authored-by: Marko Stanimirović <markostanimirovic95@gmail.com>\n\n- The minimum required version of Angular has been updated.\n\nBEFORE:\n\nThe minimum required version is Angular 19.x\n\nAFTER:\n\nThe minimum required version is Angular 20.x\n\n<a name=\"20.0.0-beta.0\"></a>\n\n# [20.0.0-beta.0](https://github.com/ngrx/platform/compare/19.2.1...20.0.0-beta.0) (2025-06-10)\n\n### Bug Fixes\n\n- **www:** Add padding to code snippets ([#4812](https://github.com/ngrx/platform/issues/4812)) ([9e942db](https://github.com/ngrx/platform/commit/9e942db)), closes [#4811](https://github.com/ngrx/platform/issues/4811)\n- **www:** remove horizontal scrollbar ([#4808](https://github.com/ngrx/platform/issues/4808)) ([2639f67](https://github.com/ngrx/platform/commit/2639f67))\n\n### build\n\n- update to Angular 20 ([#4778](https://github.com/ngrx/platform/issues/4778)) ([8a4ecd9](https://github.com/ngrx/platform/commit/8a4ecd9))\n\n### Features\n\n- **eslint-plugin:** add new rule enforce type call ([#4809](https://github.com/ngrx/platform/issues/4809)) ([9b82e67](https://github.com/ngrx/platform/commit/9b82e67)), closes [#4797](https://github.com/ngrx/platform/issues/4797)\n- **www:** add sidebar for mobile view and make home page responsive ([#4813](https://github.com/ngrx/platform/issues/4813)) ([4397bfb](https://github.com/ngrx/platform/commit/4397bfb)), closes [#4807](https://github.com/ngrx/platform/issues/4807)\n\n### BREAKING CHANGES\n\n- The minimum required version of Angular has been updated.\n\nBEFORE:\n\nThe minimum required version is Angular 19.x\n\nAFTER:\n\nThe minimum required version is Angular 20.x\n\n<a name=\"20.0.0\"></a>\n\n# [20.0.0](https://github.com/ngrx/platform/compare/19.2.1...20.0.0) (2025-06-09)\n\n### Bug Fixes\n\n- **www:** Add padding to code snippets ([#4812](https://github.com/ngrx/platform/issues/4812)) ([9e942db](https://github.com/ngrx/platform/commit/9e942db)), closes [#4811](https://github.com/ngrx/platform/issues/4811)\n- **www:** remove horizontal scrollbar ([#4808](https://github.com/ngrx/platform/issues/4808)) ([2639f67](https://github.com/ngrx/platform/commit/2639f67))\n\n### build\n\n- update to Angular 20 ([#4778](https://github.com/ngrx/platform/issues/4778)) ([8a4ecd9](https://github.com/ngrx/platform/commit/8a4ecd9))\n\n### Features\n\n- **eslint-plugin:** add new rule enforce type call ([#4809](https://github.com/ngrx/platform/issues/4809)) ([9b82e67](https://github.com/ngrx/platform/commit/9b82e67)), closes [#4797](https://github.com/ngrx/platform/issues/4797)\n- **www:** add sidebar for mobile view and make home page responsive ([#4813](https://github.com/ngrx/platform/issues/4813)) ([4397bfb](https://github.com/ngrx/platform/commit/4397bfb)), closes [#4807](https://github.com/ngrx/platform/issues/4807)\n\n### BREAKING CHANGES\n\n- The minimum required version of Angular has been updated.\n\nBEFORE:\n\nThe minimum required version is Angular 19.x\n\nAFTER:\n\nThe minimum required version is Angular 20.x\n\n<a name=\"19.2.1\"></a>\n\n## [19.2.1](https://github.com/ngrx/platform/compare/19.2.0...19.2.1) (2025-05-29)\n\n### Bug Fixes\n\n- **signals:** add current state as second argument of case reducer ([#4800](https://github.com/ngrx/platform/issues/4800)) ([95dbbfa](https://github.com/ngrx/platform/commit/95dbbfa))\n- **signals:** expose WritableStateSource to withFeature callback ([#4792](https://github.com/ngrx/platform/issues/4792)) ([afb6528](https://github.com/ngrx/platform/commit/afb6528)), closes [#4766](https://github.com/ngrx/platform/issues/4766)\n\n<a name=\"19.2.0\"></a>\n\n# [19.2.0](https://github.com/ngrx/platform/compare/19.1.0...19.2.0) (2025-05-12)\n\n### Features\n\n- **eslint-plugin:** add schematic support for flat configs ([#4747](https://github.com/ngrx/platform/issues/4747)) ([f966d0a](https://github.com/ngrx/platform/commit/f966d0a))\n- **signals:** add Events plugin ([#4769](https://github.com/ngrx/platform/issues/4769)) ([980cf6f](https://github.com/ngrx/platform/commit/980cf6f)), closes [#4580](https://github.com/ngrx/platform/issues/4580)\n\n<a name=\"19.1.0\"></a>\n\n# [19.1.0](https://github.com/ngrx/platform/compare/19.0.1...19.1.0) (2025-04-01)\n\n### Bug Fixes\n\n- **signals:** skip assertions when ngDevMode is not defined ([#4703](https://github.com/ngrx/platform/issues/4703)) ([0b43014](https://github.com/ngrx/platform/commit/0b43014)), closes [#4699](https://github.com/ngrx/platform/issues/4699)\n\n### Features\n\n- **signals:** add `withFeature` ([#4739](https://github.com/ngrx/platform/issues/4739)) ([345ab4f](https://github.com/ngrx/platform/commit/345ab4f)), closes [#4678](https://github.com/ngrx/platform/issues/4678)\n- **signals:** add prepend entity updaters ([#4721](https://github.com/ngrx/platform/issues/4721)) ([dac8665](https://github.com/ngrx/platform/commit/dac8665)), closes [#4723](https://github.com/ngrx/platform/issues/4723)\n- **signals:** add RxMethod and SignalMethod types to public API ([#4744](https://github.com/ngrx/platform/issues/4744)) ([40e78d9](https://github.com/ngrx/platform/commit/40e78d9))\n- **signals:** add unprotected testing helper ([#4725](https://github.com/ngrx/platform/issues/4725)) ([01c2327](https://github.com/ngrx/platform/commit/01c2327))\n- **signals:** add upsert entity updaters ([#4727](https://github.com/ngrx/platform/issues/4727)) ([92fd7c3](https://github.com/ngrx/platform/commit/92fd7c3))\n- **signals:** warn when reactive method runs with source injector ([#4742](https://github.com/ngrx/platform/issues/4742)) ([84a537c](https://github.com/ngrx/platform/commit/84a537c)), closes [#4726](https://github.com/ngrx/platform/issues/4726)\n\n<a name=\"19.0.1\"></a>\n\n## [19.0.1](https://github.com/ngrx/platform/compare/19.0.0...19.0.1) (2025-01-31)\n\n### Bug Fixes\n\n- **signals:** enable `withProps` to handle Symbols ([#4656](https://github.com/ngrx/platform/issues/4656)) ([02320b3](https://github.com/ngrx/platform/commit/02320b3)), closes [#4655](https://github.com/ngrx/platform/issues/4655)\n- **signals:** remove `signalMethod` instance watcher on destroy ([#4648](https://github.com/ngrx/platform/issues/4648)) ([7f42065](https://github.com/ngrx/platform/commit/7f42065)), closes [#4644](https://github.com/ngrx/platform/issues/4644)\n- **signals:** revert the protection for state mutation in dev mode ([#4686](https://github.com/ngrx/platform/issues/4686)) ([ae7922e](https://github.com/ngrx/platform/commit/ae7922e)), closes [#4683](https://github.com/ngrx/platform/issues/4683)\n\n<a name=\"19.0.0\"></a>\n\n# [19.0.0](https://github.com/ngrx/platform/compare/19.0.0-rc.0...19.0.0) (2024-12-17)\n\n<a name=\"19.0.0-rc.0\"></a>\n\n# [19.0.0-rc.0](https://github.com/ngrx/platform/compare/19.0.0-beta.0...19.0.0-rc.0) (2024-12-10)\n\n### Bug Fixes\n\n- **eslint-plugin:** support ESM modudule syntax (.mjs) ([e2f35c8](https://github.com/ngrx/platform/commit/e2f35c8))\n- **signals:** create deep signals for custom class instances ([#4614](https://github.com/ngrx/platform/issues/4614)) ([4d34dc4](https://github.com/ngrx/platform/commit/4d34dc4)), closes [#4604](https://github.com/ngrx/platform/issues/4604)\n\n### Features\n\n- **eslint-plugin:** add new rule require-super-ondestroy ([#4611](https://github.com/ngrx/platform/issues/4611)) ([2ac4372](https://github.com/ngrx/platform/commit/2ac4372)), closes [#4505](https://github.com/ngrx/platform/issues/4505)\n- **signals:** add `signalMethod` ([#4597](https://github.com/ngrx/platform/issues/4597)) ([bdd1d3e](https://github.com/ngrx/platform/commit/bdd1d3e)), closes [#4581](https://github.com/ngrx/platform/issues/4581)\n- **signals:** add `withProps` base feature ([#4607](https://github.com/ngrx/platform/issues/4607)) ([e626082](https://github.com/ngrx/platform/commit/e626082))\n- **signals:** add migration for `withProps` ([#4612](https://github.com/ngrx/platform/issues/4612)) ([5f803d0](https://github.com/ngrx/platform/commit/5f803d0)), closes [#4608](https://github.com/ngrx/platform/issues/4608)\n- **store:** enable dispatching actions on signal changes ([#4600](https://github.com/ngrx/platform/issues/4600)) ([2528d39](https://github.com/ngrx/platform/commit/2528d39)), closes [#4537](https://github.com/ngrx/platform/issues/4537)\n\n### BREAKING CHANGES\n\n- **signals:** - The `computed` property in `SignalStoreFeatureResult` type is renamed to `props`.\n\n* The `EntityComputed` and `NamedEntityComputed` types in the `entities` plugin are renamed to `EntityProps` and `NamedEntityProps`.\n\nBEFORE:\n\n```ts\nimport { computed, Signal } from '@angular/core';\nimport {\n  signalStoreFeature,\n  SignalStoreFeature,\n  type,\n  withComputed,\n} from '@ngrx/signals';\nimport { EntityComputed } from '@ngrx/signals/entities';\n\nexport function withTotalEntities<Entity>(): SignalStoreFeature<\n  { state: {}; computed: EntityComputed<Entity>; methods: {} },\n  { state: {}; computed: { total: Signal<number> }; methods: {} }\n> {\n  return signalStoreFeature(\n    { computed: type<EntityComputed<Entity>>() },\n    withComputed(({ entities }) => ({\n      total: computed(() => entities().length),\n    }))\n  );\n}\n```\n\nAFTER:\n\n```ts\nimport { computed, Signal } from '@angular/core';\nimport {\n  signalStoreFeature,\n  SignalStoreFeature,\n  type,\n  withComputed,\n} from '@ngrx/signals';\nimport { EntityProps } from '@ngrx/signals/entities';\n\nexport function withTotalEntities<Entity>(): SignalStoreFeature<\n  { state: {}; props: EntityProps<Entity>; methods: {} },\n  { state: {}; props: { total: Signal<number> }; methods: {} }\n> {\n  return signalStoreFeature(\n    { props: type<EntityProps<Entity>>() },\n    withComputed(({ entities }) => ({\n      total: computed(() => entities().length),\n    }))\n  );\n}\n```\n\n<a name=\"19.0.0-beta.0\"></a>\n\n# [19.0.0-beta.0](https://github.com/ngrx/platform/compare/18.1.1...19.0.0-beta.0\") (2024-11-20)\n\n### Features\n\n- **schematics:** change standalone default to true for components ([#4569](https://github.com/ngrx/platform/issues/4569)) ([c7d0ce6](https://github.com/ngrx/platform/commit/c7d0ce6))\n- **signals:** rename `rxMethod.unsubscribe` to `destroy` ([#4584](https://github.com/ngrx/platform/issues/4584)) ([57ad5c5](https://github.com/ngrx/platform/commit/57ad5c5))\n- **signals:** throw error in dev mode on state mutation ([#4526](https://github.com/ngrx/platform/issues/4526)) ([7a84209](https://github.com/ngrx/platform/commit/7a84209))\n\n### BREAKING CHANGES\n\n- **signals:** The `signalState`/`signalStore` state object is frozen in development mode.\n  If a mutable change occurs to the state object, an error will be thrown.\n\nBEFORE:\n\n```ts\nconst userState = signalState(initialState);\npatchState(userState, (state) => {\n  state.user.firstName = 'mutable change'; // mutable change which went through\n  return state;\n});\n```\n\nAFTER:\n\n```ts\nconst userState = signalState(initialState);\npatchState(userState, (state) => {\n  state.user.firstName = 'mutable change'; // throws in dev mode\n  return state;\n});\n```\n\n- **signals:** The `unsubscribe` method from `rxMethod` is renamed to `destroy`.\n\nBEFORE:\n\n```ts\nconst logNumber = rxMethod<number>(tap(console.log));\n\nconst num1Ref = logNumber(interval(1_000));\nconst num2Ref = logNumber(interval(2_000));\n\n// destroy `num1Ref` after 2 seconds\nsetTimeout(() => num1Ref.unsubscribe(), 2_000);\n\n// destroy all reactive method refs after 5 seconds\nsetTimeout(() => logNumber.unsubscribe(), 5_000);\n```\n\nAFTER:\n\n```ts\nconst logNumber = rxMethod<number>(tap(console.log));\n\nconst num1Ref = logNumber(interval(1_000));\nconst num2Ref = logNumber(interval(2_000));\n\n// destroy `num1Ref` after 2 seconds\nsetTimeout(() => num1Ref.destroy(), 2_000);\n\n// destroy all reactive method refs after 5 seconds\nsetTimeout(() => logNumber.destroy(), 5_000);\n```\n\n- **schematics:** The default setting for generating components using schematics is updated.\n\nBEFORE:\n\nThe default setting for generating components using schematics does not use standalone components.\n\nAFTER:\n\nThe default setting for generating components using schematics uses standalone components.\n\n- The minimum required version of Angular has been updated.\n\nBEFORE:\n\nThe minimum required version is Angular 18.x\n\nAFTER:\n\nThe minimum required version is Angular 19.x\n\n<a name=\"18.1.1\"></a>\n\n## [18.1.1](https://github.com/ngrx/platform/compare/18.1.0...18.1.1) (2024-10-29)\n\n### Bug Fixes\n\n- **data:** export HttpOptions ([#4564](https://github.com/ngrx/platform/issues/4564)) ([4909627](https://github.com/ngrx/platform/commit/4909627))\n- **router-store:** use non-const enum to allow isolatedModules tsconfig option ([#4554](https://github.com/ngrx/platform/issues/4554)) ([f993759](https://github.com/ngrx/platform/commit/f993759))\n\n<a name=\"18.1.0\"></a>\n\n# [18.1.0](https://github.com/ngrx/platform/compare/18.0.2...18.1.0) (2024-10-08)\n\n### Bug Fixes\n\n- **component-store:** remove [@ngrx](https://github.com/ngrx)/operators from dependencies ([#4532](https://github.com/ngrx/platform/issues/4532)) ([3b4b9c4](https://github.com/ngrx/platform/commit/3b4b9c4))\n- **effects:** remove [@ngrx](https://github.com/ngrx)/operators from dependencies ([#4531](https://github.com/ngrx/platform/issues/4531)) ([4fb78f1](https://github.com/ngrx/platform/commit/4fb78f1))\n- **signals:** remove usage of SIGNAL from [@angular](https://github.com/angular)/core/primitives/signals package ([#4530](https://github.com/ngrx/platform/issues/4530)) ([cae429a](https://github.com/ngrx/platform/commit/cae429a))\n- **signals:** use `Injector` of `rxMethod` instance caller if available ([#4529](https://github.com/ngrx/platform/issues/4529)) ([ffc1d87](https://github.com/ngrx/platform/commit/ffc1d87)), closes [#4528](https://github.com/ngrx/platform/issues/4528)\n- **store-devtools:** add [@angular](https://github.com/angular)/core as peer dependency ([#4478](https://github.com/ngrx/platform/issues/4478)) ([62cceeb](https://github.com/ngrx/platform/commit/62cceeb)), closes [#4479](https://github.com/ngrx/platform/issues/4479)\n\n### Features\n\n- **eslint-plugin:** add preferProtectedState rule ([#4488](https://github.com/ngrx/platform/issues/4488)) ([32c772d](https://github.com/ngrx/platform/commit/32c772d)), closes [#4474](https://github.com/ngrx/platform/issues/4474)\n- **signals:** add deepComputed function ([#4539](https://github.com/ngrx/platform/issues/4539)) ([269bd32](https://github.com/ngrx/platform/commit/269bd32))\n\n<a name=\"18.0.2\"></a>\n\n## [18.0.2](https://github.com/ngrx/platform/compare/18.0.1...18.0.2) (2024-07-31)\n\n### Bug Fixes\n\n- **eslint-plugin:** do not report non-array returns in no-multiple-actions-in-effects ([#4418](https://github.com/ngrx/platform/issues/4418)) ([92a68bc](https://github.com/ngrx/platform/commit/92a68bc))\n- **operators:** add [@ngrx](https://github.com/ngrx)/operators to packageGroup for updates ([#4472](https://github.com/ngrx/platform/issues/4472)) ([521ce7b](https://github.com/ngrx/platform/commit/521ce7b)), closes [#4465](https://github.com/ngrx/platform/issues/4465)\n- **signals:** allow modifying entity id on update ([#4404](https://github.com/ngrx/platform/issues/4404)) ([0106e93](https://github.com/ngrx/platform/commit/0106e93))\n\n<a name=\"18.0.1\"></a>\n\n## [18.0.1](https://github.com/ngrx/platform/compare/18.0.0...18.0.1) (2024-06-27)\n\n### Bug Fixes\n\n- **component-store:** resolve issues in migration script to v18 ([#4403](https://github.com/ngrx/platform/issues/4403)) ([a15d53e](https://github.com/ngrx/platform/commit/a15d53e))\n- **effects:** fix bugs in migration script to v18 ([#4402](https://github.com/ngrx/platform/issues/4402)) ([6ae4723](https://github.com/ngrx/platform/commit/6ae4723))\n- **eslint-plugin:** only take return statement into account with no-multiple-actions-in-effects ([#4410](https://github.com/ngrx/platform/issues/4410)) ([c9c646c](https://github.com/ngrx/platform/commit/c9c646c)), closes [#4409](https://github.com/ngrx/platform/issues/4409)\n\n### Features\n\n- **signals:** rename signals to computed when defining custom features with input ([#4395](https://github.com/ngrx/platform/issues/4395)) ([05f0940](https://github.com/ngrx/platform/commit/05f0940)), closes [#4391](https://github.com/ngrx/platform/issues/4391)\n- **signals:** replace `idKey` with `selectId` when defining custom entity ID ([#4396](https://github.com/ngrx/platform/issues/4396)) ([67a5a93](https://github.com/ngrx/platform/commit/67a5a93)), closes [#4217](https://github.com/ngrx/platform/issues/4217) [#4392](https://github.com/ngrx/platform/issues/4392)\n\n<a name=\"18.0.1\"></a>\n\n## [18.0.1](https://github.com/ngrx/platform/compare/18.0.0...18.0.1) (2024-06-27)\n\n<a name=\"18.0.0\"></a>\n\n# [18.0.0](https://github.com/ngrx/platform/compare/18.0.0-rc.1...18.0.0) (2024-06-13)\n\n<a name=\"18.0.0-rc.1\"></a>\n\n# [18.0.0-rc.1](https://github.com/ngrx/platform/compare/18.0.0-rc.0...18.0.0-rc.1) (2024-06-12)\n\n### Bug Fixes\n\n- **eslint-plugin:** include signals to ESLint v8 rules ([#4387](https://github.com/ngrx/platform/issues/4387)) ([7c75dcf](https://github.com/ngrx/platform/commit/7c75dcf)), closes [#4385](https://github.com/ngrx/platform/issues/4385)\n\n<a name=\"18.0.0-rc.0\"></a>\n\n# [18.0.0-rc.0](https://github.com/ngrx/platform/compare/18.0.0-beta.1...18.0.0-rc.0) (2024-06-10)\n\n### Bug Fixes\n\n- **router-store:** include string[] as return type for selectQueryParam ([#4369](https://github.com/ngrx/platform/issues/4369)) ([b0b43f7](https://github.com/ngrx/platform/commit/b0b43f7))\n- **signals:** export DeepSignal ([#4377](https://github.com/ngrx/platform/issues/4377)) ([fa26c5c](https://github.com/ngrx/platform/commit/fa26c5c))\n\n### Features\n\n- **component-store:** remove tapResponse operator ([#4366](https://github.com/ngrx/platform/issues/4366)) ([285c810](https://github.com/ngrx/platform/commit/285c810))\n- **effects:** remove concatLatestFrom operator ([#4367](https://github.com/ngrx/platform/issues/4367)) ([1a1b6df](https://github.com/ngrx/platform/commit/1a1b6df))\n- **eslint-plugin:** add signals rules ([#4380](https://github.com/ngrx/platform/issues/4380)) ([c002466](https://github.com/ngrx/platform/commit/c002466))\n- **eslint-plugin:** support ESLint v9 (and v8) ([#4371](https://github.com/ngrx/platform/issues/4371)) ([e8d9ffa](https://github.com/ngrx/platform/commit/e8d9ffa))\n\n### BREAKING CHANGES\n\n- **effects:** The concatLatestFrom operator has been removed from @ngrx/effects in favor of the @ngrx/operators package.\n\nBEFORE:\n\nimport { concatLatestFrom } from '@ngrx/effects';\n\nAFTER:\n\nimport { concatLatestFrom } from '@ngrx/operators';\n\n- **component-store:** The tapResponse operator has been removed from @ngrx/component-store in favor of the @ngrx/operators package.\n\nBEFORE:\n\nimport { tapResponse } from '@ngrx/component-store';\n\nAFTER:\n\nimport { tapResponse } from '@ngrx/operators';\n\n<a name=\"18.0.0-beta.1\"></a>\n\n# [18.0.0-beta.1](https://github.com/ngrx/platform/compare/18.0.0-beta.0...18.0.0-beta.1) (2024-05-20)\n\n<a name=\"18.0.0-beta.0\"></a>\n\n# [18.0.0-beta.0](https://github.com/ngrx/platform/compare/17.2.0...18.0.0-beta.0) (2024-05-20)\n\n### Bug Fixes\n\n- **eslint-plugin:** add as devDependency via ng add ([#4343](https://github.com/ngrx/platform/issues/4343)) ([4fe7b7f](https://github.com/ngrx/platform/commit/4fe7b7f))\n- **schematics:** set correct default value type ([#4307](https://github.com/ngrx/platform/issues/4307)) ([51034e6](https://github.com/ngrx/platform/commit/51034e6))\n\n### Features\n\n- **component-store:** add migrator for `tapResponse` ([#4321](https://github.com/ngrx/platform/issues/4321)) ([0ae21c9](https://github.com/ngrx/platform/commit/0ae21c9)), closes [#4261](https://github.com/ngrx/platform/issues/4261)\n- **effects:** add migrator for `concatLatestFrom` ([#4311](https://github.com/ngrx/platform/issues/4311)) ([d264c56](https://github.com/ngrx/platform/commit/d264c56)), closes [#4262](https://github.com/ngrx/platform/issues/4262)\n- **operators:** add `mapResponse` ([#4302](https://github.com/ngrx/platform/issues/4302)) ([c460920](https://github.com/ngrx/platform/commit/c460920)), closes [#4230](https://github.com/ngrx/platform/issues/4230)\n- **store:** add TypedAction migration ([#4325](https://github.com/ngrx/platform/issues/4325)) ([f76a401](https://github.com/ngrx/platform/commit/f76a401))\n- upgrade Angular dependencies to v18 pre-release versions ([#4308](https://github.com/ngrx/platform/issues/4308)) ([62f3971](https://github.com/ngrx/platform/commit/62f3971))\n- **store:** merge Action and TypedAction intefaces ([#4318](https://github.com/ngrx/platform/issues/4318)) ([c8bde71](https://github.com/ngrx/platform/commit/c8bde71))\n\n### BREAKING CHANGES\n\n- The minimum required version of Angular has been updated\n\nBEFORE:\n\nThe minimum required version of Angular is 17.x\n\nAFTER:\n\nThe minimum required version of Angular is 18.x\n\n- **store:** The Action and TypedAction interfaces are merged into one interface.\n\nBEFORE:\n\nThere was a separation between the Action and TypedAction interfaces.\n\nAFTER:\n\nThe Action interface accepts a generic type parameter that represents the payload type (defaults to string).\nThe TypedAction interface is removed.\n\n<a name=\"17.2.0\"></a>\n\n# [17.2.0](https://github.com/ngrx/platform/compare/17.1.1...17.2.0) (2024-04-11)\n\n### Bug Fixes\n\n- **effects:** make createEffect work with TS 5.3 ([#4296](https://github.com/ngrx/platform/issues/4296)) ([19c1f11](https://github.com/ngrx/platform/commit/19c1f11))\n- **ngrx.io:** set correct path for pwa icons ([#4263](https://github.com/ngrx/platform/issues/4263)) ([703a9c7](https://github.com/ngrx/platform/commit/703a9c7))\n- **schematics:** correct module while generating a feature ([#4289](https://github.com/ngrx/platform/issues/4289)) ([7ecffe8](https://github.com/ngrx/platform/commit/7ecffe8)), closes [#4281](https://github.com/ngrx/platform/issues/4281)\n- **signals:** make patchState work with TS 5.4 ([#4294](https://github.com/ngrx/platform/issues/4294)) ([6b440ee](https://github.com/ngrx/platform/commit/6b440ee))\n\n### Features\n\n- **component-store:** deprecate `tapResponse` export ([#4259](https://github.com/ngrx/platform/issues/4259)) ([a5958a0](https://github.com/ngrx/platform/commit/a5958a0))\n- **effects:** deprecate `concatLatestFrom` export ([#4260](https://github.com/ngrx/platform/issues/4260)) ([79674b7](https://github.com/ngrx/platform/commit/79674b7))\n\n<a name=\"17.1.1\"></a>\n\n## [17.1.1](https://github.com/ngrx/platform/compare/17.1.0...17.1.1) (2024-02-21)\n\n### Bug Fixes\n\n- **signals:** add `StateSignal` to the public API ([#4247](https://github.com/ngrx/platform/issues/4247)) ([3d45e5a](https://github.com/ngrx/platform/commit/3d45e5a))\n- **signals:** correctly infer the type of methods with generics ([#4249](https://github.com/ngrx/platform/issues/4249)) ([70517ea](https://github.com/ngrx/platform/commit/70517ea))\n- **signals:** run `rxMethod` outside of reactive context ([#4224](https://github.com/ngrx/platform/issues/4224)) ([3a691d9](https://github.com/ngrx/platform/commit/3a691d9))\n- **store-devtools:** replace direct with indirect `eval` ([#4216](https://github.com/ngrx/platform/issues/4216)) ([1df0eb5](https://github.com/ngrx/platform/commit/1df0eb5)), closes [#4213](https://github.com/ngrx/platform/issues/4213)\n\n### Performance Improvements\n\n- **signals:** avoid creating unnecessary objects in excludeKeys ([#4240](https://github.com/ngrx/platform/issues/4240)) ([b90da9d](https://github.com/ngrx/platform/commit/b90da9d))\n- **signals:** avoid unecessary observable conversions in rxMethod ([#4219](https://github.com/ngrx/platform/issues/4219)) ([fa45d92](https://github.com/ngrx/platform/commit/fa45d92))\n\n<a name=\"17.1.0\"></a>\n\n# [17.1.0](https://github.com/ngrx/platform/compare/17.0.1...17.1.0) (2024-01-16)\n\n### Bug Fixes\n\n- **eslint-plugin:** only report main pipe violations ([#4169](https://github.com/ngrx/platform/issues/4169)) ([970514e](https://github.com/ngrx/platform/commit/970514e))\n- **signals:** run `onDestroy` outside of injection context ([#4200](https://github.com/ngrx/platform/issues/4200)) ([e21df19](https://github.com/ngrx/platform/commit/e21df19))\n\n### Features\n\n- **signals:** add `withHooks` signature with factory input ([#4208](https://github.com/ngrx/platform/issues/4208)) ([916fba0](https://github.com/ngrx/platform/commit/916fba0)), closes [#4201](https://github.com/ngrx/platform/issues/4201)\n\n<a name=\"17.0.1\"></a>\n\n## [17.0.1](https://github.com/ngrx/platform/compare/17.0.0...17.0.1) (2023-11-27)\n\n### Bug Fixes\n\n- **signals:** allow using signalStore and signalState in TS libs ([#4152](https://github.com/ngrx/platform/issues/4152)) ([ecc247c](https://github.com/ngrx/platform/commit/ecc247c))\n- **signals:** define deep signals as configurable properties ([#4147](https://github.com/ngrx/platform/issues/4147)) ([890ca5b](https://github.com/ngrx/platform/commit/890ca5b))\n\n<a name=\"17.0.0\"></a>\n\n# [17.0.0](https://github.com/ngrx/platform/compare/17.0.0-rc.0...17.0.0) (2023-11-20)\n\n### Bug Fixes\n\n- **data:** DefaultDataService getAll httpOptions fix + test ([#4134](https://github.com/ngrx/platform/issues/4134)) ([213e4c9](https://github.com/ngrx/platform/commit/213e4c9))\n- **signals:** remove state checks for better DX ([#4124](https://github.com/ngrx/platform/issues/4124)) ([5749543](https://github.com/ngrx/platform/commit/5749543))\n\n### Features\n\n- **signals:** provide ability to use interface as state type ([#4133](https://github.com/ngrx/platform/issues/4133)) ([9c8304a](https://github.com/ngrx/platform/commit/9c8304a))\n\n<a name=\"17.0.0-rc.0\"></a>\n\n# [17.0.0-rc.0](https://github.com/ngrx/platform/compare/17.0.0-beta.0...17.0.0-rc.0) (2023-11-10)\n\n### Features\n\n- **signals:** add `getState` function ([#4118](https://github.com/ngrx/platform/issues/4118)) ([79b0708](https://github.com/ngrx/platform/commit/79b0708))\n- **signals:** add entities subpackage ([#4090](https://github.com/ngrx/platform/issues/4090)) ([f01bcd1](https://github.com/ngrx/platform/commit/f01bcd1))\n- **store-devtools:** add migration for connectInZone ([#4106](https://github.com/ngrx/platform/issues/4106)) ([73fda59](https://github.com/ngrx/platform/commit/73fda59))\n- **store-devtools:** change connectOutsideZone to be 'true' by default ([#4103](https://github.com/ngrx/platform/issues/4103)) ([d3b4db0](https://github.com/ngrx/platform/commit/d3b4db0)), closes [#4093](https://github.com/ngrx/platform/issues/4093)\n\n<a name=\"17.0.0-beta.0\"></a>\n\n# [17.0.0-beta.0](https://github.com/ngrx/platform/compare/16.3.0...17.0.0-beta.0) (2023-10-30)\n\n### Bug Fixes\n\n- **entity:** set correct return type for getSelectors signature with parent selector ([#4074](https://github.com/ngrx/platform/issues/4074)) ([b3b571e](https://github.com/ngrx/platform/commit/b3b571e))\n- **signals:** do not create nested signals for STATE_SIGNAL property ([#4062](https://github.com/ngrx/platform/issues/4062)) ([71a9d7f](https://github.com/ngrx/platform/commit/71a9d7f))\n- **signals:** improve state type and add type tests ([#4064](https://github.com/ngrx/platform/issues/4064)) ([10c93ed](https://github.com/ngrx/platform/commit/10c93ed)), closes [#4065](https://github.com/ngrx/platform/issues/4065)\n\n### Features\n\n- **component:** remove `LetModule` ([#4087](https://github.com/ngrx/platform/issues/4087)) ([f28ea71](https://github.com/ngrx/platform/commit/f28ea71)), closes [#4077](https://github.com/ngrx/platform/issues/4077)\n- **component:** remove PushModule ([7316d1a](https://github.com/ngrx/platform/commit/7316d1a))\n- **effects:** deprecate act operator ([#4073](https://github.com/ngrx/platform/issues/4073)) ([3dbcadc](https://github.com/ngrx/platform/commit/3dbcadc))\n- **operators:** introduce [@ngrx](https://github.com/ngrx)/operators package ([#4097](https://github.com/ngrx/platform/issues/4097)) ([e93ead4](https://github.com/ngrx/platform/commit/e93ead4)), closes [#4057](https://github.com/ngrx/platform/issues/4057)\n- **signals:** remove selectSignal and rename withSignals to withComputed ([#4075](https://github.com/ngrx/platform/issues/4075)) ([25f95bc](https://github.com/ngrx/platform/commit/25f95bc))\n- upgrade Angular dependencies to v17 pre-release versions ([#4068](https://github.com/ngrx/platform/issues/4068)) ([3d25047](https://github.com/ngrx/platform/commit/3d25047))\n- **signals:** add rxjs-interop subpackage ([#4061](https://github.com/ngrx/platform/issues/4061)) ([fd565ed](https://github.com/ngrx/platform/commit/fd565ed))\n\n### BREAKING CHANGES\n\n- **component:** The LetModule is removed in favor of the standalone LetDirective.\n\nBEFORE:\n\nimport { LetModule } from '@ngrx/component';\n\n@NgModule({\nimports: [\n// ... other imports\nLetModule,\n],\n})\nexport class MyFeatureModule {}\n\nAFTER:\n\nimport { LetDirective } from '@ngrx/component';\n\n@NgModule({\nimports: [\n// ... other imports\nLetDirective,\n],\n})\nexport class MyFeatureModule {}\n\n- **component:** The `PushModule` is deprecated in favor of the standalone `PushPipe`.\n\nBEFORE:\n\n```ts\nimport { PushModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    PushModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\nAFTER:\n\n```ts\nimport { Component } from '@angular/core';\nimport { PushPipe } from '@ngrx/component';\n\n@Component({\n  // ... other metadata\n  standalone: true,\n  imports: [\n    // ... other imports\n    PushPipe,\n  ],\n})\nexport class MyStandaloneComponent {}\n```\n\n- **entity:** Selectors returned by the `adapter.getSelectors` signature that accepts a parent selector are strongly typed.\n\nBEFORE:\n\n```ts\nconst {\n  selectIds, // type: (state: object) => string[] | number[]\n  selectEntities, // type: (state: object) => Dictionary<Book>\n  selectAll, // type: (state: object) => Book[]\n  selectTotal, // type: (state: object) => number\n} = adapter.getSelectors(selectBooksState);\n```\n\nAFTER:\n\n```ts\nconst {\n  selectIds, // type: MemoizedSelector<object, string[] | number[]>\n  selectEntities, // type: MemoizedSelector<object, Dictionary<Book>>\n  selectAll, // type: MemoizedSelector<object, Book[]>\n  selectTotal, // type: MemoizedSelector<object, number>\n} = adapter.getSelectors(selectBooksState);\n```\n\n- The minimum required version of Angular has been updated\n\nBEFORE:\n\nThe minimum required version of Angular is 16.x\n\nAFTER:\n\nThe minimum required version of Angular is 17.x\n\n<a name=\"16.3.0\"></a>\n\n# [16.3.0](https://github.com/ngrx/platform/compare/16.2.0...16.3.0) (2023-10-03)\n\n### Bug Fixes\n\n- **schematics:** make action props plural ([#4017](https://github.com/ngrx/platform/issues/4017)) ([3a56412](https://github.com/ngrx/platform/commit/3a56412))\n\n### Features\n\n- **data:** standalone support for ng add [@ngrx](https://github.com/ngrx)/data ([#4019](https://github.com/ngrx/platform/issues/4019)) ([fdd701e](https://github.com/ngrx/platform/commit/fdd701e)), closes [#3935](https://github.com/ngrx/platform/issues/3935)\n- **eslint-plugin:** add avoid-combining-component-store-selectors rule ([#4043](https://github.com/ngrx/platform/issues/4043)) ([0bff440](https://github.com/ngrx/platform/commit/0bff440))\n- **eslint-plugin:** add new avoid-mapping-component-store-selectors rule ([#4026](https://github.com/ngrx/platform/issues/4026)) ([40477dd](https://github.com/ngrx/platform/commit/40477dd)), closes [#3940](https://github.com/ngrx/platform/issues/3940)\n- **signals:** add `patchState` function and remove `$update` method ([#4037](https://github.com/ngrx/platform/issues/4037)) ([f2514ba](https://github.com/ngrx/platform/commit/f2514ba))\n- **signals:** add signalState and selectSignal APIs ([#4007](https://github.com/ngrx/platform/issues/4007)) ([745d91f](https://github.com/ngrx/platform/commit/745d91f)), closes [#3993](https://github.com/ngrx/platform/issues/3993)\n- **signals:** add signalStore and signalStoreFeature ([#4049](https://github.com/ngrx/platform/issues/4049)) ([0010281](https://github.com/ngrx/platform/commit/0010281)), closes [#4000](https://github.com/ngrx/platform/issues/4000)\n- **signals:** initial setup ([#4002](https://github.com/ngrx/platform/issues/4002)) ([b0d63fd](https://github.com/ngrx/platform/commit/b0d63fd)), closes [#3992](https://github.com/ngrx/platform/issues/3992)\n- **store:** prettify createFeature result ([#4014](https://github.com/ngrx/platform/issues/4014)) ([f179316](https://github.com/ngrx/platform/commit/f179316))\n\n<a name=\"16.2.0\"></a>\n\n# [16.2.0](https://github.com/ngrx/platform/compare/16.1.0...16.2.0) (2023-08-07)\n\n### Bug Fixes\n\n- **data:** make DataServiceError extend from Error ([#3988](https://github.com/ngrx/platform/issues/3988)) ([0b98a65](https://github.com/ngrx/platform/commit/0b98a65))\n- **effects:** register functional effects from object without prototype ([#3984](https://github.com/ngrx/platform/issues/3984)) ([1879cc9](https://github.com/ngrx/platform/commit/1879cc9)), closes [#3972](https://github.com/ngrx/platform/issues/3972)\n- **eslint-plugin:** fix prefer-contact-latest-from rule to detect inject ([#3946](https://github.com/ngrx/platform/issues/3946)) ([2efd805](https://github.com/ngrx/platform/commit/2efd805))\n- **eslint-plugin:** prefix-selectors-with-select suggestion ([#3959](https://github.com/ngrx/platform/issues/3959)) ([27f09df](https://github.com/ngrx/platform/commit/27f09df))\n- **eslint-plugin:** support inject for no-typed-global-store rule ([#3951](https://github.com/ngrx/platform/issues/3951)) ([d3e84d8](https://github.com/ngrx/platform/commit/d3e84d8))\n- **eslint-plugin:** support inject for use-consistent-global-store-name rule ([#3983](https://github.com/ngrx/platform/issues/3983)) ([caa74ff](https://github.com/ngrx/platform/commit/caa74ff))\n- **store-devtools:** resolve memory leak when devtools are destroyed ([#3965](https://github.com/ngrx/platform/issues/3965)) ([644f0b6](https://github.com/ngrx/platform/commit/644f0b6))\n\n### Features\n\n- **eslint-plugin:** include docs URL in lint message ([#3944](https://github.com/ngrx/platform/issues/3944)) ([a1576de](https://github.com/ngrx/platform/commit/a1576de))\n- **schematics:** add entity generation as part of feature schematic ([#3850](https://github.com/ngrx/platform/issues/3850)) ([19ebb0a](https://github.com/ngrx/platform/commit/19ebb0a))\n- **store-devtools:** provide the ability to connect extension outside of Angular zone ([#3970](https://github.com/ngrx/platform/issues/3970)) ([1ee80e5](https://github.com/ngrx/platform/commit/1ee80e5)), closes [#3839](https://github.com/ngrx/platform/issues/3839)\n\n<a name=\"16.1.0\"></a>\n\n# [16.1.0](https://github.com/ngrx/platform/compare/16.0.1...16.1.0) (2023-07-06)\n\n### Bug Fixes\n\n- **eslint:** fix inject function based injection not detecting store ([#3936](https://github.com/ngrx/platform/issues/3936)) ([8a5884d](https://github.com/ngrx/platform/commit/8a5884d)), closes [#3834](https://github.com/ngrx/platform/issues/3834)\n- **eslint:** updater-explicit-return-type not applied when inheritance ([#3928](https://github.com/ngrx/platform/issues/3928)) ([41a5076](https://github.com/ngrx/platform/commit/41a5076))\n\n### Features\n\n- **component-store:** added custom equal option in select ([#3933](https://github.com/ngrx/platform/issues/3933)) ([c4b5cc5](https://github.com/ngrx/platform/commit/c4b5cc5))\n\n<a name=\"16.0.1\"></a>\n\n## [16.0.1](https://github.com/ngrx/platform/compare/16.0.0...16.0.1) (2023-06-01)\n\n### Bug Fixes\n\n- **component:** untrack subscription in ngrxPush pipe ([#3918](https://github.com/ngrx/platform/issues/3918)) ([a1688e4](https://github.com/ngrx/platform/commit/a1688e4))\n- **ngrx.io:** preserve sidenav width for larger menu items ([#3923](https://github.com/ngrx/platform/issues/3923)) ([ef73714](https://github.com/ngrx/platform/commit/ef73714))\n\n<a name=\"16.0.0\"></a>\n\n# [16.0.0](https://github.com/ngrx/platform/compare/16.0.0-rc.0...16.0.0) (2023-05-09)\n\n### Bug Fixes\n\n- **component-store:** use default equality function for selectSignal ([#3884](https://github.com/ngrx/platform/issues/3884)) ([5843e7f](https://github.com/ngrx/platform/commit/5843e7f))\n- **store:** add Signal equal function for immutable object comparison ([#3883](https://github.com/ngrx/platform/issues/3883)) ([634fdcb](https://github.com/ngrx/platform/commit/634fdcb))\n- **store:** move Angular Signal interop into State service ([#3879](https://github.com/ngrx/platform/issues/3879)) ([8cb5795](https://github.com/ngrx/platform/commit/8cb5795)), closes [#3869](https://github.com/ngrx/platform/issues/3869)\n- **store-devtools:** add state signal to StateObservable ([#3889](https://github.com/ngrx/platform/issues/3889)) ([ad6e14a](https://github.com/ngrx/platform/commit/ad6e14a))\n\n### Features\n\n- add ng add support for standalone config to NgRx packages ([#3881](https://github.com/ngrx/platform/issues/3881)) ([58508e3](https://github.com/ngrx/platform/commit/58508e3))\n- **component:** add migration for LetModule and PushModule ([#3872](https://github.com/ngrx/platform/issues/3872)) ([5f07eda](https://github.com/ngrx/platform/commit/5f07eda))\n- **component:** make LetDirective and PushPipe standalone ([#3826](https://github.com/ngrx/platform/issues/3826)) ([985d80c](https://github.com/ngrx/platform/commit/985d80c)), closes [#3804](https://github.com/ngrx/platform/issues/3804)\n- **store:** add support of standalone API for ng add store ([#3874](https://github.com/ngrx/platform/issues/3874)) ([7aec84d](https://github.com/ngrx/platform/commit/7aec84d))\n\n<a name=\"16.0.0-rc.1\"></a>\n\n# [16.0.0-rc.1](https://github.com/ngrx/platform/compare/16.0.0-rc.0...16.0.0-rc.1) (2023-05-09)\n\n### Bug Fixes\n\n- **component-store:** use default equality function for selectSignal ([#3884](https://github.com/ngrx/platform/issues/3884)) ([5843e7f](https://github.com/ngrx/platform/commit/5843e7f))\n- **store:** add Signal equal function for immutable object comparison ([#3883](https://github.com/ngrx/platform/issues/3883)) ([634fdcb](https://github.com/ngrx/platform/commit/634fdcb))\n- **store:** move Angular Signal interop into State service ([#3879](https://github.com/ngrx/platform/issues/3879)) ([8cb5795](https://github.com/ngrx/platform/commit/8cb5795)), closes [#3869](https://github.com/ngrx/platform/issues/3869)\n\n### Features\n\n- add ng add support for standalone config to NgRx packages ([#3881](https://github.com/ngrx/platform/issues/3881)) ([58508e3](https://github.com/ngrx/platform/commit/58508e3))\n- **component:** add migration for LetModule and PushModule ([#3872](https://github.com/ngrx/platform/issues/3872)) ([5f07eda](https://github.com/ngrx/platform/commit/5f07eda))\n- **component:** make LetDirective and PushPipe standalone ([#3826](https://github.com/ngrx/platform/issues/3826)) ([985d80c](https://github.com/ngrx/platform/commit/985d80c)), closes [#3804](https://github.com/ngrx/platform/issues/3804)\n- **store:** add support of standalone API for ng add store ([#3874](https://github.com/ngrx/platform/issues/3874)) ([7aec84d](https://github.com/ngrx/platform/commit/7aec84d))\n\n<a name=\"16.0.0-rc.0\"></a>\n\n# [16.0.0-rc.0](https://github.com/ngrx/platform/compare/16.0.0-beta.0...16.0.0-rc.0) (2023-05-04)\n\n### Bug Fixes\n\n- **data:** make non-optimistic add command entity partial ([#3859](https://github.com/ngrx/platform/issues/3859)) ([93ee1db](https://github.com/ngrx/platform/commit/93ee1db))\n\n### Features\n\n- **component-store:** add selectSignal options ([503e9d8](https://github.com/ngrx/platform/commit/503e9d8))\n- **component-store:** add selectSignal signature that combines provided signals ([#3863](https://github.com/ngrx/platform/issues/3863)) ([07ba3fa](https://github.com/ngrx/platform/commit/07ba3fa))\n- **store:** add selectSignal options ([0a13c4d](https://github.com/ngrx/platform/commit/0a13c4d))\n\n<a name=\"16.0.0-beta.0\"></a>\n\n# [16.0.0-beta.0](https://github.com/ngrx/platform/compare/15.4.0...16.0.0-beta.0) (2023-04-27)\n\n### Bug Fixes\n\n- **data:** allow 0 to be a valid key ([#3830](https://github.com/ngrx/platform/issues/3830)) ([e50126d](https://github.com/ngrx/platform/commit/e50126d)), closes [#3828](https://github.com/ngrx/platform/issues/3828)\n- **effects:** run user provided effects defined as injection token ([#3851](https://github.com/ngrx/platform/issues/3851)) ([cdaeeb6](https://github.com/ngrx/platform/commit/cdaeeb6)), closes [#3848](https://github.com/ngrx/platform/issues/3848)\n- **store:** correctly infer action group events defined as empty object ([#3833](https://github.com/ngrx/platform/issues/3833)) ([dc78447](https://github.com/ngrx/platform/commit/dc78447))\n- **store-devtools:** correctly import state when feature is set to true ([#3855](https://github.com/ngrx/platform/issues/3855)) ([0df3419](https://github.com/ngrx/platform/commit/0df3419)), closes [#3636](https://github.com/ngrx/platform/issues/3636)\n\n### Features\n\n- **component-store:** add selectSignal method for interop with Angular Signals ([#3861](https://github.com/ngrx/platform/issues/3861)) ([195d5ac](https://github.com/ngrx/platform/commit/195d5ac))\n- **component-store:** add tapResponse signature with observer object ([#3829](https://github.com/ngrx/platform/issues/3829)) ([3a5e5d8](https://github.com/ngrx/platform/commit/3a5e5d8))\n- **effects:** accept ObservableInput as concatLatestFrom argument ([#3838](https://github.com/ngrx/platform/issues/3838)) ([34dd28c](https://github.com/ngrx/platform/commit/34dd28c))\n- **router-store:** add [@nrwl](https://github.com/nrwl)/angular data persistence operators ([#3841](https://github.com/ngrx/platform/issues/3841)) ([4482751](https://github.com/ngrx/platform/commit/4482751)), closes [#3777](https://github.com/ngrx/platform/issues/3777)\n- **router-store:** remove deprecated getSelectors method ([#3816](https://github.com/ngrx/platform/issues/3816)) ([351a75e](https://github.com/ngrx/platform/commit/351a75e)), closes [#3815](https://github.com/ngrx/platform/issues/3815)\n- **schematics:** replace `any` type with `unknown` type ([#3827](https://github.com/ngrx/platform/issues/3827)) ([0ea2933](https://github.com/ngrx/platform/commit/0ea2933))\n- **schematics:** Use createActionGroup in schematics ([#3791](https://github.com/ngrx/platform/issues/3791)) ([f6ce20f](https://github.com/ngrx/platform/commit/f6ce20f))\n- **store:** add createMockStore migration ([#3810](https://github.com/ngrx/platform/issues/3810)) ([ded4240](https://github.com/ngrx/platform/commit/ded4240))\n- **store:** add selectSignal method for interop with Angular Signals ([#3856](https://github.com/ngrx/platform/issues/3856)) ([999dcb6](https://github.com/ngrx/platform/commit/999dcb6))\n- **store:** preserve the event name case with createActionGroup ([#3832](https://github.com/ngrx/platform/issues/3832)) ([482fa5d](https://github.com/ngrx/platform/commit/482fa5d))\n- **store:** remove deprecated createFeature method ([#3825](https://github.com/ngrx/platform/issues/3825)) ([fd8f347](https://github.com/ngrx/platform/commit/fd8f347)), closes [#3814](https://github.com/ngrx/platform/issues/3814)\n- **store:** remove forbidden chars and empty str checks from createActionGroup ([#3857](https://github.com/ngrx/platform/issues/3857)) ([e37c57b](https://github.com/ngrx/platform/commit/e37c57b))\n- convert entity and router selectors interfaces to types ([#3853](https://github.com/ngrx/platform/issues/3853)) ([73eb55c](https://github.com/ngrx/platform/commit/73eb55c))\n- **store:** remove getMockStore in favor of createMockStore ([#3835](https://github.com/ngrx/platform/issues/3835)) ([8d0ed8e](https://github.com/ngrx/platform/commit/8d0ed8e))\n- **store:** use strict projectors for createFeature selectors ([#3799](https://github.com/ngrx/platform/issues/3799)) ([bafd121](https://github.com/ngrx/platform/commit/bafd121))\n- update to Angular v16.0.0-next.6 release ([#3831](https://github.com/ngrx/platform/issues/3831)) ([d7e03df](https://github.com/ngrx/platform/commit/d7e03df))\n\n### BREAKING CHANGES\n\n- **store:** The event name case is preserved when converting to the action name by using the `createActionGroup` function.\n\nBEFORE:\n\nAll letters of the event name will be lowercase, except for the initial letters of words starting from the second word, which will be uppercase.\n\n```ts\nconst authApiActions = createActionGroup({\n  source: 'Auth API',\n  events: {\n    'LogIn Success': emptyProps(),\n    'login failure': emptyProps(),\n    'Logout Success': emptyProps(),\n    logoutFailure: emptyProps(),\n  },\n});\n\n// generated actions:\nconst { loginSuccess, loginFailure, logoutSuccess, logoutfailure } =\n  authApiActions;\n```\n\nAFTER:\n\nThe initial letter of the first word of the event name will be lowercase, and the initial letters of the other words will be uppercase. The case of other letters in the event name will remain the same.\n\n```ts\nconst { logInSuccess, loginFailure, logoutSuccess, logoutFailure } =\n  authApiActions;\n```\n\n- **store:** The `createFeature` signature with root state is removed in favor of a signature without root state.\n  An automatic migration is added to remove this signature.\n\nBEFORE:\n\n```ts\ninterface AppState {\n  users: State;\n}\n\nexport const usersFeature = createFeature<AppState>({\n  name: 'users',\n  reducer: createReducer(initialState /* case reducers */),\n});\n```\n\nAFTER:\n\n```ts\nexport const usersFeature = createFeature({\n  name: 'users',\n  reducer: createReducer(initialState /* case reducers */),\n});\n```\n\n- **router-store:** The deprecated `getSelectors` function has been removed from the `@ngrx/router-store` package.\n\nBEFORE:\n\nThe @ngrx/router-store package exports the `getSelectors` function.\n\nAFTER:\n\nThe @ngrx/router-store package no longer exports the `getSelectors` function. A migration has been provided to replace existing usage\n\n- **schematics:** NgRx Schematics do not use `any` types to define actions, these are replaced with the `unknown` type.\n\nBEFORE:\n\nSchematics used the `any` type to declare action payload type.\n\nAFTER:\n\nSchematics use the `unknown` type to declare action payload type.\n\n- **store:** The `getMockStore` function is removed in favor of `createMockStore`\n\nBEFORE:\n\n```ts\nimport { getMockStore } from '@ngrx/store/testing';\nconst mockStore = getMockStore();\n```\n\nAFTER:\n\n```ts\nimport { createMockStore } from '@ngrx/store/testing';\nconst mockStore = createMockStore();\n```\n\n- **store:** Projectors of selectors generated by createFeature are strongly typed.\n\nBEFORE:\n\nProjector function arguments of selectors generated by createFeature are not strongly typed:\n\n```ts\nconst counterFeature = createFeature({\n  name: 'counter',\n  reducer: createReducer({ count: 0 }),\n});\n\ncounterFeature.selectCount.projector;\n// type: (...args: any[]) => number\n```\n\nAFTER:\n\nProjector function arguments of selectors generated by createFeature are strongly typed:\n\n```ts\nconst counterFeature = createFeature({\n  name: 'counter',\n  reducer: createReducer({ count: 0 }),\n});\n\ncounterFeature.selectCount.projector;\n// type: (featureState: { count: number; }) => number\n```\n\n<a name=\"15.4.0\"></a>\n\n# [15.4.0](https://github.com/ngrx/platform/compare/15.3.0...15.4.0) (2023-03-16)\n\n### Bug Fixes\n\n- **data:** correctly handle HttpOptions when provided ([#3795](https://github.com/ngrx/platform/issues/3795)) ([cbdf524](https://github.com/ngrx/platform/commit/cbdf524)), closes [#3794](https://github.com/ngrx/platform/issues/3794)\n- **eslint-plugin:** ignore select name within createFeature ([#3788](https://github.com/ngrx/platform/issues/3788)) ([b58f9a3](https://github.com/ngrx/platform/commit/b58f9a3)), closes [#3786](https://github.com/ngrx/platform/issues/3786)\n\n### Features\n\n- **store:** rename getMockStore to createMockStore ([#3789](https://github.com/ngrx/platform/issues/3789)) ([6d615b3](https://github.com/ngrx/platform/commit/6d615b3)), closes [#3781](https://github.com/ngrx/platform/issues/3781)\n- **store:** use createFeature in feature schematics ([#3776](https://github.com/ngrx/platform/issues/3776)) ([9b3647f](https://github.com/ngrx/platform/commit/9b3647f)), closes [#3741](https://github.com/ngrx/platform/issues/3741)\n\n<a name=\"15.3.0\"></a>\n\n# [15.3.0](https://github.com/ngrx/platform/compare/15.2.1...15.3.0) (2023-02-13)\n\n### Bug Fixes\n\n- **store:** support using factory selectors as extra selectors ([#3767](https://github.com/ngrx/platform/issues/3767)) ([f4714c3](https://github.com/ngrx/platform/commit/f4714c3))\n\n### Features\n\n- **data:** Add HttpOptions to EntityActionOptions ([#3663](https://github.com/ngrx/platform/issues/3663)) ([#3664](https://github.com/ngrx/platform/issues/3664)) ([dd745c0](https://github.com/ngrx/platform/commit/dd745c0))\n\n<a name=\"15.2.1\"></a>\n\n## [15.2.1](https://github.com/ngrx/platform/compare/15.2.0...15.2.1) (2023-01-26)\n\n<a name=\"15.2.0\"></a>\n\n# [15.2.0](https://github.com/ngrx/platform/compare/15.1.0...15.2.0) (2023-01-26)\n\n### Features\n\n- **data:** add loadWithQuery method ([#3717](https://github.com/ngrx/platform/issues/3717)) ([06b97bf](https://github.com/ngrx/platform/commit/06b97bf)), closes [#3088](https://github.com/ngrx/platform/issues/3088)\n- **effects:** add ability to create functional effects ([#3669](https://github.com/ngrx/platform/issues/3669)) ([dd76c63](https://github.com/ngrx/platform/commit/dd76c63)), closes [#3668](https://github.com/ngrx/platform/issues/3668)\n- **router-store:** add migration for getRouterSelectors ([#3753](https://github.com/ngrx/platform/issues/3753)) ([a785331](https://github.com/ngrx/platform/commit/a785331))\n- **router-store:** rename getSelectors to getRouterSelectors ([#3745](https://github.com/ngrx/platform/issues/3745)) ([7ad76b8](https://github.com/ngrx/platform/commit/7ad76b8)), closes [#3738](https://github.com/ngrx/platform/issues/3738)\n- **store:** add ability to create extra selectors with createFeature ([#3744](https://github.com/ngrx/platform/issues/3744)) ([e4f873b](https://github.com/ngrx/platform/commit/e4f873b)), closes [#3719](https://github.com/ngrx/platform/issues/3719)\n- **store:** add createFeature migration ([#3759](https://github.com/ngrx/platform/issues/3759)) ([b3c5931](https://github.com/ngrx/platform/commit/b3c5931))\n- **store:** deprecate createFeature signature with root state ([#3756](https://github.com/ngrx/platform/issues/3756)) ([ccb3b93](https://github.com/ngrx/platform/commit/ccb3b93)), closes [#3737](https://github.com/ngrx/platform/issues/3737)\n\n<a name=\"15.1.0\"></a>\n\n# [15.1.0](https://github.com/ngrx/platform/compare/15.0.0...15.1.0) (2022-12-21)\n\n### Bug Fixes\n\n- **component-store:** revert throwError usages with factory for RxJS 6 compatibility ([726dfb7](https://github.com/ngrx/platform/commit/726dfb7))\n- **data:** revert throwError usages with factory for RxJS 6 compatibility ([a137b59](https://github.com/ngrx/platform/commit/a137b59)), closes [#3702](https://github.com/ngrx/platform/issues/3702)\n- **eslint-plugin:** remove [@angular-devkit](https://github.com/angular-devkit)/schematics dependency ([#3710](https://github.com/ngrx/platform/issues/3710)) ([f0ae915](https://github.com/ngrx/platform/commit/f0ae915)), closes [#3709](https://github.com/ngrx/platform/issues/3709)\n- **schematics:** disable package json peer dep updates during build ([#3692](https://github.com/ngrx/platform/issues/3692)) ([9cfc103](https://github.com/ngrx/platform/commit/9cfc103)), closes [#3691](https://github.com/ngrx/platform/issues/3691)\n- **store:** support using `createActionGroup` with props typed as unions ([#3713](https://github.com/ngrx/platform/issues/3713)) ([e75fa1a](https://github.com/ngrx/platform/commit/e75fa1a)), closes [#3712](https://github.com/ngrx/platform/issues/3712)\n\n### Features\n\n- **router-store:** add new selectRouteDataParam selector ([#3673](https://github.com/ngrx/platform/issues/3673)) ([#3686](https://github.com/ngrx/platform/issues/3686)) ([81bc0d9](https://github.com/ngrx/platform/commit/81bc0d9))\n- **store:** support using`createSelector` with selectors dictionary ([#3703](https://github.com/ngrx/platform/issues/3703)) ([5c87dda](https://github.com/ngrx/platform/commit/5c87dda)), closes [#3677](https://github.com/ngrx/platform/issues/3677)\n\n<a name=\"15.0.0\"></a>\n\n# [15.0.0](https://github.com/ngrx/platform/compare/15.0.0-rc.0...15.0.0) (2022-11-29)\n\n### Features\n\n- **store-devtools:** add redux dev tool trace support ([#3517](https://github.com/ngrx/platform/issues/3517)) ([#3665](https://github.com/ngrx/platform/issues/3665)) ([187802a](https://github.com/ngrx/platform/commit/187802a)), closes [#1868](https://github.com/ngrx/platform/issues/1868)\n\n<a name=\"15.0.0-rc.0\"></a>\n\n# [15.0.0-rc.0](https://github.com/ngrx/platform/compare/15.0.0-beta.1...15.0.0-rc.0) (2022-11-23)\n\n### Features\n\n- **component:** clear `LetDirective` view when replaced observable is in suspense state ([#3671](https://github.com/ngrx/platform/issues/3671)) ([ec59c4b](https://github.com/ngrx/platform/commit/ec59c4b))\n- **component:** remove $ prefix from LetViewContext property names ([#3670](https://github.com/ngrx/platform/issues/3670)) ([b3b21e6](https://github.com/ngrx/platform/commit/b3b21e6))\n\n### BREAKING CHANGES\n\n- **component:** The `LetDirective` view will be cleared when the replaced observable is in a suspense state. Also, the `suspense` property is removed from the `LetViewContext` because it would always be `false` when the `LetDirective` view is rendered. Instead of `suspense` property, use the suspense template to handle the suspense state.\n\nBEFORE:\n\nThe `LetDirective` view will not be cleared when the replaced observable is in a suspense state and the suspense template is not passed:\n\n```ts\n@Component({\n  template: `\n    <!-- When button is clicked, the 'LetDirective' view won't be cleared. -->\n    <!-- Instead, the value of 'o' will be 'undefined' until the replaced -->\n    <!-- observable emits the first value (after 1 second). -->\n    <p *ngrxLet=\"obs$ as o\">{{ o }}</p>\n    <button (click)=\"replaceObs()\">Replace Observable</button>\n  `,\n})\nexport class TestComponent {\n  obs$ = of(1);\n\n  replaceObs(): void {\n    this.obs$ = of(2).pipe(delay(1000));\n  }\n}\n```\n\nAFTER:\n\nThe `LetDirective` view will be cleared when the replaced observable is in a suspense state and the suspense template is not passed:\n\n```ts\n@Component({\n  template: `\n    <!-- When button is clicked, the 'LetDirective' view will be cleared. -->\n    <!-- The view will be created again when the replaced observable -->\n    <!-- emits the first value (after 1 second). -->\n    <p *ngrxLet=\"obs$ as o\">{{ o }}</p>\n    <button (click)=\"replaceObs()\">Replace Observable</button>\n  `,\n})\nexport class TestComponent {\n  obs$ = of(1);\n\n  replaceObs(): void {\n    this.obs$ = of(2).pipe(delay(1000));\n  }\n}\n```\n\n- **component:** The `$` prefix is removed from `LetViewContext` property names.\n\nBEFORE:\n\n```html\n<ng-container *ngrxLet=\"obs$; $error as e; $complete as c\">\n  ...\n</ng-container>\n```\n\nAFTER:\n\n```html\n<ng-container *ngrxLet=\"obs$; error as e; complete as c\">\n  ...\n</ng-container>\n```\n\n<a name=\"15.0.0-beta.1\"></a>\n\n# [15.0.0-beta.1](https://github.com/ngrx/platform/compare/15.0.0-beta.0...15.0.0-beta.1) (2022-11-18)\n\n### Features\n\n- **data:** add initial standalone APIs ([#3647](https://github.com/ngrx/platform/issues/3647)) ([aa7ed66](https://github.com/ngrx/platform/commit/aa7ed66)), closes [#3553](https://github.com/ngrx/platform/issues/3553)\n- **data:** add withEffects feature for provideEntityData ([#3656](https://github.com/ngrx/platform/issues/3656)) ([a6959e8](https://github.com/ngrx/platform/commit/a6959e8))\n- **effects:** forRoot and forFeature accept spreaded array ([#3638](https://github.com/ngrx/platform/issues/3638)) ([0eaa536](https://github.com/ngrx/platform/commit/0eaa536))\n- **router-store:** return resolved title via selectTitle ([#3648](https://github.com/ngrx/platform/issues/3648)) ([cc04e2f](https://github.com/ngrx/platform/commit/cc04e2f)), closes [#3622](https://github.com/ngrx/platform/issues/3622)\n- **schematics:** add display block flag ([e0d368d](https://github.com/ngrx/platform/commit/e0d368d))\n- **schematics:** add standalone flag ([b0bd2ff](https://github.com/ngrx/platform/commit/b0bd2ff))\n- **schematics:** replace environments usage with isDevMode ([#3645](https://github.com/ngrx/platform/issues/3645)) ([4f61b63](https://github.com/ngrx/platform/commit/4f61b63)), closes [#3618](https://github.com/ngrx/platform/issues/3618)\n\n### BREAKING CHANGES\n\n- **router-store:** Property `title: string | undefined` is added to the `MinimalActivatedRouteSnapshot` interface.\n\nBEFORE:\n\nThe `MinimalActivatedRouteSnapshot` interface doesn't contain the `title` property.\n\nAFTER:\n\nThe `MinimalActivatedRouteSnapshot` interface contains the required `title` property.\n\n<a name=\"15.0.0-beta.0\"></a>\n\n# [15.0.0-beta.0](https://github.com/ngrx/platform/compare/14.3.2...15.0.0-beta.0) (2022-11-03)\n\n### Features\n\n- **component:** add migration for replacing ReactiveComponentModule ([#3506](https://github.com/ngrx/platform/issues/3506)) ([49c6cf3](https://github.com/ngrx/platform/commit/49c6cf3)), closes [#3491](https://github.com/ngrx/platform/issues/3491)\n- **component:** handle observable dictionaries ([#3602](https://github.com/ngrx/platform/issues/3602)) ([42efccb](https://github.com/ngrx/platform/commit/42efccb)), closes [#3545](https://github.com/ngrx/platform/issues/3545)\n- **component:** remove ReactiveComponentModule ([#3643](https://github.com/ngrx/platform/issues/3643)) ([4bdf345](https://github.com/ngrx/platform/commit/4bdf345)), closes [#3623](https://github.com/ngrx/platform/issues/3623)\n- **component-store:** Add SelectorObject to `select` ([#3629](https://github.com/ngrx/platform/issues/3629)) ([f8d0241](https://github.com/ngrx/platform/commit/f8d0241)), closes [#3632](https://github.com/ngrx/platform/issues/3632) [#3631](https://github.com/ngrx/platform/issues/3631)\n- **effects:** change the signature of provideEffect ([#3587](https://github.com/ngrx/platform/issues/3587)) ([899afe7](https://github.com/ngrx/platform/commit/899afe7))\n- **effects:** migration for provideEffects argument ([#3601](https://github.com/ngrx/platform/issues/3601)) ([f7dfeab](https://github.com/ngrx/platform/commit/f7dfeab))\n- **effects:** remove @Effect decorator ([#3634](https://github.com/ngrx/platform/issues/3634)) ([96c5bdd](https://github.com/ngrx/platform/commit/96c5bdd)), closes [#3617](https://github.com/ngrx/platform/issues/3617)\n- **eslint-plugin:** remove rules using @Effect ([#3635](https://github.com/ngrx/platform/issues/3635)) ([5f74e61](https://github.com/ngrx/platform/commit/5f74e61))\n- **schematics:** drop support for TypeScript <4.8 ([#3631](https://github.com/ngrx/platform/issues/3631)) ([b9c1ab6](https://github.com/ngrx/platform/commit/b9c1ab6))\n- **store:** make reducers arg of StoreModule.forRoot optional ([#3632](https://github.com/ngrx/platform/issues/3632)) ([e5177aa](https://github.com/ngrx/platform/commit/e5177aa))\n- **store:** strict projector for selectors with props ([#3640](https://github.com/ngrx/platform/issues/3640)) ([351459f](https://github.com/ngrx/platform/commit/351459f)), closes [#3571](https://github.com/ngrx/platform/issues/3571)\n- **store:** strict projectors ([#3581](https://github.com/ngrx/platform/issues/3581)) ([43198a2](https://github.com/ngrx/platform/commit/43198a2)), closes [#3571](https://github.com/ngrx/platform/issues/3571)\n\n### BREAKING CHANGES\n\n- **component:** `ReactiveComponentModule` is removed in favor of `LetModule` and `PushModule`.\n\nBEFORE:\n\n```ts\nimport { ReactiveComponentModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    ReactiveComponentModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\nAFTER:\n\n```ts\nimport { LetModule, PushModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    LetModule,\n    PushModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\n- **store:** The projector method has become strict\n\nBEFORE:\n\nYou could pass any arguments to the projector method\n\nconst selector = createSelector(\nselectString, // returning a string\nselectNumber, // returning a number\n(s, n, prefix: string) => {\nreturn prefix + s.repeat(n);\n}\n)\n\n// you could pass any argument\nselector.projector(1, 'a', true);\n\nAFTER:\n\nconst selector = createSelector(\nselectString, // returning a string\nselectNumber, // returning a number\n(s, n, prefix: string) => {\nreturn prefix + s.repeat(n);\n}\n)\n\n// this throws\nselector.projector(1, 'a', true);\n// this does not throw because the arguments have the correct type\nselector.projector(1, 'a', 'prefix');\n\n- **store:** The projector function on the selector is type-safe by default.\n\nBEFORE:\n\nThe projector is not type-safe by default, allowing for potential mismatch types in the projector function.\n\n```ts\nconst mySelector = createSelector(\n  () => 'one',\n  () => 2,\n  (one, two) => 3\n);\n\nmySelector.projector(); // <- type is projector(...args: any[]): number\n```\n\nAFTER:\n\nThe projector is strict by default, but can be bypassed with an `any` generic parameter.\n\n```ts\nconst mySelector = createSelector(\n  () => 'one',\n  () => 2,\n  (one, two) => 3\n);\n\nmySelector.projector(); // <- Results in type error. Type is projector(s1: string, s2: number): number\n```\n\nTo retain previous behavior\n\n```ts\nconst mySelector = createSelector(\n  () => 'one',\n  () => 2,\n  (one, two) => 3\n)(mySelector.projector as any)();\n```\n\n- **effects:** The @Effect decorator is removed\n\nBEFORE:\n\nDefining an effect is done with @Effect\n\n@Effect()\ndata$ = this.actions$.pipe();\n\nAFTER:\n\nDefining an effect is done with createEffect\n\ndata$ = createEffect(() => this.actions$.pipe());\n\n- **effects:** The signature of `provideEffects` is changed to expect a\n  spreaded array of effects.\n\nBEFORE:\n\n`provideEffects` expecteded the effects to be passed as an array.\n\n```ts\n// single effect\nprovideEffects([MyEffect]);\n\n// multiple effects\nprovideEffects([MyEffect, MySecondEffect]);\n```\n\nAFTER:\n\n`provideEffects` expects the effects as a spreaded array as argument.\n\n```ts\n// single effect\nprovideEffects(MyEffect);\n\n// multiple effects\nprovideEffects(MyEffect, MySecondEffect);\n```\n\n<a name=\"14.3.2\"></a>\n\n## [14.3.2](https://github.com/ngrx/platform/compare/14.3.1...14.3.2) (2022-10-04)\n\n### Bug Fixes\n\n- **component:** replace animationFrameScheduler with requestAnimationFrame ([#3592](https://github.com/ngrx/platform/issues/3592)) ([0a4d2dd](https://github.com/ngrx/platform/commit/0a4d2dd)), closes [#3591](https://github.com/ngrx/platform/issues/3591)\n- **component-store:** use asapScheduler to schedule lifecycle hooks check ([#3580](https://github.com/ngrx/platform/issues/3580)) ([02431b4](https://github.com/ngrx/platform/commit/02431b4)), closes [#3573](https://github.com/ngrx/platform/issues/3573)\n- **eslint-plugin:** avoid-combining-selectors with arrays should warn ([#3566](https://github.com/ngrx/platform/issues/3566)) ([4b0c6de](https://github.com/ngrx/platform/commit/4b0c6de))\n- **router-store:** set undefined for unserializable route title ([#3593](https://github.com/ngrx/platform/issues/3593)) ([8eb4001](https://github.com/ngrx/platform/commit/8eb4001)), closes [#3495](https://github.com/ngrx/platform/issues/3495)\n- **store:** fix typing of on fn ([#3577](https://github.com/ngrx/platform/issues/3577)) ([d054aa9](https://github.com/ngrx/platform/commit/d054aa9)), closes [#3576](https://github.com/ngrx/platform/issues/3576)\n\n<a name=\"14.3.1\"></a>\n\n## [14.3.1](https://github.com/ngrx/platform/compare/14.3.0...14.3.1) (2022-09-08)\n\n### Bug Fixes\n\n- add support for TypeScript 4.8 ([#3548](https://github.com/ngrx/platform/issues/3548)) ([d558ce1](https://github.com/ngrx/platform/commit/d558ce1)), closes [#3547](https://github.com/ngrx/platform/issues/3547)\n- **eslint-plugin:** avoid-mapping-selectors don't report on ThisExpression ([#3546](https://github.com/ngrx/platform/issues/3546)) ([a28175c](https://github.com/ngrx/platform/commit/a28175c)), closes [#3511](https://github.com/ngrx/platform/issues/3511)\n\n<a name=\"14.3.0\"></a>\n\n# [14.3.0](https://github.com/ngrx/platform/compare/14.2.0...14.3.0) (2022-08-25)\n\n### Features\n\n- **effects:** add provideEffects function ([#3524](https://github.com/ngrx/platform/issues/3524)) ([db35bfe](https://github.com/ngrx/platform/commit/db35bfe)), closes [#3522](https://github.com/ngrx/platform/issues/3522)\n- **router-store:** add provideRouterStore function ([#3532](https://github.com/ngrx/platform/issues/3532)) ([511b7cf](https://github.com/ngrx/platform/commit/511b7cf)), closes [#3528](https://github.com/ngrx/platform/issues/3528)\n- **store:** add provideStore and provideState functions for standalone APIs ([#3539](https://github.com/ngrx/platform/issues/3539)) ([5639c1e](https://github.com/ngrx/platform/commit/5639c1e)), closes [#3526](https://github.com/ngrx/platform/issues/3526)\n- **store-devtools:** add provideStoreDevtools function ([#3537](https://github.com/ngrx/platform/issues/3537)) ([6b0db4e](https://github.com/ngrx/platform/commit/6b0db4e)), closes [#3527](https://github.com/ngrx/platform/issues/3527)\n\n<a name=\"14.2.0\"></a>\n\n# [14.2.0](https://github.com/ngrx/platform/compare/14.1.0...14.2.0) (2022-08-18)\n\n### Bug Fixes\n\n- **component-store:** make synchronous updater errors catchable ([#3490](https://github.com/ngrx/platform/issues/3490)) ([1a906fd](https://github.com/ngrx/platform/commit/1a906fd))\n- **component-store:** move isInitialized check to queueScheduler context on state update ([#3492](https://github.com/ngrx/platform/issues/3492)) ([53636e4](https://github.com/ngrx/platform/commit/53636e4)), closes [#2991](https://github.com/ngrx/platform/issues/2991)\n\n### Features\n\n- **component-store:** handle errors in next callback ([#3533](https://github.com/ngrx/platform/issues/3533)) ([551c8eb](https://github.com/ngrx/platform/commit/551c8eb))\n\n<a name=\"14.1.0\"></a>\n\n# [14.1.0](https://github.com/ngrx/platform/compare/14.0.2...14.1.0) (2022-08-09)\n\n### Bug Fixes\n\n- **eslint-plugin:** allow sequential dispatches in a different block context ([#3515](https://github.com/ngrx/platform/issues/3515)) ([faf446f](https://github.com/ngrx/platform/commit/faf446f)), closes [#3513](https://github.com/ngrx/platform/issues/3513)\n- **eslint-plugin:** Remove the md suffix from the docsUrl path ([#3518](https://github.com/ngrx/platform/issues/3518)) ([71d4d4b](https://github.com/ngrx/platform/commit/71d4d4b))\n- **store:** improve error for forbidden characters in createActionGroup ([#3496](https://github.com/ngrx/platform/issues/3496)) ([398fbed](https://github.com/ngrx/platform/commit/398fbed))\n\n### Features\n\n- **component:** add RenderScheduler to the public API ([#3516](https://github.com/ngrx/platform/issues/3516)) ([4642919](https://github.com/ngrx/platform/commit/4642919))\n- **component:** replace markDirty with custom TickScheduler ([#3488](https://github.com/ngrx/platform/issues/3488)) ([3fcd8af](https://github.com/ngrx/platform/commit/3fcd8af))\n\n### Performance Improvements\n\n- **component:** do not schedule render for synchronous events ([#3487](https://github.com/ngrx/platform/issues/3487)) ([bb9071c](https://github.com/ngrx/platform/commit/bb9071c))\n\n<a name=\"14.0.2\"></a>\n\n## [14.0.2](https://github.com/ngrx/platform/compare/14.0.1...14.0.2) (2022-07-12)\n\n### Bug Fixes\n\n- **component:** import operators from rxjs/operators ([#3479](https://github.com/ngrx/platform/issues/3479)) ([20ef7a4](https://github.com/ngrx/platform/commit/20ef7a4))\n- **component-store:** effect handles generics that extend upon a type ([#3485](https://github.com/ngrx/platform/issues/3485)) ([9d2bda7](https://github.com/ngrx/platform/commit/9d2bda7)), closes [#3482](https://github.com/ngrx/platform/issues/3482)\n- **data:** add TSDoc annotations ([#3483](https://github.com/ngrx/platform/issues/3483)) ([cbbc49f](https://github.com/ngrx/platform/commit/cbbc49f))\n- **eslint-plugin:** fix configuration guide link ([#3480](https://github.com/ngrx/platform/issues/3480)) ([8219b1d](https://github.com/ngrx/platform/commit/8219b1d))\n\n<a name=\"14.0.1\"></a>\n\n## [14.0.1](https://github.com/ngrx/platform/compare/14.0.0...14.0.1) (2022-06-29)\n\n### Bug Fixes\n\n- **component-store:** allow void callbacks in effect ([#3466](https://github.com/ngrx/platform/issues/3466)) ([e6dedd6](https://github.com/ngrx/platform/commit/e6dedd6)), closes [#3462](https://github.com/ngrx/platform/issues/3462)\n- **component-store:** import operators from rxjs/operators ([#3465](https://github.com/ngrx/platform/issues/3465)) ([f9ba513](https://github.com/ngrx/platform/commit/f9ba513))\n- **schematics:** add workingDirectory to schemas ([#3473](https://github.com/ngrx/platform/issues/3473)) ([50ea6b3](https://github.com/ngrx/platform/commit/50ea6b3)), closes [#3469](https://github.com/ngrx/platform/issues/3469)\n- **schematics:** create schematicCollections if not exists ([#3470](https://github.com/ngrx/platform/issues/3470)) ([011cbcc](https://github.com/ngrx/platform/commit/011cbcc))\n\n<a name=\"14.0.0\"></a>\n\n# [14.0.0](https://github.com/ngrx/platform/compare/14.0.0-rc.0...14.0.0) (2022-06-20)\n\n### Bug Fixes\n\n- **component:** do not exclude falsy types from LetDirective's input type ([#3460](https://github.com/ngrx/platform/issues/3460)) ([7028adb](https://github.com/ngrx/platform/commit/7028adb))\n\n<a name=\"14.0.0-rc.0\"></a>\n\n# [14.0.0-rc.0](https://github.com/ngrx/platform/compare/14.0.0-beta.0...14.0.0-rc.0) (2022-06-08)\n\n### Code Refactoring\n\n- **router-store:** change name for full router state serializer ([#3430](https://github.com/ngrx/platform/issues/3430)) ([d443f50](https://github.com/ngrx/platform/commit/d443f50)), closes [#3416](https://github.com/ngrx/platform/issues/3416)\n\n### Features\n\n- **component:** add separate modules for PushPipe and LetDirective ([#3449](https://github.com/ngrx/platform/issues/3449)) ([eacc4b4](https://github.com/ngrx/platform/commit/eacc4b4)), closes [#3341](https://github.com/ngrx/platform/issues/3341)\n- **component:** deprecate ReactiveComponentModule ([#3451](https://github.com/ngrx/platform/issues/3451)) ([b4dd2c8](https://github.com/ngrx/platform/commit/b4dd2c8))\n- **eslint-plugin:** improve install flow ([#3447](https://github.com/ngrx/platform/issues/3447)) ([8ddaf60](https://github.com/ngrx/platform/commit/8ddaf60))\n- **schematics:** use schematicCollections instead of defaultCollection ([#3441](https://github.com/ngrx/platform/issues/3441)) ([5abf828](https://github.com/ngrx/platform/commit/5abf828)), closes [#3383](https://github.com/ngrx/platform/issues/3383)\n\n### BREAKING CHANGES\n\n- **router-store:** The full router state serializer has been renamed.\n\nBEFORE:\n\nThe full router state serializer is named `DefaultRouterStateSerializer`\n\nAFTER:\n\nThe full router state serializer is named `FullRouterStateSerializer`. A migration is provided to rename the export in affected projects.\n\n<a name=\"14.0.0-beta.0\"></a>\n\n# [14.0.0-beta.0](https://github.com/ngrx/platform/compare/13.1.0...14.0.0-beta.0) (2022-05-30)\n\n- Closes #3344, #3345 ([70056a8](https://github.com/ngrx/platform/commit/70056a8)), closes [#3344](https://github.com/ngrx/platform/issues/3344) [#3345](https://github.com/ngrx/platform/issues/3345)\n\n### Bug Fixes\n\n- **store:** rename template literal to string literal for createActionGroup ([#3426](https://github.com/ngrx/platform/issues/3426)) ([7d08db1](https://github.com/ngrx/platform/commit/7d08db1))\n\n### Features\n\n- **schematics:** remove creators option ([#3311](https://github.com/ngrx/platform/issues/3311)) ([e86278a](https://github.com/ngrx/platform/commit/e86278a))\n- update Angular packages to latest v14 RC ([#3425](https://github.com/ngrx/platform/issues/3425)) ([f15dd1e](https://github.com/ngrx/platform/commit/f15dd1e)), closes [#3417](https://github.com/ngrx/platform/issues/3417)\n- **component:** add error as value to LetDirective's context ([#3380](https://github.com/ngrx/platform/issues/3380)) ([6452e24](https://github.com/ngrx/platform/commit/6452e24)), closes [#3343](https://github.com/ngrx/platform/issues/3343)\n- **component:** add suspense template input to LetDirective ([#3377](https://github.com/ngrx/platform/issues/3377)) ([345ee53](https://github.com/ngrx/platform/commit/345ee53)), closes [#3340](https://github.com/ngrx/platform/issues/3340)\n- **component:** use global render strategy in zone-less mode ([#3379](https://github.com/ngrx/platform/issues/3379)) ([f233dae](https://github.com/ngrx/platform/commit/f233dae)), closes [#3342](https://github.com/ngrx/platform/issues/3342)\n- **component-store:** add OnStoreInit and OnStateInit lifecycle hooks ([#3368](https://github.com/ngrx/platform/issues/3368)) ([0ffed02](https://github.com/ngrx/platform/commit/0ffed02)), closes [#3335](https://github.com/ngrx/platform/issues/3335)\n- **eslint-plugin:** add NgRx ESLint Plugin ([#3373](https://github.com/ngrx/platform/issues/3373)) ([ae0041b](https://github.com/ngrx/platform/commit/ae0041b))\n- **store:** add createActionGroup function ([#3381](https://github.com/ngrx/platform/issues/3381)) ([2cdecb3](https://github.com/ngrx/platform/commit/2cdecb3)), closes [#3337](https://github.com/ngrx/platform/issues/3337)\n- **store:** install and configure the [@ngrx](https://github.com/ngrx)/eslint-plugin on ng-add ([#3386](https://github.com/ngrx/platform/issues/3386)) ([bf2672e](https://github.com/ngrx/platform/commit/bf2672e))\n\n### Performance Improvements\n\n- **component:** reset state / trigger CD only if necessary ([#3328](https://github.com/ngrx/platform/issues/3328)) ([f5b055b](https://github.com/ngrx/platform/commit/f5b055b))\n\n### BREAKING CHANGES\n\n- 1. The context of `LetDirective` is strongly typed when `null` or\n     `undefined` is passed as input.\n\nBEFORE:\n\n```html\n<p *ngrxLet=\"null as n\">{{ n }}</p>\n<p *ngrxLet=\"undefined as u\">{{ u }}</p>\n```\n\n- The type of `n` is `any`.\n- The type of `u` is `any`.\n\nAFTER:\n\n```html\n<p *ngrxLet=\"null as n\">{{ n }}</p>\n<p *ngrxLet=\"undefined as u\">{{ u }}</p>\n```\n\n- The type of `n` is `null`.\n- The type of `u` is `undefined`.\n\n* **schematics:** BEFORE:\n\nCreating actions, reducers, and effects is possible without using the creator syntax is possible.\n\nAFTER:\n\n- All schematics use the non-creator syntax to scaffold the code.\n- The option `--creators` (and `-c`) is removed from the schematic options.\n- The `skipTests` option is removed while generating actions.\n\n* Minimum version of Angular has been updated\n\nBEFORE:\n\nMinimum version of Angular was 13.x\n\nAFTER:\n\nMinimum version of Angular is 14.x\n\n- **component:** The native local rendering strategy is replaced by global\n  in zone-less mode for better performance.\n\nBEFORE:\n\nThe change detection is triggered via `changeDetectorRef.detectChanges`\nin zone-less mode.\n\nAFTER:\n\nThe change detection is triggered via `ɵmarkDirty` in zone-less mode.\n\n- **component:** The `$error` property from `LetDirective`'s view context is\n  a thrown error or `undefined` instead of `true`/`false`.\n\nBEFORE:\n\n```ts\n<p *ngrxLet=\"obs$; $error as e\">{{ e }}</p>\n```\n\n- `e` will be `true` when `obs$` emits error event.\n- `e` will be `false` when `obs$` emits next/complete event.\n\nAFTER:\n\n```ts\n<p *ngrxLet=\"obs$; $error as e\">{{ e }}</p>\n```\n\n- `e` will be thrown error when `obs$` emits error event.\n- `e` will be `undefined` when `obs$` emits next/complete event.\n\n<a name=\"13.1.0\"></a>\n\n# [13.1.0](https://github.com/ngrx/platform/compare/13.0.2...13.1.0) (2022-03-28)\n\n### Bug Fixes\n\n- **component-store:** memoization not working when passing selectors directly to select ([#3356](https://github.com/ngrx/platform/issues/3356)) ([38bce88](https://github.com/ngrx/platform/commit/38bce88))\n- **entity:** add default options to entity adapter when undefined is passed ([#3287](https://github.com/ngrx/platform/issues/3287)) ([17fe494](https://github.com/ngrx/platform/commit/17fe494))\n- **store:** add explicit overloads for createSelector ([#3354](https://github.com/ngrx/platform/issues/3354)) ([2f82101](https://github.com/ngrx/platform/commit/2f82101)), closes [#3268](https://github.com/ngrx/platform/issues/3268)\n\n### Features\n\n- **data:** add ability to configure trailing slashes ([#3357](https://github.com/ngrx/platform/issues/3357)) ([56aedfd](https://github.com/ngrx/platform/commit/56aedfd))\n- **store-devtools:** add REDUX_DEVTOOLS_EXTENSION injection token to public API ([#3338](https://github.com/ngrx/platform/issues/3338)) ([b55b0e4](https://github.com/ngrx/platform/commit/b55b0e4))\n\n<a name=\"13.0.2\"></a>\n\n## [13.0.2](https://github.com/ngrx/platform/compare/13.0.1...13.0.2) (2021-12-07)\n\n### Bug Fixes\n\n- **component:** fixes recursive rendering ([#3255](https://github.com/ngrx/platform/issues/3255)) ([d24dde1](https://github.com/ngrx/platform/commit/d24dde1)), closes [#3246](https://github.com/ngrx/platform/issues/3246)\n- **store:** remove afterEach hook in mock store ([#3245](https://github.com/ngrx/platform/issues/3245)) ([0640085](https://github.com/ngrx/platform/commit/0640085)), closes [#3243](https://github.com/ngrx/platform/issues/3243)\n- **store:** update installation of the NgRx ESLint Plugin ([#3259](https://github.com/ngrx/platform/issues/3259)) ([df211fe](https://github.com/ngrx/platform/commit/df211fe))\n- set correct dist paths for testing packages ([#3249](https://github.com/ngrx/platform/issues/3249)) ([ed9f6f1](https://github.com/ngrx/platform/commit/ed9f6f1)), closes [#3248](https://github.com/ngrx/platform/issues/3248)\n\n<a name=\"13.0.1\"></a>\n\n## [13.0.1](https://github.com/ngrx/platform/compare/13.0.0...13.0.1) (2021-11-17)\n\n### Bug Fixes\n\n- **store:** add migration for create selector generics ([#3237](https://github.com/ngrx/platform/issues/3237)) ([5d97a11](https://github.com/ngrx/platform/commit/5d97a11))\n\n<a name=\"13.0.0\"></a>\n\n# [13.0.0](https://github.com/ngrx/platform/compare/13.0.0-rc.0...13.0.0) (2021-11-16)\n\n<a name=\"13.0.0-rc.0\"></a>\n\n# [13.0.0-rc.0](https://github.com/ngrx/platform/compare/13.0.0-beta.0...13.0.0-rc.0) (2021-11-11)\n\n### Bug Fixes\n\n- **data:** set loaded to true on queryManySuccess ([#3228](https://github.com/ngrx/platform/issues/3228)) ([52b828e](https://github.com/ngrx/platform/commit/52b828e)), closes [#3165](https://github.com/ngrx/platform/issues/3165)\n\n### Features\n\n- **store:** use variadic tuple types for createSelector ([#3023](https://github.com/ngrx/platform/issues/3023)) ([367d9b4](https://github.com/ngrx/platform/commit/367d9b4)), closes [#2715](https://github.com/ngrx/platform/issues/2715)\n- update Angular packages to version 13.0.0 ([#3184](https://github.com/ngrx/platform/issues/3184)) ([996f1e8](https://github.com/ngrx/platform/commit/996f1e8)), closes [#3189](https://github.com/ngrx/platform/issues/3189)\n\n### BREAKING CHANGES\n\n- **store:** When manually specifying the generic arguments, you have to specify the selector's list of selector return values.\n\nBEFORE:\n\n```ts\ncreateSelector<Story[], Story[], Story[][]>;\n```\n\nAFTER:\n\n```ts\n//        needs to be a tuple 👇\ncreateSelector<Story[], [Story[]], Story[][]>;\n```\n\n- **data:** Now both the `getWithQuery` and `getAll` methods are consistent and do set `loaded` property to true on dispatching their success actions respectively.\n\nBEFORE:\n\nThe `getWithQuery` method would not set the `loaded` property to true upon success\n\nAFTER:\n\nThe `getWithQuery` method sets the `loaded` property to true upon success\n\n<a name=\"13.0.0-beta.0\"></a>\n\n# [13.0.0-beta.0](https://github.com/ngrx/platform/compare/12.5.1...13.0.0-beta.0) (2021-11-04)\n\n### Bug Fixes\n\n- **component:** remove class-level generic from PushPipe ([#3127](https://github.com/ngrx/platform/issues/3127)) ([548c72c](https://github.com/ngrx/platform/commit/548c72c)), closes [#3114](https://github.com/ngrx/platform/issues/3114)\n- **store:** infer initial store state properly with metareducers ([#3102](https://github.com/ngrx/platform/issues/3102)) ([d003b85](https://github.com/ngrx/platform/commit/d003b85)), closes [#3007](https://github.com/ngrx/platform/issues/3007)\n- **store:** remove store config from forFeature signature with slice ([#3218](https://github.com/ngrx/platform/issues/3218)) ([b1a64dd](https://github.com/ngrx/platform/commit/b1a64dd)), closes [#3216](https://github.com/ngrx/platform/issues/3216)\n\n### build\n\n- build packages with Ivy ([#3219](https://github.com/ngrx/platform/issues/3219)) ([9e28050](https://github.com/ngrx/platform/commit/9e28050))\n\n### Features\n\n- **effects:** move createEffect migration to ng-update migration ([#3074](https://github.com/ngrx/platform/issues/3074)) ([5974913](https://github.com/ngrx/platform/commit/5974913))\n- **store:** add createFeatureSelector migration ([#3214](https://github.com/ngrx/platform/issues/3214)) ([62334f9](https://github.com/ngrx/platform/commit/62334f9))\n- **store:** provide better TS errors for action creator props ([#3060](https://github.com/ngrx/platform/issues/3060)) ([5ed3c3d](https://github.com/ngrx/platform/commit/5ed3c3d)), closes [#2892](https://github.com/ngrx/platform/issues/2892)\n\n### BREAKING CHANGES\n\n- The minimum version required for Angular and RxJS has been updated\n\nBEFORE:\n\nAngular 12.x is the minimum version\nRxJS 6.5.x is the minimum required version\n\nAFTER:\n\nAngular 13.0.0-RC.0 is the minimum version\nRxJS 7.4.x is the minimum required version\n\n- **store:** The `StoreConfig` argument is removed from the `StoreModule.forFeature` signature with `FeatureSlice`.\n\nBEFORE:\n\nThe `StoreModule.forFeature` signature with `FeatureSlice` has `StoreConfig` as the second input argument, but the configuration isn't registered if passed.\n\nAFTER:\n\nThe `StoreModule.forFeature` signature with `FeatureSlice` no longer has `StoreConfig` as the second input argument.\n\n- **store:** `initialState` needs to match the interface of the store/feature.\n\nBEFORE:\n\nMissing properties were valid\n\n```ts\nStoreModule.forRoot(reducers, {\n  initialState: { notExisting: 3 },\n  metaReducers: [metaReducer],\n});\n```\n\nAFTER:\n\nA type error is produced for initialState that does not match the store/feature\n\n```ts\nStoreModule.forRoot(reducers, {\n  initialState: { notExisting: 3 },\n  metaReducers: [metaReducer],\n});\n```\n\n- **component:** PushPipe no longer has a class-level generic type parameter.\n\nBEFORE:\n\nUse of PushPipe outside of component templates required a generic\n\nAFTER:\n\nUse of PushPipe outside of component templates no longer requires a generic\n\n- **store:** Types for props outside an action creator is more strictly checked\n\nBEFORE:\n\nUsage of `props` outside of an action creator with invalid types was allowed\n\nAFTER:\n\nUsage of `props` outside of an action creator now breaks for invalid types\n\n- **effects:** The create-effect-migration migration is removed\n\nBEFORE:\n\nThe Effect decorator removal and migration are done manually through schematics.\n\nAFTER:\n\nThe Effect decorator removal and migration are performed automatically on upgrade to version 13 of NgRx Effects.\n\n<a name=\"12.5.1\"></a>\n\n## [12.5.1](https://github.com/ngrx/platform/compare/12.5.0...12.5.1) (2021-10-25)\n\n### Bug Fixes\n\n- **router-store:** google upstream ([#3177](https://github.com/ngrx/platform/issues/3177)) ([20afb21](https://github.com/ngrx/platform/commit/20afb21))\n\n<a name=\"12.5.0\"></a>\n\n# [12.5.0](https://github.com/ngrx/platform/compare/12.4.0...12.5.0) (2021-10-14)\n\n### Bug Fixes\n\n- **entity:** set correct input argument types for removeMutably methods ([#3148](https://github.com/ngrx/platform/issues/3148)) ([9611415](https://github.com/ngrx/platform/commit/9611415))\n- **schematics:** add missing method ([#3157](https://github.com/ngrx/platform/issues/3157)) ([2a927a2](https://github.com/ngrx/platform/commit/2a927a2))\n- **schematics:** use prefix option in feature schematic ([#3139](https://github.com/ngrx/platform/issues/3139)) ([5fa8890](https://github.com/ngrx/platform/commit/5fa8890)), closes [#3116](https://github.com/ngrx/platform/issues/3116)\n\n### Features\n\n- update to RxJS 7.4.x ([#3109](https://github.com/ngrx/platform/issues/3109)) ([4a42550](https://github.com/ngrx/platform/commit/4a42550))\n\n<a name=\"12.4.0\"></a>\n\n# [12.4.0](https://github.com/ngrx/platform/compare/12.3.0...12.4.0) (2021-08-11)\n\n### Bug Fixes\n\n- **component:** capture errors from observable when using `ngrxPush` pipe and `ngrxLet` directive ([23c846b](https://github.com/ngrx/platform/commit/23c846b)), closes [#3100](https://github.com/ngrx/platform/issues/3100)\n\n### Features\n\n- **router-store:** add createRouterSelector to select router data for default config ([#3103](https://github.com/ngrx/platform/issues/3103)) ([507f58e](https://github.com/ngrx/platform/commit/507f58e))\n\n<a name=\"12.3.0\"></a>\n\n# [12.3.0](https://github.com/ngrx/platform/compare/12.2.0...12.3.0) (2021-07-22)\n\n### Bug Fixes\n\n- **component-store:** accept error type in tapResponse with strict generic checks ([#3068](https://github.com/ngrx/platform/issues/3068)) ([3e02e37](https://github.com/ngrx/platform/commit/3e02e37)), closes [#3056](https://github.com/ngrx/platform/issues/3056)\n- **data:** immutably delete an entity ([#3040](https://github.com/ngrx/platform/issues/3040)) ([a6c199f](https://github.com/ngrx/platform/commit/a6c199f)), closes [#2553](https://github.com/ngrx/platform/issues/2553)\n- **data:** SAVE_ENTITIES_CANCELED type in SaveEntitiesCanceled ([#3079](https://github.com/ngrx/platform/issues/3079)) ([b24c1e0](https://github.com/ngrx/platform/commit/b24c1e0)), closes [#3065](https://github.com/ngrx/platform/issues/3065)\n- **store:** make readonly usage consistent ([#3050](https://github.com/ngrx/platform/issues/3050)) ([#3069](https://github.com/ngrx/platform/issues/3069)) ([a39b278](https://github.com/ngrx/platform/commit/a39b278))\n\n### Features\n\n- **store:** make reducers accessible from ReducerManager ([#3064](https://github.com/ngrx/platform/issues/3064)) ([bf2bd1a](https://github.com/ngrx/platform/commit/bf2bd1a))\n\n<a name=\"12.2.0\"></a>\n\n# [12.2.0](https://github.com/ngrx/platform/compare/12.1.0...12.2.0) (2021-06-29)\n\n### Bug Fixes\n\n- **component:** avoid early destruction of view in ngrxLet which interfered with animations ([#2890](https://github.com/ngrx/platform/issues/2890)) ([#3045](https://github.com/ngrx/platform/issues/3045)) ([7515e36](https://github.com/ngrx/platform/commit/7515e36))\n- **data:** make options optional on add with partial ([#3043](https://github.com/ngrx/platform/issues/3043)) ([1620df9](https://github.com/ngrx/platform/commit/1620df9))\n\n### Features\n\n- **component-store:** accept error type in `tapResponse` ([#3056](https://github.com/ngrx/platform/issues/3056)) ([61e1963](https://github.com/ngrx/platform/commit/61e1963))\n\n<a name=\"12.1.0\"></a>\n\n# [12.1.0](https://github.com/ngrx/platform/compare/12.0.0...12.1.0) (2021-06-09)\n\n### Bug Fixes\n\n- **data:** remove strict typing for optimistic false ([#3020](https://github.com/ngrx/platform/issues/3020)) ([3b565b4](https://github.com/ngrx/platform/commit/3b565b4)), closes [#2928](https://github.com/ngrx/platform/issues/2928)\n- **store:** add ESLint plugin to TS overrides when possible ([#3032](https://github.com/ngrx/platform/issues/3032)) ([5102a34](https://github.com/ngrx/platform/commit/5102a34)), closes [#3031](https://github.com/ngrx/platform/issues/3031)\n\n### Features\n\n- **entity:** new `setMany` adapter ([#3026](https://github.com/ngrx/platform/issues/3026)) ([#3029](https://github.com/ngrx/platform/issues/3029)) ([a02ea9f](https://github.com/ngrx/platform/commit/a02ea9f))\n- **schematics:** add the ability to create actions with the prefix ([#3025](https://github.com/ngrx/platform/issues/3025)) ([15bc0df](https://github.com/ngrx/platform/commit/15bc0df))\n- **store:** add createFeature ([#3033](https://github.com/ngrx/platform/issues/3033)) ([5fd1c7b](https://github.com/ngrx/platform/commit/5fd1c7b)), closes [#2974](https://github.com/ngrx/platform/issues/2974)\n- **store-devtools:** add autoPause option ([#2941](https://github.com/ngrx/platform/issues/2941)) ([698bd29](https://github.com/ngrx/platform/commit/698bd29)), closes [#2722](https://github.com/ngrx/platform/issues/2722)\n\n<a name=\"12.0.0\"></a>\n\n# [12.0.0](https://github.com/ngrx/platform/compare/12.0.0-rc.0...12.0.0) (2021-05-12)\n\n### Features\n\n- **store:** register eslint-plugin-ngrx with ng add ([#3014](https://github.com/ngrx/platform/issues/3014)) ([5259890](https://github.com/ngrx/platform/commit/5259890))\n\n<a name=\"12.0.0-rc.0\"></a>\n\n# [12.0.0-rc.0](https://github.com/ngrx/platform/compare/12.0.0-beta.1...12.0.0-rc.0) (2021-05-05)\n\n<a name=\"12.0.0-beta.1\"></a>\n\n# [12.0.0-beta.1](https://github.com/ngrx/platform/compare/12.0.0-beta.0...12.0.0-beta.1) (2021-04-29)\n\n### Bug Fixes\n\n- **schematics:** assert empty as action observable ([#3005](https://github.com/ngrx/platform/issues/3005)) ([61b1ac7](https://github.com/ngrx/platform/commit/61b1ac7)), closes [#2931](https://github.com/ngrx/platform/issues/2931)\n\n<a name=\"12.0.0-beta.0\"></a>\n\n# [12.0.0-beta.0](https://github.com/ngrx/platform/compare/11.1.1...12.0.0-beta.0) (2021-04-27)\n\n### Bug Fixes\n\n- **component:** include files in ng-add schematics ([ad13c9c](https://github.com/ngrx/platform/commit/ad13c9c))\n- **component-store:** include files in ng-add schematics ([bfef622](https://github.com/ngrx/platform/commit/bfef622))\n- **data:** include files in ng-add schematics ([526edd9](https://github.com/ngrx/platform/commit/526edd9))\n- **effects:** ng-add schematics will generate effects files properly ([4389307](https://github.com/ngrx/platform/commit/4389307))\n- **entity:** include files in ng-add schematics ([4d9f647](https://github.com/ngrx/platform/commit/4d9f647))\n- **router-store:** include files in ng-add schematics ([eb71d5c](https://github.com/ngrx/platform/commit/eb71d5c))\n- **store:** ng-add schematics will generate router files if minimal set to false ([74a2671](https://github.com/ngrx/platform/commit/74a2671))\n- **store-devtools:** include files in ng-add schematics ([ac706de](https://github.com/ngrx/platform/commit/ac706de))\n\n### build\n\n- update to Angular libraries to version 12 RC.0 ([#3000](https://github.com/ngrx/platform/issues/3000)) ([4fb030e](https://github.com/ngrx/platform/commit/4fb030e))\n- update to Nx version 12.0.x and TypeScript 4.1.x ([#2999](https://github.com/ngrx/platform/issues/2999)) ([cb258cb](https://github.com/ngrx/platform/commit/cb258cb))\n\n### Features\n\n- **store:** deprecate selectors with props ([#2993](https://github.com/ngrx/platform/issues/2993)) ([7c6d4e4](https://github.com/ngrx/platform/commit/7c6d4e4))\n\n### BREAKING CHANGES\n\n- Minimum versions of Angular and TypeScript have been updated\n\nBEFORE:\n\nMinimum of Angular version 11.x\nMinimum of TypeScript 4.1.x\n\nAFTER:\n\nMinimum of Angular version 12.x\nMinimum of TypeScript 4.2.x\n\n- The minimum TypeScript version has been updated to 4.1.x\n\nBEFORE:\n\nThe minimum TypeScript version is 4.0.x\n\nAFTER:\n\nThe minimum TypeScript version is 4.1.x\n\n<a name=\"11.1.1\"></a>\n\n## [11.1.1](https://github.com/ngrx/platform/compare/11.1.0...11.1.1) (2021-04-20)\n\n### Bug Fixes\n\n- **store:** adjust types to allow a generic reducer ([#2996](https://github.com/ngrx/platform/issues/2996)) ([7da57bc](https://github.com/ngrx/platform/commit/7da57bc)), closes [#2982](https://github.com/ngrx/platform/issues/2982)\n\n### Features\n\n- **store:** add FunctionWithParametersType to public API ([#2988](https://github.com/ngrx/platform/issues/2988)) ([fe7d058](https://github.com/ngrx/platform/commit/fe7d058)), closes [#2983](https://github.com/ngrx/platform/issues/2983)\n\n<a name=\"11.1.0\"></a>\n\n# [11.1.0](https://github.com/ngrx/platform/compare/11.0.1...11.1.0) (2021-03-31)\n\n### Bug Fixes\n\n- **effects:** add support for Proxy objects in Effects ([#2976](https://github.com/ngrx/platform/issues/2976)) ([5f5b679](https://github.com/ngrx/platform/commit/5f5b679)), closes [#2975](https://github.com/ngrx/platform/issues/2975)\n- **store:** allow default parameters in function action ([#2954](https://github.com/ngrx/platform/issues/2954)) ([9b23403](https://github.com/ngrx/platform/commit/9b23403)), closes [#2948](https://github.com/ngrx/platform/issues/2948)\n- **store:** allow primitive types ([#2967](https://github.com/ngrx/platform/issues/2967)) ([eecc8ce](https://github.com/ngrx/platform/commit/eecc8ce)), closes [#2966](https://github.com/ngrx/platform/issues/2966)\n\n### Features\n\n- **component-store:** add ability for patchState to accept Observable ([#2937](https://github.com/ngrx/platform/issues/2937)) ([8930e22](https://github.com/ngrx/platform/commit/8930e22)), closes [#2852](https://github.com/ngrx/platform/issues/2852)\n- **schematics:** add component store schematics ([#2886](https://github.com/ngrx/platform/issues/2886)) ([f086f80](https://github.com/ngrx/platform/commit/f086f80)), closes [#2570](https://github.com/ngrx/platform/issues/2570)\n\n<a name=\"11.0.1\"></a>\n\n## [11.0.1](https://github.com/ngrx/platform/compare/11.0.0...11.0.1) (2021-02-15)\n\n### Bug Fixes\n\n- **schematics:** add index file ([#2923](https://github.com/ngrx/platform/issues/2923)) ([775c794](https://github.com/ngrx/platform/commit/775c794)), closes [#2917](https://github.com/ngrx/platform/issues/2917)\n- **store:** forFeature using instanceof instead of typeof ([#2922](https://github.com/ngrx/platform/issues/2922)) ([2bea205](https://github.com/ngrx/platform/commit/2bea205)), closes [#2919](https://github.com/ngrx/platform/issues/2919)\n\n<a name=\"11.0.0\"></a>\n\n# [11.0.0](https://github.com/ngrx/platform/compare/11.0.0-rc.0...11.0.0) (2021-02-09)\n\n<a name=\"11.0.0-rc.0\"></a>\n\n# [11.0.0-rc.0](https://github.com/ngrx/platform/compare/11.0.0-beta.2...11.0.0-rc.0) (2021-02-04)\n\n<a name=\"11.0.0-beta.2\"></a>\n\n# [11.0.0-beta.2](https://github.com/ngrx/platform/compare/11.0.0-beta.0...11.0.0-beta.2) (2021-02-02)\n\n### Bug Fixes\n\n- **component:** remove ? from LetViewContext props to prevent 'possibly undefined' error in strict mode ([#2876](https://github.com/ngrx/platform/issues/2876)) ([c3ac252](https://github.com/ngrx/platform/commit/c3ac252))\n- **component:** transform to Observable if Input is Promise ([b611367](https://github.com/ngrx/platform/commit/b611367))\n- **data:** make entity param partial when is not optimistic ([#2899](https://github.com/ngrx/platform/issues/2899)) ([bb70e6c](https://github.com/ngrx/platform/commit/bb70e6c)), closes [#2870](https://github.com/ngrx/platform/issues/2870)\n- **data:** type overloaded add for is optimistic true | undefined ([#2906](https://github.com/ngrx/platform/issues/2906)) ([6d46ac4](https://github.com/ngrx/platform/commit/6d46ac4))\n- **push:** fix return typing for observables to include undefined ([#2907](https://github.com/ngrx/platform/issues/2907)) ([abcc599](https://github.com/ngrx/platform/commit/abcc599)), closes [#2888](https://github.com/ngrx/platform/issues/2888)\n- **router-store:** cast return type as RouterReducerState ([#2887](https://github.com/ngrx/platform/issues/2887)) ([d489484](https://github.com/ngrx/platform/commit/d489484))\n\n### Features\n\n- **effects:** concatLatestFrom operator ([#2760](https://github.com/ngrx/platform/issues/2760)) ([55f0f7a](https://github.com/ngrx/platform/commit/55f0f7a))\n- **effects:** deprecate @Effect decorator ([#2855](https://github.com/ngrx/platform/issues/2855)) ([dbd1ecf](https://github.com/ngrx/platform/commit/dbd1ecf))\n- **store:** add object-style StoreModule.forFeature overload w/fixes ([#2885](https://github.com/ngrx/platform/issues/2885)) ([a9468e1](https://github.com/ngrx/platform/commit/a9468e1)), closes [#2821](https://github.com/ngrx/platform/issues/2821) [#2809](https://github.com/ngrx/platform/issues/2809)\n- **store-devtools:** pass entire error object to the error handler ([#2853](https://github.com/ngrx/platform/issues/2853)) ([ce28b44](https://github.com/ngrx/platform/commit/ce28b44)), closes [#2824](https://github.com/ngrx/platform/issues/2824)\n\n### Performance Improvements\n\n- **schematics:** speed up create effect migration ([#2873](https://github.com/ngrx/platform/issues/2873)) ([2f5dcb4](https://github.com/ngrx/platform/commit/2f5dcb4))\n\n### BREAKING CHANGES\n\n- **push:** BEFORE:\n\nngrxPush typing doesn't consider `undefined` when the input type is an observable\n\nAFTER:\n\nngrxPush typing considers `undefined` when the input type is an observable\n\n<a name=\"11.0.0-beta.0\"></a>\n\n# [11.0.0-beta.0](https://github.com/ngrx/platform/compare/10.0.1...11.0.0-beta.0) (2021-01-05)\n\n### Bug Fixes\n\n- update Angular peer dependencies to version 11 ([#2843](https://github.com/ngrx/platform/issues/2843)) ([f63d281](https://github.com/ngrx/platform/commit/f63d281)), closes [#2842](https://github.com/ngrx/platform/issues/2842)\n- **component:** add schematic assets to ng-package.json ([9598527](https://github.com/ngrx/platform/commit/9598527)), closes [#2819](https://github.com/ngrx/platform/issues/2819)\n- **component-store:** add schematic assets to ng-package.json ([0e3b52d](https://github.com/ngrx/platform/commit/0e3b52d)), closes [#2819](https://github.com/ngrx/platform/issues/2819)\n- **component-store:** adjust updater to accept partials ([#2765](https://github.com/ngrx/platform/issues/2765)) ([b54b9b6](https://github.com/ngrx/platform/commit/b54b9b6)), closes [#2754](https://github.com/ngrx/platform/issues/2754)\n- **router-store:** ingore slash when comparing routes ([#2834](https://github.com/ngrx/platform/issues/2834)) ([cad3f60](https://github.com/ngrx/platform/commit/cad3f60)), closes [#2829](https://github.com/ngrx/platform/issues/2829) [#1781](https://github.com/ngrx/platform/issues/1781)\n- **schematics:** add schematics to devDependencies ([#2784](https://github.com/ngrx/platform/issues/2784)) ([daf1889](https://github.com/ngrx/platform/commit/daf1889))\n- **store:** add noop for all methods in MockReducerManager ([#2777](https://github.com/ngrx/platform/issues/2777)) ([a489b48](https://github.com/ngrx/platform/commit/a489b48)), closes [#2776](https://github.com/ngrx/platform/issues/2776)\n- **store:** correct types for SelectorFactoryConfig ([#2752](https://github.com/ngrx/platform/issues/2752)) ([aa9bf1a](https://github.com/ngrx/platform/commit/aa9bf1a))\n\n### Code Refactoring\n\n- use consistent naming of injection tokens across packages ([#2737](https://github.com/ngrx/platform/issues/2737)) ([e02d0d4](https://github.com/ngrx/platform/commit/e02d0d4))\n\n### Features\n\n- **component-store:** add patchState method ([#2788](https://github.com/ngrx/platform/issues/2788)) ([ecedadb](https://github.com/ngrx/platform/commit/ecedadb))\n- **component-store:** add tapResponse operator ([#2763](https://github.com/ngrx/platform/issues/2763)) ([d1873c9](https://github.com/ngrx/platform/commit/d1873c9))\n- **component-store:** allow more than 4 selects ([#2841](https://github.com/ngrx/platform/issues/2841)) ([7c29320](https://github.com/ngrx/platform/commit/7c29320))\n- **effects:** add support for provideMockActions outside of the TestBed ([#2762](https://github.com/ngrx/platform/issues/2762)) ([c47114c](https://github.com/ngrx/platform/commit/c47114c))\n- **effects:** allow usage of empty forRoot array multiple times ([#2774](https://github.com/ngrx/platform/issues/2774)) ([5219ff5](https://github.com/ngrx/platform/commit/5219ff5))\n- **entity:** remove addAll ([#2783](https://github.com/ngrx/platform/issues/2783)) ([93a4754](https://github.com/ngrx/platform/commit/93a4754))\n- **router-store:** add selectParamFromRouterState selector ([#2771](https://github.com/ngrx/platform/issues/2771)) ([3a1f359](https://github.com/ngrx/platform/commit/3a1f359)), closes [#2758](https://github.com/ngrx/platform/issues/2758)\n- **router-store:** Add urlAfterRedirects ([#2775](https://github.com/ngrx/platform/issues/2775)) ([14553f6](https://github.com/ngrx/platform/commit/14553f6))\n- **store:** add object-style StoreModule.forFeature overload ([#2821](https://github.com/ngrx/platform/issues/2821)) ([17571e5](https://github.com/ngrx/platform/commit/17571e5)), closes [#2809](https://github.com/ngrx/platform/issues/2809)\n- **store:** add support for provideMockStore outside of the TestBed ([#2759](https://github.com/ngrx/platform/issues/2759)) ([1650582](https://github.com/ngrx/platform/commit/1650582)), closes [#2745](https://github.com/ngrx/platform/issues/2745)\n\n### Performance Improvements\n\n- **router-store:** optimize selectQueryParams, selectQueryParam and selectFragment selectors ([#2764](https://github.com/ngrx/platform/issues/2764)) ([918f184](https://github.com/ngrx/platform/commit/918f184))\n\n### BREAKING CHANGES\n\n- **router-store:** Router-store selectors for query params and fragment select from the root router state node. This could potentially break unit tests, but is functionally equivalent to the current behavior at runtime.\n\nBEFORE:\n\nselectQueryParams - returns query params from the last router state node\nselectQueryParam - returns a query param from the last router state node\nselectFragment - returns the fragment from the last router state node\n\nAFTER:\n\nselectQueryParams - returns query params from routerState.root\nselectQueryParam - returns a query param from routerState.root\nselectFragment - returns the fragment from routerState.root\n\n- Angular peer dependency versions are bumped to latest major (11)\n\nBEFORE:\n\nMinimum Angular peer dependency version is ^10.0.0\n\nAFTER:\n\nMinimum Angular peer dependency version is ^11.0.0\n\n- **entity:** To overwrite the entities, we previously used the `addAll` method but the method name was confusing.\n\nBEFORE:\n\n```ts\nadapter.addAll(action.entities, state);\n```\n\nAFTER:\n\nThe new method name `setAll` describes the intention better.\n\n```ts\nadapter.setAll(action.entities, state);\n```\n\n- refactor(data): use the setAll adapter method\n- The initial state Injection Token for `@ngrx/component-store` has been renamed\n\nBEFORE:\n\nInjection Token is `initialStateToken`\n\nAFTER:\n\nInjection Token is `INITIAL_STATE_TOKEN`\n\n<a name=\"10.0.1\"></a>\n\n## [10.0.1](https://github.com/ngrx/platform/compare/10.0.0...10.0.1) (2020-10-07)\n\n### Bug Fixes\n\n- **component:** add entry point for schematic ([#2688](https://github.com/ngrx/platform/issues/2688)) ([d937275](https://github.com/ngrx/platform/commit/d937275)), closes [#2683](https://github.com/ngrx/platform/issues/2683)\n- **component-store:** add entry point for schematic ([#2687](https://github.com/ngrx/platform/issues/2687)) ([f8928e3](https://github.com/ngrx/platform/commit/f8928e3)), closes [#2682](https://github.com/ngrx/platform/issues/2682)\n- **schematics:** prevent ng-add from rewriting other workspace cli options ([#2731](https://github.com/ngrx/platform/issues/2731)) ([37354aa](https://github.com/ngrx/platform/commit/37354aa))\n- **store:** prevent unexpected behavior of {} as a props type ([#2728](https://github.com/ngrx/platform/issues/2728)) ([63510a8](https://github.com/ngrx/platform/commit/63510a8))\n\n<a name=\"10.0.0\"></a>\n\n# [10.0.0](https://github.com/ngrx/platform/compare/10.0.0-rc.0...10.0.0) (2020-08-10)\n\n<a name=\"10.0.0-rc.0\"></a>\n\n# [10.0.0-rc.0](https://github.com/ngrx/platform/compare/10.0.0-beta.1...10.0.0-rc.0) (2020-08-06)\n\n### Bug Fixes\n\n- **router-store:** add safety check to schematic ([#2632](https://github.com/ngrx/platform/issues/2632)) ([255e9e8](https://github.com/ngrx/platform/commit/255e9e8))\n\n### Code Refactoring\n\n- **component-store:** fine-tune effect types ([#2645](https://github.com/ngrx/platform/issues/2645)) ([ee92912](https://github.com/ngrx/platform/commit/ee92912))\n\n### Features\n\n- **entity:** add mapOne adapter method ([#2628](https://github.com/ngrx/platform/issues/2628)) ([d1891ad](https://github.com/ngrx/platform/commit/d1891ad)), closes [#2538](https://github.com/ngrx/platform/issues/2538)\n\n### BREAKING CHANGES\n\n- **component-store:** EffectReturnFn has been removed and the effect type is stricter and more predictable.\n\nBEFORE:\n\nIf effect was const e = effect((o: Observable<string>) => ....) it was still possible to call e() without passing any strings\n\nAFTER:\n\nIf effect was const e = effect((o: Observable<string>) => ....) its not allowed to call e() without passing any strings\n\n<a name=\"10.0.0-beta.1\"></a>\n\n# [10.0.0-beta.1](https://github.com/ngrx/platform/compare/10.0.0-beta.0...10.0.0-beta.1) (2020-07-20)\n\n### Bug Fixes\n\n- **example-app:** update snapshot ([f2af688](https://github.com/ngrx/platform/commit/f2af688))\n- **schematics:** fix unit tests for JSON with comments ([155ec1c](https://github.com/ngrx/platform/commit/155ec1c))\n\n### Features\n\n- **component:** add ng-add and ng-update schematics ([#2611](https://github.com/ngrx/platform/issues/2611)) ([3f2bea4](https://github.com/ngrx/platform/commit/3f2bea4))\n- **component-store:** add config for debounce selectors ([#2606](https://github.com/ngrx/platform/issues/2606)) ([ddf0271](https://github.com/ngrx/platform/commit/ddf0271))\n- **component-store:** add imperative reads ([#2614](https://github.com/ngrx/platform/issues/2614)) ([2146774](https://github.com/ngrx/platform/commit/2146774))\n- **component-store:** add ng-add and ng-update schematics ([#2598](https://github.com/ngrx/platform/issues/2598)) ([af7b2cc](https://github.com/ngrx/platform/commit/af7b2cc)), closes [#2569](https://github.com/ngrx/platform/issues/2569)\n\n### schematics\n\n- remove skipTest option ([#2596](https://github.com/ngrx/platform/issues/2596)) ([60cd5cc](https://github.com/ngrx/platform/commit/60cd5cc)), closes [#2561](https://github.com/ngrx/platform/issues/2561)\n\n### BREAKING CHANGES\n\n- The skipTest option has been renamed to skipTests\n\nBEFORE:\n\nng generate container UsersPage --skipTest\n\nAFTER:\n\nng generate container UsersPage --skipTests\n\n<a name=\"10.0.0-beta.0\"></a>\n\n# [10.0.0-beta.0](https://github.com/ngrx/platform/compare/9.2.0...10.0.0-beta.0) (2020-06-25)\n\n### Bug Fixes\n\n- **component:** detect zone.js using instanceof comparison ([#2547](https://github.com/ngrx/platform/issues/2547)) ([7128667](https://github.com/ngrx/platform/commit/7128667))\n- **component:** removed ivy checks as obsolete ([#2579](https://github.com/ngrx/platform/issues/2579)) ([e239950](https://github.com/ngrx/platform/commit/e239950))\n- **component-store:** export EffectReturnFn interface ([#2555](https://github.com/ngrx/platform/issues/2555)) ([f2a2212](https://github.com/ngrx/platform/commit/f2a2212))\n- **data:** mergeQuerySet uses mergeStrategy ([#2430](https://github.com/ngrx/platform/issues/2430)) ([e1720b4](https://github.com/ngrx/platform/commit/e1720b4)), closes [#2368](https://github.com/ngrx/platform/issues/2368)\n- **entity:** remove incorrect ComparerStr type ([#2584](https://github.com/ngrx/platform/issues/2584)) ([4796c97](https://github.com/ngrx/platform/commit/4796c97))\n- **schematics:** add comma before devtools for empty imports ([#2542](https://github.com/ngrx/platform/issues/2542)) ([f2d4ebc](https://github.com/ngrx/platform/commit/f2d4ebc))\n\n### Chores\n\n- update Angular dependencies to latest v10 RC ([#2573](https://github.com/ngrx/platform/issues/2573)) ([ed28449](https://github.com/ngrx/platform/commit/ed28449))\n\n### Features\n\n- **component-store:** add support for selectors ([#2539](https://github.com/ngrx/platform/issues/2539)) ([47e7ba3](https://github.com/ngrx/platform/commit/47e7ba3))\n- **component-store:** add support for side effects ([#2544](https://github.com/ngrx/platform/issues/2544)) ([f892cc8](https://github.com/ngrx/platform/commit/f892cc8))\n- **component-store:** make library compatible with ViewEngine ([#2580](https://github.com/ngrx/platform/issues/2580)) ([ba0818e](https://github.com/ngrx/platform/commit/ba0818e))\n- **router-store:** add route fragment selector ([#2543](https://github.com/ngrx/platform/issues/2543)) ([aba7368](https://github.com/ngrx/platform/commit/aba7368))\n\n### Performance Improvements\n\n- **component-store:** push updates to queueScheduler and single selectors to asapSchedulers ([#2586](https://github.com/ngrx/platform/issues/2586)) ([58073ab](https://github.com/ngrx/platform/commit/58073ab))\n\n### BREAKING CHANGES\n\n- **entity:** The compare function is used in two places, neither of which expect it to be able to return a string:\n  The first caller is the Array prototype sort function, and there it \"should return a negative, zero, or positive value, depending on the arguments\".\n  The second caller does a numerical comparison with the result.\n\nEven though an id can be a string, the result of a comparison shouldn't be.\n\nBEFORE:\n\nThe sortComparer types allow for a string to be returned\n\nAFTER:\n\nThe sortComparer types only allow a number to be returned\n\n- BEFORE:\n\nAngular v9 are minimum dependencies\n\nAFTER:\n\nAngular v10 are minimum dependencies\n\n<a name=\"9.2.0\"></a>\n\n# [9.2.0](https://github.com/ngrx/platform/compare/9.1.2...9.2.0) (2020-05-28)\n\n### Bug Fixes\n\n- **router-store:** selects should return selectors ([#2517](https://github.com/ngrx/platform/issues/2517)) ([831e1e4](https://github.com/ngrx/platform/commit/831e1e4)), closes [#2516](https://github.com/ngrx/platform/issues/2516)\n- **schematics:** components should inject the store without generic ([#2512](https://github.com/ngrx/platform/issues/2512)) ([4f7dcdc](https://github.com/ngrx/platform/commit/4f7dcdc))\n- **schematics:** use skipTests flag consistently, deprecate skipTest option ([#2522](https://github.com/ngrx/platform/issues/2522)) ([83033d7](https://github.com/ngrx/platform/commit/83033d7)), closes [#2521](https://github.com/ngrx/platform/issues/2521)\n- **store:** remove circular dependency for mock import ([#2540](https://github.com/ngrx/platform/issues/2540)) ([4892fa2](https://github.com/ngrx/platform/commit/4892fa2))\n\n### Features\n\n- **component:** add ngrxPush migration ([#2452](https://github.com/ngrx/platform/issues/2452)) ([0775093](https://github.com/ngrx/platform/commit/0775093)), closes [#2450](https://github.com/ngrx/platform/issues/2450)\n- **component-store:** add initial setup ([#2519](https://github.com/ngrx/platform/issues/2519)) ([a2657ac](https://github.com/ngrx/platform/commit/a2657ac))\n- **component-store:** initialization + updater/setState ([#2528](https://github.com/ngrx/platform/issues/2528)) ([3545df2](https://github.com/ngrx/platform/commit/3545df2))\n- **effects:** catch action creators being returned in effect without being called ([#2536](https://github.com/ngrx/platform/issues/2536)) ([100970b](https://github.com/ngrx/platform/commit/100970b))\n- **store:** add ngrxMockEnvironment function to control output during testing ([#2513](https://github.com/ngrx/platform/issues/2513)) ([da1a0c0](https://github.com/ngrx/platform/commit/da1a0c0)), closes [#2363](https://github.com/ngrx/platform/issues/2363)\n- **store:** add runtime check for action type uniqueness ([#2520](https://github.com/ngrx/platform/issues/2520)) ([2972980](https://github.com/ngrx/platform/commit/2972980))\n\n<a name=\"9.1.2\"></a>\n\n## [9.1.2](https://github.com/ngrx/platform/compare/9.1.1...9.1.2) (2020-05-06)\n\n<a name=\"9.1.1\"></a>\n\n## [9.1.1](https://github.com/ngrx/platform/compare/9.1.0...9.1.1) (2020-05-05)\n\n### Bug Fixes\n\n- **router-store:** selectors should return MemoizedSelector ([#2492](https://github.com/ngrx/platform/issues/2492)) ([39a4b91](https://github.com/ngrx/platform/commit/39a4b91))\n- **schematics:** use Angular default properties when not defined ([#2507](https://github.com/ngrx/platform/issues/2507)) ([7cd0624](https://github.com/ngrx/platform/commit/7cd0624)), closes [#1036](https://github.com/ngrx/platform/issues/1036)\n- **store:** ignore Ivy in runtime checks ([#2491](https://github.com/ngrx/platform/issues/2491)) ([46d752f](https://github.com/ngrx/platform/commit/46d752f)), closes [#2404](https://github.com/ngrx/platform/issues/2404)\n\n<a name=\"9.1.0\"></a>\n\n# [9.1.0](https://github.com/ngrx/platform/compare/9.0.0...9.1.0) (2020-04-07)\n\n### Bug Fixes\n\n- **component:** add docs overview ([#2444](https://github.com/ngrx/platform/issues/2444)) ([a279dd1](https://github.com/ngrx/platform/commit/a279dd1)), closes [#2442](https://github.com/ngrx/platform/issues/2442)\n- **example:** optimistically add/remove book from collection ([#2429](https://github.com/ngrx/platform/issues/2429)) ([b0aacf7](https://github.com/ngrx/platform/commit/b0aacf7)), closes [#2417](https://github.com/ngrx/platform/issues/2417)\n- **schematics:** install v9 with ng-add ([#2455](https://github.com/ngrx/platform/issues/2455)) ([19f1bda](https://github.com/ngrx/platform/commit/19f1bda))\n- **toObservableValue:** accommodate all observable inputs ([#2471](https://github.com/ngrx/platform/issues/2471)) ([468303a](https://github.com/ngrx/platform/commit/468303a))\n\n### Features\n\n- **component:** add ngrxPush pipe and ngrxLet directive to [@ngrx](https://github.com/ngrx)/component package ([#2046](https://github.com/ngrx/platform/issues/2046)) ([464073d](https://github.com/ngrx/platform/commit/464073d))\n- **effects:** add user provided effects to EffectsModule.forFeature ([#2231](https://github.com/ngrx/platform/issues/2231)) ([59ce3e2](https://github.com/ngrx/platform/commit/59ce3e2)), closes [#2232](https://github.com/ngrx/platform/issues/2232)\n- **schematics:** export reducer directly when Ivy is enabled ([#2440](https://github.com/ngrx/platform/issues/2440)) ([b68fa67](https://github.com/ngrx/platform/commit/b68fa67))\n\n<a name=\"9.0.0\"></a>\n\n# [9.0.0](https://github.com/ngrx/platform/compare/9.0.0-rc.0...9.0.0) (2020-03-09)\n\n### Features\n\n- **effects:** add EffectsRunner to public API ([#2427](https://github.com/ngrx/platform/issues/2427)) ([f66fd64](https://github.com/ngrx/platform/commit/f66fd64))\n\n<a name=\"9.0.0-rc.0\"></a>\n\n# [9.0.0-rc.0](https://github.com/ngrx/platform/compare/9.0.0-beta.2...9.0.0-rc.0) (2020-03-02)\n\n### Bug Fixes\n\n- **data:** correct AppEntityServices example in ngrx data doc page ([#2413](https://github.com/ngrx/platform/issues/2413)) ([711ba0e](https://github.com/ngrx/platform/commit/711ba0e)), closes [#2280](https://github.com/ngrx/platform/issues/2280)\n- **example:** fix a typo selectShowSidenav ([#2414](https://github.com/ngrx/platform/issues/2414)) ([c9ebb06](https://github.com/ngrx/platform/commit/c9ebb06))\n\n### Features\n\n- **entity:** add 'setOne' method to entity adapter ([#2410](https://github.com/ngrx/platform/issues/2410)) ([4b4bb85](https://github.com/ngrx/platform/commit/4b4bb85)), closes [#2369](https://github.com/ngrx/platform/issues/2369)\n\n<a name=\"9.0.0-beta.2\"></a>\n\n# [9.0.0-beta.2](https://github.com/ngrx/platform/compare/9.0.0-beta.1...9.0.0-beta.2) (2020-02-25)\n\n### Bug Fixes\n\n- **docs:** replace duplicate link ([#2399](https://github.com/ngrx/platform/issues/2399)) ([d4502b4](https://github.com/ngrx/platform/commit/d4502b4))\n- **effects:** use source instance for ngrxOnRunEffects to retain context ([#2401](https://github.com/ngrx/platform/issues/2401)) ([79c830c](https://github.com/ngrx/platform/commit/79c830c))\n\n### Features\n\n- **store-devtools:** add INITIAL_OPTIONS to public API ([#2405](https://github.com/ngrx/platform/issues/2405)) ([37f91db](https://github.com/ngrx/platform/commit/37f91db)), closes [#1470](https://github.com/ngrx/platform/issues/1470)\n\n<a name=\"9.0.0-beta.1\"></a>\n\n# [9.0.0-beta.1](https://github.com/ngrx/platform/compare/9.0.0-beta.0...9.0.0-beta.1) (2020-02-18)\n\n### Bug Fixes\n\n- **data:** Angular 9 style ModuleWithProvider ([#2356](https://github.com/ngrx/platform/issues/2356)) ([#2357](https://github.com/ngrx/platform/issues/2357)) ([182f140](https://github.com/ngrx/platform/commit/182f140))\n- **data:** change type of filter observable ([#2349](https://github.com/ngrx/platform/issues/2349)) ([94f3ef1](https://github.com/ngrx/platform/commit/94f3ef1)), closes [#2337](https://github.com/ngrx/platform/issues/2337)\n- **data:** EntityDataModuleWithoutEffect ModuleWithProviders ([#2366](https://github.com/ngrx/platform/issues/2366)) ([234ce84](https://github.com/ngrx/platform/commit/234ce84))\n- **data:** make mergeServerUpserts change state immutably ([#2374](https://github.com/ngrx/platform/issues/2374)) ([#2389](https://github.com/ngrx/platform/issues/2389)) ([b3a49c1](https://github.com/ngrx/platform/commit/b3a49c1))\n- **data:** make undoMany remove tracking changes in changeState ([#2346](https://github.com/ngrx/platform/issues/2346)) ([#2352](https://github.com/ngrx/platform/issues/2352)) ([637b2c7](https://github.com/ngrx/platform/commit/637b2c7))\n- **data:** use ng_package for bundling instead of pkg_npm ([9a935b1](https://github.com/ngrx/platform/commit/9a935b1))\n- **effects:** dispatch OnInitEffects action after registration ([#2386](https://github.com/ngrx/platform/issues/2386)) ([daf1e64](https://github.com/ngrx/platform/commit/daf1e64)), closes [#2373](https://github.com/ngrx/platform/issues/2373)\n- **store:** provide the same instance of MockStore ([#2381](https://github.com/ngrx/platform/issues/2381)) ([827f336](https://github.com/ngrx/platform/commit/827f336)), closes [#2362](https://github.com/ngrx/platform/issues/2362)\n\n### Features\n\n- **effects:** limit retries to 10 by default ([#2376](https://github.com/ngrx/platform/issues/2376)) ([88124a7](https://github.com/ngrx/platform/commit/88124a7)), closes [#2303](https://github.com/ngrx/platform/issues/2303)\n- **store:** add strictActionWithinNgZone runtime check ([#2364](https://github.com/ngrx/platform/issues/2364)) ([4cae255](https://github.com/ngrx/platform/commit/4cae255)), closes [#2339](https://github.com/ngrx/platform/issues/2339)\n- **store:** testing - clean up mock store and remove static property ([#2361](https://github.com/ngrx/platform/issues/2361)) ([ee2c114](https://github.com/ngrx/platform/commit/ee2c114))\n\n<a name=\"9.0.0-beta.0\"></a>\n\n# [9.0.0-beta.0](https://github.com/ngrx/platform/compare/8.6.0...9.0.0-beta.0) (2020-02-06)\n\n### Bug Fixes\n\n- **data:** allow additional selectors in entitySelectors$ ([#2332](https://github.com/ngrx/platform/issues/2332)) ([900bf75](https://github.com/ngrx/platform/commit/900bf75))\n- **effects:** dispatch init action once ([#2164](https://github.com/ngrx/platform/issues/2164)) ([a528320](https://github.com/ngrx/platform/commit/a528320)), closes [#2106](https://github.com/ngrx/platform/issues/2106)\n- **effects:** fix specs for ng-add tests ([#2314](https://github.com/ngrx/platform/issues/2314)) ([98d6606](https://github.com/ngrx/platform/commit/98d6606))\n- **schematics:** migrate spec to skipTest to be in line with Angular CLI ([#2253](https://github.com/ngrx/platform/issues/2253)) ([714ae5f](https://github.com/ngrx/platform/commit/714ae5f)), closes [#2242](https://github.com/ngrx/platform/issues/2242)\n- **store:** add not allowed check to action creator config ([#2313](https://github.com/ngrx/platform/issues/2313)) ([f6336d5](https://github.com/ngrx/platform/commit/f6336d5))\n- **store:** allow union of types in props ([#2301](https://github.com/ngrx/platform/issues/2301)) ([33241cb](https://github.com/ngrx/platform/commit/33241cb))\n- **store:** replace Creator with ActionCreator on createAction ([#2299](https://github.com/ngrx/platform/issues/2299)) ([fe6bfa7](https://github.com/ngrx/platform/commit/fe6bfa7))\n\n### Chores\n\n- update deps to Angular version 9 ([#2234](https://github.com/ngrx/platform/issues/2234)) ([b146af5](https://github.com/ngrx/platform/commit/b146af5))\n\n### Code Refactoring\n\n- **schematics:** Flag minimal is set to true ([#2258](https://github.com/ngrx/platform/issues/2258)) ([7ecaa22](https://github.com/ngrx/platform/commit/7ecaa22)), closes [#2250](https://github.com/ngrx/platform/issues/2250)\n- **schematics:** migrate from styleext to style ([#2254](https://github.com/ngrx/platform/issues/2254)) ([2801018](https://github.com/ngrx/platform/commit/2801018)), closes [#2248](https://github.com/ngrx/platform/issues/2248)\n\n### Features\n\n- **component:** initial setup ([#2257](https://github.com/ngrx/platform/issues/2257)) ([b8a769a](https://github.com/ngrx/platform/commit/b8a769a))\n- **docs:** add presskit page ([#2296](https://github.com/ngrx/platform/issues/2296)) ([9ac1165](https://github.com/ngrx/platform/commit/9ac1165)), closes [#2293](https://github.com/ngrx/platform/issues/2293)\n- **effects:** add migration for breaking change that renames effects error handler config key ([#2335](https://github.com/ngrx/platform/issues/2335)) ([93b4081](https://github.com/ngrx/platform/commit/93b4081))\n- **effects:** make resubscription handler overridable ([#2295](https://github.com/ngrx/platform/issues/2295)) ([3a9ad63](https://github.com/ngrx/platform/commit/3a9ad63)), closes [#2294](https://github.com/ngrx/platform/issues/2294)\n- **entity:** deprecate addAll and rename it to setAll ([#2348](https://github.com/ngrx/platform/issues/2348)) ([27f5059](https://github.com/ngrx/platform/commit/27f5059)), closes [#2330](https://github.com/ngrx/platform/issues/2330)\n- **router:** enabling MinimalRouterStateSerializer by default ([#2326](https://github.com/ngrx/platform/issues/2326)) ([ba37ad8](https://github.com/ngrx/platform/commit/ba37ad8)), closes [#2225](https://github.com/ngrx/platform/issues/2225)\n- **router-store:** add migration to add the default serializer ([#2291](https://github.com/ngrx/platform/issues/2291)) ([b742a8c](https://github.com/ngrx/platform/commit/b742a8c))\n- **schematics:** update creators to the default ([6149753](https://github.com/ngrx/platform/commit/6149753))\n- **store:** add default generic type to Store and MockStore ([#2325](https://github.com/ngrx/platform/issues/2325)) ([09daeb9](https://github.com/ngrx/platform/commit/09daeb9))\n- **store:** ignore actions from NgRx libraries in runtime checks ([#2351](https://github.com/ngrx/platform/issues/2351)) ([0dabfc4](https://github.com/ngrx/platform/commit/0dabfc4))\n- update to Angular 9-rc.13 ([#2345](https://github.com/ngrx/platform/issues/2345)) ([d7fdf7f](https://github.com/ngrx/platform/commit/d7fdf7f))\n- **store:** add clearResult to reset a mock selector ([#2270](https://github.com/ngrx/platform/issues/2270)) ([803295b](https://github.com/ngrx/platform/commit/803295b)), closes [#2244](https://github.com/ngrx/platform/issues/2244)\n- **store:** compile time errors when action creators being passed to dispatch without () ([#2306](https://github.com/ngrx/platform/issues/2306)) ([98b74ad](https://github.com/ngrx/platform/commit/98b74ad))\n- **store:** enable immutability checks by default ([#2266](https://github.com/ngrx/platform/issues/2266)) ([1758d34](https://github.com/ngrx/platform/commit/1758d34)), closes [#2217](https://github.com/ngrx/platform/issues/2217)\n- **store:** testing - expose MockStore provider ([#2331](https://github.com/ngrx/platform/issues/2331)) ([ef5cd5f](https://github.com/ngrx/platform/commit/ef5cd5f)), closes [#2328](https://github.com/ngrx/platform/issues/2328)\n\n### BREAKING CHANGES\n\n- **router:** The MinimalRouterStateSerializer is enabled by default.\n\nBEFORE:\n\nIf no router state serializer is provided through the configuration of router store, the DefaultRouterStateSerializer is used.\n\nAFTER:\n\nIf no router state serializer is provided through the configuration of router store, the MinimalRouterStateSerializer is used.\n\n- **effects:** `resubscribeOnError` renamed to `useEffectsErrorHandler` in `createEffect` metadata\n\nBEFORE:\n\n```ts\nclass MyEffects {\n  effect$ = createEffect(() => stream$, {\n    resubscribeOnError: true, // default\n  });\n}\n```\n\nAFTER:\n\n```ts\nclass MyEffects {\n  effect$ = createEffect(() => stream$, {\n    useEffectsErrorHandler: true, // default\n  });\n}\n```\n\n- **effects:** BEFORE:\n\nWhen the effect class was registered, the init action would be dispatched.\nIf the effect was provided in multiple lazy loaded modules, the init action would be dispatched for every module.\n\nAFTER:\n\nThe init action is only dispatched once\nThe init action is now dispatched based on the identifier of the effect (via ngrxOnIdentifyEffects)\n\n- **schematics:** To be inline with the Angular CLI, we migrated the `--spec` to `--skipTest`.\n  By default skipTest is false, this way you will always be provided with `*.spec.ts files`\n\nBEFORE:\n\n```sh\n\nng generate action User --spec\n\n```\n\nAFTER:\n\n```sh\n\nng generate action User\n\n```\n\n- **store:** BEFORE:\n\nUsing `mockSelector.setResult(undefined)` resulted in clearing the\nreturn value.\n\nAFTER:\n\nUsing `mockSelector.setResult(undefined)` will set the return value of\nthe selector to `undefined`.\nTo reset the mock selector, use `mockSelector.clearResult()`.\n\n- **schematics:** To be inline with the Angular CLI, the `styleExt` option has been changed to `style`.\n\nBEFORE:\n\n```ts\n\"@schematics/angular:component\": {\n      \"inlineStyle\": true,\n      \"prefix\": \"aio\",\n      \"styleext\": \"scss\"\n    }\n...\n```\n\nAFTER:\n\n```ts\n\"@schematics/angular:component\": {\n      \"inlineStyle\": true,\n      \"prefix\": \"aio\",\n      \"style\": \"scss\"\n    }\n....\n```\n\n- **store:** Immutability checks are enabled by default.\n\nBEFORE:\n\nImmutability checks are opt-in.\n\nAFTER:\n\nIf state or action is mutated then there will be a run time exception thrown.\n\n- **schematics:** With this change by default the minimal setup for `@ngrx/store` will be generated.\n\nBEFORE:\n\n```ts\n@NgModule({\n  declarations: [\n    AppComponent,\n  ],\n  imports: [\n    BrowserModule,\n    StoreModule.forRoot(reducers, {\n      metaReducers,\n      runtimeChecks: {\n        strictStateImmutability: true,\n        strictActionImmutability: true\n      }\n    }),\n    .....\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\n```\n\nAFTER:\n\n```ts\n@NgModule({\n  declarations: [\n    AppComponent,\n  ],\n  imports: [\n    BrowserModule,\n    StoreModule.forRoot({})\n    ....\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\n```\n\n- **schematics:** BEFORE:\n\nThe create functions weren't the default to create actions, reducers and effects\n\nAFTER:\n\nThe create functions are the default to create actions (createAction, reducers (createReducer) and effects (createEffect)\nTo fallback to the previous generators, use\n\n`sh\nng generate reducer ReducerName --creators=false\n`\n\n- Libraries will depend on Angular version 9\n\n<a name=\"8.6.0\"></a>\n\n# [8.6.0](https://github.com/ngrx/platform/compare/8.5.2...8.6.0) (2019-12-18)\n\n### Features\n\n- **router-store:** add action creator for root router actions ([#2272](https://github.com/ngrx/platform/issues/2272)) ([f17589f](https://github.com/ngrx/platform/commit/f17589f)), closes [#2206](https://github.com/ngrx/platform/issues/2206)\n\n<a name=\"8.5.2\"></a>\n\n## [8.5.2](https://github.com/ngrx/platform/compare/8.5.1...8.5.2) (2019-11-21)\n\n### Bug Fixes\n\n- **effects:** add EffectsRootModule and EffectsFeatureModule to public API ([#2273](https://github.com/ngrx/platform/issues/2273)) ([abe1f6b](https://github.com/ngrx/platform/commit/abe1f6b))\n- **store:** added noop for addFeature in MockReducerManager ([#2265](https://github.com/ngrx/platform/issues/2265)) ([c42e444](https://github.com/ngrx/platform/commit/c42e444)), closes [#2263](https://github.com/ngrx/platform/issues/2263)\n- **store-devtools:** escaping the safelist and blocklist strings ([#2259](https://github.com/ngrx/platform/issues/2259)) ([e888977](https://github.com/ngrx/platform/commit/e888977)), closes [#2228](https://github.com/ngrx/platform/issues/2228)\n\n<a name=\"8.5.1\"></a>\n\n## [8.5.1](https://github.com/ngrx/platform/compare/8.5.0...8.5.1) (2019-11-12)\n\n### Bug Fixes\n\n- **effects:** add action creator for root effects init action ([#2219](https://github.com/ngrx/platform/issues/2219)) ([de9a590](https://github.com/ngrx/platform/commit/de9a590)), closes [#2218](https://github.com/ngrx/platform/issues/2218)\n- **effects:** export CreateEffectMetadata ([#2245](https://github.com/ngrx/platform/issues/2245)) ([bfe4c81](https://github.com/ngrx/platform/commit/bfe4c81)), closes [#2243](https://github.com/ngrx/platform/issues/2243)\n\n<a name=\"8.5.0\"></a>\n\n# [8.5.0](https://github.com/ngrx/platform/compare/8.4.0...8.5.0) (2019-11-07)\n\n### Bug Fixes\n\n- **effects:** resubscribe every time an error occurs ([#2165](https://github.com/ngrx/platform/issues/2165)) ([0d59783](https://github.com/ngrx/platform/commit/0d59783))\n- **store:** disallow arrays in action creators ([#2155](https://github.com/ngrx/platform/issues/2155)) ([1e4c0be](https://github.com/ngrx/platform/commit/1e4c0be))\n- **store:** improve createFeatureSelector warning ([#2163](https://github.com/ngrx/platform/issues/2163)) ([e4765d6](https://github.com/ngrx/platform/commit/e4765d6)), closes [#2116](https://github.com/ngrx/platform/issues/2116)\n- **store:** improve types for string selectors ([#2174](https://github.com/ngrx/platform/issues/2174)) ([46a8467](https://github.com/ngrx/platform/commit/46a8467))\n\n### Features\n\n- **data:** add entity config in app module declaration for ng-add ([#2133](https://github.com/ngrx/platform/issues/2133)) ([6ca3056](https://github.com/ngrx/platform/commit/6ca3056))\n- **effects:** createEffect returns specific type for dispatch false ([#2195](https://github.com/ngrx/platform/issues/2195)) ([f70600f](https://github.com/ngrx/platform/commit/f70600f))\n- **effects:** improve types for ofType with action creators ([#2175](https://github.com/ngrx/platform/issues/2175)) ([cf02dd2](https://github.com/ngrx/platform/commit/cf02dd2))\n- **schematics:** add message prompts for individual schematics ([#2143](https://github.com/ngrx/platform/issues/2143)) ([fcb01e2](https://github.com/ngrx/platform/commit/fcb01e2))\n- **schematics:** add selector schematics ([#2160](https://github.com/ngrx/platform/issues/2160)) ([78817c7](https://github.com/ngrx/platform/commit/78817c7)), closes [#2140](https://github.com/ngrx/platform/issues/2140)\n- **store:** add On interface to public api ([#2157](https://github.com/ngrx/platform/issues/2157)) ([1b4ba1a](https://github.com/ngrx/platform/commit/1b4ba1a))\n\n<a name=\"8.4.0\"></a>\n\n# [8.4.0](https://github.com/ngrx/platform/compare/8.3.0...8.4.0) (2019-10-09)\n\n### Bug Fixes\n\n- **schematics:** fixed the schematics/action spec template ([#2092](https://github.com/ngrx/platform/issues/2092)) ([ed3b1f9](https://github.com/ngrx/platform/commit/ed3b1f9)), closes [#2082](https://github.com/ngrx/platform/issues/2082)\n- **store:** improve consistency of memoized selector result when projection fails ([#2101](https://github.com/ngrx/platform/issues/2101)) ([c63941c](https://github.com/ngrx/platform/commit/c63941c)), closes [#2100](https://github.com/ngrx/platform/issues/2100)\n\n### Features\n\n- **effects:** throw error when forRoot() is used more than once ([b46748c](https://github.com/ngrx/platform/commit/b46748c))\n- **schematics:** add createEffect migration schematic ([#2136](https://github.com/ngrx/platform/issues/2136)) ([9eb1bd5](https://github.com/ngrx/platform/commit/9eb1bd5))\n- **store:** add refreshState method to mock store ([#2148](https://github.com/ngrx/platform/issues/2148)) ([30e876f](https://github.com/ngrx/platform/commit/30e876f)), closes [#2121](https://github.com/ngrx/platform/issues/2121)\n- **store:** allow multiple on handlers for the same action in createReducer([#2103](https://github.com/ngrx/platform/issues/2103)) ([9a70262](https://github.com/ngrx/platform/commit/9a70262)), closes [#1956](https://github.com/ngrx/platform/issues/1956)\n- **store:** cleanup selector after a test ([2964e2b](https://github.com/ngrx/platform/commit/2964e2b))\n- **store:** throw error when forRoot() is used more than once ([4304865](https://github.com/ngrx/platform/commit/4304865))\n\n<a name=\"8.3.0\"></a>\n\n# [8.3.0](https://github.com/ngrx/platform/compare/8.2.0...8.3.0) (2019-08-29)\n\n### Bug Fixes\n\n- **data:** use correct guard when handling optimistic update ([#2060](https://github.com/ngrx/platform/issues/2060)) ([34c0420](https://github.com/ngrx/platform/commit/34c0420)), closes [#2059](https://github.com/ngrx/platform/issues/2059)\n- **store:** add DefaultProjectorFn to public API ([#2090](https://github.com/ngrx/platform/issues/2090)) ([2d37b48](https://github.com/ngrx/platform/commit/2d37b48))\n- **store:** should not run schematics when not using named imports ([#2095](https://github.com/ngrx/platform/issues/2095)) ([7cadbc0](https://github.com/ngrx/platform/commit/7cadbc0)), closes [#2093](https://github.com/ngrx/platform/issues/2093)\n\n### Features\n\n- **store:** add verbose error message for undefined feature state in development mode ([#2078](https://github.com/ngrx/platform/issues/2078)) ([6946e2e](https://github.com/ngrx/platform/commit/6946e2e)), closes [#1897](https://github.com/ngrx/platform/issues/1897)\n\n<a name=\"8.2.0\"></a>\n\n# [8.2.0](https://github.com/ngrx/platform/compare/8.1.0...8.2.0) (2019-07-31)\n\n### Bug Fixes\n\n- **effects:** resubscribe every time an error occurs ([#2023](https://github.com/ngrx/platform/issues/2023)) ([#2026](https://github.com/ngrx/platform/issues/2026)) ([5b48912](https://github.com/ngrx/platform/commit/5b48912))\n- **store:** add missing StoreConfig and RootStoreConfig exports ([#2009](https://github.com/ngrx/platform/issues/2009)) ([5e01e50](https://github.com/ngrx/platform/commit/5e01e50)), closes [#2007](https://github.com/ngrx/platform/issues/2007)\n\n### Features\n\n- **docs:** enable search functionality ([#2020](https://github.com/ngrx/platform/issues/2020)) ([3cc4f3d](https://github.com/ngrx/platform/commit/3cc4f3d))\n- **router-store:** add selectQueryParam and selectRouteParam ([#2014](https://github.com/ngrx/platform/issues/2014)) ([57fd3d7](https://github.com/ngrx/platform/commit/57fd3d7))\n- **schematics:** add option to use MockStore in container tests ([#2029](https://github.com/ngrx/platform/issues/2029)) ([6905d52](https://github.com/ngrx/platform/commit/6905d52)), closes [#2028](https://github.com/ngrx/platform/issues/2028)\n- **store:** add USER_RUNTIME_CHECKS public token ([#2006](https://github.com/ngrx/platform/issues/2006)) ([fa8da34](https://github.com/ngrx/platform/commit/fa8da34)), closes [#1973](https://github.com/ngrx/platform/issues/1973)\n\n<a name=\"8.1.0\"></a>\n\n# [8.1.0](https://github.com/ngrx/platform/compare/8.0.1...8.1.0) (2019-07-09)\n\n### Bug Fixes\n\n- **data:** allow ChangeSetItemFactory to update entities with number ids ([#1995](https://github.com/ngrx/platform/issues/1995)) ([f11c7b2](https://github.com/ngrx/platform/commit/f11c7b2)), closes [#1988](https://github.com/ngrx/platform/issues/1988)\n- **data:** search for replacements in all files when using ng-add ([#1971](https://github.com/ngrx/platform/issues/1971)) ([30ce2c6](https://github.com/ngrx/platform/commit/30ce2c6))\n- **store:** add immutability check for IE compatibility ([#1997](https://github.com/ngrx/platform/issues/1997)) ([11c0864](https://github.com/ngrx/platform/commit/11c0864)), closes [#1991](https://github.com/ngrx/platform/issues/1991)\n- **store:** fix typo in runtime checks injection token description ([#1975](https://github.com/ngrx/platform/issues/1975)) ([125d950](https://github.com/ngrx/platform/commit/125d950)), closes [#1972](https://github.com/ngrx/platform/issues/1972)\n\n### Features\n\n- **effects:** add support for minimal setup option for ng-add ([e839568](https://github.com/ngrx/platform/commit/e839568))\n- **effects:** export EffectConfig and add docs ([6a4bbcf](https://github.com/ngrx/platform/commit/6a4bbcf))\n- **schematics:** add support for minimal setup option for store and effects ([cede393](https://github.com/ngrx/platform/commit/cede393))\n- **schematics:** enable immutability checks for root store by default ([#1983](https://github.com/ngrx/platform/issues/1983)) ([2b8178d](https://github.com/ngrx/platform/commit/2b8178d)), closes [#1950](https://github.com/ngrx/platform/issues/1950)\n- **store:** add support for minimal setup option for ng-add ([12202a7](https://github.com/ngrx/platform/commit/12202a7))\n\n<a name=\"8.0.1\"></a>\n\n## [8.0.1](https://github.com/ngrx/platform/compare/8.0.0...8.0.1) (2019-06-10)\n\n### Bug Fixes\n\n- **store:** prevent passing of action creator function to store dispatch and effects ([#1914](https://github.com/ngrx/platform/issues/1914)) ([78153cb](https://github.com/ngrx/platform/commit/78153cb)), closes [#1906](https://github.com/ngrx/platform/issues/1906)\n\n<a name=\"8.0.0\"></a>\n\n# [8.0.0](https://github.com/ngrx/platform/compare/8.0.0-rc.1...8.0.0) (2019-06-06)\n\n### Features\n\n- **store:** add protection from type property use ([#1923](https://github.com/ngrx/platform/issues/1923)) ([bb9add7](https://github.com/ngrx/platform/commit/bb9add7)), closes [#1917](https://github.com/ngrx/platform/issues/1917)\n- **store:** capture the type of a selector projector function ([#1920](https://github.com/ngrx/platform/issues/1920)) ([4e39cc1](https://github.com/ngrx/platform/commit/4e39cc1)), closes [#1908](https://github.com/ngrx/platform/issues/1908)\n\n### Performance Improvements\n\n- fine tune schematics to only commit changes ([#1925](https://github.com/ngrx/platform/issues/1925)) ([5fcdd3b](https://github.com/ngrx/platform/commit/5fcdd3b))\n\n<a name=\"8.0.0-rc.1\"></a>\n\n# [8.0.0-rc.1](https://github.com/ngrx/platform/compare/8.0.0-rc.0...8.0.0-rc.1) (2019-06-04)\n\n### Bug Fixes\n\n- **router-store:** remove circular dependency in serializers ([#1904](https://github.com/ngrx/platform/issues/1904)) ([0407c5b](https://github.com/ngrx/platform/commit/0407c5b)), closes [#1902](https://github.com/ngrx/platform/issues/1902)\n\n### Features\n\n- **store:** add ngrx-store-freeze migration ([#1901](https://github.com/ngrx/platform/issues/1901)) ([4146650](https://github.com/ngrx/platform/commit/4146650)), closes [#1896](https://github.com/ngrx/platform/issues/1896)\n\n<a name=\"8.0.0-rc.0\"></a>\n\n# [8.0.0-rc.0](https://github.com/ngrx/platform/compare/8.0.0-beta.2...8.0.0-rc.0) (2019-05-30)\n\n### Bug Fixes\n\n- update signature for createSelectorFactory and createSelector to return a MemoizedSelector ([#1883](https://github.com/ngrx/platform/issues/1883)) ([8b31da7](https://github.com/ngrx/platform/commit/8b31da7))\n- **store:** adjust mock store to handle selectors with props ([#1878](https://github.com/ngrx/platform/issues/1878)) ([a7ded00](https://github.com/ngrx/platform/commit/a7ded00)), closes [#1864](https://github.com/ngrx/platform/issues/1864) [#1873](https://github.com/ngrx/platform/issues/1873)\n\n### Features\n\n- **effects:** resubscribe to effects on error ([#1881](https://github.com/ngrx/platform/issues/1881)) ([71137e5](https://github.com/ngrx/platform/commit/71137e5))\n- **example:** add examples of effects not based on the Actions stream ([#1845](https://github.com/ngrx/platform/issues/1845)) ([3454e70](https://github.com/ngrx/platform/commit/3454e70)), closes [#1830](https://github.com/ngrx/platform/issues/1830)\n- **router-store:** add routerState config option ([#1847](https://github.com/ngrx/platform/issues/1847)) ([d874cfc](https://github.com/ngrx/platform/commit/d874cfc)), closes [#1834](https://github.com/ngrx/platform/issues/1834)\n- **router-store:** add selectors for router state ([#1874](https://github.com/ngrx/platform/issues/1874)) ([21c67cc](https://github.com/ngrx/platform/commit/21c67cc)), closes [#1854](https://github.com/ngrx/platform/issues/1854)\n- **store:** split immutibility checks in state and action checks ([#1894](https://github.com/ngrx/platform/issues/1894)) ([c59c211](https://github.com/ngrx/platform/commit/c59c211))\n\n### Reverts\n\n- **store:** store should fail synchronously ([#1871](https://github.com/ngrx/platform/issues/1871)) ([59a9e6c](https://github.com/ngrx/platform/commit/59a9e6c)), closes [#1865](https://github.com/ngrx/platform/issues/1865)\n\n### BREAKING CHANGES\n\n- **effects:** Prior to introduction of automatic resubscriptions on errors, all effects had effectively {resubscribeOnError: false} behavior. For the rare cases when this is still wanted please add {resubscribeOnError: false} to the effect metadata.\n\nBEFORE:\n\n```ts\nlogin$ = createEffect(() =>\n  this.actions$.pipe(\n    ofType(LoginPageActions.login),\n    mapToAction(\n      // Happy path callback\n      (action) =>\n        this.authService\n          .login(action.credentials)\n          .pipe(map((user) => AuthApiActions.loginSuccess({ user }))),\n      // error callback\n      (error) => AuthApiActions.loginFailure({ error })\n    )\n  )\n);\n```\n\nAFTER:\n\n```ts\nlogin$ = createEffect(\n  () =>\n    this.actions$.pipe(\n      ofType(LoginPageActions.login),\n      mapToAction(\n        // Happy path callback\n        (action) =>\n          this.authService\n            .login(action.credentials)\n            .pipe(\n              map((user) => AuthApiActions.loginSuccess({ user }))\n            ),\n        // error callback\n        (error) => AuthApiActions.loginFailure({ error })\n      )\n      // Errors are handled and it is safe to disable resubscription\n    ),\n  { resubscribeOnError: false }\n);\n```\n\n- The return type of the createSelectorFactory and createSelector is now a MemoizedSelector instead of a Selector\n\n<a name=\"8.0.0-beta.2\"></a>\n\n# [8.0.0-beta.2](https://github.com/ngrx/platform/compare/8.0.0-beta.1...8.0.0-beta.2) (2019-05-15)\n\n### Bug Fixes\n\n- **data:** update the package name for replacement to ngrx-data ([#1805](https://github.com/ngrx/platform/issues/1805)) ([00c00e0](https://github.com/ngrx/platform/commit/00c00e0)), closes [#1802](https://github.com/ngrx/platform/issues/1802)\n- **example:** resolve circular dependency ([#1833](https://github.com/ngrx/platform/issues/1833)) ([1fbd59c](https://github.com/ngrx/platform/commit/1fbd59c))\n\n### Features\n\n- **effects:** add mapToAction operator ([#1822](https://github.com/ngrx/platform/issues/1822)) ([1ff986f](https://github.com/ngrx/platform/commit/1ff986f)), closes [#1224](https://github.com/ngrx/platform/issues/1224)\n- **store:** add option to mock selectors in MockStoreConfig ([#1836](https://github.com/ngrx/platform/issues/1836)) ([070228c](https://github.com/ngrx/platform/commit/070228c)), closes [#1827](https://github.com/ngrx/platform/issues/1827)\n- **store:** expand createReducer type signature to support up to ten action creators ([#1803](https://github.com/ngrx/platform/issues/1803)) ([63e4926](https://github.com/ngrx/platform/commit/63e4926))\n- **store:** warn when same action is registered ([#1801](https://github.com/ngrx/platform/issues/1801)) ([ecda5f7](https://github.com/ngrx/platform/commit/ecda5f7)), closes [#1758](https://github.com/ngrx/platform/issues/1758)\n\n### Reverts\n\n- warn when same action is registered ([#1801](https://github.com/ngrx/platform/issues/1801)) ([#1841](https://github.com/ngrx/platform/issues/1841)) ([b07ae4e](https://github.com/ngrx/platform/commit/b07ae4e))\n\n<a name=\"8.0.0-beta.1\"></a>\n\n# [8.0.0-beta.1](https://github.com/ngrx/platform/compare/8.0.0-beta.0...8.0.0-beta.1) (2019-04-24)\n\n### Features\n\n- **data:** add schematics and migrations ([#1782](https://github.com/ngrx/platform/issues/1782)) ([c79362d](https://github.com/ngrx/platform/commit/c79362d))\n- **schematics:** add support for reducer creators ([#1785](https://github.com/ngrx/platform/issues/1785)) ([8a0a049](https://github.com/ngrx/platform/commit/8a0a049)), closes [#1764](https://github.com/ngrx/platform/issues/1764)\n- **store:** change createReducer to avoid generic ([#1796](https://github.com/ngrx/platform/issues/1796)) ([8f2cb7b](https://github.com/ngrx/platform/commit/8f2cb7b))\n\n<a name=\"8.0.0-beta.0\"></a>\n\n# [8.0.0-beta.0](https://github.com/ngrx/platform/compare/7.4.0...8.0.0-beta.0) (2019-04-17)\n\n### Bug Fixes\n\n- **effects:** add the export of EffectMetadata ([#1720](https://github.com/ngrx/platform/issues/1720)) ([214316f](https://github.com/ngrx/platform/commit/214316f))\n- **example:** handle possible undefined results from Dictionary ([#1745](https://github.com/ngrx/platform/issues/1745)) ([861b0cb](https://github.com/ngrx/platform/commit/861b0cb)), closes [#1735](https://github.com/ngrx/platform/issues/1735)\n- **schematics:** check for empty name when using store schematic for feature states ([#1659](https://github.com/ngrx/platform/issues/1659)) ([#1666](https://github.com/ngrx/platform/issues/1666)) ([3b9b890](https://github.com/ngrx/platform/commit/3b9b890))\n- **store:** add the missing bracket in immutability meta-reducer ([#1721](https://github.com/ngrx/platform/issues/1721)) ([56f8a59](https://github.com/ngrx/platform/commit/56f8a59))\n- **Store:** selector with only a projector ([#1579](https://github.com/ngrx/platform/issues/1579)) ([da1ec80](https://github.com/ngrx/platform/commit/da1ec80)), closes [#1558](https://github.com/ngrx/platform/issues/1558)\n- **StoreDevTools:** rename action list filters ([#1589](https://github.com/ngrx/platform/issues/1589)) ([5581826](https://github.com/ngrx/platform/commit/5581826)), closes [#1557](https://github.com/ngrx/platform/issues/1557)\n\n### Code Refactoring\n\n- **Store:** don't export internal functions and tokens ([#1679](https://github.com/ngrx/platform/issues/1679)) ([0446a15](https://github.com/ngrx/platform/commit/0446a15)), closes [#1657](https://github.com/ngrx/platform/issues/1657)\n\n### Features\n\n- **schematics:** add support for action creators to schematics ([#1765](https://github.com/ngrx/platform/issues/1765)) ([876f80a](https://github.com/ngrx/platform/commit/876f80a)), closes [#1670](https://github.com/ngrx/platform/issues/1670)\n- **store:** add createReducer function ([#1746](https://github.com/ngrx/platform/issues/1746)) ([f954e14](https://github.com/ngrx/platform/commit/f954e14)), closes [#1724](https://github.com/ngrx/platform/issues/1724)\n- introduce [@ngrx](https://github.com/ngrx)/data library to the platform ([#1733](https://github.com/ngrx/platform/issues/1733)) ([5d569c3](https://github.com/ngrx/platform/commit/5d569c3))\n- introduce [@ngrx](https://github.com/ngrx)/data library to the platform ([#1754](https://github.com/ngrx/platform/issues/1754)) ([dbfdbaf](https://github.com/ngrx/platform/commit/dbfdbaf))\n- **effects:** add createEffect function ([#1667](https://github.com/ngrx/platform/issues/1667)) ([ced2d3d](https://github.com/ngrx/platform/commit/ced2d3d)), closes [#1368](https://github.com/ngrx/platform/issues/1368)\n- **effects:** allow non-dispatching effects to not return an action ([#1689](https://github.com/ngrx/platform/issues/1689)) ([04e07a6](https://github.com/ngrx/platform/commit/04e07a6))\n- **effects:** allow ofType to handle ActionCreator ([#1676](https://github.com/ngrx/platform/issues/1676)) ([a41d1d6](https://github.com/ngrx/platform/commit/a41d1d6))\n- **entity:** add undefined to Dictionary's index signature ([#1719](https://github.com/ngrx/platform/issues/1719)) ([d472757](https://github.com/ngrx/platform/commit/d472757))\n- **example:** update ofType in effects per [#1676](https://github.com/ngrx/platform/issues/1676) ([#1691](https://github.com/ngrx/platform/issues/1691)) ([c9c9a0e](https://github.com/ngrx/platform/commit/c9c9a0e))\n- **router-store:** add v8 migration schematic ([#1699](https://github.com/ngrx/platform/issues/1699)) ([0b794ce](https://github.com/ngrx/platform/commit/0b794ce))\n- **router-store:** Make usage of forRoot required ([#1662](https://github.com/ngrx/platform/issues/1662)) ([#1672](https://github.com/ngrx/platform/issues/1672)) ([c7e1406](https://github.com/ngrx/platform/commit/c7e1406))\n- **schematics:** add support for effect creators to schematics ([#1725](https://github.com/ngrx/platform/issues/1725)) ([8901abd](https://github.com/ngrx/platform/commit/8901abd)), closes [#1682](https://github.com/ngrx/platform/issues/1682)\n- **store:** add API to mock selectors ([#1688](https://github.com/ngrx/platform/issues/1688)) ([2a9b067](https://github.com/ngrx/platform/commit/2a9b067)), closes [#1504](https://github.com/ngrx/platform/issues/1504)\n- **store:** add immutability and serializability runtime checks ([#1613](https://github.com/ngrx/platform/issues/1613)) ([60633b7](https://github.com/ngrx/platform/commit/60633b7)), closes [#857](https://github.com/ngrx/platform/issues/857)\n- **store:** add META_REDUCERS replacement migration ([#1640](https://github.com/ngrx/platform/issues/1640)) ([57bacf5](https://github.com/ngrx/platform/commit/57bacf5))\n- **store:** run migration schema for v8 beta ([#1716](https://github.com/ngrx/platform/issues/1716)) ([0abc948](https://github.com/ngrx/platform/commit/0abc948))\n\n### BREAKING CHANGES\n\n- **entity:** Dictionary could be producing undefined but previous typings were not explicit about it.\n- **Store:** Internal functions and tokens are removed from the public API\n- **router-store:** usage of forRoot is now required for StoreRouterConnectingModule\n\nBEFORE:\n\n```ts\n@NgModule({\n  imports: [StoreRouterConnectingModule],\n})\nexport class AppModule {}\n```\n\nAFTER:\n\n```ts\n@NgModule({\n  imports: [StoreRouterConnectingModule.forRoot()],\n})\nexport class AppModule {}\n```\n\n- **Store:** Selectors with only a projector function aren't valid anymore.\n  This change will make the usage more consistent.\n\nBEFORE:\n\n```ts\nconst getTodosById = createSelector(\n  (state: TodoAppSchema, id: number) =>\n    state.todos.find((p) => p.id === id)\n);\n```\n\nAFTER:\n\n```ts\nconst getTodosById = createSelector(\n  (state: TodoAppSchema) => state.todos,\n  (todos: Todo[], id: number) => todos.find((p) => p.id === id)\n);\n```\n\n- **StoreDevTools:** `actionsWhitelist` is renamed to `actionsSafelist`\n  `actionsBlacklist` is renamed to `actionsBlocklist`\n\nBEFORE:\n\n```ts\nStoreDevtoolsModule.instrument({\n  actionsWhitelist: ['...'],\n  actionsBlacklist: ['...'],\n});\n```\n\nAFTER:\n\n```ts\nStoreDevtoolsModule.instrument({\n  actionsSafelist: ['...'],\n  actionsBlocklist: ['...'],\n});\n```\n\n<a name=\"7.4.0\"></a>\n\n# [7.4.0](https://github.com/ngrx/platform/compare/7.3.0...7.4.0) (2019-03-29)\n\n### Bug Fixes\n\n- **Example:** linter problems ([#1597](https://github.com/ngrx/platform/issues/1597)) ([4cfcc08](https://github.com/ngrx/platform/commit/4cfcc08))\n\n### Features\n\n- **example-app:** add visual testing with Applitools ([#1605](https://github.com/ngrx/platform/issues/1605)) ([8856210](https://github.com/ngrx/platform/commit/8856210))\n- **schematics:** use plural for entity schematics reducer key ([#1596](https://github.com/ngrx/platform/issues/1596)) ([1e49530](https://github.com/ngrx/platform/commit/1e49530)), closes [#1412](https://github.com/ngrx/platform/issues/1412)\n- **store:** add action creator functions ([#1654](https://github.com/ngrx/platform/issues/1654)) ([e7fe28b](https://github.com/ngrx/platform/commit/e7fe28b)), closes [#1480](https://github.com/ngrx/platform/issues/1480) [#1634](https://github.com/ngrx/platform/issues/1634)\n\n<a name=\"7.3.0\"></a>\n\n# [7.3.0](https://github.com/ngrx/platform/compare/7.2.0...7.3.0) (2019-02-27)\n\n### Bug Fixes\n\n- **schematics:** type actions and avoid endless loop in effect schematic ([#1576](https://github.com/ngrx/platform/issues/1576)) ([5fbcb3c](https://github.com/ngrx/platform/commit/5fbcb3c)), closes [#1573](https://github.com/ngrx/platform/issues/1573)\n- **store:** deprecate signature for selector with only a projector ([#1580](https://github.com/ngrx/platform/issues/1580)) ([e86c5f6](https://github.com/ngrx/platform/commit/e86c5f6))\n\n### Features\n\n- **schematics:** Add ng-add support with prompt for making our schematics default ([#1552](https://github.com/ngrx/platform/issues/1552)) ([01ff157](https://github.com/ngrx/platform/commit/01ff157))\n\n<a name=\"7.2.0\"></a>\n\n# [7.2.0](https://github.com/ngrx/platform/compare/7.1.0...7.2.0) (2019-01-29)\n\n### Bug Fixes\n\n- **Entity:** add schematics to bazel build ([88d0ad5](https://github.com/ngrx/platform/commit/88d0ad5))\n- **RouterStore:** add schematics to bazel build ([7465af9](https://github.com/ngrx/platform/commit/7465af9))\n- **StoreDevTools:** out of bounds when actions are filtered ([#1532](https://github.com/ngrx/platform/issues/1532)) ([d532979](https://github.com/ngrx/platform/commit/d532979)), closes [#1522](https://github.com/ngrx/platform/issues/1522)\n\n### Features\n\n- **schematics:** add api success/failure effects/actions to ng generate feature ([#1530](https://github.com/ngrx/platform/issues/1530)) ([e17a787](https://github.com/ngrx/platform/commit/e17a787))\n- **schematics:** bump platformVersion to ^7.0.0 per issue [#1489](https://github.com/ngrx/platform/issues/1489) ([#1527](https://github.com/ngrx/platform/issues/1527)) ([a71aa71](https://github.com/ngrx/platform/commit/a71aa71))\n\n<a name=\"7.1.0\"></a>\n\n# [7.1.0](https://github.com/ngrx/platform/compare/7.0.0...7.1.0) (2019-01-21)\n\n### Bug Fixes\n\n- **store:** call metareducer with the user's config initial state ([#1498](https://github.com/ngrx/platform/issues/1498)) ([2aabe0f](https://github.com/ngrx/platform/commit/2aabe0f)), closes [#1464](https://github.com/ngrx/platform/issues/1464)\n- **store:** don't call the projector function if there are no selectors and props ([#1515](https://github.com/ngrx/platform/issues/1515)) ([e0ad3c3](https://github.com/ngrx/platform/commit/e0ad3c3)), closes [#1501](https://github.com/ngrx/platform/issues/1501)\n\n### Features\n\n- **example:** make the example app more user friendly ([#1508](https://github.com/ngrx/platform/issues/1508)) ([ac4fb88](https://github.com/ngrx/platform/commit/ac4fb88))\n- **router-store:** add routerState to action payload ([#1511](https://github.com/ngrx/platform/issues/1511)) ([283424f](https://github.com/ngrx/platform/commit/283424f))\n- **schematics:** add ng add support for [@ngrx](https://github.com/ngrx)/entity ([#1503](https://github.com/ngrx/platform/issues/1503)) ([da1c955](https://github.com/ngrx/platform/commit/da1c955))\n- **schematics:** implement router store ng-add ([#1487](https://github.com/ngrx/platform/issues/1487)) ([9da4aac](https://github.com/ngrx/platform/commit/9da4aac)), closes [#1479](https://github.com/ngrx/platform/issues/1479)\n- **store:** support store config factory for feature ([#1445](https://github.com/ngrx/platform/issues/1445)) ([6aa5645](https://github.com/ngrx/platform/commit/6aa5645)), closes [#1414](https://github.com/ngrx/platform/issues/1414)\n\n<a name=\"7.0.0\"></a>\n\n# [7.0.0](https://github.com/ngrx/platform/compare/7.0.0-beta.1...7.0.0) (2018-12-20)\n\n### Features\n\n- **Effects:** add OnInitEffects interface to dispatch an action on initialization ([e921cd9](https://github.com/ngrx/platform/commit/e921cd9))\n- **RouterStore:** make the router store key selector generic ([a30a514](https://github.com/ngrx/platform/commit/a30a514)), closes [#1457](https://github.com/ngrx/platform/issues/1457)\n- **schematics:** add project flag support to specify apps or libs ([#1477](https://github.com/ngrx/platform/issues/1477)) ([af39fd2](https://github.com/ngrx/platform/commit/af39fd2)), closes [#1455](https://github.com/ngrx/platform/issues/1455)\n\n### Reverts\n\n- **Effects:** dispatch init feature effects action on init [#1305](https://github.com/ngrx/platform/issues/1305) ([e9cc9ae](https://github.com/ngrx/platform/commit/e9cc9ae))\n\n<a name=\"7.0.0-beta.1\"></a>\n\n# [7.0.0-beta.1](https://github.com/ngrx/platform/compare/7.0.0-beta.0...7.0.0-beta.1) (2018-12-04)\n\n### Features\n\n- **effects:** add OnIdentifyEffects interface to register multiple effect instances ([#1448](https://github.com/ngrx/platform/issues/1448)) ([b553ce7](https://github.com/ngrx/platform/commit/b553ce7))\n- **store-devtools:** catch and log redux devtools errors ([#1450](https://github.com/ngrx/platform/issues/1450)) ([4ed16cd](https://github.com/ngrx/platform/commit/4ed16cd))\n\n<a name=\"7.0.0-beta.0\"></a>\n\n# [7.0.0-beta.0](https://github.com/ngrx/platform/compare/6.1.0...7.0.0-beta.0) (2018-11-03)\n\n### Bug Fixes\n\n- **docs-infra:** ARIA roles used must conform to valid values ([8a4b2de](https://github.com/ngrx/platform/commit/8a4b2de))\n- **docs-infra:** elements must have sufficient color contrast ([c5dfaef](https://github.com/ngrx/platform/commit/c5dfaef))\n- **docs-infra:** html element must have a lang attribute ([32256de](https://github.com/ngrx/platform/commit/32256de))\n- **docs-infra:** Images must have alternate text ([8241f99](https://github.com/ngrx/platform/commit/8241f99))\n- **docs-infra:** notification must have sufficient color contrast ([ac24cc3](https://github.com/ngrx/platform/commit/ac24cc3))\n- **example:** close side nav when escape key is pressed ([#1244](https://github.com/ngrx/platform/issues/1244)) ([b3fc5dd](https://github.com/ngrx/platform/commit/b3fc5dd)), closes [#1172](https://github.com/ngrx/platform/issues/1172)\n- **router-store:** Added new imports to index.ts, codestyle ([293f960](https://github.com/ngrx/platform/commit/293f960))\n- **router-store:** allow compilation with strictFunctionTypes ([#1385](https://github.com/ngrx/platform/issues/1385)) ([0e38673](https://github.com/ngrx/platform/commit/0e38673)), closes [#1344](https://github.com/ngrx/platform/issues/1344)\n- **router-store:** Avoiding [@ngrx](https://github.com/ngrx)/effects dependency inside tests ([11d3b9f](https://github.com/ngrx/platform/commit/11d3b9f))\n- **router-store:** handle internal navigation error, dispatch cancel/error action with previous state ([#1294](https://github.com/ngrx/platform/issues/1294)) ([5300e7d](https://github.com/ngrx/platform/commit/5300e7d))\n- **schematics:** correct spec description in reducer template ([#1269](https://github.com/ngrx/platform/issues/1269)) ([b7ab4f8](https://github.com/ngrx/platform/commit/b7ab4f8))\n- **schematics:** fix effects code generated by schematics:feature ([#1357](https://github.com/ngrx/platform/issues/1357)) ([458e2b4](https://github.com/ngrx/platform/commit/458e2b4))\n- **store:** add typing to allow props with store.select ([#1387](https://github.com/ngrx/platform/issues/1387)) ([a9e7cbd](https://github.com/ngrx/platform/commit/a9e7cbd))\n- **store:** memoize selector arguments ([#1393](https://github.com/ngrx/platform/issues/1393)) ([7cc9702](https://github.com/ngrx/platform/commit/7cc9702)), closes [#1389](https://github.com/ngrx/platform/issues/1389)\n- **store:** remove deprecation from Store.select ([#1382](https://github.com/ngrx/platform/issues/1382)) ([626784e](https://github.com/ngrx/platform/commit/626784e))\n\n### Code Refactoring\n\n- **routerstore:** change default state key to router ([#1258](https://github.com/ngrx/platform/issues/1258)) ([e8173d9](https://github.com/ngrx/platform/commit/e8173d9))\n- **RouterStore:** normalize actions ([#1302](https://github.com/ngrx/platform/issues/1302)) ([466e2cd](https://github.com/ngrx/platform/commit/466e2cd))\n\n### Features\n\n- update angular dependencies to V7 ([e6048bd](https://github.com/ngrx/platform/commit/e6048bd)), closes [#1340](https://github.com/ngrx/platform/issues/1340)\n- **effects:** add smarter type inference for ofType operator. ([#1183](https://github.com/ngrx/platform/issues/1183)) ([8d56a6f](https://github.com/ngrx/platform/commit/8d56a6f))\n- **effects:** add support for effects of different instances of same class ([#1249](https://github.com/ngrx/platform/issues/1249)) ([518e561](https://github.com/ngrx/platform/commit/518e561)), closes [#1246](https://github.com/ngrx/platform/issues/1246)\n- **effects:** dispatch feature effects action on init ([#1305](https://github.com/ngrx/platform/issues/1305)) ([15a4b58](https://github.com/ngrx/platform/commit/15a4b58)), closes [#683](https://github.com/ngrx/platform/issues/683)\n- **entity:** add support for predicate to removeMany ([#900](https://github.com/ngrx/platform/issues/900)) ([d7daa2f](https://github.com/ngrx/platform/commit/d7daa2f))\n- **entity:** add support for predicate to updateMany ([#907](https://github.com/ngrx/platform/issues/907)) ([4e4c50f](https://github.com/ngrx/platform/commit/4e4c50f))\n- **example:** add logout confirmation ([#1287](https://github.com/ngrx/platform/issues/1287)) ([ba8d300](https://github.com/ngrx/platform/commit/ba8d300)), closes [#1271](https://github.com/ngrx/platform/issues/1271)\n- **router-store:** Add custom serializer to config object ([5c814a9](https://github.com/ngrx/platform/commit/5c814a9)), closes [#1262](https://github.com/ngrx/platform/issues/1262)\n- **router-store:** Add support for serializers with injected values ([959cfac](https://github.com/ngrx/platform/commit/959cfac))\n- **router-store:** config option to dispatch ROUTER_NAVIGATION later ([fe71ffb](https://github.com/ngrx/platform/commit/fe71ffb)), closes [#1263](https://github.com/ngrx/platform/issues/1263)\n- **router-store:** New router Actions ROUTER_REQUEST and ROUTER_NAVIGATED ([9f731c3](https://github.com/ngrx/platform/commit/9f731c3)), closes [#1010](https://github.com/ngrx/platform/issues/1010) [#1263](https://github.com/ngrx/platform/issues/1263)\n- **router-store:** serialize routeConfig inside the default serializer ([#1384](https://github.com/ngrx/platform/issues/1384)) ([18a16d4](https://github.com/ngrx/platform/commit/18a16d4))\n- **router-store:** update stateKey definition to take a string or selector ([4ad9a94](https://github.com/ngrx/platform/commit/4ad9a94)), closes [#1300](https://github.com/ngrx/platform/issues/1300)\n- **store:** add testing package ([#1027](https://github.com/ngrx/platform/issues/1027)) ([ab56aac](https://github.com/ngrx/platform/commit/ab56aac)), closes [#915](https://github.com/ngrx/platform/issues/915)\n- **store:** dispatch one update action when features are added or removed ([#1240](https://github.com/ngrx/platform/issues/1240)) ([0b90f91](https://github.com/ngrx/platform/commit/0b90f91))\n- **Store:** export SelectorWithProps and MemoizedSelectorWithProps ([#1341](https://github.com/ngrx/platform/issues/1341)) ([df8fc60](https://github.com/ngrx/platform/commit/df8fc60))\n- **store-devtools:** add support for persist, lock, pause ([#955](https://github.com/ngrx/platform/issues/955)) ([93fcf56](https://github.com/ngrx/platform/commit/93fcf56)), closes [#853](https://github.com/ngrx/platform/issues/853) [#919](https://github.com/ngrx/platform/issues/919)\n- **store-devtools:** use different action when recomputing state history ([#1353](https://github.com/ngrx/platform/issues/1353)) ([1448a0e](https://github.com/ngrx/platform/commit/1448a0e)), closes [#1255](https://github.com/ngrx/platform/issues/1255)\n- **StoreDevtools:** implement actionsBlacklist/Whitelist & predicate ([#970](https://github.com/ngrx/platform/issues/970)) ([7ee46d2](https://github.com/ngrx/platform/commit/7ee46d2)), closes [#938](https://github.com/ngrx/platform/issues/938)\n\n### BREAKING CHANGES\n\n- **router-store:** The default router serializer now returns a `null` value for\n  `routeConfig` when `routeConfig` doesn't exist on the\n  `ActivatedRouteSnapshot` instead of an empty object.\n\nBEFORE:\n\n```json\n{\n  \"routeConfig\": {}\n}\n```\n\nAFTER:\n\n```json\n{\n  \"routeConfig\": null\n}\n```\n\n- **effects:** Removes .ofType method on Actions. Instead use the provided 'ofType'\n  rxjs operator.\n\nBEFORE:\n\n```ts\nthis.actions.ofType('INCREMENT');\n```\n\nAFTER:\n\n```ts\nimport { ofType } from '@ngrx/store';\n...\nthis.action.pipe(ofType('INCREMENT'))\n```\n\n- **RouterStore:** Normalize router store actions to be consistent with the other modules\n\nBEFORE:\n\n- ROUTER_REQUEST\n- ROUTER_NAVIGATION\n- ROUTER_CANCEL\n- ROUTER_ERROR\n- ROUTER_NAVIGATED\n\nAFTER\n\n- @ngrx/router-store/request\n- @ngrx/router-store/navigation\n- @ngrx/router-store/cancel\n- @ngrx/router-store/error\n- @ngrx/router-store/navigated\n\n* **router-store:** StoreRouterConfigFunction is removed. It is no longer possible to pass a function returning a StoreRouterConfig to StoreRouterConnectingModule.forRoot\n\nIf you still need this, pass a provider like this:\n{\nprovide: ROUTER_CONFIG,\nuseFactory: \\_createRouterConfig // you function\n}\n\n- **routerstore:** The default state key is changed from routerReducer to router\n- **store:** BEFORE:\n\n```ts\n{type: '@ngrx/store/update-reducers', feature: 'feature1'}\n{type: '@ngrx/store/update-reducers', feature: 'feature2'}\n```\n\nAFTER:\n\n```ts\n{type: '@ngrx/store/update-reducers', features: ['feature1',\n'feature2']}\n```\n\n<a name=\"6.1.0\"></a>\n\n# [6.1.0](https://github.com/ngrx/platform/compare/6.0.1...6.1.0) (2018-08-02)\n\n### Bug Fixes\n\n- **effects:** Add deprecation notice for ofType instance operator ([830c8fa](https://github.com/ngrx/platform/commit/830c8fa))\n- **Effects:** Added defaults for ng-add schematic ([9d36016](https://github.com/ngrx/platform/commit/9d36016))\n- **example:** adjust styles to display spinner correctly ([#1203](https://github.com/ngrx/platform/issues/1203)) ([4a0b580](https://github.com/ngrx/platform/commit/4a0b580))\n- **example:** remove custom router state serializer ([#1129](https://github.com/ngrx/platform/issues/1129)) ([389cd78](https://github.com/ngrx/platform/commit/389cd78))\n- **schematics:** correct a type of action class generated ([#1140](https://github.com/ngrx/platform/issues/1140)) ([bbb7e8c](https://github.com/ngrx/platform/commit/bbb7e8c))\n- **schematics:** exclude environment imports for libraries ([#1213](https://github.com/ngrx/platform/issues/1213)) ([541de02](https://github.com/ngrx/platform/commit/541de02)), closes [#1205](https://github.com/ngrx/platform/issues/1205) [#1197](https://github.com/ngrx/platform/issues/1197)\n- **schematics:** Remove peer dependencies on Angular DevKit ([#1222](https://github.com/ngrx/platform/issues/1222)) ([fd3da16](https://github.com/ngrx/platform/commit/fd3da16)), closes [#1206](https://github.com/ngrx/platform/issues/1206)\n- **Schematics:** correct resolution of environments path for module ([#1094](https://github.com/ngrx/platform/issues/1094)) ([d24ed10](https://github.com/ngrx/platform/commit/d24ed10)), closes [#1090](https://github.com/ngrx/platform/issues/1090)\n- **store:** Add deprecation notice for select instance operator ([232ca7a](https://github.com/ngrx/platform/commit/232ca7a))\n- **store:** Compare results in addition to arguments change in memoizer ([#1175](https://github.com/ngrx/platform/issues/1175)) ([99e1313](https://github.com/ngrx/platform/commit/99e1313))\n- **Store:** bootstrap store with partial initial state ([#1163](https://github.com/ngrx/platform/issues/1163)) ([11bd465](https://github.com/ngrx/platform/commit/11bd465)), closes [#906](https://github.com/ngrx/platform/issues/906) [#909](https://github.com/ngrx/platform/issues/909)\n- **Store:** Fix import bug with ng-add and added defaults ([ff7dc72](https://github.com/ngrx/platform/commit/ff7dc72))\n\n### Features\n\n- **effects:** stringify action when reporting as invalid ([#1219](https://github.com/ngrx/platform/issues/1219)) ([73d32eb](https://github.com/ngrx/platform/commit/73d32eb))\n- **entity:** log a warning message when selectId returns undefined in dev mode ([#1169](https://github.com/ngrx/platform/issues/1169)) ([8f05f1f](https://github.com/ngrx/platform/commit/8f05f1f)), closes [#1133](https://github.com/ngrx/platform/issues/1133)\n- **Entity:** expose Dictionary as part of the public API ([#1118](https://github.com/ngrx/platform/issues/1118)) ([2a267b6](https://github.com/ngrx/platform/commit/2a267b6)), closes [#865](https://github.com/ngrx/platform/issues/865)\n- **schematics:** display provided path when displaying an error ([#1208](https://github.com/ngrx/platform/issues/1208)) ([91cc6ed](https://github.com/ngrx/platform/commit/91cc6ed)), closes [#1200](https://github.com/ngrx/platform/issues/1200)\n- **schematics:** use ofType operator function instead of Actions#ofType ([#1154](https://github.com/ngrx/platform/issues/1154)) ([cb58ff1](https://github.com/ngrx/platform/commit/cb58ff1))\n- **store:** add an overload to createFeatureSelector to provide better type checking ([#1171](https://github.com/ngrx/platform/issues/1171)) ([03db76f](https://github.com/ngrx/platform/commit/03db76f)), closes [#1136](https://github.com/ngrx/platform/issues/1136)\n- **store:** provide props to createSelector projector function ([#1210](https://github.com/ngrx/platform/issues/1210)) ([b1f9b34](https://github.com/ngrx/platform/commit/b1f9b34))\n- **Store:** createSelector allow props in selector ([53832a1](https://github.com/ngrx/platform/commit/53832a1))\n- **Store:** createSelector with only a props selector ([35a4848](https://github.com/ngrx/platform/commit/35a4848))\n- **StoreDevtools:** Add ng-add support ([be28d8d](https://github.com/ngrx/platform/commit/be28d8d))\n- **StoreDevtools:** Allow custom serializer options ([#1121](https://github.com/ngrx/platform/issues/1121)) ([55a0488](https://github.com/ngrx/platform/commit/55a0488))\n\n### Performance Improvements\n\n- **Effects:** remove path filters in ng-add ([5318913](https://github.com/ngrx/platform/commit/5318913))\n- **Schematics:** remove path filters in effects schematics ([6d3f5a1](https://github.com/ngrx/platform/commit/6d3f5a1))\n- **Schematics:** remove path filters in reducer schematics ([055f6ef](https://github.com/ngrx/platform/commit/055f6ef))\n- **Schematics:** remove path filters in store schematics ([762cf2e](https://github.com/ngrx/platform/commit/762cf2e))\n- **Store:** remove path filters in ng-add ([ec6adb5](https://github.com/ngrx/platform/commit/ec6adb5))\n- **StoreDevtools:** remove path filters in ng-add ([3ba463f](https://github.com/ngrx/platform/commit/3ba463f))\n\n<a name=\"6.0.1\"></a>\n\n## [6.0.1](https://github.com/ngrx/platform/compare/6.0.0...6.0.1) (2018-05-23)\n\n<a name=\"6.0.0\"></a>\n\n# [6.0.0](https://github.com/ngrx/platform/compare/v6.0.0-beta.3...6.0.0) (2018-05-23)\n\n### Bug Fixes\n\n- **Schematics:** remove ts extension when importing reducer in container ([#1061](https://github.com/ngrx/platform/issues/1061)) ([d1ed9e5](https://github.com/ngrx/platform/commit/d1ed9e5)), closes [#1056](https://github.com/ngrx/platform/issues/1056)\n- **Schematics:** Update parsed path logic to split path and name ([a1e9530](https://github.com/ngrx/platform/commit/a1e9530)), closes [#1064](https://github.com/ngrx/platform/issues/1064)\n- **Store:** Resolve environment path when generating a new store ([#1071](https://github.com/ngrx/platform/issues/1071)) ([599cfb6](https://github.com/ngrx/platform/commit/599cfb6))\n\n### Features\n\n- implement ng add for store and effects packages ([db94db7](https://github.com/ngrx/platform/commit/db94db7))\n\n<a name=\"6.0.0-beta.3\"></a>\n\n# [6.0.0-beta.3](https://github.com/ngrx/platform/compare/v6.0.0-beta.2...v6.0.0-beta.3) (2018-05-12)\n\n### Bug Fixes\n\n- **build:** Use default build for schematics ([#1057](https://github.com/ngrx/platform/issues/1057)) ([355b12f](https://github.com/ngrx/platform/commit/355b12f))\n\n<a name=\"6.0.0-beta.2\"></a>\n\n# [6.0.0-beta.2](https://github.com/ngrx/platform/compare/v6.0.0-beta.1...v6.0.0-beta.2) (2018-05-11)\n\n### Bug Fixes\n\n- **build:** Fix UMD global names ([#1005](https://github.com/ngrx/platform/issues/1005)) ([413efd4](https://github.com/ngrx/platform/commit/413efd4)), closes [#1004](https://github.com/ngrx/platform/issues/1004)\n- **RouterStore:** Reset dispatch-tracking booleans after navigation end ([#968](https://github.com/ngrx/platform/issues/968)) ([48305aa](https://github.com/ngrx/platform/commit/48305aa))\n- **Schematics:** Add check for app/lib to project helper function ([5942885](https://github.com/ngrx/platform/commit/5942885))\n- **Schematics:** Add smart default to blueprint schemas ([cdd247e](https://github.com/ngrx/platform/commit/cdd247e))\n- **Schematics:** Remove aliases for state and stateInterface options ([f4520a2](https://github.com/ngrx/platform/commit/f4520a2))\n- **Schematics:** Update upsert actions for entity blueprint ([#1042](https://github.com/ngrx/platform/issues/1042)) ([0d1d309](https://github.com/ngrx/platform/commit/0d1d309)), closes [#1039](https://github.com/ngrx/platform/issues/1039)\n- **Schematics:** Upgrade schematics to new CLI structure ([b99d9ff](https://github.com/ngrx/platform/commit/b99d9ff))\n- **Store:** Fix type annotations for select methods ([#953](https://github.com/ngrx/platform/issues/953)) ([4d74bd2](https://github.com/ngrx/platform/commit/4d74bd2))\n- **StoreDevtools:** Refresh devtools when extension is started ([#1017](https://github.com/ngrx/platform/issues/1017)) ([c6e33d9](https://github.com/ngrx/platform/commit/c6e33d9)), closes [#508](https://github.com/ngrx/platform/issues/508)\n- Update minimum node version to 8.9.0 ([#989](https://github.com/ngrx/platform/issues/989)) ([0baaad8](https://github.com/ngrx/platform/commit/0baaad8))\n\n### Features\n\n- Add ng update support to ngrx packages ([#1053](https://github.com/ngrx/platform/issues/1053)) ([4f91e9e](https://github.com/ngrx/platform/commit/4f91e9e))\n- **Schematics:** Rename default action type for action blueprint ([#1047](https://github.com/ngrx/platform/issues/1047)) ([4c4e6a9](https://github.com/ngrx/platform/commit/4c4e6a9)), closes [#1040](https://github.com/ngrx/platform/issues/1040)\n- **Store:** Add support ng update ([#1032](https://github.com/ngrx/platform/issues/1032)) ([5b4f067](https://github.com/ngrx/platform/commit/5b4f067))\n\n### BREAKING CHANGES\n\n- **Schematics:** The action blueprint has been updated to be less generic, with associated reducer and effects updated for the feature blueprint\n\nBEFORE:\n\nexport enum UserActionTypes {\nUserAction = '[User] Action'\n}\n\nexport class User implements Action {\nreadonly type = UserActionTypes.UserAction;\n}\n\nexport type UserActions = User;\n\nAFTER:\n\nexport enum UserActionTypes {\nLoadUsers = '[User] Load Users'\n}\n\nexport class LoadUsers implements Action {\nreadonly type = UserActionTypes.LoadUsers;\n}\n\nexport type UserActions = LoadUsers;\n\n- **Schematics:** Aliases for `state` and `stateInterface` were removed due to conflicts with component aliases without reasonable alternatives.\n- **Schematics:** Minimum dependency for @ngrx/schematics has changed:\n\n@angular-devkit/core: ^0.5.0\n@angular-devkit/schematics: ^0.5.0\n\n<a name=\"6.0.0-beta.1\"></a>\n\n# [6.0.0-beta.1](https://github.com/ngrx/platform/compare/v6.0.0-beta.0...v6.0.0-beta.1) (2018-04-02)\n\n### Bug Fixes\n\n- Declare global NgRx packages for UMD bundles ([#952](https://github.com/ngrx/platform/issues/952)) ([ba2139d](https://github.com/ngrx/platform/commit/ba2139d)), closes [#949](https://github.com/ngrx/platform/issues/949)\n\n<a name=\"6.0.0-beta.0\"></a>\n\n# [6.0.0-beta.0](https://github.com/ngrx/platform/compare/v5.2.0...v6.0.0-beta.0) (2018-03-31)\n\n### Bug Fixes\n\n- **Entity:** Change EntityAdapter upsertOne/upsertMany to accept an entity ([a0f45ff](https://github.com/ngrx/platform/commit/a0f45ff))\n- **RouterStore:** Allow strict mode with router reducer ([#903](https://github.com/ngrx/platform/issues/903)) ([f17a032](https://github.com/ngrx/platform/commit/f17a032))\n- **RouterStore:** change the default serializer to work around cycles in RouterStateSnapshot ([7917a27](https://github.com/ngrx/platform/commit/7917a27))\n- **RouterStore:** Replace RouterStateSnapshot with SerializedRouterStateSnapshot ([bd415a1](https://github.com/ngrx/platform/commit/bd415a1))\n- **StoreDevtools:** pass timestamp to actions ([df2411f](https://github.com/ngrx/platform/commit/df2411f))\n- **StoreDevtools:** report errors to ErrorHandler instead of console ([32df3f0](https://github.com/ngrx/platform/commit/32df3f0))\n- Add support for Angular 6 and RxJS 6 ([d1286d2](https://github.com/ngrx/platform/commit/d1286d2))\n\n### Features\n\n- **Schematcis:** Extend from [@schematics](https://github.com/schematics)/angular ([0e17aad](https://github.com/ngrx/platform/commit/0e17aad))\n- **Schematics:** Add support for custom store interface name ([#810](https://github.com/ngrx/platform/issues/810)) ([1352d83](https://github.com/ngrx/platform/commit/1352d83))\n\n### BREAKING CHANGES\n\n- **StoreDevtools:** Errors in reducers are no longer hidden from ErrorHandler by\n  StoreDevtools\n\nBEFORE:\n\nErrors in reducers are caught by StoreDevtools and logged to the console\n\nAFTER:\n\nErrors in reducers are reported to ErrorHandler\n\n- **Schematcis:** NgRx Schematics now has a minimum version dependency on @angular-devkit/core\n  and @angular-devkit/schematics of v0.4.0.\n- **RouterStore:** Default router state is serialized to a shape that removes cycles\n\nBEFORE:\n\nFull RouterStateSnapshot is returned\n\nAFTER:\n\nRouter state snapshot is returned as a SerializedRouterStateSnapshot with cyclical dependencies removed\n\n- **Entity:** The signature of the upsertOne/upsertMany functions in the EntityAdapter\n  has been changed to accept a fully qualified entity instead of an update\n  object that implements the Update<T> interface.\n\n  Before:\n\n  ```ts\n  entityAdapter.upsertOne(\n    {\n      id: 'Entity ID',\n      changes: { id: 'Entity ID', name: 'Entity Name' },\n    },\n    state\n  );\n  ```\n\n  After:\n\n  ```ts\n  entityAdapter.upsertOne(\n    {\n      id: 'Entity ID',\n      name: 'Entity Name',\n    },\n    state\n  );\n  ```\n\n- NgRx now has a minimum version requirement on Angular 6 and RxJS 6.\n\n<a name=\"5.2.0\"></a>\n\n# [5.2.0](https://github.com/ngrx/platform/compare/v5.1.0...v5.2.0) (2018-03-07)\n\n### Bug Fixes\n\n- **Schematics:** Correct usage of upsert actions for entity blueprint ([#821](https://github.com/ngrx/platform/issues/821)) ([1ffb5a9](https://github.com/ngrx/platform/commit/1ffb5a9))\n- **Store:** only default to initialValue when store value is undefined ([#886](https://github.com/ngrx/platform/issues/886)) ([51a1547](https://github.com/ngrx/platform/commit/51a1547))\n- **StoreDevtools:** Fix bug when exporting/importing state history ([#855](https://github.com/ngrx/platform/issues/855)) ([a5dcdb1](https://github.com/ngrx/platform/commit/a5dcdb1))\n- **StoreDevtools:** Recompute state history when reducers are updated ([#844](https://github.com/ngrx/platform/issues/844)) ([10debcc](https://github.com/ngrx/platform/commit/10debcc))\n\n### Features\n\n- **Entity:** Add 'selectId' and 'sortComparer' to state adapter ([#889](https://github.com/ngrx/platform/issues/889)) ([69a62f2](https://github.com/ngrx/platform/commit/69a62f2))\n- **Store:** Added feature name to Update Reducers action ([730361e](https://github.com/ngrx/platform/commit/730361e))\n\n<a name=\"5.1.0\"></a>\n\n# [5.1.0](https://github.com/ngrx/platform/compare/v5.0.1...v5.1.0) (2018-02-13)\n\n### Bug Fixes\n\n- **Devtools:** Ensure Store is loaded eagerly ([#801](https://github.com/ngrx/platform/issues/801)) ([ecf1ebf](https://github.com/ngrx/platform/commit/ecf1ebf)), closes [#624](https://github.com/ngrx/platform/issues/624) [#741](https://github.com/ngrx/platform/issues/741)\n- **Effects:** Make ofType operator strictFunctionTypes safe ([#789](https://github.com/ngrx/platform/issues/789)) ([c8560e4](https://github.com/ngrx/platform/commit/c8560e4)), closes [#753](https://github.com/ngrx/platform/issues/753)\n- **Entity:** Avoid for..in iteration in sorted state adapter ([#805](https://github.com/ngrx/platform/issues/805)) ([4192645](https://github.com/ngrx/platform/commit/4192645))\n- **Entity:** Do not add Array.prototype properties to store ([#782](https://github.com/ngrx/platform/issues/782)) ([d537758](https://github.com/ngrx/platform/commit/d537758)), closes [#781](https://github.com/ngrx/platform/issues/781)\n- **Entity:** Properly iterate over array in upsert ([#802](https://github.com/ngrx/platform/issues/802)) ([779d689](https://github.com/ngrx/platform/commit/779d689))\n- **Schematics:** Add store import to container blueprint ([#763](https://github.com/ngrx/platform/issues/763)) ([a140fa9](https://github.com/ngrx/platform/commit/a140fa9)), closes [#760](https://github.com/ngrx/platform/issues/760)\n- **Schematics:** Remove extra braces from constructor for container blueprint ([#791](https://github.com/ngrx/platform/issues/791)) ([945bf40](https://github.com/ngrx/platform/commit/945bf40)), closes [#778](https://github.com/ngrx/platform/issues/778)\n- **Schematics:** Use correct paths for nested and grouped feature blueprint ([#756](https://github.com/ngrx/platform/issues/756)) ([c219770](https://github.com/ngrx/platform/commit/c219770))\n- **StoreDevtools:** Add internal support for ActionSanitizer and StateSanitizer ([#795](https://github.com/ngrx/platform/issues/795)) ([a7de2a6](https://github.com/ngrx/platform/commit/a7de2a6))\n- **StoreDevtools:** Do not send full liftedState for application actions ([#790](https://github.com/ngrx/platform/issues/790)) ([c11504f](https://github.com/ngrx/platform/commit/c11504f))\n\n### Features\n\n- **Entity:** Add upsertOne and upsertMany functions to entity adapters ([#780](https://github.com/ngrx/platform/issues/780)) ([f871540](https://github.com/ngrx/platform/commit/f871540)), closes [#421](https://github.com/ngrx/platform/issues/421)\n- **Schematics:** Add group option to entity blueprint ([#792](https://github.com/ngrx/platform/issues/792)) ([0429276](https://github.com/ngrx/platform/commit/0429276)), closes [#779](https://github.com/ngrx/platform/issues/779)\n- **Schematics:** Add upsert methods to entity blueprint ([#809](https://github.com/ngrx/platform/issues/809)) ([7acdc79](https://github.com/ngrx/platform/commit/7acdc79)), closes [#592](https://github.com/ngrx/platform/issues/592)\n\n<a name=\"5.0.1\"></a>\n\n## [5.0.1](https://github.com/ngrx/platform/compare/v5.0.0...v5.0.1) (2018-01-25)\n\n### Bug Fixes\n\n- **Effects:** Provide instance from actions to ofType lettable operator ([#751](https://github.com/ngrx/platform/issues/751)) ([33d48e7](https://github.com/ngrx/platform/commit/33d48e7)), closes [#739](https://github.com/ngrx/platform/issues/739)\n\n<a name=\"5.0.0\"></a>\n\n# [5.0.0](https://github.com/ngrx/platform/compare/v4.1.1...v5.0.0) (2018-01-22)\n\n### Bug Fixes\n\n- **Effects:** Ensure Store modules are loaded eagerly ([#658](https://github.com/ngrx/platform/issues/658)) ([0a3398d](https://github.com/ngrx/platform/commit/0a3398d)), closes [#642](https://github.com/ngrx/platform/issues/642)\n- **Effects:** Remove toPayload utility function ([#738](https://github.com/ngrx/platform/issues/738)) ([b390ef5](https://github.com/ngrx/platform/commit/b390ef5))\n- **Entity:** updateOne/updateMany should not change ids state on existing entity ([#581](https://github.com/ngrx/platform/issues/581)) ([b989e4b](https://github.com/ngrx/platform/commit/b989e4b)), closes [#571](https://github.com/ngrx/platform/issues/571)\n- **RouterStore:** Fix usage of config object if provided ([#575](https://github.com/ngrx/platform/issues/575)) ([4125914](https://github.com/ngrx/platform/commit/4125914))\n- **RouterStore:** Match RouterAction type parameters ([#562](https://github.com/ngrx/platform/issues/562)) ([980a653](https://github.com/ngrx/platform/commit/980a653))\n- **Schematics:** Add group folder after feature name folder ([#737](https://github.com/ngrx/platform/issues/737)) ([317fb94](https://github.com/ngrx/platform/commit/317fb94))\n- **Schematics:** Add handling of flat option to entity blueprint ([fb8d2c6](https://github.com/ngrx/platform/commit/fb8d2c6))\n- **Schematics:** Distinguish between root and feature effect arrays when registering ([#718](https://github.com/ngrx/platform/issues/718)) ([95ff6c8](https://github.com/ngrx/platform/commit/95ff6c8))\n- **Schematics:** Don't add state import if not provided ([#697](https://github.com/ngrx/platform/issues/697)) ([e5c2aed](https://github.com/ngrx/platform/commit/e5c2aed))\n- **Schematics:** Make variable naming consistent for entity blueprint ([#716](https://github.com/ngrx/platform/issues/716)) ([765b15a](https://github.com/ngrx/platform/commit/765b15a))\n- **Store:** Compose provided metareducers for a feature reducer ([#704](https://github.com/ngrx/platform/issues/704)) ([1454620](https://github.com/ngrx/platform/commit/1454620)), closes [#701](https://github.com/ngrx/platform/issues/701)\n- **StoreDevtools:** Only recompute current state when reducers are updated ([#570](https://github.com/ngrx/platform/issues/570)) ([247ae1a](https://github.com/ngrx/platform/commit/247ae1a)), closes [#229](https://github.com/ngrx/platform/issues/229) [#487](https://github.com/ngrx/platform/issues/487)\n- **typo:** update login error to use correct css font color property ([41723fc](https://github.com/ngrx/platform/commit/41723fc))\n\n### Features\n\n- **Effects:** Add lettable ofType operator ([d5e1814](https://github.com/ngrx/platform/commit/d5e1814))\n- **ErrorHandler:** Use the Angular ErrorHandler for reporting errors ([#667](https://github.com/ngrx/platform/issues/667)) ([8f297d1](https://github.com/ngrx/platform/commit/8f297d1)), closes [#626](https://github.com/ngrx/platform/issues/626)\n- **material:** Upgrade [@angular](https://github.com/angular)/material to v 2.0.0-beta.12 ([#482](https://github.com/ngrx/platform/issues/482)) ([aedf20e](https://github.com/ngrx/platform/commit/aedf20e)), closes [#448](https://github.com/ngrx/platform/issues/448)\n- **Schematics:** Add alias for container, store and action blueprints ([#685](https://github.com/ngrx/platform/issues/685)) ([dc64ac9](https://github.com/ngrx/platform/commit/dc64ac9))\n- **Schematics:** Add alias for reducer blueprint ([#684](https://github.com/ngrx/platform/issues/684)) ([ea98fb7](https://github.com/ngrx/platform/commit/ea98fb7))\n- **Schematics:** Add effect to registered effects array ([#717](https://github.com/ngrx/platform/issues/717)) ([f1082fe](https://github.com/ngrx/platform/commit/f1082fe))\n- **Schematics:** Add option to group feature blueprints in respective folders ([#736](https://github.com/ngrx/platform/issues/736)) ([b82c35d](https://github.com/ngrx/platform/commit/b82c35d))\n- **Schematics:** Introduce [@ngrx](https://github.com/ngrx)/schematics ([#631](https://github.com/ngrx/platform/issues/631)) ([1837dba](https://github.com/ngrx/platform/commit/1837dba)), closes [#53](https://github.com/ngrx/platform/issues/53)\n- **Store:** Add lettable select operator ([77eed24](https://github.com/ngrx/platform/commit/77eed24))\n- **Store:** Add support for generating custom createSelector functions ([#734](https://github.com/ngrx/platform/issues/734)) ([cb0d185](https://github.com/ngrx/platform/commit/cb0d185)), closes [#478](https://github.com/ngrx/platform/issues/478) [#724](https://github.com/ngrx/platform/issues/724)\n- **StoreDevtools:** Add option to configure extension in log-only mode ([#712](https://github.com/ngrx/platform/issues/712)) ([1ecd658](https://github.com/ngrx/platform/commit/1ecd658)), closes [#643](https://github.com/ngrx/platform/issues/643) [#374](https://github.com/ngrx/platform/issues/374)\n- **StoreDevtools:** Add support for custom instance name ([#517](https://github.com/ngrx/platform/issues/517)) ([00be3d1](https://github.com/ngrx/platform/commit/00be3d1)), closes [#463](https://github.com/ngrx/platform/issues/463)\n- **StoreDevtools:** Add support for extension sanitizers ([#544](https://github.com/ngrx/platform/issues/544)) ([6ed92b0](https://github.com/ngrx/platform/commit/6ed92b0)), closes [#494](https://github.com/ngrx/platform/issues/494)\n- **StoreDevtools:** Add support for jumping to a specific action ([#703](https://github.com/ngrx/platform/issues/703)) ([b9f6442](https://github.com/ngrx/platform/commit/b9f6442)), closes [#681](https://github.com/ngrx/platform/issues/681)\n\n### BREAKING CHANGES\n\n- **Effects:** The utility function `toPayload`, deprecated in @ngrx/effects v4.0, has been removed.\n\n  Before:\n\n  ```ts\n  import { toPayload } from '@ngrx/effects';\n\n  actions$.ofType('SOME_ACTION').map(toPayload);\n  ```\n\n  After:\n\n  ```ts\n  actions$\n    .ofType('SOME_ACTION')\n    .map((action: SomeActionWithPayload) => action.payload);\n  ```\n\n- **ErrorHandler:** The ErrorReporter has been replaced with ErrorHandler\n  from angular/core.\n\nBEFORE:\n\nErrors were reported to the ngrx/effects ErrorReporter. The\nErrorReporter would log to the console by default.\n\nAFTER:\n\nErrors are now reported to the @angular/core ErrorHandler.\n\n- **Store:** Updates minimum version of RxJS dependency.\n\nBEFORE:\n\nMinimum peer dependency of RxJS ^5.0.0\n\nAFTER:\n\nMinimum peer dependency of RxJS ^5.5.0\n\n- **Effects:** Updates minimum version of RxJS dependency.\n\nBEFORE:\n\nMinimum peer dependency of RxJS ^5.0.0\n\nAFTER:\n\nMinimum peer dependency of RxJS ^5.5.0\n\n<a name=\"4.1.1\"></a>\n\n## [4.1.1](https://github.com/ngrx/platform/compare/v4.1.0...v4.1.1) (2017-11-07)\n\n### Bug Fixes\n\n- Add support for Angular 5 ([30a8c56](https://github.com/ngrx/platform/commit/30a8c56))\n- **Entity:** Fix type error for id selectors ([#533](https://github.com/ngrx/platform/issues/533)) ([88f672c](https://github.com/ngrx/platform/commit/88f672c)), closes [#525](https://github.com/ngrx/platform/issues/525)\n\n### Features\n\n- **Codegen:** Add base code and build for [@ngrx](https://github.com/ngrx)/codegen ([#534](https://github.com/ngrx/platform/issues/534)) ([2a22211](https://github.com/ngrx/platform/commit/2a22211))\n- **RouterStore:** Add configurable option for router reducer name ([#417](https://github.com/ngrx/platform/issues/417)) ([ab7de5c](https://github.com/ngrx/platform/commit/ab7de5c)), closes [#410](https://github.com/ngrx/platform/issues/410)\n\n<a name=\"4.1.0\"></a>\n\n# [4.1.0](https://github.com/ngrx/platform/compare/v4.0.5...v4.1.0) (2017-10-19)\n\n### Bug Fixes\n\n- **Build:** Fix build with space in path ([#331](https://github.com/ngrx/platform/issues/331)) ([257fc9d](https://github.com/ngrx/platform/commit/257fc9d))\n- **combineSelectors:** Remove default parameter from function signature for Closure ([ae7d5e1](https://github.com/ngrx/platform/commit/ae7d5e1))\n- **decorator:** add ExportDecoratedItems jsdoc for g3 ([#456](https://github.com/ngrx/platform/issues/456)) ([2b0e0cf](https://github.com/ngrx/platform/commit/2b0e0cf))\n- **Effects:** Simplify decorator handling for Closure compatibility ([ad30d40](https://github.com/ngrx/platform/commit/ad30d40))\n- **Entity:** Change type for EntityState to interface ([#454](https://github.com/ngrx/platform/issues/454)) ([d5640ec](https://github.com/ngrx/platform/commit/d5640ec)), closes [#458](https://github.com/ngrx/platform/issues/458)\n- **Example:** Add missing import for catch operator ([#409](https://github.com/ngrx/platform/issues/409)) ([193e8b3](https://github.com/ngrx/platform/commit/193e8b3))\n- **RouterStore:** Fix cancelled navigation with async guard (fixes [#354](https://github.com/ngrx/platform/issues/354)) ([#355](https://github.com/ngrx/platform/issues/355)) ([920c0ba](https://github.com/ngrx/platform/commit/920c0ba)), closes [#201](https://github.com/ngrx/platform/issues/201)\n- **RouterStore:** Stringify error from navigation error event ([#357](https://github.com/ngrx/platform/issues/357)) ([0528d2d](https://github.com/ngrx/platform/commit/0528d2d)), closes [#356](https://github.com/ngrx/platform/issues/356)\n- **Store:** Fix typing for feature to accept InjectionToken ([#375](https://github.com/ngrx/platform/issues/375)) ([38b2f95](https://github.com/ngrx/platform/commit/38b2f95))\n- **Store:** Refactor parameter initialization in combineReducers for Closure ([5c60cba](https://github.com/ngrx/platform/commit/5c60cba))\n- **Store:** Set initial value for state action pair to object ([#480](https://github.com/ngrx/platform/issues/480)) ([100a8ef](https://github.com/ngrx/platform/commit/100a8ef)), closes [#477](https://github.com/ngrx/platform/issues/477)\n\n### Features\n\n- **createSelector:** Expose projector function on selectors to improve testability ([56cb21f](https://github.com/ngrx/platform/commit/56cb21f)), closes [#290](https://github.com/ngrx/platform/issues/290)\n- **Effects:** Add getEffectsMetadata() helper for verifying metadata ([628b865](https://github.com/ngrx/platform/commit/628b865)), closes [#491](https://github.com/ngrx/platform/issues/491)\n- **Effects:** Add root effects init action ([#473](https://github.com/ngrx/platform/issues/473)) ([838ba17](https://github.com/ngrx/platform/commit/838ba17)), closes [#246](https://github.com/ngrx/platform/issues/246)\n- **Entity:** Add default selectId function for EntityAdapter ([#405](https://github.com/ngrx/platform/issues/405)) ([2afb792](https://github.com/ngrx/platform/commit/2afb792))\n- **Entity:** Add support for string or number type for ID ([#441](https://github.com/ngrx/platform/issues/441)) ([46d6f2f](https://github.com/ngrx/platform/commit/46d6f2f))\n- **Entity:** Enable creating entity selectors without composing a state selector ([#490](https://github.com/ngrx/platform/issues/490)) ([aae4064](https://github.com/ngrx/platform/commit/aae4064))\n- **Entity:** Rename 'sort' to 'sortComparer' ([274554b](https://github.com/ngrx/platform/commit/274554b)), closes [#370](https://github.com/ngrx/platform/issues/370)\n- **Store:** createSelector with an array of selectors ([#340](https://github.com/ngrx/platform/issues/340)) ([2f6a035](https://github.com/ngrx/platform/commit/2f6a035)), closes [#192](https://github.com/ngrx/platform/issues/192)\n\n<a name=\"4.0.5\"></a>\n\n## [4.0.5](https://github.com/ngrx/platform/compare/v4.0.4...v4.0.5) (2017-08-18)\n\n### Bug Fixes\n\n- **Effects:** Do not complete effects if one source errors or completes ([#297](https://github.com/ngrx/platform/issues/297)) ([54747cf](https://github.com/ngrx/platform/commit/54747cf)), closes [#232](https://github.com/ngrx/platform/issues/232)\n- **Entity:** Return a referentially equal state if state did not change ([fbd6a66](https://github.com/ngrx/platform/commit/fbd6a66))\n- **Entity:** Simplify target index finder for sorted entities ([335d255](https://github.com/ngrx/platform/commit/335d255))\n\n<a name=\"4.0.4\"></a>\n\n## [4.0.4](https://github.com/ngrx/platform/compare/v4.0.3...v4.0.4) (2017-08-17)\n\n### Bug Fixes\n\n- **Effects:** Use factory provide for console ([#288](https://github.com/ngrx/platform/issues/288)) ([bf7f70c](https://github.com/ngrx/platform/commit/bf7f70c)), closes [#276](https://github.com/ngrx/platform/issues/276)\n- **RouterStore:** Add generic type to RouterReducerState ([#292](https://github.com/ngrx/platform/issues/292)) ([6da3ec5](https://github.com/ngrx/platform/commit/6da3ec5)), closes [#289](https://github.com/ngrx/platform/issues/289)\n- **RouterStore:** Only serialize snapshot in preactivation hook ([#287](https://github.com/ngrx/platform/issues/287)) ([bbb7c99](https://github.com/ngrx/platform/commit/bbb7c99)), closes [#286](https://github.com/ngrx/platform/issues/286)\n\n<a name=\"4.0.3\"></a>\n\n## [4.0.3](https://github.com/ngrx/platform/compare/v4.0.2...v4.0.3) (2017-08-16)\n\n### Bug Fixes\n\n- **Effects:** Deprecate toPayload utility function ([#266](https://github.com/ngrx/platform/issues/266)) ([1cbb2c9](https://github.com/ngrx/platform/commit/1cbb2c9))\n- **Effects:** Ensure StoreModule is loaded before effects ([#230](https://github.com/ngrx/platform/issues/230)) ([065d33e](https://github.com/ngrx/platform/commit/065d33e)), closes [#184](https://github.com/ngrx/platform/issues/184) [#219](https://github.com/ngrx/platform/issues/219)\n- **Effects:** Export EffectsNotification interface ([#231](https://github.com/ngrx/platform/issues/231)) ([2b1a076](https://github.com/ngrx/platform/commit/2b1a076))\n- **Store:** Add type signature for metareducer ([#270](https://github.com/ngrx/platform/issues/270)) ([57633d2](https://github.com/ngrx/platform/commit/57633d2)), closes [#264](https://github.com/ngrx/platform/issues/264) [#170](https://github.com/ngrx/platform/issues/170)\n- **Store:** Set initial state for feature modules ([#235](https://github.com/ngrx/platform/issues/235)) ([4aec80c](https://github.com/ngrx/platform/commit/4aec80c)), closes [#206](https://github.com/ngrx/platform/issues/206) [#233](https://github.com/ngrx/platform/issues/233)\n- **Store:** Update usage of compose for reducer factory ([#252](https://github.com/ngrx/platform/issues/252)) ([683013c](https://github.com/ngrx/platform/commit/683013c)), closes [#247](https://github.com/ngrx/platform/issues/247)\n- **Store:** Use existing reducers when providing reducers without an InjectionToken ([#254](https://github.com/ngrx/platform/issues/254)) ([c409252](https://github.com/ngrx/platform/commit/c409252)), closes [#250](https://github.com/ngrx/platform/issues/250) [#116](https://github.com/ngrx/platform/issues/116)\n- **Store:** Use injector to get reducers provided via InjectionTokens ([#259](https://github.com/ngrx/platform/issues/259)) ([bd968fa](https://github.com/ngrx/platform/commit/bd968fa)), closes [#189](https://github.com/ngrx/platform/issues/189)\n\n### Features\n\n- **RouterStore:** Add serializer for router state snapshot ([#188](https://github.com/ngrx/platform/issues/188)) ([0fc1bcc](https://github.com/ngrx/platform/commit/0fc1bcc)), closes [#97](https://github.com/ngrx/platform/issues/97) [#104](https://github.com/ngrx/platform/issues/104) [#237](https://github.com/ngrx/platform/issues/237)\n\n<a name=\"4.0.2\"></a>\n\n## [4.0.2](https://github.com/ngrx/platform/compare/v4.0.1...v4.0.2) (2017-08-02)\n\n### Bug Fixes\n\n- **createSelector:** memoize projector function ([#228](https://github.com/ngrx/platform/issues/228)) ([e2f1e57](https://github.com/ngrx/platform/commit/e2f1e57)), closes [#226](https://github.com/ngrx/platform/issues/226)\n- **docs:** update angular-cli variable ([eeb7d5d](https://github.com/ngrx/platform/commit/eeb7d5d))\n- **Docs:** update effects description ([#164](https://github.com/ngrx/platform/issues/164)) ([c77b2d9](https://github.com/ngrx/platform/commit/c77b2d9))\n- **Effects:** Wrap testing source in an Actions observable ([#121](https://github.com/ngrx/platform/issues/121)) ([bfdb83b](https://github.com/ngrx/platform/commit/bfdb83b)), closes [#117](https://github.com/ngrx/platform/issues/117)\n- **RouterStore:** Add support for cancellation with CanLoad guard ([#223](https://github.com/ngrx/platform/issues/223)) ([2c006e8](https://github.com/ngrx/platform/commit/2c006e8)), closes [#213](https://github.com/ngrx/platform/issues/213)\n- **Store:** Remove auto-memoization of selector functions ([90899f7](https://github.com/ngrx/platform/commit/90899f7)), closes [#118](https://github.com/ngrx/platform/issues/118)\n\n### Features\n\n- **Effects:** Add generic type to the \"ofType\" operator ([55c13b2](https://github.com/ngrx/platform/commit/55c13b2))\n- **Platform:** Introduce [@ngrx](https://github.com/ngrx)/entity ([#207](https://github.com/ngrx/platform/issues/207)) ([9bdfd70](https://github.com/ngrx/platform/commit/9bdfd70))\n- **Store:** Add injection token option for feature modules ([#153](https://github.com/ngrx/platform/issues/153)) ([7f77693](https://github.com/ngrx/platform/commit/7f77693)), closes [#116](https://github.com/ngrx/platform/issues/116) [#141](https://github.com/ngrx/platform/issues/141) [#147](https://github.com/ngrx/platform/issues/147)\n- **Store:** Added initial state function support for features. Added more tests ([#85](https://github.com/ngrx/platform/issues/85)) ([5e5d7dd](https://github.com/ngrx/platform/commit/5e5d7dd))\n\n<a name=\"4.0.1\"></a>\n\n## [4.0.1](https://github.com/ngrx/platform/compare/v4.0.0...v4.0.1) (2017-07-18)\n\n### Bug Fixes\n\n- **effects:** allow downleveled annotations ([#98](https://github.com/ngrx/platform/issues/98)) ([875b326](https://github.com/ngrx/platform/commit/875b326)), closes [#93](https://github.com/ngrx/platform/issues/93)\n- **effects:** make correct export path for testing module ([#96](https://github.com/ngrx/platform/issues/96)) ([a5aad22](https://github.com/ngrx/platform/commit/a5aad22)), closes [#94](https://github.com/ngrx/platform/issues/94)\n\n<a name=\"4.0.0\"></a>\n\n# [4.0.0](https://github.com/ngrx/platform/compare/68bd9df...v4.0.0) (2017-07-18)\n\n### Bug Fixes\n\n- **build:** Fixed deployment of latest master as commit ([#18](https://github.com/ngrx/platform/issues/18)) ([5d0ecf9](https://github.com/ngrx/platform/commit/5d0ecf9))\n- **build:** Get tests running for each project ([c4a1054](https://github.com/ngrx/platform/commit/c4a1054))\n- **build:** Limit concurrency for lerna bootstrap ([7e7a7d8](https://github.com/ngrx/platform/commit/7e7a7d8))\n- **Devtools:** Removed SHOULD_INSTRUMENT token used to eagerly inject providers ([#57](https://github.com/ngrx/platform/issues/57)) ([b90df34](https://github.com/ngrx/platform/commit/b90df34))\n- **Effects:** Start child effects after running root effects ([#43](https://github.com/ngrx/platform/issues/43)) ([931adb1](https://github.com/ngrx/platform/commit/931adb1))\n- **Effects:** Use Actions generic type for the return of the ofType operator ([d176a11](https://github.com/ngrx/platform/commit/d176a11))\n- **Example:** Fix Book State interface parent ([#90](https://github.com/ngrx/platform/issues/90)) ([6982952](https://github.com/ngrx/platform/commit/6982952))\n- **example-app:** Suppress StoreDevtoolsConfig compiler warning ([8804156](https://github.com/ngrx/platform/commit/8804156))\n- **omit:** Strengthen the type checking of the omit utility function ([3982038](https://github.com/ngrx/platform/commit/3982038))\n- **router-store:** NavigationCancel and NavigationError creates a cycle when used with routerReducer ([a085730](https://github.com/ngrx/platform/commit/a085730)), closes [#68](https://github.com/ngrx/platform/issues/68)\n- **Store:** Exported initial state tokens ([#65](https://github.com/ngrx/platform/issues/65)) ([4b27b6d](https://github.com/ngrx/platform/commit/4b27b6d))\n- **Store:** pass all required arguments to projector ([#74](https://github.com/ngrx/platform/issues/74)) ([9b82b3a](https://github.com/ngrx/platform/commit/9b82b3a))\n- **Store:** Remove parameter destructuring for strict mode ([#33](https://github.com/ngrx/platform/issues/33)) ([#77](https://github.com/ngrx/platform/issues/77)) ([c9d6a45](https://github.com/ngrx/platform/commit/c9d6a45))\n- **Store:** Removed readonly from type ([#72](https://github.com/ngrx/platform/issues/72)) ([68274c9](https://github.com/ngrx/platform/commit/68274c9))\n- **StoreDevtools:** Type InjectionToken for AOT compilation ([e21d688](https://github.com/ngrx/platform/commit/e21d688))\n\n### Code Refactoring\n\n- **Effects:** Simplified AP, added better error reporting and effects stream control ([015107f](https://github.com/ngrx/platform/commit/015107f))\n\n### Features\n\n- **build:** Updated build pipeline for modules ([68bd9df](https://github.com/ngrx/platform/commit/68bd9df))\n- **Effects:** Ensure effects are only subscribed to once ([089abdc](https://github.com/ngrx/platform/commit/089abdc))\n- **Effects:** Introduce new Effects testing module ([#70](https://github.com/ngrx/platform/issues/70)) ([7dbb571](https://github.com/ngrx/platform/commit/7dbb571))\n- **router-store:** Added action types ([#47](https://github.com/ngrx/platform/issues/47)) ([1f67cb3](https://github.com/ngrx/platform/commit/1f67cb3)), closes [#44](https://github.com/ngrx/platform/issues/44)\n- **store:** Add 'createSelector' and 'createFeatureSelector' utils ([#10](https://github.com/ngrx/platform/issues/10)) ([41758b1](https://github.com/ngrx/platform/commit/41758b1))\n- **Store:** Allow initial state function for AoT compatibility ([#59](https://github.com/ngrx/platform/issues/59)) ([1a166ec](https://github.com/ngrx/platform/commit/1a166ec)), closes [#51](https://github.com/ngrx/platform/issues/51)\n- **Store:** Allow parent modules to provide reducers with tokens ([#36](https://github.com/ngrx/platform/issues/36)) ([069b12f](https://github.com/ngrx/platform/commit/069b12f)), closes [#34](https://github.com/ngrx/platform/issues/34)\n- **Store:** Simplify API for adding meta-reducers ([#87](https://github.com/ngrx/platform/issues/87)) ([d2295c7](https://github.com/ngrx/platform/commit/d2295c7))\n\n### BREAKING CHANGES\n\n- **Effects:** Effects API for registering effects has been updated to allow for multiple classes to be provided.\n\nBEFORE:\n\n```ts\n@NgModule({\n  imports: [EffectsModule.run(SourceA), EffectsModule.run(SourceB)],\n})\nexport class AppModule {}\n```\n\nAFTER:\n\n```ts\n@NgModule({\n  imports: [EffectsModule.forRoot([SourceA, SourceB, SourceC])],\n})\nexport class AppModule {}\n\n@NgModule({\n  imports: [\n    EffectsModule.forFeature([\n      FeatureSourceA,\n      FeatureSourceB,\n      FeatureSourceC,\n    ]),\n  ],\n})\nexport class SomeFeatureModule {}\n```\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nngrx[.]team[@]gmail.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Developing\n\n## Getting started with GitHub Codespaces\n\nTo get started, create a codespace for this repository by clicking this 👇\n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=83716883)\n\nA codespace will open in a web-based version of Visual Studio Code. The [dev container](.devcontainer/devcontainer.json) is fully configured with software needed for this project.\n\n**Note**: Dev containers is an open spec which is supported by [GitHub Codespaces](https://github.com/codespaces) and [other tools](https://containers.dev/supporting).\n\n## Setup\n\n```shell\npnpm install\n```\n\n## Testing\n\n```shell\npnpm exec test\n```\n\n### Testing for a specific library\n\n```shell\npnpm exec nx test effects --watchAll\npnpm exec nx test <module-name> --watchAll\n```\n\n### Testing for a specific schematic unit test\n\n```shell\npnpm exec jest modules/schematics/src/effect/index.spec.ts --watch\npnpm exec jest <relative path> --watch\n```\n\n## Submitting Pull Requests\n\n**Please follow these basic steps to simplify pull request reviews. If you don't you'll probably just be asked to anyway.**\n\n- Please rebase your branch against the current main.\n- Run the `Setup` command to make sure your development dependencies are up-to-date.\n- Please ensure the test suite passes before submitting a PR.\n- If you've added new functionality, **please** include tests which validate its behavior.\n- Make reference to possible [issues](https://github.com/ngrx/platform/issues) on PR comment.\n\n## Submitting bug reports\n\n- Search through issues to see if a previous issue has already been reported and/or fixed.\n- Provide a _small_ reproduction using a [StackBlitz project](https://stackblitz.com/edit/ngrx-seed) or a GitHub repository.\n- Please detail the affected browser(s) and operating system(s).\n- Please be sure to state which version of Angular, node and npm you're using.\n\n## Submitting new features\n\n- We value keeping the API surface small and concise, which factors into whether new features are accepted.\n- Submit an issue with the prefix `RFC:` with your feature request.\n- The feature will be discussed and considered.\n- Once the PR is submitted, it will be reviewed and merged once approved.\n\n## Questions and requests for support\n\nQuestions and requests for support should not be opened as issues and should be handled in the following ways:\n\n- Start a new [Q&A Discussion](https://github.com/ngrx/platform/discussions/new?category=q-a) on GitHub.\n- Ask a question on [StackOverflow](https://stackoverflow.com/questions/tagged/ngrx) using the `ngrx` tag.\n- Join our [Discord server](https://discord.com/invite/ngrx).\n\n## <a name=\"commit\"></a> Commit Message Guidelines\n\nWe have very precise rules over how our git commit messages can be formatted. This leads to **more\nreadable messages** that are easy to follow when looking through the **project history**. But also,\nwe use the git commit messages to **generate the NgRx changelog**.\n\n### Commit Message Format\n\nEach commit message consists of a **header**, a **body** and a **footer**. The header has a special\nformat that includes a **type**, a **scope** and a **subject**:\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\nAny line of the commit message cannot be longer than 100 characters! This allows the message to be easier\nto read on GitHub as well as in various git tools.\n\nThe footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.\n\nSamples: (even more [samples](https://github.com/ngrx/platform/commits/main))\n\n```\ndocs(changelog): update changelog to beta.5\n```\n\n```\nfix(release): need to depend on latest rxjs and zone.js\n\nThe version in our package.json gets copied to the one we publish, and users need the latest of these.\n```\n\n### Revert\n\nIf the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.\n\n### Type\n\nMust be one of the following:\n\n- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)\n- **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)\n- **docs**: Documentation only changes\n- **feat**: A new feature\n- **fix**: A bug fix\n- **perf**: A code change that improves performance\n- **refactor**: A code change that neither fixes a bug nor adds a feature\n- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.)\n- **test**: Adding missing tests or correcting existing tests\n\n### Scope\n\nThe scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages.\n\nThe following is the list of supported scopes:\n\n- **component**\n- **component-store**\n- **data**\n- **effects**\n- **entity**\n- **eslint-plugin**\n- **example**\n- **operators**\n- **router-store**\n- **schematics**\n- **signals**\n- **store**\n- **store-devtools**\n\n### Subject\n\nThe subject contains a succinct description of the change:\n\n- use the imperative, present tense: \"change\" not \"changed\" nor \"changes\"\n- don't capitalize the first letter\n- no dot (.) at the end\n\n### Body\n\nJust as in the **subject**, use the imperative, present tense: \"change\" not \"changed\" nor \"changes\".\nThe body should include the motivation for the change and contrast this with previous behavior.\n\n### Footer\n\nThe footer should contain any information about **Breaking Changes** and is also the place to\nreference GitHub issues that this commit **Closes**.\n\n**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.\n\nExample:\n\n```\nfeat(scope): commit message\n\nBREAKING CHANGES:\n\nDescribe breaking changes here\n\nBEFORE:\n\nPrevious code example here\n\nAFTER:\n\nNew code example here\n```\n\n## Financial Contributions\n\nWe also welcome sponsorships through [GitHub Sponsors](https://github.com/sponsors/ngrx). Sponsorships aid in the continued development of NgRx libraries, along with supporting core contributors on the project. If you are looking for more hands-on support such as training or workshops, check out our [Enterprise Support](https://ngrx.io/enterprise-support) page.\n\nThank you to all the people who have already contributed to NgRx!\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017-2023 Brandon Roberts, Mike Ryan, Victor Savkin, Rob Wormald\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nThis repository includes a file \"debounceSync.ts\" originially copied from\nhttps://github.com/cartant/rxjs-etc by Nicholas Jamieson, MIT licensed. See the\nfile header for details.\n"
  },
  {
    "path": "MIGRATION.md",
    "content": "# V21 Migration guide\n\nThis document has been moved to https://ngrx.io/guide/migration/v21.\n"
  },
  {
    "path": "README.md",
    "content": "# @ngrx\n\nReactive State for Angular\n\n![CI](https://github.com/ngrx/platform/actions/workflows/main.yml/badge.svg)\n[![Join the discord server at https://discord.com/invite/ngrx](https://img.shields.io/discord/740557383109050469.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square)](https://discord.com/invite/ngrx)\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](https://commitizen.github.io/cz-cli/)\n[![npm version](https://badge.fury.io/js/%40ngrx%2Fstore.svg)](https://www.npmjs.com/@ngrx/store)\n\n## Documentation\n\nCheck out our website: [ngrx.io](https://ngrx.io).\n\n## Contributing\n\nNgRx is a community-driven project. Read our [contributing guidelines](./CONTRIBUTING.md) on how to get involved.\n\n## Sponsoring NgRx\n\nSponsorships aid in the continued development and maintenance of NgRx libraries, along with supporting core contributors on the project. Consider asking your company to sponsor NgRx as its core to their business and application development.\n\n### Gold Sponsors\n\n<a href=\"https://nx.dev\" target=\"_blank\">\n  <img src=\"https://github.com/ngrx/platform/blob/main/projects/www/public/images/sponsors/nx.svg\" width=\"200px\" height=\"200px\" alt=\"NxDevTools logo\">\n</a>\n\nBecome a gold sponsor and get your logo on our README on GitHub and the front page of [ngrx.io](https://ngrx.io).\n\n### Silver Sponsors\n\nBecome a silver sponsor and get your logo on our README on GitHub.\n\n### Bronze Sponsors\n\n<a href=\"https://houseofangular.io\" target=\"_blank\">\n  <img src=\"https://github.com/ngrx/platform/blob/main/projects/www/public/images/sponsors/house-of-angular.png\" width=\"50px\" height=\"50px\" alt=\"House of Angular\" />\n</a>\n\nBecome a bronze sponsor and get your logo on our README on GitHub.\n\n## Enterprise Support\n\nIf your team or your company is looking for more hands-on support such as training or workshops, check out our [Enterprise Support](https://ngrx.io/enterprise-support) page.\n"
  },
  {
    "path": "build/config.ts",
    "content": "import * as fs from 'fs';\n\nexport interface PackageDescription {\n  name: string;\n}\n\nexport interface Config {\n  packages: PackageDescription[];\n  scope: string;\n}\n\nexport const modulesDir = './modules/';\nexport const packages: PackageDescription[] = fs\n  .readdirSync(modulesDir)\n  .filter((path) => {\n    const stat = fs.statSync(`${modulesDir}${path}`);\n    const isDir = stat.isDirectory();\n\n    if (!isDir) {\n      return false;\n    }\n\n    if (path.includes('eslint-plugin')) {\n      return false;\n    }\n\n    const hasBuild = fs.existsSync(`${modulesDir}${path}/tsconfig.build.json`);\n\n    return hasBuild;\n  })\n  .map((pkg) => ({ name: pkg }));\n"
  },
  {
    "path": "build/copy-schematics-core.ts",
    "content": "import * as tasks from './tasks';\nimport { createBuilder } from './util';\nimport { packages } from './config';\n\nconst copySchematics = createBuilder([\n  ['Copy Schematics Core Files', tasks.copySchematicsCore],\n]);\n\ncopySchematics({\n  scope: '@ngrx',\n  packages: [...packages],\n}).catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "build/deploy-build.ts",
    "content": "import * as tasks from './tasks';\nimport { createBuilder } from './util';\nimport { packages } from './config';\n\nconst deploy = createBuilder([['Deploy builds', tasks.publishToRepo]]);\n\ndeploy({\n  scope: '@ngrx',\n  packages,\n}).catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "build/example-app-server.js",
    "content": "const express = require('express');\nconst path = require('path');\n\nconst CONTEXT = `/${process.env.CONTEXT || 'platform/example-app'}`;\nconst PORT = process.env.PORT || 4000;\nconst DIST = path.join(__dirname, '../projects/example-app/dist');\n\nconst app = express();\n\napp.use(CONTEXT, express.static(DIST));\napp.use('/', express.static(DIST));\napp.listen(PORT, () =>\n  console.log(`App running on http://localhost:${PORT}${CONTEXT}`)\n);\n"
  },
  {
    "path": "build/generate-eslint-plugin.ts",
    "content": "import { createBuilder } from './util';\n\nconst update = createBuilder([\n  [\n    'Update config',\n    () => require('../modules/eslint-plugin/scripts/generate-config'),\n  ],\n  [\n    'Update overview',\n    () => require('../modules/eslint-plugin/scripts/generate-overview'),\n  ],\n  [\n    'Update docs',\n    () => require('../modules/eslint-plugin/scripts/generate-docs'),\n  ],\n]);\n\nupdate({\n  scope: '@ngrx',\n  packages: [{ name: 'eslint-plugin' }],\n}).catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "build/publish-latest.ts",
    "content": "import { createBuilder } from './util';\nimport { packages } from './config';\nimport * as shelljs from 'shelljs';\n\n/**\n * Publish release to NPM on \"latest\" tag\n */\nexport async function publishLatestToNpm() {\n  const publishablePackages = [\n    'store',\n    'effects',\n    'router-store',\n    'store-devtools',\n    'entity',\n    'component-store',\n    'component',\n    'schematics',\n    'eslint-plugin',\n    'data',\n    'signals',\n    'operators',\n  ];\n\n  for (const pkg of publishablePackages) {\n    console.log(`Publishing @ngrx/${pkg}`);\n\n    const cmd = [\n      'npm publish',\n      `./dist/modules/${pkg}`,\n      '--access=public',\n      '--tag=latest',\n    ];\n\n    shelljs.exec(cmd.join(' '));\n  }\n}\n\nconst publishLatest = createBuilder([\n  ['Publish packages on latest\\n', publishLatestToNpm],\n]);\n\npublishLatest({\n  scope: '@ngrx',\n  packages,\n}).catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "build/publish-next.ts",
    "content": "import { createBuilder } from './util';\nimport { packages } from './config';\nimport * as shelljs from 'shelljs';\n\n/**\n * Publish release to NPM on \"next\" tag\n */\nexport async function publishNextToNpm() {\n  const publishablePackages = [\n    'store',\n    'effects',\n    'router-store',\n    'store-devtools',\n    'entity',\n    'component-store',\n    'component',\n    'schematics',\n    'eslint-plugin',\n    'data',\n    'signals',\n    'operators',\n  ];\n\n  for (const pkg of publishablePackages) {\n    console.log(`Publishing @ngrx/${pkg}`);\n\n    const cmd = [\n      'npm publish',\n      `./dist/modules/${pkg}`,\n      '--access=public',\n      '--tag=next',\n    ];\n\n    shelljs.exec(cmd.join(' '));\n  }\n}\n\nconst publishNext = createBuilder([\n  ['Publish packages on next\\n', publishNextToNpm],\n]);\n\npublishNext({\n  scope: '@ngrx',\n  packages,\n}).catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "build/publish-release.ts",
    "content": "import { createBuilder } from './util';\nimport { packages } from './config';\nimport { execSync } from 'child_process';\n\nconst RELEASE_TAG = process.env.RELEASE_TAG;\nconst RELEASE_VERSION = process.env.RELEASE_VERSION;\nconst DRY_RUN = process.env.DRY_RUN === 'true';\nconst NPM_TOKEN = process.env.NPM_TOKEN;\n\n/**\n * Publish release to NPM on \"latest/next\" tag\n */\nexport async function publishLatestToNpm() {\n  if (!process.env.CI || !process.env.GITHUB_WORKFLOW) {\n    throw new Error('Invalid release environment!');\n  }\n\n  const publishablePackages = [\n    'store',\n    'effects',\n    'router-store',\n    'store-devtools',\n    'entity',\n    'component-store',\n    'component',\n    'schematics',\n    'eslint-plugin',\n    'data',\n    'signals',\n    'operators',\n  ];\n\n  execSync(`npm config set //registry.npmjs.org/:_authToken=${NPM_TOKEN}`);\n\n  for (const pkg of publishablePackages) {\n    console.log(\n      `Publishing @ngrx/${pkg} ${RELEASE_VERSION} on ${RELEASE_TAG} with dry run set to ${DRY_RUN}`\n    );\n\n    const cmd = [\n      'npm publish',\n      `./dist/modules/${pkg}`,\n      '--provenance',\n      '--access=public',\n      `--tag=${RELEASE_TAG}`,\n      DRY_RUN ? `--dry-run` : '',\n    ];\n\n    execSync(cmd.join(' '));\n  }\n}\n\nconst publishLatest = createBuilder([\n  ['Publish packages on latest\\n', publishLatestToNpm],\n]);\n\npublishLatest({\n  scope: '@ngrx',\n  packages,\n}).catch((err) => {\n  console.error(err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "build/stackblitz.ts",
    "content": "import { glob } from 'tinyglobby';\nimport * as fs from 'fs';\nimport { packages as ngrxPackages } from './config';\n\nconst EXAMPLE_FILES = [\n  '!**',\n  'angular.json',\n  'projects/example-app/**/*.*',\n  '!projects/example-app/coverage/**/*.*',\n  '!projects/example-app/dist/**/*.*',\n  '!**/*.spec.ts',\n  '!**/*.snap',\n  '!**/*.js',\n  '!**/README.md',\n];\n\n(async () => {\n  const paths = await glob(EXAMPLE_FILES, { ignore: ['**/node_modules/**'] });\n\n  const files = paths.reduce((files, filePath) => {\n    const contents = fs.readFileSync(filePath, 'utf-8');\n\n    return { ...files, [filePath]: contents };\n  }, {});\n\n  const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));\n  const dependencies = packageJson.dependencies;\n  const version = packageJson.version;\n  const ngrxDependencies = ngrxPackages\n    .map((pkg) => pkg.name)\n    .reduce((packages, packageName) => {\n      return { ...packages, [`@ngrx/${packageName}`]: version };\n    }, {});\n\n  const template = `\n    <html>\n    <head>\n      <script src=\"https://unpkg.com/@stackblitz/sdk/bundles/sdk.umd.js\"></script>\n    </head>\n    <body>\n    <script>\n      const project = {\n        files: ${JSON.stringify(files)\n          .replace(/\\\\r\\\\n/g, '')\n          .replace(/\\\\n/g, '')},\n        title: 'NgRx Example App',\n        description: 'NgRx example application with common patterns and best practices',\n        template: 'angular-cli',\n        tags: ['angular', 'ngrx', 'redux', 'example'],\n        dependencies: ${JSON.stringify({\n          ...dependencies,\n          ...ngrxDependencies,\n        })}\n      };\n\n      StackBlitzSDK.openProject(project, { newWindow: false });\n    </script>\n    </body>\n    </html>\n  `;\n\n  fs.writeFileSync('stackblitz.html', template, 'utf-8');\n})();\n"
  },
  {
    "path": "build/tasks.ts",
    "content": "import { Config, modulesDir } from './config';\nimport * as util from './util';\nimport * as fs from 'fs';\nimport { ncp } from 'ncp';\n\n/**\n *\n * Copies the schematics-core package into any package that provides\n * schematics or migrations\n */\nexport async function copySchematicsCore(config: Config) {\n  (ncp as any).limit = 1;\n  const filter = (name: string) =>\n    !name.endsWith('.eslintrc.json') && !name.endsWith('project.json');\n\n  for (const pkg of util.getTopLevelPackages(config)) {\n    const packageJson = fs\n      .readFileSync(`${modulesDir}${pkg}/package.json`)\n      .toString('utf-8');\n    const pkgConfig = JSON.parse(packageJson);\n\n    if (\n      pkgConfig.schematics ||\n      (pkgConfig['ng-update'] && pkgConfig['ng-update'].migrations)\n    ) {\n      const destDir = `${modulesDir}/${pkg}/schematics-core`;\n\n      // Remove the destination directory if it exists to ensure clean copy\n      if (fs.existsSync(destDir)) {\n        fs.rmSync(destDir, { recursive: true });\n      }\n\n      ncp(\n        `${modulesDir}/schematics-core`,\n        destDir,\n        { filter },\n        function (err: any) {\n          if (err) {\n            return console.error(err);\n          }\n        }\n      );\n    }\n  }\n}\n\n/**\n * Deploy build artifacts to repos\n */\nexport async function publishToRepo(config: Config) {\n  for (const pkg of util.getTopLevelPackages(config)) {\n    const SOURCE_DIR = `./dist/modules/${pkg}`;\n    const REPO_URL = `git@github.com:ngrx/${pkg}-builds.git`;\n    const REPO_DIR = `./tmp/${pkg}`;\n\n    console.log(`Preparing and deploying @ngrx/${pkg} to ${REPO_URL}`);\n    await prepareAndPublish(SOURCE_DIR, REPO_URL, REPO_DIR);\n  }\n}\n\nexport async function prepareAndPublish(\n  sourceDir: string,\n  repoUrl: string,\n  repoDir: string,\n  clean = true,\n  depth = 1\n) {\n  const COMMITTER_USER_NAME = await util.git([\n    `--no-pager show -s --format='%cN' HEAD`,\n  ]);\n  const COMMITTER_USER_EMAIL = await util.git([\n    `--no-pager show -s --format='%cE' HEAD`,\n  ]);\n\n  await util.cmd('rm -rf', [`${repoDir}`]);\n  await util.cmd('mkdir ', [`-p ${repoDir}`]);\n  await process.chdir(`${repoDir}`);\n  await util.git([`init`]);\n  await util.git([`remote add origin ${repoUrl}`]);\n  await util.git([`fetch origin main${depth ? ` --depth=${depth}` : ''}`]);\n  await util.git(['checkout origin/main']);\n  await util.git(['checkout -b main']);\n  await process.chdir('../../');\n\n  if (clean) {\n    await util.cmd('rm -rf', [`${repoDir}/*`]);\n  }\n\n  await util.git([`log --format=\"%h %s\" -n 1 > ${repoDir}/commit_message`]);\n  await util.cmd('cp', [`-R ${sourceDir}/* ${repoDir}/`]);\n  await process.chdir(`${repoDir}`);\n  await util.git([`config user.name \"${COMMITTER_USER_NAME}\"`]);\n  await util.git([`config user.email \"${COMMITTER_USER_EMAIL}\"`]);\n  await util.git(['add --all']);\n  await util.git([`commit -F commit_message`]);\n  await util.cmd('rm', ['commit_message']);\n\n  await util.git(['push origin main --force']);\n  await process.chdir('../../');\n}\n"
  },
  {
    "path": "build/update-version-numbers.ts",
    "content": "import { readFileSync, writeFileSync } from 'fs';\nimport { EOL } from 'os';\nimport * as readline from 'readline';\nimport { globSync } from 'tinyglobby';\nimport { createBuilder } from './util';\nimport { packages } from './config';\nimport { join } from 'path';\n\nconst CONFIG = {\n  navigationFile: './projects/www/src/app/services/guide-menu.service.ts',\n  migrationDirectory: './projects/www/src/app/pages/guide/migration',\n  currentVersion: JSON.parse(readFileSync('./package.json', 'utf-8')).version,\n};\n\n// get the version from the command\n// e.g. npx tsx ./build/update-version-numbers.ts 10.0.0\nconst [newVersion] = process.argv.slice(2);\n\nif (newVersion) {\n  updateVersions(newVersion);\n} else {\n  // if no version is provided, ask for it\n  const rl = readline.createInterface({\n    input: process.stdin,\n    output: process.stdout,\n  });\n\n  rl.question(`What's the new version? `, (version) => {\n    rl.close();\n    updateVersions(version);\n  });\n}\n\nfunction updateVersions(version: string) {\n  const publishNext = createBuilder([\n    ['Update package.json', createPackageJsonBuilder(version)],\n    ['Update ng-add schematic', createUpdateAddSchematicBuilder(version)],\n    // ['Update docs version picker', createArchivePreviousDocsBuilder(version)],\n    ['Create migration docs', createMigrationDocs(version)],\n    ['Update GitHub MIGRATION.MD', createMigrationMD(version)],\n  ]);\n\n  publishNext({\n    scope: '@ngrx',\n    packages,\n  }).catch((err) => {\n    console.error(err);\n    process.exit(1);\n  });\n}\n\n/**\n * Updates package versions in package.json files\n * Updates peerDependencies versions of NgRx packages in package.json files\n */\nfunction createPackageJsonBuilder(version: string) {\n  const [major] = version.split('.');\n  return async () => {\n    globSync('**/package.json', { ignore: '**/node_modules/**' }).map(\n      (file) => {\n        const content = readFileSync(file, 'utf-8');\n        const pkg = JSON.parse(content);\n        let saveFile = false;\n\n        if (pkg?.version && pkg?.name?.startsWith('@ngrx')) {\n          pkg.version = version;\n          saveFile = true;\n        }\n\n        if (pkg?.peerDependencies) {\n          Object.keys(pkg.peerDependencies).forEach((key) => {\n            if (key.startsWith('@ngrx')) {\n              pkg.peerDependencies[key] = version;\n              saveFile = true;\n            } else if (key.startsWith('@angular')) {\n              // because the NgRx version is in sync with the Angular version\n              // we can also update the Angular dependencies\n              pkg.peerDependencies[key] = `^${major}.0.0`;\n              saveFile = true;\n            }\n          });\n        }\n\n        if (saveFile) {\n          writeAsJson(file, pkg);\n        }\n      }\n    );\n  };\n}\n\n/**\n * Updates the platform version for our schematics\n */\nfunction createUpdateAddSchematicBuilder(version: string) {\n  return async () => {\n    globSync('**/libs-version.ts', { ignore: '**/node_modules/**' }).map(\n      (file) => {\n        writeFileSync(\n          file,\n          `export const platformVersion = '^${version}';${EOL}`\n        );\n      }\n    );\n  };\n}\n\n/**\n * Creates an archive version (in the dropdown) on a major release\n */\nfunction createArchivePreviousDocsBuilder(version: string) {\n  return async () => {\n    // only deprecate previous version on MAJOR releases\n    if (!version.endsWith('.0.0')) {\n      return;\n    }\n    const [major] = version.split('.');\n    const previousVersion = Number(major) - 1;\n    const content = readFileSync(CONFIG.navigationFile, 'utf-8');\n    const navigation = JSON.parse(content);\n    navigation['docVersions'] = [\n      {\n        title: `v${previousVersion}`,\n        url: `https://v${previousVersion}.ngrx.io`,\n      },\n      ...navigation['docVersions'],\n    ];\n    writeAsJson(CONFIG.navigationFile, navigation);\n\n    console.log(\n      '\\r\\n⚠ Previous version added to website doc versions but site needs to be archived manually.'\n    );\n  };\n}\n\n/**\n * Creates a migration file when the major version changes\n * Also adds the new migration to the side navigation\n */\nfunction createMigrationDocs(version: string) {\n  return async () => {\n    const [newMajor] = version.split('.');\n    const [currentMajor] = CONFIG.currentVersion.split('.');\n\n    if (newMajor === currentMajor) {\n      return;\n    }\n\n    const navigationContent = readFileSync(CONFIG.navigationFile, 'utf-8');\n    const migrationLinkPattern = /section\\('Migrations',\\s*\\[([\\s\\S]*?)\\]\\)/;\n    const match = navigationContent.match(migrationLinkPattern);\n\n    if (match) {\n      const newMigrationLink = `link('V${newMajor}', '/guide/migration/v${newMajor}'),\\n        `;\n      const migrationsLineMatch = navigationContent.match(\n        /section\\('Migrations',\\s*\\[\\s*\\n/\n      );\n\n      if (migrationsLineMatch?.index) {\n        const insertPosition =\n          migrationsLineMatch.index + migrationsLineMatch[0].length;\n        const updatedContent =\n          navigationContent.slice(0, insertPosition) +\n          newMigrationLink +\n          navigationContent.slice(insertPosition);\n        writeFileSync(CONFIG.navigationFile, updatedContent);\n      }\n    } else {\n      console.log(\n        '\\r\\n ⚠ Not able to find Migrations in guide-menu.service.ts'\n      );\n    }\n\n    const migrationDocPath = join(CONFIG.migrationDirectory, `v${newMajor}.md`);\n\n    console.log(\n      `\\r\\n ✍   Please write a migration guide at ${migrationDocPath}`\n    );\n    const migrationPlaceholder = `# V${newMajor} Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI \\`ng update\\` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n\\`\\`\\`sh\nng update @ngrx/store@${newMajor}\n\\`\\`\\`\n\n## Dependencies\n\nVersion ${newMajor} has the minimum version requirements:\n\n- Angular version TK\n- Angular CLI version TK\n- TypeScript version TK\n- RxJS version TK\n\n## Breaking changes\n\n### @ngrx/store\n\nTK\n`;\n    writeFileSync(migrationDocPath, migrationPlaceholder);\n  };\n}\n\nfunction writeAsJson(path: string, json: object) {\n  const content = JSON.stringify(json, null, 2);\n  writeFileSync(path, `${content}${EOL}`);\n}\n\n/**\n * Update the migration.MD file that is visible in GitHub\n */\nfunction createMigrationMD(version: string) {\n  return async () => {\n    const [newMajor] = version.split('.');\n\n    const migrationPlaceholder = `# V${newMajor} Migration guide\n\nThis document has been moved to https://ngrx.io/guide/migration/v${newMajor}.\n`;\n    writeFileSync('./MIGRATION.md', migrationPlaceholder);\n  };\n}\n"
  },
  {
    "path": "build/util.ts",
    "content": "import * as path from 'node:path';\nimport * as cp from 'child_process';\nimport ora from 'ora';\nimport { Config } from './config';\n\nexport type RunnerFn = (config: Config) => Promise<any>;\nexport type TaskDef = [string, RunnerFn];\nexport type BaseFn = (command: string) => string;\n\nexport function createBuilder(tasks: TaskDef[]) {\n  return async function (config: Config) {\n    for (const [name, runner] of tasks) {\n      await runTask(name, () => runner(config));\n    }\n  };\n}\n\nexport function exec(\n  command: string,\n  args: string[],\n  base: BaseFn = fromNpm\n): Promise<string> {\n  return new Promise((resolve, reject) => {\n    cp.exec(base(command) + ' ' + args.join(' '), (err, stdout) => {\n      if (err) {\n        return reject(err);\n      }\n\n      resolve(stdout.toString());\n    });\n  });\n}\n\nexport function getTopLevelPackages(config: Config) {\n  return config.packages.map((packageDescription) => packageDescription.name);\n}\n\nexport function cmd(command: string, args: string[]): Promise<string> {\n  return exec(command, args, (command: string) => command);\n}\n\nexport function git(args: string[]): Promise<string> {\n  return cmd('git', args);\n}\n\nasync function runTask(name: string, taskFn: () => Promise<any>) {\n  const spinner = ora(name);\n\n  try {\n    spinner.start();\n\n    await taskFn();\n\n    spinner.succeed();\n  } catch (e) {\n    spinner.fail();\n\n    throw e;\n  }\n}\n\nfunction baseDir(...dirs: string[]): string {\n  return `\"${path.resolve(__dirname, '../', ...dirs)}\"`;\n}\n\nfunction fromNpm(command: string) {\n  return baseDir(`./node_modules/.bin/${command}`);\n}\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport nxEslintPlugin from '@nx/eslint-plugin';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  { plugins: { '@nx': nxEslintPlugin } },\n  {\n    files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],\n    rules: {\n      '@nx/enforce-module-boundaries': [\n        'error',\n        {\n          allow: [],\n          depConstraints: [\n            {\n              sourceTag: '*',\n              onlyDependOnLibsWithTags: ['*'],\n            },\n          ],\n        },\n      ],\n      '@typescript-eslint/member-ordering': 'off',\n      '@typescript-eslint/no-inferrable-types': 'warn',\n    },\n  },\n  ...compat\n    .config({\n      extends: ['plugin:@nx/typescript'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],\n      rules: {\n        ...config.rules,\n        'no-extra-semi': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/javascript'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],\n      rules: {\n        ...config.rules,\n        'no-extra-semi': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      plugins: ['eslint-plugin-import', '@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@typescript-eslint/no-explicit-any': 'off',\n        '@typescript-eslint/no-unused-vars': [\n          'warn',\n          {\n            argsIgnorePattern: '^_',\n            varsIgnorePattern: '^_',\n            caughtErrorsIgnorePattern: '^_',\n          },\n        ],\n        '@typescript-eslint/naming-convention': 'off',\n        '@typescript-eslint/prefer-namespace-keyword': 'error',\n        '@typescript-eslint/no-empty-function': 'warn',\n        '@typescript-eslint/no-empty-object-type': 'warn',\n        eqeqeq: ['off', 'smart'],\n        'id-blacklist': [\n          'error',\n          'any',\n          'Number',\n          'number',\n          'String',\n          'string',\n          'Boolean',\n          'boolean',\n          'Undefined',\n          'undefined',\n        ],\n        'id-match': 'error',\n        'no-eval': 'off',\n        'no-redeclare': 'off',\n        'no-underscore-dangle': 'off',\n        'no-var': 'error',\n        'no-prototype-builtins': 'off',\n        '@typescript-eslint/no-wrapper-object-types': 'off',\n        '@typescript-eslint/no-unsafe-function-type': 'off',\n      },\n    })),\n  {\n    files: ['**/*.html'],\n    // Override or add rules here\n    rules: {},\n  },\n];\n"
  },
  {
    "path": "jest.config.ts",
    "content": "const { getJestProjectsAsync } = require('@nx/jest');\n\nexport default async () => ({ projects: await getJestProjectsAsync() });\n"
  },
  {
    "path": "jest.preset.js",
    "content": "const nxPreset = require('@nx/jest/preset').default;\n\nmodule.exports = { ...nxPreset };\n"
  },
  {
    "path": "modules/README.md",
    "content": "# NgRx\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/component/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/component/README.md",
    "content": "# @ngrx/component\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/component/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: [\n      '**/dist',\n      '**/jest.config.ts',\n      '**/schematics-core/**/*.ts',\n      '**/vite.config.*.timestamp*',\n      '**/vitest.config.*.timestamp*'\n    ],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        '@angular-eslint/no-input-rename': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/component/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n  {\n    ignores: ['schematics-core'],\n  },\n];\n"
  },
  {
    "path": "modules/component/index.ts",
    "content": "/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\nexport * from './public_api';\n"
  },
  {
    "path": "modules/component/migrations/15_0_0-beta/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createPackageJson } from '@ngrx/schematics-core/testing/create-package';\nimport { waitForAsync } from '@angular/core/testing';\n\ndescribe('Component Migration 15_0_0-beta', () => {\n  let appTree: UnitTestTree;\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/component/migrations/migration.json'\n  );\n  const pkgName = 'component';\n\n  beforeEach(() => {\n    appTree = new UnitTestTree(Tree.empty());\n    appTree.create(\n      '/tsconfig.json',\n      `\n        {\n          \"include\": [**./*.ts\"]\n        }\n       `\n    );\n    createPackageJson('', pkgName, appTree);\n  });\n\n  describe('Replace ReactiveComponentModule', () => {\n    it(`should replace the ReactiveComponentModule in NgModules with LetModule and PushModule`, waitForAsync(async () => {\n      const input = `\n      import { ReactiveComponentModule } from '@ngrx/component';\n\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          ReactiveComponentModule,\n          CoreModule,\n        ],\n        exports: [ReactiveComponentModule],\n        bootstrap: [AppComponent]\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { LetModule, PushModule } from '@ngrx/component';\n\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          LetModule, PushModule,\n          CoreModule,\n        ],\n        exports: [LetModule, PushModule],\n        bootstrap: [AppComponent]\n      })\n      export class AppModule {}\n    `;\n\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-15-beta`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }));\n    it(`should replace the ReactiveComponentModule in standalone components with LetModule and PushModule`, waitForAsync(async () => {\n      const input = `\n      import { ReactiveComponentModule } from '@ngrx/component';\n\n      @Component({\n        imports: [\n          AuthModule,\n          ReactiveComponentModule\n        ]\n      })\n      export class SomeStandaloneComponent {}\n    `;\n      const expected = `\n      import { LetModule, PushModule } from '@ngrx/component';\n\n      @Component({\n        imports: [\n          AuthModule,\n          LetModule, PushModule\n        ]\n      })\n      export class SomeStandaloneComponent {}\n    `;\n\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-15-beta`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }));\n    it(`should not remove the ReactiveComponentModule JS import when used as a type`, waitForAsync(async () => {\n      const input = `\n      import { ReactiveComponentModule } from '@ngrx/component';\n\n      const reactiveComponentModule: ReactiveComponentModule;\n\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          ReactiveComponentModule,\n          CoreModule\n        ],\n        bootstrap: [AppComponent]\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { ReactiveComponentModule, LetModule, PushModule } from '@ngrx/component';\n\n      const reactiveComponentModule: ReactiveComponentModule;\n\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          LetModule, PushModule,\n          CoreModule\n        ],\n        bootstrap: [AppComponent]\n      })\n      export class AppModule {}\n    `;\n\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-15-beta`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }));\n  });\n});\n"
  },
  {
    "path": "modules/component/migrations/15_0_0-beta/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  commitChanges,\n  createReplaceChange,\n  ReplaceChange,\n} from '../../schematics-core';\n\nconst reactiveComponentModuleText = 'ReactiveComponentModule';\nconst reactiveComponentModuleReplacement = 'LetModule, PushModule';\nconst moduleLocations = {\n  imports: ['NgModule', 'Component'],\n  exports: ['NgModule'],\n};\n\nfunction migrateReactiveComponentModule() {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const componentImports = sourceFile.statements\n        .filter(ts.isImportDeclaration)\n        .filter(({ moduleSpecifier }) =>\n          moduleSpecifier.getText(sourceFile).includes('@ngrx/component')\n        );\n\n      if (componentImports.length === 0) {\n        return;\n      }\n\n      const ngModuleReplacements =\n        findReactiveComponentModuleNgModuleReplacements(sourceFile);\n\n      const possibleUsagesOfReactiveComponentModuleCount =\n        findPossibleReactiveComponentModuleUsageCount(sourceFile);\n\n      const importAdditionReplacements =\n        findReactiveComponentModuleImportDeclarationAdditions(\n          sourceFile,\n          componentImports\n        );\n\n      const importUsagesCount = importAdditionReplacements.length;\n\n      const jsImportDeclarationReplacements =\n        possibleUsagesOfReactiveComponentModuleCount >\n        ngModuleReplacements.length + importUsagesCount\n          ? importAdditionReplacements\n          : findReactiveComponentModuleImportDeclarationReplacements(\n              sourceFile,\n              componentImports\n            );\n\n      const changes = [\n        ...jsImportDeclarationReplacements,\n        ...ngModuleReplacements,\n      ];\n\n      commitChanges(tree, sourceFile.fileName, changes);\n    });\n  };\n}\n\nfunction findReactiveComponentModuleImportDeclarationReplacements(\n  sourceFile: ts.SourceFile,\n  imports: ts.ImportDeclaration[]\n) {\n  const changes = imports\n    .map((p) => (p?.importClause?.namedBindings as ts.NamedImports)?.elements)\n    .reduce(\n      (imports, curr) => imports.concat(curr ?? []),\n      [] as ts.ImportSpecifier[]\n    )\n    .map((specifier) => {\n      if (!ts.isImportSpecifier(specifier)) {\n        return { hit: false };\n      }\n\n      if (specifier.name.text === reactiveComponentModuleText) {\n        return { hit: true, specifier, text: specifier.name.text };\n      }\n\n      // if import is renamed\n      if (\n        specifier.propertyName &&\n        specifier.propertyName.text === reactiveComponentModuleText\n      ) {\n        return { hit: true, specifier, text: specifier.propertyName.text };\n      }\n\n      return { hit: false };\n    })\n    .filter(({ hit }) => hit)\n    .map(({ specifier, text }) =>\n      !!specifier && !!text\n        ? createReplaceChange(\n            sourceFile,\n            specifier,\n            text,\n            reactiveComponentModuleReplacement\n          )\n        : undefined\n    )\n    .filter((change) => !!change) as Array<ReplaceChange>;\n\n  return changes;\n}\n\nfunction findReactiveComponentModuleImportDeclarationAdditions(\n  sourceFile: ts.SourceFile,\n  imports: ts.ImportDeclaration[]\n) {\n  const changes = imports\n    .map((p) => (p?.importClause?.namedBindings as ts.NamedImports)?.elements)\n    .reduce(\n      (imports, curr) => imports.concat(curr ?? []),\n      [] as ts.ImportSpecifier[]\n    )\n    .map((specifier) => {\n      if (!ts.isImportSpecifier(specifier)) {\n        return { hit: false };\n      }\n\n      if (specifier.name.text === reactiveComponentModuleText) {\n        return { hit: true, specifier, text: specifier.name.text };\n      }\n\n      // if import is renamed\n      if (\n        specifier.propertyName &&\n        specifier.propertyName.text === reactiveComponentModuleText\n      ) {\n        return { hit: true, specifier, text: specifier.propertyName.text };\n      }\n\n      return { hit: false };\n    })\n    .filter(({ hit }) => hit)\n    .map(({ specifier, text }) =>\n      !!specifier && !!text\n        ? createReplaceChange(\n            sourceFile,\n            specifier,\n            text,\n            `${text}, ${reactiveComponentModuleReplacement}`\n          )\n        : undefined\n    )\n    .filter((change) => !!change) as Array<ReplaceChange>;\n\n  return changes;\n}\n\nfunction findPossibleReactiveComponentModuleUsageCount(\n  sourceFile: ts.SourceFile\n): number {\n  let count = 0;\n  ts.forEachChild(sourceFile, (node) => countUsages(node));\n  return count;\n\n  function countUsages(node: ts.Node) {\n    if (ts.isIdentifier(node) && node.text === reactiveComponentModuleText) {\n      count = count + 1;\n    }\n\n    ts.forEachChild(node, (childNode) => countUsages(childNode));\n  }\n}\n\nfunction findReactiveComponentModuleNgModuleReplacements(\n  sourceFile: ts.SourceFile\n) {\n  const changes: ReplaceChange[] = [];\n  ts.forEachChild(sourceFile, (node) => find(node, changes));\n  return changes;\n\n  function find(node: ts.Node, changes: ReplaceChange[]) {\n    let change = undefined;\n\n    if (\n      ts.isIdentifier(node) &&\n      node.text === reactiveComponentModuleText &&\n      ts.isArrayLiteralExpression(node.parent) &&\n      ts.isPropertyAssignment(node.parent.parent)\n    ) {\n      const property = node.parent.parent;\n      if (ts.isIdentifier(property.name)) {\n        const propertyName = String(property.name.escapedText);\n        if (Object.keys(moduleLocations).includes(propertyName)) {\n          const decorator = property.parent.parent.parent;\n          if (\n            ts.isDecorator(decorator) &&\n            ts.isCallExpression(decorator.expression) &&\n            ts.isIdentifier(decorator.expression.expression) &&\n            moduleLocations[propertyName as 'imports' | 'exports'].includes(\n              String(decorator.expression.expression.escapedText)\n            )\n          ) {\n            change = {\n              node: node,\n              text: node.text,\n            };\n          }\n        }\n      }\n    }\n\n    if (change) {\n      changes.push(\n        createReplaceChange(\n          sourceFile,\n          change.node,\n          change.text,\n          reactiveComponentModuleReplacement\n        )\n      );\n    }\n\n    ts.forEachChild(node, (childNode) => find(childNode, changes));\n  }\n}\n\nexport default function (): Rule {\n  return chain([migrateReactiveComponentModule()]);\n}\n"
  },
  {
    "path": "modules/component/migrations/16_0_0/index.spec.ts",
    "content": "import * as path from 'path';\nimport { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport { createPackageJson } from '@ngrx/schematics-core/testing/create-package';\n\ndescribe('Component Migration 16_0_0', () => {\n  let appTree: UnitTestTree;\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/component/migrations/migration.json'\n  );\n  const pkgName = 'component';\n\n  beforeEach(() => {\n    appTree = new UnitTestTree(Tree.empty());\n    appTree.create(\n      '/tsconfig.json',\n      `\n        {\n          \"include\": [**./*.ts\"]\n        }\n     `\n    );\n    createPackageJson('', pkgName, appTree);\n  });\n\n  [\n    { module: 'LetModule', declarable: 'LetDirective' },\n    {\n      module: 'PushModule',\n      declarable: 'PushPipe',\n    },\n  ].forEach(({ module, declarable }) => {\n    describe(`${module} => ${declarable}`, () => {\n      it(`should replace the ${module} in NgModule with ${declarable}`, async () => {\n        const input = `\n          import { ${module} } from '@ngrx/component';\n\n          @NgModule({\n            imports: [\n              AuthModule,\n              AppRoutingModule,\n              ${module},\n              CoreModule,\n            ],\n            exports: [${module}],\n            bootstrap: [AppComponent]\n          })\n          export class AppModule {}\n        `;\n        const expected = `\n          import { ${declarable} } from '@ngrx/component';\n\n          @NgModule({\n            imports: [\n              AuthModule,\n              AppRoutingModule,\n              ${declarable},\n              CoreModule,\n            ],\n            exports: [${declarable}],\n            bootstrap: [AppComponent]\n          })\n          export class AppModule {}\n        `;\n\n        appTree.create('./app.module.ts', input);\n        const runner = new SchematicTestRunner('schematics', collectionPath);\n\n        const newTree = await runner.runSchematic(\n          `ngrx-${pkgName}-migration-16`,\n          {},\n          appTree\n        );\n        const file = newTree.readContent('app.module.ts');\n\n        expect(file).toBe(expected);\n      });\n\n      it(`should replace the ${module} in standalone component with ${declarable}`, async () => {\n        const input = `\n          import { ${module} } from '@ngrx/component';\n\n          @Component({\n            imports: [\n              AuthModule,\n              ${module}\n            ]\n          })\n          export class SomeStandaloneComponent {}\n        `;\n        const expected = `\n          import { ${declarable} } from '@ngrx/component';\n\n          @Component({\n            imports: [\n              AuthModule,\n              ${declarable}\n            ]\n          })\n          export class SomeStandaloneComponent {}\n        `;\n\n        appTree.create('./app.module.ts', input);\n        const runner = new SchematicTestRunner('schematics', collectionPath);\n\n        const newTree = await runner.runSchematic(\n          `ngrx-${pkgName}-migration-16`,\n          {},\n          appTree\n        );\n        const file = newTree.readContent('app.module.ts');\n\n        expect(file).toBe(expected);\n      });\n\n      it(`should not remove the ${module} JS import when used as a type`, async () => {\n        const input = `\n          import { ${module} } from '@ngrx/component';\n\n          const module: ${module};\n\n          @NgModule({\n            imports: [\n              AuthModule,\n              AppRoutingModule,\n              ${module},\n              CoreModule\n            ],\n            bootstrap: [AppComponent]\n          })\n          export class AppModule {}\n        `;\n        const expected = `\n          import { ${module}, ${declarable} } from '@ngrx/component';\n\n          const module: ${module};\n\n          @NgModule({\n            imports: [\n              AuthModule,\n              AppRoutingModule,\n              ${declarable},\n              CoreModule\n            ],\n            bootstrap: [AppComponent]\n          })\n          export class AppModule {}\n        `;\n\n        appTree.create('./app.module.ts', input);\n        const runner = new SchematicTestRunner('schematics', collectionPath);\n\n        const newTree = await runner.runSchematic(\n          `ngrx-${pkgName}-migration-16`,\n          {},\n          appTree\n        );\n        const file = newTree.readContent('app.module.ts');\n\n        expect(file).toBe(expected);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/component/migrations/16_0_0/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  commitChanges,\n  createReplaceChange,\n  ReplaceChange,\n} from '../../schematics-core';\n\nconst letModuleText = 'LetModule';\nconst letDirectiveText = 'LetDirective';\nconst pushModuleText = 'PushModule';\nconst pushPipeText = 'PushPipe';\nconst moduleLocations = {\n  imports: ['NgModule', 'Component'],\n  exports: ['NgModule'],\n};\n\nfunction migrateToStandaloneAPIs() {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const componentImports = sourceFile.statements\n        .filter(ts.isImportDeclaration)\n        .filter(({ moduleSpecifier }) =>\n          moduleSpecifier.getText(sourceFile).includes('@ngrx/component')\n        );\n\n      if (componentImports.length === 0) {\n        return;\n      }\n\n      const ngModuleReplacements = findNgModuleReplacements(sourceFile);\n      const possibleModulesUsageCount =\n        findPossibleModulesUsageCount(sourceFile);\n      const importAdditionReplacements = findImportDeclarationAdditions(\n        sourceFile,\n        componentImports\n      );\n      const jsImportDeclarationReplacements =\n        possibleModulesUsageCount >\n        ngModuleReplacements.length + importAdditionReplacements.length\n          ? importAdditionReplacements\n          : findImportDeclarationReplacements(sourceFile, componentImports);\n\n      const changes = [\n        ...jsImportDeclarationReplacements,\n        ...ngModuleReplacements,\n      ];\n\n      commitChanges(tree, sourceFile.fileName, changes);\n    });\n  };\n}\n\nfunction findImportDeclarationReplacements(\n  sourceFile: ts.SourceFile,\n  imports: ts.ImportDeclaration[]\n) {\n  return findImportDeclarations(sourceFile, imports)\n    .map(({ specifier, oldText, newText }) =>\n      !!specifier && !!oldText\n        ? createReplaceChange(sourceFile, specifier, oldText, newText)\n        : undefined\n    )\n    .filter((change) => !!change) as Array<ReplaceChange>;\n}\n\nfunction findImportDeclarationAdditions(\n  sourceFile: ts.SourceFile,\n  imports: ts.ImportDeclaration[]\n) {\n  return findImportDeclarations(sourceFile, imports)\n    .map(({ specifier, oldText, newText }) =>\n      !!specifier && !!oldText\n        ? createReplaceChange(\n            sourceFile,\n            specifier,\n            oldText,\n            `${oldText}, ${newText}`\n          )\n        : undefined\n    )\n    .filter((change) => !!change) as Array<ReplaceChange>;\n}\n\nfunction findImportDeclarations(\n  sourceFile: ts.SourceFile,\n  imports: ts.ImportDeclaration[]\n) {\n  return imports\n    .map((p) => (p?.importClause?.namedBindings as ts.NamedImports)?.elements)\n    .reduce(\n      (imports, curr) => imports.concat(curr ?? []),\n      [] as ts.ImportSpecifier[]\n    )\n    .map((specifier) => {\n      if (!ts.isImportSpecifier(specifier)) {\n        return { hit: false };\n      }\n\n      if (specifier.name.text === letModuleText) {\n        return {\n          hit: true,\n          specifier,\n          oldText: specifier.name.text,\n          newText: letDirectiveText,\n        };\n      }\n\n      if (specifier.name.text === pushModuleText) {\n        return {\n          hit: true,\n          specifier,\n          oldText: specifier.name.text,\n          newText: pushPipeText,\n        };\n      }\n\n      // if `LetModule` import is renamed\n      if (specifier.propertyName?.text === letModuleText) {\n        return {\n          hit: true,\n          specifier,\n          oldText: specifier.propertyName.text,\n          newText: letDirectiveText,\n        };\n      }\n\n      // if `PushModule` import is renamed\n      if (specifier.propertyName?.text === pushModuleText) {\n        return {\n          hit: true,\n          specifier,\n          oldText: specifier.propertyName.text,\n          newText: pushPipeText,\n        };\n      }\n\n      return { hit: false };\n    })\n    .filter(({ hit }) => hit);\n}\n\nfunction findPossibleModulesUsageCount(sourceFile: ts.SourceFile): number {\n  let count = 0;\n  ts.forEachChild(sourceFile, (node) => countUsages(node));\n  return count;\n\n  function countUsages(node: ts.Node) {\n    if (\n      ts.isIdentifier(node) &&\n      (node.text === letModuleText || node.text === pushModuleText)\n    ) {\n      count = count + 1;\n    }\n\n    ts.forEachChild(node, (childNode) => countUsages(childNode));\n  }\n}\n\nfunction findNgModuleReplacements(sourceFile: ts.SourceFile) {\n  const changes: ReplaceChange[] = [];\n  ts.forEachChild(sourceFile, (node) => find(node, changes));\n  return changes;\n\n  function find(node: ts.Node, changes: ReplaceChange[]) {\n    let change = undefined;\n\n    if (\n      ts.isIdentifier(node) &&\n      (node.text === letModuleText || node.text === pushModuleText) &&\n      ts.isArrayLiteralExpression(node.parent) &&\n      ts.isPropertyAssignment(node.parent.parent)\n    ) {\n      const property = node.parent.parent;\n      if (ts.isIdentifier(property.name)) {\n        const propertyName = String(property.name.escapedText);\n        if (Object.keys(moduleLocations).includes(propertyName)) {\n          const decorator = property.parent.parent.parent;\n          if (\n            ts.isDecorator(decorator) &&\n            ts.isCallExpression(decorator.expression) &&\n            ts.isIdentifier(decorator.expression.expression) &&\n            moduleLocations[propertyName as 'imports' | 'exports'].includes(\n              String(decorator.expression.expression.escapedText)\n            )\n          ) {\n            change = {\n              node: node,\n              oldText: node.text,\n              newText:\n                node.text === letModuleText ? letDirectiveText : pushPipeText,\n            };\n          }\n        }\n      }\n    }\n\n    if (change) {\n      changes.push(\n        createReplaceChange(\n          sourceFile,\n          change.node,\n          change.oldText,\n          change.newText\n        )\n      );\n    }\n\n    ts.forEachChild(node, (childNode) => find(childNode, changes));\n  }\n}\n\nexport default function (): Rule {\n  return chain([migrateToStandaloneAPIs()]);\n}\n"
  },
  {
    "path": "modules/component/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"ngrx-component-migration-15-beta\": {\n      \"description\": \"As of NgRx v14, `ReactiveComponentModule` is deprecated. It is replaced by `LetModule` and `PushModule`.\",\n      \"version\": \"15.0.0-beta\",\n      \"factory\": \"./15_0_0-beta/index\"\n    },\n    \"ngrx-component-migration-16\": {\n      \"description\": \"As of NgRx v16, `LetModule` and `PushModule` are deprecated in favor of standalone `LetDirective` and `PushPipe`.\",\n      \"version\": \"16.0.0\",\n      \"factory\": \"./16_0_0/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/component/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/component\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/component/package.json",
    "content": "{\n  \"name\": \"@ngrx/component\",\n  \"version\": \"21.0.1\",\n  \"description\": \"Reactive Extensions for Angular Components\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"Angular\",\n    \"RxJS\",\n    \"NgRx\",\n    \"Components\",\n    \"Angular CLI\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"@angular/common\": \"^21.0.0\",\n    \"@angular/core\": \"^21.0.0\",\n    \"rxjs\": \"^6.5.3 || ^7.5.0\"\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"sideEffects\": false,\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"dependencies\": {\n    \"tslib\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "modules/component/project.json",
    "content": "{\n  \"name\": \"component\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/component/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/component/tsconfig.build.json\",\n        \"project\": \"modules/component/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package component\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/component/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/component\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/component\"\n          },\n          {\n            \"command\": \"ncp dist/modules/component node_modules/@ngrx/component\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/component\"\n          }\n        ]\n      },\n      \"outputs\": [\n        \"{workspaceRoot}/dist/modules/component\",\n        \"{workspaceRoot}/node_modules/@ngrx/component\"\n      ]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/component/*/**/*.ts\",\n          \"modules/component/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/component\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/component/public_api.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/component/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Add @ngrx/component to your application\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/component/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as SchemaOptions } from './schema';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('Component ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/component',\n    path.join(\n      process.cwd(),\n      'dist/modules/component/schematics/collection.json'\n    )\n  );\n  const defaultOptions: SchemaOptions = {\n    skipPackageJson: false,\n  };\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/component']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/component']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "modules/component/schematics/ng-add/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  chain,\n  noop,\n} from '@angular-devkit/schematics';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\nimport {\n  addPackageToPackageJson,\n  platformVersion,\n} from '../../schematics-core';\nimport { Schema as SchemaOptions } from './schema';\n\nfunction addModuleToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/component',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nexport default function (options: SchemaOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    return chain([\n      options && options.skipPackageJson ? noop() : addModuleToPackageJson(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/component/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxComponent\",\n  \"title\": \"NgRx Component Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/component as dependency to package.json (e.g., --skipPackageJson).\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/component/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  skipPackageJson?: boolean;\n}\n"
  },
  {
    "path": "modules/component/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/component/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/component/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/component/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/component/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/component/spec/core/potential-observable.spec.ts",
    "content": "import { of } from 'rxjs';\nimport { TestScheduler } from 'rxjs/testing';\nimport { fromPotentialObservable } from '../../src/core/potential-observable';\n\ndescribe('fromPotentialObservable', () => {\n  function setup() {\n    return {\n      testScheduler: new TestScheduler((actual, expected) =>\n        expect(actual).toEqual(expected)\n      ),\n    };\n  }\n\n  function testNonObservableInput(input: any, label = input): void {\n    it(`should create observable from ${label}`, () => {\n      const { testScheduler } = setup();\n\n      testScheduler.run(({ expectObservable }) => {\n        const obs$ = fromPotentialObservable(input);\n        expectObservable(obs$).toBe('x', { x: input });\n      });\n    });\n  }\n\n  testNonObservableInput(null);\n\n  testNonObservableInput(undefined);\n\n  testNonObservableInput(100, 'number');\n\n  testNonObservableInput('ngrx', 'string');\n\n  testNonObservableInput(true, 'boolean');\n\n  testNonObservableInput({}, 'empty object');\n\n  testNonObservableInput(\n    { ngrx: 'component' },\n    'object with non-observable values'\n  );\n\n  testNonObservableInput(\n    { x: of(1), y: 2 },\n    'object with at least one non-observable value'\n  );\n\n  testNonObservableInput([1, 2, 3], 'array');\n\n  it('should create observable from promise', () =>\n    new Promise<void>((done) => {\n      const promise = Promise.resolve(100);\n      const obs$ = fromPotentialObservable(promise);\n\n      // promises cannot be tested with test scheduler\n      obs$.subscribe((value) => {\n        expect(value).toBe(100);\n        done();\n      });\n    }));\n\n  it('should return passed observable', () => {\n    const obs1$ = of('ngrx');\n    const obs2$ = fromPotentialObservable(obs1$);\n    expect(obs1$).toBe(obs2$);\n  });\n\n  it('should combine observables and distinct same values from observable dictionary', () => {\n    const { testScheduler } = setup();\n\n    testScheduler.run(({ cold, expectObservable }) => {\n      const o1$ = cold('o--p-q', { o: 1, p: 2, q: 2 });\n      const o2$ = cold('-xy-z-', { x: 3, y: 3, z: 4 });\n\n      const result$ = fromPotentialObservable({ o1: o1$, o2: o2$ });\n      expectObservable(result$).toBe('-k-lm-', {\n        k: { o1: 1, o2: 3 },\n        l: { o1: 2, o2: 3 },\n        m: { o1: 2, o2: 4 },\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/component/spec/core/render-event/handlers.spec.ts",
    "content": "import { vi } from 'vitest';\nimport { combineRenderEventHandlers } from '../../../src/core/render-event/handlers';\nimport {\n  CompleteRenderEvent,\n  ErrorRenderEvent,\n  NextRenderEvent,\n  RenderEvent,\n  SuspenseRenderEvent,\n} from '../../../src/core/render-event/models';\n\ndescribe('combineRenderEventHandlers', () => {\n  function testRenderEvent<T>(event: RenderEvent<T>): void {\n    describe(`when ${event.type} is emitted`, () => {\n      it(`should call ${event.type} handler`, () => {\n        const mockHandler = vi.fn();\n        const handleRenderEvent = combineRenderEventHandlers({\n          [event.type]: mockHandler,\n        });\n\n        handleRenderEvent(event);\n        expect(mockHandler).toHaveBeenCalledWith(event);\n      });\n\n      it(`should not throw error if ${event.type} handler is not defined`, () => {\n        const handleRenderEvent = combineRenderEventHandlers({});\n        expect(() => handleRenderEvent(event)).not.toThrowError();\n      });\n    });\n  }\n\n  const suspenseEvent: SuspenseRenderEvent = {\n    type: 'suspense',\n    reset: true,\n    synchronous: true,\n  };\n  testRenderEvent(suspenseEvent);\n\n  const nextEvent: NextRenderEvent<number> = {\n    type: 'next',\n    value: 1,\n    reset: true,\n    synchronous: false,\n  };\n  testRenderEvent(nextEvent);\n\n  const errorEvent: ErrorRenderEvent = {\n    type: 'error',\n    error: 'ERROR!',\n    reset: false,\n    synchronous: true,\n  };\n  testRenderEvent(errorEvent);\n\n  const completeEvent: CompleteRenderEvent = {\n    type: 'complete',\n    reset: false,\n    synchronous: false,\n  };\n  testRenderEvent(completeEvent);\n});\n"
  },
  {
    "path": "modules/component/spec/core/render-event/manager.spec.ts",
    "content": "import { vi } from 'vitest';\nimport { fakeAsync, tick } from '@angular/core/testing';\nimport {\n  BehaviorSubject,\n  delay,\n  EMPTY,\n  merge,\n  NEVER,\n  Observable,\n  of,\n  switchMap,\n  throwError,\n  timer,\n} from 'rxjs';\nimport { createRenderEventManager } from '../../../src/core/render-event/manager';\n\ndescribe('createRenderEventManager', () => {\n  function setup<T>() {\n    const suspenseHandler = vi.fn();\n    const nextHandler = vi.fn();\n    const errorHandler = vi.fn();\n    const completeHandler = vi.fn();\n    const renderEventManager = createRenderEventManager<Observable<T>>({\n      suspense: suspenseHandler,\n      next: nextHandler,\n      error: errorHandler,\n      complete: completeHandler,\n    });\n    renderEventManager.handlePotentialObservableChanges().subscribe();\n\n    return {\n      renderEventManager,\n      suspenseHandler,\n      nextHandler,\n      errorHandler,\n      completeHandler,\n    };\n  }\n\n  describe('with first observable', () => {\n    describe('that emits first event synchronously', () => {\n      it('should call next handler with reset flag when first value is emitted', () => {\n        const { renderEventManager, nextHandler } = setup<string>();\n\n        renderEventManager.nextPotentialObservable(of('ngrx'));\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 'ngrx',\n          reset: true,\n          synchronous: true,\n        });\n      });\n\n      it('should call next handler with synchronous and without reset flag when another value is emitted synchronously', () => {\n        const { renderEventManager, nextHandler } = setup<string>();\n\n        renderEventManager.nextPotentialObservable(of('angular', 'ngrx'));\n        expect(nextHandler.mock.calls[0][0]).toEqual({\n          type: 'next',\n          value: 'angular',\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler.mock.calls[1][0]).toEqual({\n          type: 'next',\n          value: 'ngrx',\n          reset: false,\n          synchronous: true,\n        });\n        expect(nextHandler).toHaveBeenCalledTimes(2);\n      });\n\n      it('should call next handler without reset and synchronous flags when another value is emitted asynchronously', () => {\n        const { renderEventManager, nextHandler } = setup<string>();\n        const subject = new BehaviorSubject('ngrx');\n\n        renderEventManager.nextPotentialObservable(subject);\n        subject.next('angular');\n\n        expect(nextHandler.mock.calls[0][0]).toEqual({\n          type: 'next',\n          value: 'ngrx',\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler.mock.calls[1][0]).toEqual({\n          type: 'next',\n          value: 'angular',\n          reset: false,\n          synchronous: false,\n        });\n        expect(nextHandler).toHaveBeenCalledTimes(2);\n      });\n\n      it('should not call next handler second time when same value is emitted twice synchronously', () => {\n        const { renderEventManager, nextHandler } = setup<string>();\n\n        renderEventManager.nextPotentialObservable(\n          of('ngrx', 'component', 'component')\n        );\n        expect(nextHandler.mock.calls[0][0]).toEqual({\n          type: 'next',\n          value: 'ngrx',\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler.mock.calls[1][0]).toEqual({\n          type: 'next',\n          value: 'component',\n          reset: false,\n          synchronous: true,\n        });\n        expect(nextHandler).toHaveBeenCalledTimes(2);\n      });\n\n      it('should not call next handler second time when same value is emitted twice asynchronously', fakeAsync(() => {\n        const { renderEventManager, nextHandler } = setup<string>();\n        const subject = new BehaviorSubject('angular');\n\n        renderEventManager.nextPotentialObservable(subject);\n        tick(10);\n        subject.next('ngrx/component');\n        tick(10);\n        subject.next('ngrx/component');\n\n        expect(nextHandler.mock.calls[0][0]).toEqual({\n          type: 'next',\n          value: 'angular',\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler.mock.calls[1][0]).toEqual({\n          type: 'next',\n          value: 'ngrx/component',\n          reset: false,\n          synchronous: false,\n        });\n        expect(nextHandler).toHaveBeenCalledTimes(2);\n      }));\n\n      it('should not call next handler second time when same value is emitted first synchronously then asynchronously', fakeAsync(() => {\n        const { renderEventManager, nextHandler } = setup<string>();\n\n        renderEventManager.nextPotentialObservable(\n          merge(\n            of('ngrx', 'component'),\n            timer(10).pipe(switchMap(() => of('component')))\n          )\n        );\n        tick(10);\n\n        expect(nextHandler.mock.calls[0][0]).toEqual({\n          type: 'next',\n          value: 'ngrx',\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler.mock.calls[1][0]).toEqual({\n          type: 'next',\n          value: 'component',\n          reset: false,\n          synchronous: true,\n        });\n        expect(nextHandler).toHaveBeenCalledTimes(2);\n      }));\n\n      it('should call error handler with reset and synchronous flags when error is emitted', () => {\n        const { renderEventManager, errorHandler } = setup<number>();\n\n        renderEventManager.nextPotentialObservable(throwError(() => 'ERROR!'));\n        expect(errorHandler).toHaveBeenCalledWith({\n          type: 'error',\n          error: 'ERROR!',\n          reset: true,\n          synchronous: true,\n        });\n      });\n\n      it('should call error handler with synchronous and without reset flag when error is emitted synchronously as second event', () => {\n        const { renderEventManager, errorHandler, nextHandler } =\n          setup<number>();\n\n        renderEventManager.nextPotentialObservable(\n          new Observable<number>((subscriber) => {\n            subscriber.next(1);\n            subscriber.error('ERROR!!!');\n          })\n        );\n\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 1,\n          reset: true,\n          synchronous: true,\n        });\n        expect(errorHandler).toHaveBeenCalledWith({\n          type: 'error',\n          error: 'ERROR!!!',\n          reset: false,\n          synchronous: true,\n        });\n\n        expect(nextHandler).toHaveBeenCalledTimes(1);\n        expect(errorHandler).toHaveBeenCalledTimes(1);\n      });\n\n      it('should call error handler without reset and synchronous flags when error is emitted asynchronously as second event', () => {\n        const { renderEventManager, errorHandler, nextHandler } =\n          setup<number>();\n        const subject = new BehaviorSubject(100);\n\n        renderEventManager.nextPotentialObservable(subject);\n        subject.error('ERROR!');\n\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 100,\n          reset: true,\n          synchronous: true,\n        });\n        expect(errorHandler).toHaveBeenCalledWith({\n          type: 'error',\n          error: 'ERROR!',\n          reset: false,\n          synchronous: false,\n        });\n\n        expect(nextHandler).toHaveBeenCalledTimes(1);\n        expect(errorHandler).toHaveBeenCalledTimes(1);\n      });\n\n      it('should call complete handler with reset and synchronous flags when complete is emitted', () => {\n        const { renderEventManager, completeHandler } = setup<boolean>();\n\n        renderEventManager.nextPotentialObservable(EMPTY);\n        expect(completeHandler).toHaveBeenCalledWith({\n          type: 'complete',\n          reset: true,\n          synchronous: true,\n        });\n      });\n\n      it('should call complete handler with synchronous and without reset flag when complete is emitted synchronously as second event', () => {\n        const { renderEventManager, nextHandler, completeHandler } =\n          setup<number>();\n\n        renderEventManager.nextPotentialObservable(of(100));\n\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 100,\n          reset: true,\n          synchronous: true,\n        });\n        expect(completeHandler).toHaveBeenCalledWith({\n          type: 'complete',\n          reset: false,\n          synchronous: true,\n        });\n\n        expect(nextHandler).toHaveBeenCalledTimes(1);\n        expect(completeHandler).toHaveBeenCalledTimes(1);\n      });\n\n      it('should call complete handler without reset and synchronous flags when complete is emitted asynchronously as second event', () => {\n        const { renderEventManager, nextHandler, completeHandler } =\n          setup<boolean>();\n        const subject = new BehaviorSubject(true);\n\n        renderEventManager.nextPotentialObservable(subject);\n        subject.complete();\n\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: true,\n          reset: true,\n          synchronous: true,\n        });\n        expect(completeHandler).toHaveBeenCalledWith({\n          type: 'complete',\n          reset: false,\n          synchronous: false,\n        });\n\n        expect(nextHandler).toHaveBeenCalledTimes(1);\n        expect(completeHandler).toHaveBeenCalledTimes(1);\n      });\n    });\n\n    describe('that emits first event asynchronously', () => {\n      it('should call suspense handler immediately and next handler when value is emitted', fakeAsync(() => {\n        const { renderEventManager, suspenseHandler, nextHandler } =\n          setup<string>();\n\n        renderEventManager.nextPotentialObservable(of('ngrx').pipe(delay(100)));\n\n        expect(suspenseHandler).toHaveBeenCalledWith({\n          type: 'suspense',\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler).not.toHaveBeenCalled();\n\n        tick(100);\n\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 'ngrx',\n          reset: false,\n          synchronous: false,\n        });\n      }));\n\n      it('should call suspense handler immediately and error handler when error is emitted', fakeAsync(() => {\n        const { renderEventManager, suspenseHandler, errorHandler } =\n          setup<number>();\n\n        renderEventManager.nextPotentialObservable(\n          timer(100).pipe(switchMap(() => throwError(() => 'ERROR!')))\n        );\n\n        expect(suspenseHandler).toHaveBeenCalledWith({\n          type: 'suspense',\n          reset: true,\n          synchronous: true,\n        });\n        expect(errorHandler).not.toHaveBeenCalled();\n\n        tick(100);\n\n        expect(errorHandler).toHaveBeenCalledWith({\n          type: 'error',\n          error: 'ERROR!',\n          reset: false,\n          synchronous: false,\n        });\n      }));\n\n      it('should call suspense handler immediately and complete handler when complete is emitted', fakeAsync(() => {\n        const { renderEventManager, suspenseHandler, completeHandler } =\n          setup<boolean>();\n\n        renderEventManager.nextPotentialObservable(\n          timer(100).pipe(switchMap(() => EMPTY))\n        );\n\n        expect(suspenseHandler).toHaveBeenCalledWith({\n          type: 'suspense',\n          reset: true,\n          synchronous: true,\n        });\n        expect(completeHandler).not.toHaveBeenCalled();\n\n        tick(100);\n\n        expect(completeHandler).toHaveBeenCalledWith({\n          type: 'complete',\n          reset: false,\n          synchronous: false,\n        });\n      }));\n    });\n\n    describe('that never emits event', () => {\n      it('should call suspense handler', () => {\n        const { renderEventManager, suspenseHandler } = setup();\n\n        renderEventManager.nextPotentialObservable(NEVER);\n        expect(suspenseHandler).toHaveBeenCalledWith({\n          type: 'suspense',\n          reset: true,\n          synchronous: true,\n        });\n      });\n    });\n  });\n\n  describe('with next observable', () => {\n    function withNextObservableSetup<T>(firstObservable$: Observable<T>) {\n      const { renderEventManager, ...rest } = setup<T>();\n      renderEventManager.nextPotentialObservable(firstObservable$);\n\n      return { renderEventManager, ...rest };\n    }\n\n    describe('that emits first event synchronously', () => {\n      it('should call next handler with reset and synchronous flags when first value is emitted', () => {\n        const {\n          renderEventManager,\n          nextHandler,\n          errorHandler,\n          completeHandler,\n        } = withNextObservableSetup<number>(throwError(() => 'ERROR!'));\n\n        renderEventManager.nextPotentialObservable(of(100));\n\n        // first observable\n        expect(errorHandler).toHaveBeenCalledWith({\n          type: 'error',\n          error: 'ERROR!',\n          reset: true,\n          synchronous: true,\n        });\n\n        // next observable\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 100,\n          reset: true,\n          synchronous: true,\n        });\n        expect(completeHandler).toHaveBeenCalledWith({\n          type: 'complete',\n          reset: false,\n          synchronous: true,\n        });\n\n        expect(nextHandler).toHaveBeenCalledTimes(1);\n        expect(errorHandler).toHaveBeenCalledTimes(1);\n        expect(completeHandler).toHaveBeenCalledTimes(1);\n      });\n\n      it('should call next handler with synchronous and without reset flag when another value is emitted synchronously', () => {\n        const { renderEventManager, nextHandler } = withNextObservableSetup(\n          new BehaviorSubject(1)\n        );\n\n        renderEventManager.nextPotentialObservable(\n          new Observable((subscriber) => {\n            subscriber.next(10);\n            subscriber.next(100);\n          })\n        );\n\n        // first observable\n        expect(nextHandler.mock.calls[0][0]).toEqual({\n          type: 'next',\n          value: 1,\n          reset: true,\n          synchronous: true,\n        });\n\n        // next observable\n        expect(nextHandler.mock.calls[1][0]).toEqual({\n          type: 'next',\n          value: 10,\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler.mock.calls[2][0]).toEqual({\n          type: 'next',\n          value: 100,\n          reset: false,\n          synchronous: true,\n        });\n\n        expect(nextHandler).toHaveBeenCalledTimes(3);\n      });\n\n      it('should call next handler without reset and synchronous flags when another value is emitted asynchronously', () => {\n        const { renderEventManager, nextHandler } = withNextObservableSetup(\n          new BehaviorSubject(1)\n        );\n        const nextSubject = new BehaviorSubject(10);\n\n        renderEventManager.nextPotentialObservable(nextSubject);\n        nextSubject.next(100);\n\n        // first observable\n        expect(nextHandler.mock.calls[0][0]).toEqual({\n          type: 'next',\n          value: 1,\n          reset: true,\n          synchronous: true,\n        });\n\n        // next observable\n        expect(nextHandler.mock.calls[1][0]).toEqual({\n          type: 'next',\n          value: 10,\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler.mock.calls[2][0]).toEqual({\n          type: 'next',\n          value: 100,\n          reset: false,\n          synchronous: false,\n        });\n\n        expect(nextHandler).toHaveBeenCalledTimes(3);\n      });\n\n      it('should not call next handler second time when same value is emitted twice', () => {\n        const { renderEventManager, nextHandler } = withNextObservableSetup(\n          new BehaviorSubject(100)\n        );\n        const nextSubject = new BehaviorSubject(100);\n\n        renderEventManager.nextPotentialObservable(nextSubject);\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 100,\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler).toHaveBeenCalledTimes(1);\n      });\n\n      it('should call error handler with reset and synchronous flags when error is emitted', () => {\n        const {\n          renderEventManager,\n          nextHandler,\n          errorHandler,\n          completeHandler,\n        } = withNextObservableSetup(of(200));\n\n        renderEventManager.nextPotentialObservable(throwError(() => 'ERROR!'));\n\n        // first observable\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 200,\n          reset: true,\n          synchronous: true,\n        });\n        expect(completeHandler).toHaveBeenCalledWith({\n          type: 'complete',\n          reset: false,\n          synchronous: true,\n        });\n\n        // next observable\n        expect(errorHandler).toHaveBeenCalledWith({\n          type: 'error',\n          error: 'ERROR!',\n          reset: true,\n          synchronous: true,\n        });\n\n        expect(nextHandler).toHaveBeenCalledTimes(1);\n        expect(errorHandler).toHaveBeenCalledTimes(1);\n        expect(completeHandler).toHaveBeenCalledTimes(1);\n      });\n\n      it('should not call error handler second time when same error is emitted twice', () => {\n        const { renderEventManager, errorHandler } = withNextObservableSetup(\n          throwError(() => 'SAME_ERROR!')\n        );\n\n        renderEventManager.nextPotentialObservable(\n          throwError(() => 'SAME_ERROR!')\n        );\n        expect(errorHandler).toHaveBeenCalledWith({\n          type: 'error',\n          error: 'SAME_ERROR!',\n          reset: true,\n          synchronous: true,\n        });\n        expect(errorHandler).toHaveBeenCalledTimes(1);\n      });\n\n      it('should call complete handler with reset flag when complete is emitted', () => {\n        const { renderEventManager, nextHandler, completeHandler } =\n          withNextObservableSetup(of(1));\n\n        renderEventManager.nextPotentialObservable(EMPTY);\n\n        // first observable\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 1,\n          reset: true,\n          synchronous: true,\n        });\n        expect(completeHandler.mock.calls[0][0]).toEqual({\n          type: 'complete',\n          reset: false,\n          synchronous: true,\n        });\n\n        // next observable\n        expect(completeHandler.mock.calls[1][0]).toEqual({\n          type: 'complete',\n          reset: true,\n          synchronous: true,\n        });\n\n        expect(completeHandler).toHaveBeenCalledTimes(2);\n      });\n\n      it('should not call complete handler second time when complete is emitted twice ', () => {\n        const { renderEventManager, completeHandler } = withNextObservableSetup(\n          new Observable((subscriber) => subscriber.complete())\n        );\n\n        renderEventManager.nextPotentialObservable(NEVER);\n        expect(completeHandler).toHaveBeenCalledWith({\n          type: 'complete',\n          reset: true,\n          synchronous: true,\n        });\n        expect(completeHandler).toHaveBeenCalledTimes(1);\n      });\n    });\n\n    describe('that emits first event asynchronously', () => {\n      it('should call suspense handler immediately and next handler when value is emitted', fakeAsync(() => {\n        const { renderEventManager, suspenseHandler, nextHandler } =\n          withNextObservableSetup(of('ngrx'));\n\n        renderEventManager.nextPotentialObservable(\n          of('component').pipe(delay(100))\n        );\n\n        // first observable\n        expect(nextHandler.mock.calls[0][0]).toEqual({\n          type: 'next',\n          value: 'ngrx',\n          reset: true,\n          synchronous: true,\n        });\n\n        // next observable\n        expect(suspenseHandler).toHaveBeenCalledWith({\n          type: 'suspense',\n          reset: true,\n          synchronous: true,\n        });\n        expect(nextHandler.mock.calls[1]).not.toBeDefined();\n\n        tick(100);\n\n        expect(nextHandler.mock.calls[1][0]).toEqual({\n          type: 'next',\n          value: 'component',\n          reset: false,\n          synchronous: false,\n        });\n\n        expect(suspenseHandler).toHaveBeenCalledTimes(1);\n        expect(nextHandler).toHaveBeenCalledTimes(2);\n      }));\n\n      it('should call suspense handler immediately and error handler when error is emitted', fakeAsync(() => {\n        const {\n          renderEventManager,\n          suspenseHandler,\n          nextHandler,\n          errorHandler,\n        } = withNextObservableSetup(new BehaviorSubject('ngrx/component'));\n\n        renderEventManager.nextPotentialObservable(\n          timer(100).pipe(switchMap(() => throwError(() => 'ERROR!')))\n        );\n\n        // first observable\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 'ngrx/component',\n          reset: true,\n          synchronous: true,\n        });\n\n        // next observable\n        expect(suspenseHandler).toHaveBeenCalledWith({\n          type: 'suspense',\n          reset: true,\n          synchronous: true,\n        });\n        expect(errorHandler).not.toHaveBeenCalled();\n\n        tick(100);\n\n        expect(errorHandler).toHaveBeenCalledWith({\n          type: 'error',\n          error: 'ERROR!',\n          reset: false,\n          synchronous: false,\n        });\n\n        expect(suspenseHandler).toHaveBeenCalledTimes(1);\n        expect(nextHandler).toHaveBeenCalledTimes(1);\n        expect(errorHandler).toHaveBeenCalledTimes(1);\n      }));\n\n      it('should call suspense handler immediately and complete handler when complete is emitted', fakeAsync(() => {\n        const {\n          renderEventManager,\n          suspenseHandler,\n          nextHandler,\n          completeHandler,\n        } = withNextObservableSetup(new BehaviorSubject(false));\n\n        renderEventManager.nextPotentialObservable(\n          timer(100).pipe(switchMap(() => EMPTY))\n        );\n\n        // first observable\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: false,\n          reset: true,\n          synchronous: true,\n        });\n\n        // next observable\n        expect(suspenseHandler).toHaveBeenCalledWith({\n          type: 'suspense',\n          reset: true,\n          synchronous: true,\n        });\n        expect(completeHandler).not.toHaveBeenCalled();\n\n        tick(100);\n\n        expect(completeHandler).toHaveBeenCalledWith({\n          type: 'complete',\n          reset: false,\n          synchronous: false,\n        });\n\n        expect(suspenseHandler).toHaveBeenCalledTimes(1);\n        expect(nextHandler).toHaveBeenCalledTimes(1);\n        expect(completeHandler).toHaveBeenCalledTimes(1);\n      }));\n    });\n\n    describe('that never emits event', () => {\n      it('should call suspense handler', () => {\n        const { renderEventManager, suspenseHandler, nextHandler } =\n          withNextObservableSetup(new BehaviorSubject(10));\n\n        renderEventManager.nextPotentialObservable(NEVER);\n        expect(nextHandler).toHaveBeenCalledWith({\n          type: 'next',\n          value: 10,\n          reset: true,\n          synchronous: true,\n        });\n        expect(suspenseHandler).toHaveBeenCalledWith({\n          type: 'suspense',\n          reset: true,\n          synchronous: true,\n        });\n      });\n\n      it('should not call suspense handler second time when first observable never emits', () => {\n        const { renderEventManager, suspenseHandler } = withNextObservableSetup(\n          new Observable()\n        );\n\n        renderEventManager.nextPotentialObservable(new Observable());\n        expect(suspenseHandler).toHaveBeenCalledWith({\n          type: 'suspense',\n          reset: true,\n          synchronous: true,\n        });\n        expect(suspenseHandler).toHaveBeenCalledTimes(1);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/component/spec/core/render-scheduler.spec.ts",
    "content": "import { vi } from 'vitest';\nimport {\n  createRenderScheduler,\n  RenderScheduler,\n} from '../../src/core/render-scheduler';\nimport { NoopTickScheduler } from '../../src/core/tick-scheduler';\nimport { MockChangeDetectorRef } from '../fixtures/fixtures';\nimport { TestBed } from '@angular/core/testing';\nimport { ChangeDetectorRef, Injectable } from '@angular/core';\n\ndescribe('createRenderScheduler', () => {\n  it('should initialize within injection context', () => {\n    @Injectable({ providedIn: 'root' })\n    class Service {\n      readonly renderScheduler = createRenderScheduler();\n    }\n\n    TestBed.configureTestingModule({\n      providers: [\n        { provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },\n      ],\n    });\n\n    const renderScheduler = TestBed.inject(Service).renderScheduler;\n    expect(renderScheduler).toBeInstanceOf(RenderScheduler);\n  });\n\n  it('should throw an error out of injection context', () => {\n    expect(() => createRenderScheduler()).toThrowError();\n  });\n});\n\ndescribe('RenderScheduler', () => {\n  function setup() {\n    const cdRef = new MockChangeDetectorRef();\n    const tickScheduler = new NoopTickScheduler();\n    vi.spyOn(tickScheduler, 'schedule');\n    const renderScheduler = new RenderScheduler(cdRef, tickScheduler);\n\n    return { cdRef, renderScheduler, tickScheduler };\n  }\n\n  describe('schedule', () => {\n    it('should call cdRef.markForCheck and tickScheduler.schedule', () => {\n      const { cdRef, renderScheduler, tickScheduler } = setup();\n      renderScheduler.schedule();\n\n      expect(cdRef.markForCheck).toHaveBeenCalledTimes(1);\n      expect(tickScheduler.schedule).toHaveBeenCalledTimes(1);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/component/spec/core/tick-scheduler.spec.ts",
    "content": "import { vi } from 'vitest';\nimport {\n  fakeAsync,\n  flushMicrotasks,\n  TestBed,\n  tick,\n} from '@angular/core/testing';\nimport { ApplicationRef, NgZone, PLATFORM_ID } from '@angular/core';\nimport {\n  NoopTickScheduler,\n  TickScheduler,\n  ZonelessTickScheduler,\n} from '../../src/core/tick-scheduler';\nimport { ngZoneMock, noopNgZoneMock } from '../fixtures/fixtures';\n\ndescribe('TickScheduler', () => {\n  function setup(ngZone: unknown, isSsrMode = false) {\n    TestBed.configureTestingModule({\n      providers: [\n        { provide: NgZone, useValue: ngZone },\n        {\n          provide: PLATFORM_ID,\n          useValue: isSsrMode ? 'server' : 'browser',\n        },\n      ],\n    });\n    const tickScheduler = TestBed.inject(TickScheduler);\n    const appRef = TestBed.inject(ApplicationRef);\n    vi.spyOn(appRef, 'tick');\n\n    return { tickScheduler, appRef };\n  }\n\n  describe('when NgZone is provided', () => {\n    it('should initialize NoopTickScheduler', () => {\n      const { tickScheduler } = setup(ngZoneMock);\n      expect(tickScheduler instanceof NoopTickScheduler).toBe(true);\n    });\n  });\n\n  describe('when NgZone is not provided and running in browser mode', () => {\n    // `fakeAsync` uses 16ms as `requestAnimationFrame` delay\n    const animationFrameDelay = 16;\n\n    it('should initialize ZonelessTickScheduler', () => {\n      const { tickScheduler } = setup(noopNgZoneMock);\n      expect(tickScheduler instanceof ZonelessTickScheduler).toBe(true);\n    });\n\n    it('should schedule tick using requestAnimationFrame', fakeAsync(() => {\n      const { tickScheduler, appRef } = setup(noopNgZoneMock);\n\n      tickScheduler.schedule();\n\n      expect(appRef.tick).toHaveBeenCalledTimes(0);\n      tick(animationFrameDelay / 2);\n      expect(appRef.tick).toHaveBeenCalledTimes(0);\n      tick(animationFrameDelay / 2);\n      expect(appRef.tick).toHaveBeenCalledTimes(1);\n    }));\n\n    it('should coalesce multiple synchronous schedule calls', fakeAsync(() => {\n      const { tickScheduler, appRef } = setup(noopNgZoneMock);\n\n      tickScheduler.schedule();\n      tickScheduler.schedule();\n      tickScheduler.schedule();\n\n      tick(animationFrameDelay);\n      expect(appRef.tick).toHaveBeenCalledTimes(1);\n    }));\n\n    it('should coalesce multiple schedule calls that are queued to the microtask queue', fakeAsync(() => {\n      const { tickScheduler, appRef } = setup(noopNgZoneMock);\n\n      queueMicrotask(() => tickScheduler.schedule());\n      queueMicrotask(() => tickScheduler.schedule());\n      queueMicrotask(() => tickScheduler.schedule());\n\n      flushMicrotasks();\n      expect(appRef.tick).toHaveBeenCalledTimes(0);\n      tick(animationFrameDelay);\n      expect(appRef.tick).toHaveBeenCalledTimes(1);\n    }));\n\n    it('should schedule multiple ticks for multiple asynchronous schedule calls', fakeAsync(() => {\n      const { tickScheduler, appRef } = setup(noopNgZoneMock);\n\n      setTimeout(() => tickScheduler.schedule(), 100);\n      setTimeout(() => tickScheduler.schedule(), 200);\n      setTimeout(() => tickScheduler.schedule(), 300);\n\n      tick(300 + animationFrameDelay);\n      expect(appRef.tick).toHaveBeenCalledTimes(3);\n    }));\n\n    it('should ensure requestAnimationFrame is not bound to the tick scheduler', () => {\n      const rafSpy = vi.spyOn(window, 'requestAnimationFrame');\n      const { tickScheduler } = setup(noopNgZoneMock);\n\n      tickScheduler.schedule();\n\n      const invocationContext = rafSpy.mock.contexts[0];\n\n      expect(invocationContext).not.toBe(tickScheduler);\n\n      rafSpy.mockRestore();\n    });\n  });\n\n  describe('when NgZone is not provided and running in SSR mode', () => {\n    it('should initialize ZonelessTickScheduler', () => {\n      const { tickScheduler } = setup(noopNgZoneMock, true);\n      expect(tickScheduler instanceof ZonelessTickScheduler).toBe(true);\n    });\n\n    it('should schedule tick using setTimeout', fakeAsync(() => {\n      const { tickScheduler, appRef } = setup(noopNgZoneMock, true);\n\n      tickScheduler.schedule();\n\n      expect(appRef.tick).toHaveBeenCalledTimes(0);\n      tick();\n      expect(appRef.tick).toHaveBeenCalledTimes(1);\n    }));\n\n    it('should coalesce multiple synchronous schedule calls', fakeAsync(() => {\n      const { tickScheduler, appRef } = setup(noopNgZoneMock, true);\n\n      tickScheduler.schedule();\n      tickScheduler.schedule();\n      tickScheduler.schedule();\n\n      tick();\n      expect(appRef.tick).toHaveBeenCalledTimes(1);\n    }));\n\n    it('should coalesce multiple schedule calls that are queued to the microtask queue', fakeAsync(() => {\n      const { tickScheduler, appRef } = setup(noopNgZoneMock, true);\n\n      queueMicrotask(() => tickScheduler.schedule());\n      queueMicrotask(() => tickScheduler.schedule());\n      queueMicrotask(() => tickScheduler.schedule());\n\n      flushMicrotasks();\n      expect(appRef.tick).toHaveBeenCalledTimes(0);\n      tick();\n      expect(appRef.tick).toHaveBeenCalledTimes(1);\n    }));\n\n    it('should schedule multiple ticks for multiple asynchronous schedule calls', fakeAsync(() => {\n      const { tickScheduler, appRef } = setup(noopNgZoneMock, true);\n\n      setTimeout(() => tickScheduler.schedule(), 100);\n      setTimeout(() => tickScheduler.schedule(), 200);\n      setTimeout(() => tickScheduler.schedule(), 300);\n\n      tick(300);\n      expect(appRef.tick).toHaveBeenCalledTimes(3);\n    }));\n  });\n});\n"
  },
  {
    "path": "modules/component/spec/core/zone-helpers.spec.ts",
    "content": "import { isNgZone } from '../../src/core/zone-helpers';\nimport { ngZoneMock, noopNgZoneMock } from '../fixtures/fixtures';\n\ndescribe('isNgZone', () => {\n  it('should return true with NgZone instance', () => {\n    expect(isNgZone(ngZoneMock)).toBe(true);\n  });\n\n  it('should return false with NoopNgZone instance', () => {\n    expect(isNgZone(noopNgZoneMock)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "modules/component/spec/fixtures/fixtures.ts",
    "content": "import { vi } from 'vitest';\nimport { NgZone } from '@angular/core';\nimport { MockNoopNgZone } from './mock-noop-ng-zone';\n\nexport const ngZoneMock = new NgZone({\n  enableLongStackTrace: false,\n  shouldCoalesceEventChangeDetection: false,\n});\nexport const noopNgZoneMock = new MockNoopNgZone({\n  enableLongStackTrace: false,\n  shouldCoalesceEventChangeDetection: false,\n});\n\nexport class MockChangeDetectorRef {\n  markForCheck = vi.fn();\n  detectChanges = vi.fn();\n  checkNoChanges = vi.fn();\n  detach = vi.fn();\n  reattach = vi.fn();\n  context = { x: 1, y: 2 };\n}\n\nexport class MockErrorHandler {\n  handleError = vi.fn();\n}\n"
  },
  {
    "path": "modules/component/spec/fixtures/mock-event-emitter.ts",
    "content": "import { EventEmitter } from '@angular/core';\n\nexport class MockEventEmitter<T> extends EventEmitter<T> {\n  override next(value: any) {}\n  override error(error: any) {}\n  override complete() {}\n  override emit() {}\n}\n"
  },
  {
    "path": "modules/component/spec/fixtures/mock-noop-ng-zone.ts",
    "content": "import { MockEventEmitter } from './mock-event-emitter';\n\n/**\n * source: https://github.com/angular/angular/blob/master/packages/core/src/zone/ng_zone.ts#L88\n */\nexport class MockNoopNgZone {\n  readonly hasPendingMacrotasks: boolean = false;\n  readonly hasPendingMicrotasks: boolean = false;\n  readonly isStable: boolean = true;\n  readonly onUnstable: MockEventEmitter<any> = new MockEventEmitter(false);\n  readonly onMicrotaskEmpty: MockEventEmitter<any> = new MockEventEmitter(\n    false\n  );\n  readonly onStable: MockEventEmitter<any> = new MockEventEmitter(false);\n  readonly onError: MockEventEmitter<any> = new MockEventEmitter(false);\n\n  static isInAngularZone(): boolean {\n    return true;\n  }\n\n  static assertInAngularZone(): void {}\n\n  static assertNotInAngularZone(): void {}\n\n  constructor({\n    enableLongStackTrace = false,\n    shouldCoalesceEventChangeDetection = false,\n  }) {}\n\n  run(fn: Function): any {\n    return fn();\n  }\n\n  runTask<T>(\n    fn: (...args: any[]) => T,\n    applyThis?: any,\n    applyArgs?: any[],\n    name?: string\n  ): T {\n    return {} as any;\n  }\n\n  runGuarded<T>(\n    fn: (...args: any[]) => T,\n    applyThis?: any,\n    applyArgs?: any[]\n  ): T {\n    return {} as any;\n  }\n\n  runOutsideAngular(fn: Function): any {\n    return fn();\n  }\n}\n"
  },
  {
    "path": "modules/component/spec/helpers.ts",
    "content": "export function stripSpaces(str: string): string {\n  return str.replace(/[\\n\\r\\s]+/g, '');\n}\n\nexport function wrapWithSpace(str: string): string {\n  return ' ' + str + ' ';\n}\n"
  },
  {
    "path": "modules/component/spec/let/let.directive.spec.ts",
    "content": "import {\n  ChangeDetectorRef,\n  Component,\n  Directive,\n  ErrorHandler,\n} from '@angular/core';\nimport {\n  ComponentFixture,\n  fakeAsync,\n  flushMicrotasks,\n  TestBed,\n  tick,\n} from '@angular/core/testing';\nimport {\n  BehaviorSubject,\n  delay,\n  EMPTY,\n  interval,\n  NEVER,\n  Observable,\n  of,\n  switchMap,\n  take,\n  throwError,\n  timer,\n} from 'rxjs';\nimport { MockChangeDetectorRef, MockErrorHandler } from '../fixtures/fixtures';\nimport { stripSpaces } from '../helpers';\nimport { JsonPipe } from '@angular/common';\nimport { LetDirective } from '../..';\n\n@Component({\n  template: `\n    <ng-container *ngrxLet=\"value$ as value\">{{\n      value === null ? 'null' : (value | json) || 'undefined'\n    }}</ng-container>\n  `,\n  imports: [JsonPipe, LetDirective],\n})\nclass LetDirectiveTestComponent {\n  value$: unknown;\n}\n\n@Component({\n  template: `\n    <ng-container *ngrxLet=\"value$; error as error\">{{\n      error === undefined ? 'undefined' : error\n    }}</ng-container>\n  `,\n  imports: [LetDirective],\n})\nclass LetDirectiveTestErrorComponent {\n  value$ = of(42);\n}\n\n@Component({\n  template: `\n    <ng-container *ngrxLet=\"value$; complete as complete\">{{\n      complete\n    }}</ng-container>\n  `,\n  imports: [LetDirective],\n})\nclass LetDirectiveTestCompleteComponent {\n  value$ = of(42);\n}\n\n@Component({\n  template: `\n    <ng-container *ngrxLet=\"value$ as value\">{{ value }}</ng-container>\n  `,\n  imports: [LetDirective],\n})\nclass LetDirectiveTestSuspenseComponent {\n  value$ = of(42);\n}\n\n@Component({\n  template: `\n    <ng-container *ngrxLet=\"value$ as value; suspenseTpl: loading\">{{\n      value === undefined ? 'undefined' : value\n    }}</ng-container>\n    <ng-template #loading>Loading...</ng-template>\n  `,\n  imports: [LetDirective],\n})\nclass LetDirectiveTestSuspenseTplComponent {\n  value$ = of(42);\n}\n\n@Directive({\n  selector: '[recursiveDirective]',\n})\nexport class RecursiveDirective {\n  constructor(private subject: BehaviorSubject<number>) {\n    this.subject.next(1);\n  }\n}\n\n@Component({\n  template: `\n    <ng-container recursiveDirective *ngrxLet=\"subject as value\">{{\n      value\n    }}</ng-container>\n  `,\n  imports: [RecursiveDirective, LetDirective],\n})\nclass LetDirectiveTestRecursionComponent {\n  constructor(public subject: BehaviorSubject<number>) {}\n\n  get value$() {\n    return this.subject;\n  }\n}\n\nlet fixtureLetDirectiveTestComponent: ComponentFixture<LetDirectiveTestComponent>;\nlet letDirectiveTestComponent: {\n  value$: unknown;\n};\nlet componentNativeElement: any;\n\nconst setupLetDirectiveTestComponent = (): void => {\n  TestBed.configureTestingModule({\n    providers: [\n      { provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },\n    ],\n  });\n  fixtureLetDirectiveTestComponent = TestBed.createComponent(\n    LetDirectiveTestComponent\n  );\n  letDirectiveTestComponent =\n    fixtureLetDirectiveTestComponent.componentInstance;\n  componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;\n};\n\nconst setupLetDirectiveTestComponentError = (): void => {\n  TestBed.configureTestingModule({\n    providers: [\n      { provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },\n      { provide: ErrorHandler, useClass: MockErrorHandler },\n    ],\n  });\n\n  fixtureLetDirectiveTestComponent = TestBed.createComponent(\n    LetDirectiveTestErrorComponent\n  );\n  letDirectiveTestComponent =\n    fixtureLetDirectiveTestComponent.componentInstance;\n  componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;\n};\n\nconst setupLetDirectiveTestComponentComplete = (): void => {\n  TestBed.configureTestingModule({\n    providers: [\n      { provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },\n    ],\n  });\n\n  fixtureLetDirectiveTestComponent = TestBed.createComponent(\n    LetDirectiveTestCompleteComponent\n  );\n  letDirectiveTestComponent =\n    fixtureLetDirectiveTestComponent.componentInstance;\n  componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;\n};\n\nconst setupLetDirectiveTestComponentSuspense = (): void => {\n  TestBed.configureTestingModule({\n    providers: [\n      { provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },\n      { provide: ErrorHandler, useClass: MockErrorHandler },\n    ],\n  });\n\n  fixtureLetDirectiveTestComponent = TestBed.createComponent(\n    LetDirectiveTestSuspenseComponent\n  );\n  letDirectiveTestComponent =\n    fixtureLetDirectiveTestComponent.componentInstance;\n  componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;\n};\n\nconst setupLetDirectiveTestComponentSuspenseTpl = (): void => {\n  TestBed.configureTestingModule({\n    providers: [\n      { provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },\n      { provide: ErrorHandler, useClass: MockErrorHandler },\n    ],\n  });\n\n  fixtureLetDirectiveTestComponent = TestBed.createComponent(\n    LetDirectiveTestSuspenseTplComponent\n  );\n  letDirectiveTestComponent =\n    fixtureLetDirectiveTestComponent.componentInstance;\n  componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;\n};\n\nconst setupLetDirectiveTestRecursionComponent = (): void => {\n  const subject = new BehaviorSubject(0);\n  TestBed.configureTestingModule({\n    providers: [\n      { provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },\n      { provide: BehaviorSubject, useValue: subject },\n    ],\n  });\n  fixtureLetDirectiveTestComponent = TestBed.createComponent(\n    LetDirectiveTestRecursionComponent\n  );\n  letDirectiveTestComponent =\n    fixtureLetDirectiveTestComponent.componentInstance;\n  componentNativeElement = fixtureLetDirectiveTestComponent.nativeElement;\n};\n\nfunction markAndDetect() {\n  fixtureLetDirectiveTestComponent.componentRef.changeDetectorRef.markForCheck();\n  fixtureLetDirectiveTestComponent.detectChanges();\n}\n\ndescribe('LetDirective', () => {\n  describe('when nexting values', () => {\n    beforeEach(setupLetDirectiveTestComponent);\n\n    it('should be instantiable', () => {\n      expect(fixtureLetDirectiveTestComponent).toBeDefined();\n      expect(letDirectiveTestComponent).toBeDefined();\n      expect(componentNativeElement).toBeDefined();\n    });\n\n    it('should render undefined as value when initially undefined was passed (as no value ever was emitted)', () => {\n      letDirectiveTestComponent.value$ = undefined;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('undefined');\n    });\n\n    it('should render null as value when initially null was passed (as no value ever was emitted)', () => {\n      letDirectiveTestComponent.value$ = null;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('null');\n    });\n\n    it('should render initially passed number', () => {\n      letDirectiveTestComponent.value$ = 10;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('10');\n    });\n\n    it('should render initially passed string', () => {\n      letDirectiveTestComponent.value$ = 'ngrx';\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('\"ngrx\"');\n    });\n\n    it('should render initially passed boolean', () => {\n      letDirectiveTestComponent.value$ = true;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('true');\n    });\n\n    it('should render initially passed empty object', () => {\n      letDirectiveTestComponent.value$ = {};\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(stripSpaces(componentNativeElement.textContent)).toBe('{}');\n    });\n\n    it('should render initially passed object with non-observable values', () => {\n      letDirectiveTestComponent.value$ = { ngrx: 'component' };\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(stripSpaces(componentNativeElement.textContent)).toBe(\n        '{\"ngrx\":\"component\"}'\n      );\n    });\n\n    it('should render initially passed object with at least one non-observable value', () => {\n      letDirectiveTestComponent.value$ = { ngrx: 'component', o$: of(1) };\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(stripSpaces(componentNativeElement.textContent)).toBe(\n        '{\"ngrx\":\"component\",\"o$\":{}}'\n      );\n    });\n\n    it('should render initially passed array', () => {\n      letDirectiveTestComponent.value$ = [1, 2, 3];\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(stripSpaces(componentNativeElement.textContent)).toBe('[1,2,3]');\n    });\n\n    it('should render undefined as value when initially of(undefined) was passed (as undefined was emitted)', () => {\n      letDirectiveTestComponent.value$ = of(undefined);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('undefined');\n    });\n\n    it('should render null as value when initially of(null) was passed (as null was emitted)', () => {\n      letDirectiveTestComponent.value$ = of(null);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('null');\n    });\n\n    it('should render undefined as value when initially EMPTY was passed (as no value ever was emitted)', () => {\n      letDirectiveTestComponent.value$ = EMPTY;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('undefined');\n    });\n\n    it('should render nothing as value when initially NEVER was passed (as no value ever was emitted)', () => {\n      letDirectiveTestComponent.value$ = NEVER;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('');\n    });\n\n    it('should render emitted value from passed observable without changing it', () => {\n      letDirectiveTestComponent.value$ = of(42);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('42');\n    });\n\n    it('should render emitted value from passed promise without changing it', fakeAsync(() => {\n      letDirectiveTestComponent.value$ = Promise.resolve(42);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      flushMicrotasks();\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('42');\n    }));\n\n    it('should clear the view when a new observable NEVER was passed (as no value ever was emitted from new observable)', () => {\n      letDirectiveTestComponent.value$ = of(42);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('42');\n      letDirectiveTestComponent.value$ = NEVER;\n      markAndDetect();\n      expect(componentNativeElement.textContent).toBe('');\n    });\n\n    it('should render new value when a new observable was passed', () => {\n      letDirectiveTestComponent.value$ = of(42);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('42');\n      letDirectiveTestComponent.value$ = of(45);\n      markAndDetect();\n      expect(componentNativeElement.textContent).toBe('45');\n    });\n\n    it('should render the last value when a new observable was passed', () => {\n      letDirectiveTestComponent.value$ = of(42, 45);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('45');\n    });\n\n    it('should render values over time when a new observable was passed', fakeAsync(() => {\n      letDirectiveTestComponent.value$ = interval(1000).pipe(take(3));\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('');\n      tick(1000);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('0');\n      tick(1000);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('1');\n      tick(1000);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('2');\n\n      tick(1000);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      // Remains at 2, since that was the last value.\n      expect(componentNativeElement.textContent).toBe('2');\n    }));\n\n    it('should render non-observable value when it was passed after observable', () => {\n      letDirectiveTestComponent.value$ = of(100);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('100');\n      letDirectiveTestComponent.value$ = 200;\n      markAndDetect();\n      expect(componentNativeElement.textContent).toBe('200');\n    });\n\n    it('should render non-observable value when it was passed after another non-observable', () => {\n      letDirectiveTestComponent.value$ = 'ngrx';\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('\"ngrx\"');\n      letDirectiveTestComponent.value$ = 'component';\n      markAndDetect();\n      expect(componentNativeElement.textContent).toBe('\"component\"');\n    });\n  });\n\n  describe('when error', () => {\n    beforeEach(setupLetDirectiveTestComponentError);\n\n    it('should render undefined when next event is emitted', () => {\n      letDirectiveTestComponent.value$ = new BehaviorSubject(1);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('undefined');\n    });\n\n    it('should render undefined when complete event is emitted', () => {\n      letDirectiveTestComponent.value$ = EMPTY;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('undefined');\n    });\n\n    it('should render error when error event is emitted', () => {\n      letDirectiveTestComponent.value$ = throwError(() => 'error message');\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('error message');\n    });\n\n    it('should call error handler when error event is emitted', () => {\n      const errorHandler = TestBed.inject(ErrorHandler);\n      const error = new Error('ERROR');\n      letDirectiveTestComponent.value$ = throwError(() => error);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(errorHandler.handleError).toHaveBeenCalledWith(error);\n    });\n  });\n\n  describe('when complete', () => {\n    beforeEach(setupLetDirectiveTestComponentComplete);\n\n    it('should render true if completed', () => {\n      letDirectiveTestComponent.value$ = EMPTY;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('true');\n    });\n  });\n\n  describe('when suspense', () => {\n    beforeEach(setupLetDirectiveTestComponentSuspense);\n\n    it('should not render when first observable is in suspense state', fakeAsync(() => {\n      letDirectiveTestComponent.value$ = of(true).pipe(delay(1000));\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('');\n      tick(1000);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('true');\n    }));\n\n    it('should clear the view when next observable is in suspense state', fakeAsync(() => {\n      letDirectiveTestComponent.value$ = of(true);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      letDirectiveTestComponent.value$ = of(false).pipe(delay(1000));\n      markAndDetect();\n      expect(componentNativeElement.textContent).toBe('');\n      tick(1000);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('false');\n    }));\n  });\n\n  describe('when suspense template is passed', () => {\n    beforeEach(setupLetDirectiveTestComponentSuspenseTpl);\n\n    it('should render main template when observable emits next event', () => {\n      letDirectiveTestComponent.value$ = new BehaviorSubject('ngrx');\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('ngrx');\n    });\n\n    it('should render main template when observable emits error event', () => {\n      letDirectiveTestComponent.value$ = throwError(() => 'ERROR!');\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('undefined');\n    });\n\n    it('should render main template when observable emits complete event', () => {\n      letDirectiveTestComponent.value$ = EMPTY;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('undefined');\n    });\n\n    it('should render suspense template when observable does not emit', () => {\n      letDirectiveTestComponent.value$ = NEVER;\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('Loading...');\n    });\n\n    it('should render suspense template when initial observable is in suspense state', fakeAsync(() => {\n      letDirectiveTestComponent.value$ = of('component').pipe(delay(100));\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('Loading...');\n      tick(100);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('component');\n    }));\n\n    it('should render suspense template when next observable is in suspense state', fakeAsync(() => {\n      letDirectiveTestComponent.value$ = new BehaviorSubject('ngrx');\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('ngrx');\n      letDirectiveTestComponent.value$ = timer(100).pipe(\n        switchMap(() => throwError(() => 'ERROR!'))\n      );\n      markAndDetect();\n      expect(componentNativeElement.textContent).toBe('Loading...');\n      tick(100);\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(componentNativeElement.textContent).toBe('undefined');\n    }));\n  });\n\n  describe('when rendering recursively', () => {\n    beforeEach(setupLetDirectiveTestRecursionComponent);\n\n    it('should render 2nd emitted value if the observable emits while the view is being rendered', fakeAsync(() => {\n      fixtureLetDirectiveTestComponent.detectChanges();\n      expect(letDirectiveTestComponent).toBeDefined();\n      expect(componentNativeElement.textContent).toBe('1');\n    }));\n  });\n\n  describe('with observable dictionary', () => {\n    function withObservableDictionarySetup<\n      O1 extends Observable<unknown>,\n      O2 extends Observable<unknown>,\n    >(config: { o1$: O1; o2$: O2 }) {\n      @Component({\n        template: `\n          <ng-container *ngrxLet=\"{ o1: o1$, o2: o2$ } as vm\">{{\n            vm.o1 + '-' + vm.o2\n          }}</ng-container>\n        `,\n        imports: [LetDirective],\n      })\n      class LetDirectiveTestComponent {\n        o1$ = config.o1$;\n        o2$ = config.o2$;\n      }\n\n      TestBed.configureTestingModule({\n        providers: [\n          { provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },\n          { provide: ErrorHandler, useClass: MockErrorHandler },\n        ],\n      });\n\n      const fixture = TestBed.createComponent(LetDirectiveTestComponent);\n\n      return {\n        fixture,\n        nativeElement: fixture.nativeElement,\n      };\n    }\n\n    it('should not create embedded view until all observables from dictionary emit first value', fakeAsync(() => {\n      const { fixture, nativeElement } = withObservableDictionarySetup({\n        o1$: of(1).pipe(delay(10)),\n        o2$: of(2).pipe(delay(20)),\n      });\n\n      fixture.detectChanges();\n      expect(nativeElement.textContent).toBe('');\n\n      tick(10);\n      fixture.detectChanges();\n      expect(nativeElement.textContent).toBe('');\n\n      tick(20);\n      fixture.detectChanges();\n      expect(nativeElement.textContent).toBe('1-2');\n    }));\n\n    it('should update embedded view when any observable from dictionary emits value', () => {\n      const o1$ = new BehaviorSubject(1);\n      const o2$ = new BehaviorSubject(2);\n      const { fixture, nativeElement } = withObservableDictionarySetup({\n        o1$,\n        o2$,\n      });\n\n      fixture.detectChanges();\n      expect(nativeElement.textContent).toBe('1-2');\n\n      o1$.next(10);\n      fixture.detectChanges();\n      expect(nativeElement.textContent).toBe('10-2');\n\n      o2$.next(20);\n      fixture.detectChanges();\n      expect(nativeElement.textContent).toBe('10-20');\n\n      o1$.next(100);\n      o2$.next(200);\n      fixture.detectChanges();\n      expect(nativeElement.textContent).toBe('100-200');\n    });\n  });\n});\n"
  },
  {
    "path": "modules/component/spec/push/push.pipe.spec.ts",
    "content": "import {\n  ChangeDetectorRef,\n  Component,\n  ErrorHandler,\n  computed,\n  signal,\n} from '@angular/core';\nimport {\n  ComponentFixture,\n  fakeAsync,\n  flushMicrotasks,\n  TestBed,\n  waitForAsync,\n} from '@angular/core/testing';\nimport {\n  BehaviorSubject,\n  delay,\n  EMPTY,\n  NEVER,\n  Observable,\n  of,\n  throwError,\n} from 'rxjs';\nimport { PushPipe } from '../../src/push/push.pipe';\nimport { MockChangeDetectorRef, MockErrorHandler } from '../fixtures/fixtures';\nimport { stripSpaces, wrapWithSpace } from '../helpers';\nimport { JsonPipe } from '@angular/common';\n\nlet pushPipe: PushPipe;\n\n@Component({\n  template: ` {{ (value$ | ngrxPush | json) || 'undefined' }} `,\n  imports: [PushPipe, JsonPipe],\n})\nclass PushPipeTestComponent {\n  value$: unknown = of(42);\n}\n\nlet fixturePushPipeTestComponent: ComponentFixture<PushPipeTestComponent>;\nlet pushPipeTestComponent: {\n  value$: unknown;\n};\nlet componentNativeElement: any;\n\nconst setupPushPipeComponent = () => {\n  TestBed.configureTestingModule({\n    providers: [\n      PushPipe,\n      { provide: ChangeDetectorRef, useClass: MockChangeDetectorRef },\n      { provide: ErrorHandler, useClass: MockErrorHandler },\n    ],\n  });\n  pushPipe = TestBed.inject(PushPipe);\n};\n\nfunction markAndDetect() {\n  fixturePushPipeTestComponent.componentRef.changeDetectorRef.markForCheck();\n  fixturePushPipeTestComponent.detectChanges();\n}\n\ndescribe('PushPipe', () => {\n  describe('used as a Service', () => {\n    beforeEach(waitForAsync(setupPushPipeComponent));\n\n    it('should be instantiable', () => {\n      expect(pushPipe).toBeDefined();\n    });\n\n    describe('transform function', () => {\n      it('should return undefined as value when initially undefined was passed (as no value ever was emitted)', () => {\n        expect(pushPipe.transform(undefined)).toBe(undefined);\n      });\n\n      it('should return null as value when initially null was passed (as no value ever was emitted)', () => {\n        expect(pushPipe.transform(null)).toBe(null);\n      });\n\n      it('should return initially passed number', () => {\n        expect(pushPipe.transform(10)).toBe(10);\n      });\n\n      it('should return initially passed string', () => {\n        expect(pushPipe.transform('ngrx')).toBe('ngrx');\n      });\n\n      it('should return initially passed boolean', () => {\n        expect(pushPipe.transform(true)).toBe(true);\n      });\n\n      it('should return initially passed empty object', () => {\n        const obj = {};\n        expect(pushPipe.transform(obj)).toBe(obj);\n      });\n\n      it('should return initially passed object with non-observable values', () => {\n        const obj = { ngrx: 'component' };\n        expect(pushPipe.transform(obj)).toBe(obj);\n      });\n\n      it('should return initially passed object with at least one non-observable value', () => {\n        const obj = { ngrx: 'component', obs$: of(10) };\n        expect(pushPipe.transform(obj)).toBe(obj);\n      });\n\n      it('should return initially passed array', () => {\n        const arr = [1, 2, 3];\n        expect(pushPipe.transform(arr)).toBe(arr);\n      });\n\n      it('should return undefined as value when initially of(undefined) was passed (as undefined was emitted)', () => {\n        expect(pushPipe.transform(of(undefined))).toBe(undefined);\n      });\n\n      it('should return null as value when initially of(null) was passed (as null was emitted)', () => {\n        expect(pushPipe.transform(of(null))).toBe(null);\n      });\n\n      it('should return undefined as value when initially EMPTY was passed (as no value ever was emitted)', () => {\n        expect(pushPipe.transform(EMPTY)).toBe(undefined);\n      });\n\n      it('should return undefined as value when initially NEVER was passed (as no value ever was emitted)', () => {\n        expect(pushPipe.transform(NEVER)).toBe(undefined);\n      });\n\n      it('should return undefined as value when error observable is initially passed', () => {\n        expect(pushPipe.transform(throwError(() => 'ERROR!'))).toBe(undefined);\n      });\n\n      it('should call error handler when error observable is passed', () => {\n        const errorHandler = TestBed.inject(ErrorHandler);\n        pushPipe.transform(throwError(() => 'ERROR!'));\n        expect(errorHandler.handleError).toHaveBeenCalledWith('ERROR!');\n      });\n\n      it('should return emitted value from passed observable without changing it', () => {\n        expect(pushPipe.transform(of(42))).toBe(42);\n      });\n\n      it('should return emitted value from passed promise without changing it', () =>\n        new Promise<void>((done) => {\n          const promise = Promise.resolve(42);\n          pushPipe.transform(promise);\n          setTimeout(() => {\n            expect(pushPipe.transform(promise)).toBe(42);\n            done();\n          });\n        }));\n\n      it('should return undefined when any observable from dictionary emits first value asynchronously', () => {\n        const result = pushPipe.transform({\n          o1$: of(1).pipe(delay(1)),\n          o2$: of(2),\n        });\n        expect(result).toBe(undefined);\n      });\n\n      it('should return emitted values from observables that are passed as dictionary and emit first values synchronously', () => {\n        const result = pushPipe.transform({ o1: of(10), o2: of(20) });\n        expect(result).toEqual({ o1: 10, o2: 20 });\n      });\n\n      it('should return undefined as value when a new observable NEVER was passed (as no value ever was emitted from new observable)', () => {\n        expect(pushPipe.transform(of(42))).toBe(42);\n        expect(pushPipe.transform(NEVER)).toBe(undefined);\n      });\n\n      it('should return undefined as value when new error observable is passed', () => {\n        expect(pushPipe.transform(of(10))).toBe(10);\n        expect(pushPipe.transform(throwError(() => 'ERROR!'))).toBe(undefined);\n      });\n\n      it('should return non-observable value when it was passed after observable', () => {\n        expect(pushPipe.transform(of('ngrx'))).toBe('ngrx');\n        expect(pushPipe.transform('component')).toBe('component');\n      });\n\n      it('should return non-observable value when it was passed after another non-observable', () => {\n        expect(pushPipe.transform(100)).toBe(100);\n        expect(pushPipe.transform('ngrx')).toBe('ngrx');\n      });\n\n      it('should not track signal reads in subscriptions', () => {\n        const trigger = signal(false);\n\n        const obs = new Observable(() => {\n          // Whenever `obs` is subscribed, synchronously read `trigger`.\n          trigger();\n        });\n\n        let trackCount = 0;\n        const tracker = computed(() => {\n          // Subscribe to `obs` within this `computed`. If the subscription side effect runs\n          // within the computed, then changes to `trigger` will invalidate this computed.\n          pushPipe.transform(obs);\n\n          // The computed returns how many times it's run.\n          return ++trackCount;\n        });\n\n        expect(tracker()).toBe(1);\n        trigger.set(true);\n        expect(tracker()).toBe(1);\n      });\n    });\n  });\n\n  describe('used as a Pipe', () => {\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        providers: [{ provide: ErrorHandler, useClass: MockErrorHandler }],\n      });\n\n      fixturePushPipeTestComponent = TestBed.createComponent(\n        PushPipeTestComponent\n      );\n      pushPipeTestComponent = fixturePushPipeTestComponent.componentInstance;\n      componentNativeElement = fixturePushPipeTestComponent.nativeElement;\n    });\n\n    it('should be instantiable', () => {\n      expect(fixturePushPipeTestComponent).toBeDefined();\n      expect(pushPipeTestComponent).toBeDefined();\n      expect(componentNativeElement).toBeDefined();\n    });\n\n    describe('transform function', () => {\n      it('should render undefined as value when initially undefined was passed (as no value ever was emitted)', () => {\n        pushPipeTestComponent.value$ = undefined;\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('undefined')\n        );\n      });\n\n      it('should render null as value when initially null was passed (as no value ever was emitted)', () => {\n        pushPipeTestComponent.value$ = null;\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('null'));\n      });\n\n      it('should render initially passed number', () => {\n        pushPipeTestComponent.value$ = 1000;\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('1000'));\n      });\n\n      it('should render initially passed string', () => {\n        pushPipeTestComponent.value$ = 'ngrx';\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('\"ngrx\"')\n        );\n      });\n\n      it('should render initially passed boolean', () => {\n        pushPipeTestComponent.value$ = true;\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('true'));\n      });\n\n      it('should render initially passed empty object', () => {\n        pushPipeTestComponent.value$ = {};\n        fixturePushPipeTestComponent.detectChanges();\n        expect(stripSpaces(componentNativeElement.textContent)).toBe('{}');\n      });\n\n      it('should render initially passed object with non-observable values', () => {\n        pushPipeTestComponent.value$ = { ngrx: 'component' };\n        fixturePushPipeTestComponent.detectChanges();\n        expect(stripSpaces(componentNativeElement.textContent)).toBe(\n          '{\"ngrx\":\"component\"}'\n        );\n      });\n\n      it('should render initially passed object with at least one non-observable value', () => {\n        pushPipeTestComponent.value$ = { ngrx: 'component', o: of('ngrx') };\n        fixturePushPipeTestComponent.detectChanges();\n        expect(stripSpaces(componentNativeElement.textContent)).toBe(\n          '{\"ngrx\":\"component\",\"o\":{}}'\n        );\n      });\n\n      it('should render initially passed array', () => {\n        pushPipeTestComponent.value$ = [1, 2, 3];\n        fixturePushPipeTestComponent.detectChanges();\n        expect(stripSpaces(componentNativeElement.textContent)).toBe('[1,2,3]');\n      });\n\n      it('should render undefined as value when initially of(undefined) was passed (as undefined was emitted)', () => {\n        pushPipeTestComponent.value$ = of(undefined);\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('undefined')\n        );\n      });\n\n      it('should render null as value when initially of(null) was passed (as null was emitted)', () => {\n        pushPipeTestComponent.value$ = of(null);\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('null'));\n      });\n\n      it('should render undefined as value when initially EMPTY was passed (as no value ever was emitted)', () => {\n        pushPipeTestComponent.value$ = EMPTY;\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('undefined')\n        );\n      });\n\n      it('should render undefined as value when initially NEVER was passed (as no value ever was emitted)', () => {\n        pushPipeTestComponent.value$ = NEVER;\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('undefined')\n        );\n      });\n\n      it('should render undefined when error observable is initially passed', () => {\n        pushPipeTestComponent.value$ = throwError(() => 'ERROR!');\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('undefined')\n        );\n      });\n\n      it('should call error handler when error observable is passed', () => {\n        const errorHandler = TestBed.inject(ErrorHandler);\n        pushPipeTestComponent.value$ = throwError(() => 'ERROR!');\n        fixturePushPipeTestComponent.detectChanges();\n        expect(errorHandler.handleError).toHaveBeenCalledWith('ERROR!');\n      });\n\n      it('should render emitted value from passed observable without changing it', () => {\n        pushPipeTestComponent.value$ = of(42);\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('42'));\n      });\n\n      it('should render emitted value from passed promise without changing it', fakeAsync(() => {\n        pushPipeTestComponent.value$ = Promise.resolve(42);\n        fixturePushPipeTestComponent.detectChanges();\n        flushMicrotasks();\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('42'));\n      }));\n\n      it('should render undefined initially when any observable from dictionary emits first value asynchronously', () => {\n        pushPipeTestComponent.value$ = {\n          o1: of(100),\n          o2: of(200).pipe(delay(1)),\n        };\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('undefined')\n        );\n      });\n\n      it('should render emitted values from observables that are passed as dictionary', () => {\n        const o1 = new BehaviorSubject('ng');\n        const o2 = new BehaviorSubject('rx');\n        pushPipeTestComponent.value$ = { o1, o2 };\n        fixturePushPipeTestComponent.detectChanges();\n        expect(stripSpaces(componentNativeElement.textContent)).toBe(\n          '{\"o1\":\"ng\",\"o2\":\"rx\"}'\n        );\n\n        o1.next('ngrx');\n        fixturePushPipeTestComponent.detectChanges();\n        expect(stripSpaces(componentNativeElement.textContent)).toBe(\n          '{\"o1\":\"ngrx\",\"o2\":\"rx\"}'\n        );\n\n        o2.next('component');\n        fixturePushPipeTestComponent.detectChanges();\n        expect(stripSpaces(componentNativeElement.textContent)).toBe(\n          '{\"o1\":\"ngrx\",\"o2\":\"component\"}'\n        );\n      });\n\n      it('should render undefined as value when a new observable NEVER was passed (as no value ever was emitted from new observable)', () => {\n        pushPipeTestComponent.value$ = of(42);\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('42'));\n        pushPipeTestComponent.value$ = NEVER;\n        markAndDetect();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('undefined')\n        );\n      });\n\n      it('should render emitted value when error event is emitted after next event', () => {\n        const subject = new BehaviorSubject(1000);\n        pushPipeTestComponent.value$ = subject;\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('1000'));\n        subject.error('ERROR!');\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('1000'));\n      });\n\n      it('should render undefined when new error observable is passed', () => {\n        pushPipeTestComponent.value$ = of(10);\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('10'));\n        pushPipeTestComponent.value$ = throwError(() => 'ERROR!');\n        markAndDetect();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('undefined')\n        );\n      });\n\n      it('should render non-observable value when it was passed after observable', () => {\n        pushPipeTestComponent.value$ = of('ngrx');\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('\"ngrx\"')\n        );\n        pushPipeTestComponent.value$ = 'component';\n        markAndDetect();\n        expect(componentNativeElement.textContent).toBe(\n          wrapWithSpace('\"component\"')\n        );\n      });\n\n      it('should render non-observable value when it was passed after another non-observable', () => {\n        pushPipeTestComponent.value$ = 10;\n        fixturePushPipeTestComponent.detectChanges();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('10'));\n        pushPipeTestComponent.value$ = 100;\n        markAndDetect();\n        expect(componentNativeElement.textContent).toBe(wrapWithSpace('100'));\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/component/spec/types/let.directive.types.spec.ts",
    "content": "import { potentialObservableExpecter } from './utils';\n\ndescribe('LetDirective', () => {\n  const expectPotentialObservable = potentialObservableExpecter(\n    (potentialObservableType) => `\n      import { Observable } from 'rxjs';\n      import { LetDirective } from '@ngrx/component';\n\n      const directive = {} as LetDirective<${potentialObservableType}>;\n      const ctx = {};\n\n      if (LetDirective.ngTemplateContextGuard(directive, ctx)) {\n        const value = ctx.$implicit;\n      }\n    `\n  );\n\n  it('should infer the value when potential observable is a non-observable', () => {\n    expectPotentialObservable('number').toBeInferredAs('number');\n    expectPotentialObservable('null').toBeInferredAs('null');\n    expectPotentialObservable('string[]').toBeInferredAs('string[]');\n    expectPotentialObservable('{}').toBeInferredAs('{}');\n    expectPotentialObservable('{ ngrx: boolean; }').toBeInferredAs(\n      '{ ngrx: boolean; }'\n    );\n    expectPotentialObservable(\n      'User',\n      'interface User { name: string; }'\n    ).toBeInferredAs('User');\n  }, 30_000);\n\n  it('should infer the value when potential observable is a union of non-observables', () => {\n    expectPotentialObservable(\n      'string | { ngrx: boolean; } | null'\n    ).toBeInferredAs('string | { ngrx: boolean; } | null');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a promise', () => {\n    expectPotentialObservable('Promise<{ ngrx: number; }>').toBeInferredAs(\n      '{ ngrx: number; }'\n    );\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of promises', () => {\n    expectPotentialObservable(\n      'Promise<string> | Promise<boolean | { ngrx: number; }>'\n    ).toBeInferredAs('string | boolean | { ngrx: number; }');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of promise and non-observable', () => {\n    expectPotentialObservable(\n      'Promise<{ ngrx: string; }> | number[] | null'\n    ).toBeInferredAs('{ ngrx: string; } | number[] | null');\n  }, 8_000);\n\n  it('should infer the value when potential observable is an observable', () => {\n    expectPotentialObservable(\n      'Observable<{ component: boolean; }>'\n    ).toBeInferredAs('{ component: boolean; }');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of observables', () => {\n    expectPotentialObservable(\n      'Observable<string> | Observable<{ ngrx: number; } | null>'\n    ).toBeInferredAs('string | { ngrx: number; } | null');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of observable and promise', () => {\n    expectPotentialObservable(\n      'Observable<{ component: number; }> | Promise<string | undefined>'\n    ).toBeInferredAs('string | { component: number; } | undefined');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of observable and non-observable', () => {\n    expectPotentialObservable(\n      'Observable<Record<string, unknown>> | boolean[] | undefined'\n    ).toBeInferredAs('Record<string, unknown> | boolean[] | undefined');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of observable, promise, and non-observable', () => {\n    expectPotentialObservable(\n      'Observable<number> | Promise<{ ngrx: string; }> | boolean | null'\n    ).toBeInferredAs('number | boolean | { ngrx: string; } | null');\n  }, 8_000);\n\n  it('should infer the value when potential observable is an observable dictionary', () => {\n    expectPotentialObservable(\n      '{ o1: Observable<number>; o2: Observable<{ ngrx: string }> }'\n    ).toBeInferredAs('{ o1: number; o2: { ngrx: string; }; }');\n  }, 8_000);\n\n  it('should infer the value when potential observable is an observable dictionary typed as interface', () => {\n    expectPotentialObservable(\n      'Dictionary',\n      'interface Dictionary { x: Observable<string>; y: Observable<boolean | undefined> }'\n    ).toBeInferredAs('{ x: string; y: boolean | undefined; }');\n  }, 8_000);\n\n  it('should infer the value as static when potential observable is a dictionary with at least one non-observable property', () => {\n    expectPotentialObservable(\n      '{ o: Observable<bigint>; n: number }'\n    ).toBeInferredAs('{ o: Observable<bigint>; n: number; }');\n    expectPotentialObservable(\n      'Dictionary',\n      'interface Dictionary { o: Observable<number>; p: Promise<string> }'\n    ).toBeInferredAs('Dictionary');\n  }, 8_000);\n\n  it('should infer the value as static when potential observable is an observable dictionary with optional properties', () => {\n    expectPotentialObservable(\n      '{ o1: Observable<boolean>; o2?: Observable<string> }'\n    ).toBeInferredAs(\n      '{ o1: Observable<boolean>; o2?: Observable<string> | undefined; }'\n    );\n    expectPotentialObservable(\n      'Dictionary',\n      'interface Dictionary { o1: Observable<number>; o2?: Observable<bigint> }'\n    ).toBeInferredAs('Dictionary');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of observable dictionary and non-observable', () => {\n    expectPotentialObservable(\n      '{ o: Observable<string> } | { ngrx: string }'\n    ).toBeInferredAs('{ ngrx: string; } | { o: string; }');\n    expectPotentialObservable(\n      'Dictionary | number',\n      'interface Dictionary { o: Observable<number> }'\n    ).toBeInferredAs('number | { o: number; }');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of observable dictionary and promise', () => {\n    expectPotentialObservable(\n      '{ o: Observable<symbol> } | Promise<{ ngrx: string }>'\n    ).toBeInferredAs('{ ngrx: string; } | { o: symbol; }');\n    expectPotentialObservable(\n      'Dictionary | Promise<number>',\n      'interface Dictionary { o: Observable<number> }'\n    ).toBeInferredAs('number | { o: number; }');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of observable dictionary and observable', () => {\n    expectPotentialObservable(\n      '{ o: Observable<number> } | Observable<{ ngrx: string }>'\n    ).toBeInferredAs('{ ngrx: string; } | { o: number; }');\n    expectPotentialObservable(\n      'Dictionary | Observable<boolean>',\n      'interface Dictionary { o: Observable<boolean> }'\n    ).toBeInferredAs('boolean | { o: boolean; }');\n  }, 8_000);\n});\n"
  },
  {
    "path": "modules/component/spec/types/push.pipe.types.spec.ts",
    "content": "import { potentialObservableExpecter } from './utils';\n\ndescribe('PushPipe', () => {\n  const expectPotentialObservable = potentialObservableExpecter(\n    (potentialObservableType) => `\n      import { Observable } from 'rxjs';\n      import { PushPipe } from '@ngrx/component';\n\n      const unknownVal: unknown = {};\n      const pushPipe = unknownVal as PushPipe;\n      const value = pushPipe.transform(unknownVal as ${potentialObservableType});\n    `\n  );\n\n  it('should infer the result when potential observable is a non-observable', () => {\n    expectPotentialObservable('string').toBeInferredAs('string');\n    expectPotentialObservable('undefined').toBeInferredAs('undefined');\n    expectPotentialObservable('boolean[]').toBeInferredAs('boolean[]');\n    expectPotentialObservable('{}').toBeInferredAs('{}');\n    expectPotentialObservable(\n      '{ x: number; y: string; z: symbol; }'\n    ).toBeInferredAs('{ x: number; y: string; z: symbol; }');\n    expectPotentialObservable(\n      'Book',\n      'interface Book { title: string; }'\n    ).toBeInferredAs('Book');\n  }, 30_000);\n\n  it('should infer the result when potential observable is a union of non-observables', () => {\n    expectPotentialObservable('number | { x: symbol; } | null').toBeInferredAs(\n      'number | { x: symbol; } | null'\n    );\n  }, 8_000);\n\n  it('should infer the result when potential observable is a promise', () => {\n    expectPotentialObservable('Promise<string>').toBeInferredAs(\n      'string | undefined'\n    );\n  }, 8_000);\n\n  it('should infer the result when potential observable is a union of promises', () => {\n    expectPotentialObservable(\n      'Promise<{ y: number[]; }> | Promise<string | { z: boolean; }>'\n    ).toBeInferredAs('string | { y: number[]; } | { z: boolean; } | undefined');\n  }, 8_000);\n\n  it('should infer the result when potential observable is a union of promise and non-observable', () => {\n    expectPotentialObservable(\n      'Promise<string[]> | boolean[] | null'\n    ).toBeInferredAs('string[] | boolean[] | null | undefined');\n  }, 8_000);\n\n  it('should infer the result when potential observable is an observable', () => {\n    expectPotentialObservable('Observable<{ x: number; }>').toBeInferredAs(\n      '{ x: number; } | undefined'\n    );\n  }, 8_000);\n\n  it('should infer the result when potential observable is a union of observables', () => {\n    expectPotentialObservable(\n      'Observable<symbol> | Observable<{ z: string; }> | null'\n    ).toBeInferredAs('symbol | { z: string; } | null | undefined');\n  }, 8_000);\n\n  it('should infer the result when potential observable is a union of observable and promise', () => {\n    expectPotentialObservable(\n      'Observable<boolean> | Promise<string | undefined>'\n    ).toBeInferredAs('string | boolean | undefined');\n  }, 8_000);\n\n  it('should infer the result when potential observable is a union of observable and non-observable', () => {\n    expectPotentialObservable(\n      'Observable<number[]> | Record<string, unknown>'\n    ).toBeInferredAs('Record<string, unknown> | number[] | undefined');\n  }, 8_000);\n\n  it('should infer the result when potential observable is a union of observable, promise, and non-observable', () => {\n    expectPotentialObservable(\n      'Observable<{ z: symbol; }> | Promise<number[]> | boolean | null'\n    ).toBeInferredAs('boolean | { z: symbol; } | number[] | null | undefined');\n  }, 8_000);\n\n  it('should infer the result when potential observable is an observable dictionary', () => {\n    expectPotentialObservable(\n      '{ o1: Observable<number | boolean>; o2: Observable<{ ngrx: string }> }'\n    ).toBeInferredAs(\n      '{ o1: number | boolean; o2: { ngrx: string; }; } | undefined'\n    );\n  }, 8_000);\n\n  it('should infer the result when potential observable is an observable dictionary typed as interface', () => {\n    expectPotentialObservable(\n      'Dictionary',\n      'interface Dictionary { o1: Observable<bigint>; o2: Observable<string | symbol> }'\n    ).toBeInferredAs('{ o1: bigint; o2: string | symbol; } | undefined');\n  }, 8_000);\n\n  it('should infer the result as static when potential observable is a dictionary with at least one non-observable property', () => {\n    expectPotentialObservable(\n      '{ o: Observable<string>; b: boolean }'\n    ).toBeInferredAs('{ o: Observable<string>; b: boolean; }');\n    expectPotentialObservable(\n      'Dictionary',\n      'interface Dictionary { o: Observable<string>; p: Promise<null> }'\n    ).toBeInferredAs('Dictionary');\n  }, 8_000);\n\n  it('should infer the result as static when potential observable is an observable dictionary with optional properties', () => {\n    expectPotentialObservable(\n      '{ x: Observable<number>; y?: Observable<bigint> }'\n    ).toBeInferredAs(\n      '{ x: Observable<number>; y?: Observable<bigint> | undefined; }'\n    );\n    expectPotentialObservable(\n      'Dictionary',\n      'interface Dictionary { o1: Observable<number>; o2?: Observable<bigint> }'\n    ).toBeInferredAs('Dictionary');\n  }, 8_000);\n\n  it('should infer the result when potential observable is a union of observable dictionary and non-observable', () => {\n    expectPotentialObservable(\n      '{ x: Observable<string> } | { y: number; z: bigint; }'\n    ).toBeInferredAs('{ y: number; z: bigint; } | { x: string; } | undefined');\n    expectPotentialObservable(\n      'Dictionary | number',\n      'interface Dictionary { x: Observable<string> }'\n    ).toBeInferredAs('number | { x: string; } | undefined');\n  }, 8_000);\n\n  it('should infer the result when potential observable is a union of observable dictionary and promise', () => {\n    expectPotentialObservable(\n      '{ ngrx: Observable<string> } | Promise<string>'\n    ).toBeInferredAs('string | { ngrx: string; } | undefined');\n    expectPotentialObservable(\n      'Dictionary | Promise<symbol>',\n      'interface Dictionary { o: Observable<symbol> }'\n    ).toBeInferredAs('symbol | { o: symbol; } | undefined');\n  }, 8_000);\n\n  it('should infer the value when potential observable is a union of observable dictionary and observable', () => {\n    expectPotentialObservable(\n      '{ o: Observable<number> } | Observable<number>'\n    ).toBeInferredAs('number | { o: number; } | undefined');\n    expectPotentialObservable(\n      'Dictionary | Observable<bigint>',\n      'interface Dictionary { o: Observable<bigint> }'\n    ).toBeInferredAs('bigint | { o: bigint; } | undefined');\n  }, 8_000);\n});\n"
  },
  {
    "path": "modules/component/spec/types/utils.ts",
    "content": "import { expecter } from 'ts-snippet';\n\nexport const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  strict: true,\n  paths: {\n    '@ngrx/component': ['./modules/component'],\n  },\n});\n\nexport function potentialObservableExpecter(\n  snippetFactory: (potentialObservableType: string) => string\n) {\n  if (!snippetFactory('').includes('const value')) {\n    throw new Error('Snippet must include a constant named `value`!');\n  }\n\n  return (potentialObservableType: string, typeDefinition = '') => {\n    const expectSnippet = expecter(\n      () => `${typeDefinition} ${snippetFactory(potentialObservableType)}`,\n      compilerOptions()\n    );\n\n    return {\n      toBeInferredAs(valueType: string): void {\n        expectSnippet(potentialObservableType).toInfer('value', valueType);\n      },\n    };\n  };\n}\n"
  },
  {
    "path": "modules/component/src/core/potential-observable.ts",
    "content": "import { combineLatest, from, isObservable, Observable } from 'rxjs';\nimport { distinctUntilChanged } from 'rxjs/operators';\n\ntype Primitive = string | number | bigint | boolean | symbol | null | undefined;\n\ntype ObservableOrPromise<T> = Observable<T> | PromiseLike<T>;\n\ntype ObservableDictionary<PO> = Required<{\n  [Key in keyof PO]: Observable<unknown>;\n}>;\n\nexport type PotentialObservableResult<PO, ExtendedResult = never> =\n  PO extends ObservableOrPromise<infer Result>\n    ? Result | ExtendedResult\n    : PO extends Primitive\n      ? PO\n      : keyof PO extends never\n        ? PO\n        : PO extends ObservableDictionary<PO>\n          ?\n              | {\n                  [Key in keyof PO]: PO[Key] extends Observable<infer Value>\n                    ? Value\n                    : never;\n                }\n              | ExtendedResult\n          : PO;\n\nexport function fromPotentialObservable<PO>(\n  potentialObservable: PO\n): Observable<PotentialObservableResult<PO>> {\n  type Result = ReturnType<typeof fromPotentialObservable<PO>>;\n\n  if (isObservable(potentialObservable)) {\n    return potentialObservable as Result;\n  }\n\n  if (isObservableDictionary(potentialObservable)) {\n    return combineLatest(\n      toDistinctObsDictionary(potentialObservable)\n    ) as Result;\n  }\n\n  if (isPromiseLike(potentialObservable)) {\n    return from(potentialObservable) as Result;\n  }\n\n  return new Observable<PO>((subscriber) => {\n    subscriber.next(potentialObservable);\n  }) as Result;\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n  return typeof (value as PromiseLike<unknown>)?.then === 'function';\n}\n\nfunction isObservableDictionary(\n  value: unknown\n): value is Record<string, Observable<unknown>> {\n  return (\n    isDictionary(value) &&\n    Object.keys(value).length > 0 &&\n    Object.values(value).every(isObservable)\n  );\n}\n\nfunction isDictionary(value: unknown): value is Record<string, unknown> {\n  return !!value && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction toDistinctObsDictionary<\n  OD extends Record<string, Observable<unknown>>,\n>(obsDictionary: OD): OD {\n  return Object.keys(obsDictionary).reduce(\n    (acc, key) => ({\n      ...acc,\n      [key]: obsDictionary[key].pipe(distinctUntilChanged()),\n    }),\n    {} as OD\n  );\n}\n"
  },
  {
    "path": "modules/component/src/core/render-event/handlers.ts",
    "content": "import {\n  CompleteRenderEvent,\n  ErrorRenderEvent,\n  NextRenderEvent,\n  RenderEvent,\n  SuspenseRenderEvent,\n} from './models';\n\nexport interface RenderEventHandlers<T> {\n  suspense?(event: SuspenseRenderEvent): void;\n  next?(event: NextRenderEvent<T>): void;\n  error?(event: ErrorRenderEvent): void;\n  complete?(event: CompleteRenderEvent): void;\n}\n\nexport function combineRenderEventHandlers<T>(\n  handlers: RenderEventHandlers<T>\n): (event: RenderEvent<T>) => void {\n  return (event) => handlers[event.type]?.(event as any);\n}\n"
  },
  {
    "path": "modules/component/src/core/render-event/manager.ts",
    "content": "import { Observable, pipe, ReplaySubject } from 'rxjs';\nimport { distinctUntilChanged, switchMap, tap } from 'rxjs/operators';\nimport { ErrorRenderEvent, NextRenderEvent, RenderEvent } from './models';\nimport { combineRenderEventHandlers, RenderEventHandlers } from './handlers';\nimport {\n  fromPotentialObservable,\n  PotentialObservableResult,\n} from '../potential-observable';\nimport { untracked } from '@angular/core';\n\nexport interface RenderEventManager<PO> {\n  nextPotentialObservable(potentialObservable: PO): void;\n  handlePotentialObservableChanges(): Observable<\n    RenderEvent<PotentialObservableResult<PO>>\n  >;\n}\n\nexport function createRenderEventManager<PO>(\n  handlers: RenderEventHandlers<PotentialObservableResult<PO>>\n): RenderEventManager<PO> {\n  const handleRenderEvent = combineRenderEventHandlers(handlers);\n  const potentialObservable$ = new ReplaySubject<PO>(1);\n\n  return {\n    nextPotentialObservable(potentialObservable) {\n      potentialObservable$.next(potentialObservable);\n    },\n    handlePotentialObservableChanges() {\n      return potentialObservable$.pipe(\n        distinctUntilChanged(),\n        switchMapToRenderEvent(),\n        distinctUntilChanged(renderEventComparator),\n        tap(handleRenderEvent)\n      );\n    },\n  };\n}\n\nfunction switchMapToRenderEvent<PO>(): (\n  source: Observable<PO>\n) => Observable<RenderEvent<PotentialObservableResult<PO>>> {\n  return pipe(\n    switchMap((potentialObservable) => {\n      const observable$ = fromPotentialObservable(potentialObservable);\n      let reset = true;\n      let synchronous = true;\n\n      return new Observable<RenderEvent<PotentialObservableResult<PO>>>(\n        (subscriber) => {\n          const subscription = untracked(() =>\n            observable$.subscribe({\n              next(value) {\n                subscriber.next({ type: 'next', value, reset, synchronous });\n                reset = false;\n              },\n              error(error) {\n                subscriber.next({ type: 'error', error, reset, synchronous });\n                reset = false;\n              },\n              complete() {\n                subscriber.next({ type: 'complete', reset, synchronous });\n                reset = false;\n              },\n            })\n          );\n\n          if (reset) {\n            subscriber.next({ type: 'suspense', reset, synchronous: true });\n            reset = false;\n          }\n          synchronous = false;\n\n          return subscription;\n        }\n      );\n    })\n  );\n}\n\nfunction renderEventComparator<T>(\n  previous: RenderEvent<T>,\n  current: RenderEvent<T>\n): boolean {\n  if (previous.type !== current.type || previous.reset !== current.reset) {\n    return false;\n  }\n\n  if (current.type === 'next') {\n    return (previous as NextRenderEvent<T>).value === current.value;\n  }\n\n  if (current.type === 'error') {\n    return (previous as ErrorRenderEvent).error === current.error;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "modules/component/src/core/render-event/models.ts",
    "content": "interface BaseRenderEvent {\n  /**\n   * true if the event is emitted by a new source\n   */\n  reset: boolean;\n  /**\n   * true if the synchronous event is emitted\n   */\n  synchronous: boolean;\n}\n\nexport interface SuspenseRenderEvent extends BaseRenderEvent {\n  type: 'suspense';\n  reset: true;\n  synchronous: true;\n}\n\nexport interface NextRenderEvent<T> extends BaseRenderEvent {\n  type: 'next';\n  value: T;\n}\n\nexport interface ErrorRenderEvent extends BaseRenderEvent {\n  type: 'error';\n  error: unknown;\n}\n\nexport interface CompleteRenderEvent extends BaseRenderEvent {\n  type: 'complete';\n}\n\nexport type RenderEvent<T> =\n  | SuspenseRenderEvent\n  | NextRenderEvent<T>\n  | ErrorRenderEvent\n  | CompleteRenderEvent;\n"
  },
  {
    "path": "modules/component/src/core/render-scheduler.ts",
    "content": "import { ChangeDetectorRef, inject, Injectable } from '@angular/core';\nimport { TickScheduler } from './tick-scheduler';\n\n/**\n * Provides rendering functionality regardless of whether `zone.js` is present\n * or not. It must be provided at the component/directive level.\n *\n * @usageNotes\n *\n * ### Rerender zone-less app on route changes\n *\n * ```ts\n * @Component({\n *   selector: 'app-root',\n *   template: '<router-outlet>',\n *   // 👇 `RenderScheduler` is provided at the component level\n *   providers: [RenderScheduler],\n *   changeDetection: ChangeDetectionStrategy.OnPush,\n * })\n * export class AppComponent implements OnInit {\n *   constructor(\n *     private readonly router: Router,\n *     private readonly renderScheduler: RenderScheduler\n *   ) {}\n *\n *   ngOnInit(): void {\n *     this.router.events\n *       .pipe(filter((e) => e instanceof NavigationEnd))\n *       .subscribe(() => this.renderScheduler.schedule());\n *   }\n * }\n * ```\n *\n * ### Rerender component on interval\n *\n * ```ts\n * @Component({\n *   selector: 'app-interval',\n *   template: '{{ elapsedTime }}ms',\n *   // 👇 `RenderScheduler` is provided at the component level\n *   providers: [RenderScheduler],\n *   changeDetection: ChangeDetectionStrategy.OnPush,\n * })\n * export class IntervalComponent implements OnInit {\n *   elapsedTime = 0;\n *\n *   constructor(private readonly renderScheduler: RenderScheduler) {}\n *\n *   ngOnInit(): void {\n *     setInterval(() => {\n *       this.elapsedTime += 1000;\n *       this.renderScheduler.schedule();\n *     }, 1000);\n *   }\n * }\n * ```\n */\n@Injectable()\nexport class RenderScheduler {\n  constructor(\n    private readonly cdRef: ChangeDetectorRef,\n    private readonly tickScheduler: TickScheduler\n  ) {}\n\n  /**\n   * Marks component and its ancestors as dirty.\n   * It also schedules a new change detection cycle in zone-less mode.\n   */\n  schedule(): void {\n    this.cdRef.markForCheck();\n    this.tickScheduler.schedule();\n  }\n}\n\nexport function createRenderScheduler(): RenderScheduler {\n  return new RenderScheduler(inject(ChangeDetectorRef), inject(TickScheduler));\n}\n"
  },
  {
    "path": "modules/component/src/core/tick-scheduler.ts",
    "content": "import {\n  ApplicationRef,\n  inject,\n  Injectable,\n  NgZone,\n  PLATFORM_ID,\n} from '@angular/core';\nimport { isPlatformServer } from '@angular/common';\nimport { isNgZone } from './zone-helpers';\n\n@Injectable({\n  providedIn: 'root',\n  useFactory: () => {\n    const zone = inject(NgZone);\n    return isNgZone(zone)\n      ? new NoopTickScheduler()\n      : inject(ZonelessTickScheduler);\n  },\n})\nexport abstract class TickScheduler {\n  abstract schedule(): void;\n}\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ZonelessTickScheduler extends TickScheduler {\n  private readonly appRef = inject(ApplicationRef);\n  private readonly platformId = inject(PLATFORM_ID);\n  private readonly isServer = isPlatformServer(this.platformId);\n  private readonly scheduleFn = (callback: () => void): void => {\n    if (this.isServer) {\n      setTimeout(callback);\n    } else {\n      requestAnimationFrame(callback);\n    }\n  };\n  private isScheduled = false;\n\n  schedule(): void {\n    if (!this.isScheduled) {\n      this.isScheduled = true;\n      this.scheduleFn(() => {\n        this.appRef.tick();\n        this.isScheduled = false;\n      });\n    }\n  }\n}\n\nexport class NoopTickScheduler extends TickScheduler {\n  // eslint-disable-next-line @typescript-eslint/no-empty-function\n  schedule(): void {}\n}\n"
  },
  {
    "path": "modules/component/src/core/zone-helpers.ts",
    "content": "import { NgZone } from '@angular/core';\n\nexport function isNgZone(zone: unknown): zone is NgZone {\n  return zone instanceof NgZone;\n}\n"
  },
  {
    "path": "modules/component/src/index.ts",
    "content": "export { RenderScheduler } from './core/render-scheduler';\nexport { LetDirective } from './let/let.directive';\nexport { PushPipe } from './push/push.pipe';\n"
  },
  {
    "path": "modules/component/src/let/let.directive.ts",
    "content": "import {\n  Directive,\n  ErrorHandler,\n  Input,\n  OnDestroy,\n  OnInit,\n  TemplateRef,\n  ViewContainerRef,\n} from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { PotentialObservableResult } from '../core/potential-observable';\nimport { RenderScheduler } from '../core/render-scheduler';\nimport { createRenderEventManager } from '../core/render-event/manager';\n\ntype LetViewContextValue<PO> = PotentialObservableResult<PO>;\n\nexport interface LetViewContext<PO> {\n  /**\n   * using `$implicit` to enable `let` syntax: `*ngrxLet=\"obs$; let o\"`\n   */\n  $implicit: LetViewContextValue<PO>;\n  /**\n   * using `ngrxLet` to enable `as` syntax: `*ngrxLet=\"obs$ as o\"`\n   */\n  ngrxLet: LetViewContextValue<PO>;\n  /**\n   * `*ngrxLet=\"obs$; let e = error\"` or `*ngrxLet=\"obs$; error as e\"`\n   */\n  error: any;\n  /**\n   * `*ngrxLet=\"obs$; let c = complete\"` or `*ngrxLet=\"obs$; complete as c\"`\n   */\n  complete: boolean;\n}\n\n/**\n *\n * @description\n *\n * The `*ngrxLet` directive serves a convenient way of binding observables to a view context\n * (DOM element's scope). It also helps with several internal processing under the hood.\n *\n * @usageNotes\n *\n * ### Displaying Observable Values\n *\n * ```html\n * <ng-container *ngrxLet=\"number$ as n\">\n *   <app-number [number]=\"n\"></app-number>\n * </ng-container>\n *\n * <ng-container *ngrxLet=\"number$; let n\">\n *   <app-number [number]=\"n\"></app-number>\n * </ng-container>\n * ```\n *\n * ### Tracking Different Observable Events\n *\n * ```html\n * <ng-container *ngrxLet=\"number$ as n; error as e; complete as c\">\n *   <app-number [number]=\"n\" *ngIf=\"!e && !c\">\n *   </app-number>\n *\n *   <p *ngIf=\"e\">There is an error: {{ e }}</p>\n *   <p *ngIf=\"c\">Observable is completed.</p>\n * </ng-container>\n * ```\n *\n * ### Combining Multiple Observables\n *\n * ```html\n * <ng-container *ngrxLet=\"{ users: users$, query: query$ } as vm\">\n *   <app-search-bar [query]=\"vm.query\"></app-search-bar>\n *   <app-user-list [users]=\"vm.users\"></app-user-list>\n * </ng-container>\n * ```\n *\n * ### Using Suspense Template\n *\n * ```html\n * <ng-container *ngrxLet=\"number$ as n; suspenseTpl: loading\">\n *   <app-number [number]=\"n\"></app-number>\n * </ng-container>\n *\n * <ng-template #loading>\n *   <p>Loading...</p>\n * </ng-template>\n * ```\n *\n * ### Using Aliases for Non-Observable Values\n *\n * ```html\n * <ng-container *ngrxLet=\"userForm.controls.email as email\">\n *   <input type=\"text\" [formControl]=\"email\" />\n *\n *   <ng-container *ngIf=\"email.errors && (email.touched || email.dirty)\">\n *     <p *ngIf=\"email.errors.required\">This field is required.</p>\n *     <p *ngIf=\"email.errors.email\">This field must be an email.</p>\n *   </ng-container>\n * </ng-container>\n * ```\n *\n * @publicApi\n */\n@Directive({\n  selector: '[ngrxLet]',\n  providers: [RenderScheduler],\n})\nexport class LetDirective<PO> implements OnInit, OnDestroy {\n  private isMainViewCreated = false;\n  private isSuspenseViewCreated = false;\n  private readonly viewContext: LetViewContext<PO | undefined> = {\n    $implicit: undefined,\n    ngrxLet: undefined,\n    error: undefined,\n    complete: false,\n  };\n  private readonly renderEventManager = createRenderEventManager<PO>({\n    suspense: () => {\n      this.viewContext.$implicit = undefined;\n      this.viewContext.ngrxLet = undefined;\n      this.viewContext.error = undefined;\n      this.viewContext.complete = false;\n\n      this.renderSuspenseView();\n    },\n    next: (event) => {\n      this.viewContext.$implicit = event.value;\n      this.viewContext.ngrxLet = event.value;\n\n      if (event.reset) {\n        this.viewContext.error = undefined;\n        this.viewContext.complete = false;\n      }\n\n      this.renderMainView(event.synchronous);\n    },\n    error: (event) => {\n      this.viewContext.error = event.error;\n\n      if (event.reset) {\n        this.viewContext.$implicit = undefined;\n        this.viewContext.ngrxLet = undefined;\n        this.viewContext.complete = false;\n      }\n\n      this.renderMainView(event.synchronous);\n      this.errorHandler.handleError(event.error);\n    },\n    complete: (event) => {\n      this.viewContext.complete = true;\n\n      if (event.reset) {\n        this.viewContext.$implicit = undefined;\n        this.viewContext.ngrxLet = undefined;\n        this.viewContext.error = undefined;\n      }\n\n      this.renderMainView(event.synchronous);\n    },\n  });\n  private readonly subscription = new Subscription();\n\n  @Input()\n  set ngrxLet(potentialObservable: PO) {\n    this.renderEventManager.nextPotentialObservable(potentialObservable);\n  }\n\n  @Input('ngrxLetSuspenseTpl') suspenseTemplateRef?: TemplateRef<unknown>;\n\n  constructor(\n    private readonly mainTemplateRef: TemplateRef<\n      LetViewContext<PO | undefined>\n    >,\n    private readonly viewContainerRef: ViewContainerRef,\n    private readonly errorHandler: ErrorHandler,\n    private readonly renderScheduler: RenderScheduler\n  ) {}\n\n  static ngTemplateContextGuard<PO>(\n    dir: LetDirective<PO>,\n    ctx: unknown\n  ): ctx is LetViewContext<PO> {\n    return true;\n  }\n\n  ngOnInit(): void {\n    this.subscription.add(\n      this.renderEventManager.handlePotentialObservableChanges().subscribe()\n    );\n  }\n\n  ngOnDestroy(): void {\n    this.subscription.unsubscribe();\n  }\n\n  private renderMainView(isSyncEvent: boolean): void {\n    if (this.isSuspenseViewCreated) {\n      this.isSuspenseViewCreated = false;\n      this.viewContainerRef.clear();\n    }\n\n    if (!this.isMainViewCreated) {\n      this.isMainViewCreated = true;\n      this.viewContainerRef.createEmbeddedView(\n        this.mainTemplateRef,\n        this.viewContext\n      );\n    }\n\n    if (!isSyncEvent) {\n      this.renderScheduler.schedule();\n    }\n  }\n\n  private renderSuspenseView(): void {\n    if (this.isMainViewCreated) {\n      this.isMainViewCreated = false;\n      this.viewContainerRef.clear();\n    }\n\n    if (this.suspenseTemplateRef && !this.isSuspenseViewCreated) {\n      this.isSuspenseViewCreated = true;\n      this.viewContainerRef.createEmbeddedView(this.suspenseTemplateRef);\n    }\n  }\n}\n"
  },
  {
    "path": "modules/component/src/push/push.pipe.ts",
    "content": "import { ErrorHandler, OnDestroy, Pipe, PipeTransform } from '@angular/core';\nimport { Unsubscribable } from 'rxjs';\nimport { PotentialObservableResult } from '../core/potential-observable';\nimport { createRenderScheduler } from '../core/render-scheduler';\nimport { createRenderEventManager } from '../core/render-event/manager';\n\ntype PushPipeResult<PO> = PotentialObservableResult<PO, undefined>;\n\n/**\n * @description\n *\n * The `ngrxPush` pipe serves as a drop-in replacement for the `async` pipe.\n * It contains intelligent handling of change detection to enable us\n * running in zone-full as well as zone-less mode without any changes to the code.\n *\n * @usageNotes\n *\n * ### Displaying Observable Values\n *\n * ```html\n * <p>{{ number$ | ngrxPush }}</p>\n *\n * <ng-container *ngIf=\"number$ | ngrxPush as n\">{{ n }}</ng-container>\n *\n * <app-number [number]=\"number$ | ngrxPush\"></app-number>\n * ```\n *\n * ### Combining Multiple Observables\n *\n * ```html\n * <code>\n *   {{ { users: users$, query: query$ } | ngrxPush | json }}\n * </code>\n * ```\n *\n * @publicApi\n */\n@Pipe({\n  name: 'ngrxPush',\n  pure: false,\n})\nexport class PushPipe implements PipeTransform, OnDestroy {\n  private renderedValue: unknown;\n  private readonly renderScheduler = createRenderScheduler();\n  private readonly renderEventManager = createRenderEventManager({\n    suspense: (event) => this.setRenderedValue(undefined, event.synchronous),\n    next: (event) => this.setRenderedValue(event.value, event.synchronous),\n    error: (event) => {\n      if (event.reset) {\n        this.setRenderedValue(undefined, event.synchronous);\n      }\n      this.errorHandler.handleError(event.error);\n    },\n    complete: (event) => {\n      if (event.reset) {\n        this.setRenderedValue(undefined, event.synchronous);\n      }\n    },\n  });\n  private readonly subscription: Unsubscribable;\n\n  constructor(private readonly errorHandler: ErrorHandler) {\n    this.subscription = this.renderEventManager\n      .handlePotentialObservableChanges()\n      .subscribe();\n  }\n\n  transform<PO>(potentialObservable: PO): PushPipeResult<PO> {\n    this.renderEventManager.nextPotentialObservable(potentialObservable);\n    return this.renderedValue as PushPipeResult<PO>;\n  }\n\n  ngOnDestroy(): void {\n    this.subscription.unsubscribe();\n  }\n\n  private setRenderedValue(value: unknown, isSyncEvent: boolean): void {\n    if (value !== this.renderedValue) {\n      this.renderedValue = value;\n\n      if (!isSyncEvent) {\n        this.renderScheduler.schedule();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "modules/component/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/component/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"strictPropertyInitialization\": true,\n    \"strictNullChecks\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmitOnError\": false,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/packages/component\",\n    \"paths\": {},\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"skipLibCheck\": true\n  },\n  \"files\": [\"public_api.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"strictMetadataEmit\": true,\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/component\"\n  }\n}\n"
  },
  {
    "path": "modules/component/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/component\",\n    \"paths\": {\n      \"@ngrx/component/schematics-core\": [\"./schematics-core\"],\n      \"@ngrx/component\": [\"./src\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/component/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"vitest/globals\", \"node\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/component/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [angular(), nxViteTsPaths()],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default'],\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "modules/component-store/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/component-store/README.md",
    "content": "# @ngrx/component-store\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n"
  },
  {
    "path": "modules/component-store/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: [\n      '**/dist',\n      '**/schematics-core/**/*.ts',\n    ],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/component-store/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n  {\n    ignores: ['schematics-core'],\n  },\n];\n"
  },
  {
    "path": "modules/component-store/index.ts",
    "content": "/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\nexport * from './public_api';\n"
  },
  {
    "path": "modules/component-store/migrations/18_0_0-beta/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\nimport { tags } from '@angular-devkit/core';\nimport * as path from 'path';\nimport { logging } from '@angular-devkit/core';\n\ndescribe('ComponentStore Migration to 18.0.0-beta', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/component-store/migrations/migration.json'\n  );\n  const schematicRunner = new SchematicTestRunner('schematics', collectionPath);\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  const verifySchematic = async (input: string, output: string) => {\n    appTree.create('main.ts', input);\n\n    const tree = await schematicRunner.runSchematic(\n      `ngrx-component-store-migration-18-beta`,\n      {},\n      appTree\n    );\n\n    const actual = tree.readContent('main.ts');\n\n    expect(actual).toBe(output);\n  };\n\n  describe('replacements', () => {\n    it('should replace the import', async () => {\n      const input = tags.stripIndent`\nimport { tapResponse } from '@ngrx/component-store';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n      `;\n      const output = tags.stripIndent`\nimport { tapResponse } from '@ngrx/operators';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n      `;\n\n      await verifySchematic(input, output);\n    });\n\n    it('should also work with \" in imports', async () => {\n      const input = tags.stripIndent`\nimport { tapResponse } from \"@ngrx/component-store\";\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n      `;\n      const output = tags.stripIndent`\nimport { tapResponse } from '@ngrx/operators';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n      `;\n      await verifySchematic(input, output);\n    });\n\n    it('should replace if multiple imports are inside an import statement', async () => {\n      const input = tags.stripIndent`\nimport { ComponentStore, tapResponse } from '@ngrx/component-store';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n      `;\n      const output = tags.stripIndent`\nimport { ComponentStore } from '@ngrx/component-store';\nimport { tapResponse } from '@ngrx/operators';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n      `;\n\n      await verifySchematic(input, output);\n    });\n\n    it('should add tapResponse to existing import', async () => {\n      const input = tags.stripIndent`\nimport { ComponentStore, tapResponse } from '@ngrx/component-store';\nimport { concatLatestFrom } from '@ngrx/operators';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n      `;\n      const output = tags.stripIndent`\nimport { ComponentStore } from '@ngrx/component-store';\nimport { concatLatestFrom, tapResponse } from '@ngrx/operators';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n      `;\n      await verifySchematic(input, output);\n    });\n  });\n\n  it('should work with prior import from same namespace', async () => {\n    const input = tags.stripIndent`\nimport { ComponentStore, provideComponentStore } from '@ngrx/component-store';\nimport { tapResponse } from '@ngrx/component-store';\n\nexport class MyStore extends ComponentStore {}\n      `;\n    const output = tags.stripIndent`\nimport { ComponentStore, provideComponentStore } from '@ngrx/component-store';\nimport { tapResponse } from '@ngrx/operators';\n\nexport class MyStore extends ComponentStore {}\n      `;\n    await verifySchematic(input, output);\n  });\n\n  it('should operate on multiple files', async () => {\n    const inputMainOne = tags.stripIndent`\nimport { ComponentStore, tapResponse } from '@ngrx/component-store';\nimport { concatLatestFrom } from '@ngrx/operators';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n`;\n\n    const outputMainOne = tags.stripIndent`\nimport { ComponentStore } from '@ngrx/component-store';\nimport { concatLatestFrom, tapResponse } from '@ngrx/operators';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n`;\n\n    const inputMainTwo = tags.stripIndent`\nimport { tapResponse } from \"@ngrx/component-store\";\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n      `;\n    const outputMainTwo = tags.stripIndent`\nimport { tapResponse } from '@ngrx/operators';\n\n@Injectable()\nexport class MyStore extends ComponentStore {\n\n}\n`;\n    appTree.create('mainOne.ts', inputMainOne);\n    appTree.create('mainTwo.ts', inputMainTwo);\n\n    const tree = await schematicRunner.runSchematic(\n      `ngrx-component-store-migration-18-beta`,\n      {},\n      appTree\n    );\n\n    const actualMainOne = tree.readContent('mainOne.ts');\n    const actualMainTwo = tree.readContent('mainTwo.ts');\n\n    expect(actualMainOne).toBe(outputMainOne);\n    expect(actualMainTwo).toBe(outputMainTwo);\n  });\n\n  it('should report a warning on multiple imports of tapResponse', async () => {\n    const input = tags.stripIndent`\nimport { tapResponse } from '@ngrx/component-store';\nimport { tapResponse, ComponentStore } from '@ngrx/component-store';\n\nclass SomeEffects {}\n      `;\n\n    appTree.create('main.ts', input);\n    const logEntries: logging.LogEntry[] = [];\n    schematicRunner.logger.subscribe((logEntry) => logEntries.push(logEntry));\n    await schematicRunner.runSchematic(\n      `ngrx-component-store-migration-18-beta`,\n      {},\n      appTree\n    );\n\n    expect(logEntries).toHaveLength(1);\n    expect(logEntries[0]).toMatchObject({\n      message:\n        '[@ngrx/component-store] Skipping because of multiple `tapResponse` imports',\n      level: 'info',\n    });\n  });\n\n  it('should add @ngrx/operators if they are missing', async () => {\n    const originalPackageJson = JSON.parse(\n      appTree.readContent('/package.json')\n    );\n    expect(originalPackageJson.dependencies['@ngrx/operators']).toBeUndefined();\n    expect(\n      originalPackageJson.devDependencies['@ngrx/operators']\n    ).toBeUndefined();\n\n    const tree = await schematicRunner.runSchematic(\n      `ngrx-component-store-migration-18-beta`,\n      {},\n      appTree\n    );\n\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n    expect(packageJson.dependencies['@ngrx/operators']).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "modules/component-store/migrations/18_0_0-beta/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Tree,\n  Rule,\n  chain,\n  SchematicContext,\n} from '@angular-devkit/schematics';\nimport {\n  addPackageToPackageJson,\n  Change,\n  commitChanges,\n  createReplaceChange,\n  InsertChange,\n  visitTSSourceFiles,\n} from '../../schematics-core';\nimport { createRemoveChange } from '../../schematics-core/utility/change';\n\nexport function migrateTapResponseImport(): Rule {\n  return (tree: Tree, ctx: SchematicContext) => {\n    addPackageToPackageJson(tree, 'dependencies', '@ngrx/operators', '^18.0.0');\n\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const importDeclarations = new Array<ts.ImportDeclaration>();\n      getImportDeclarations(sourceFile, importDeclarations);\n\n      const componentStoreImportsAndDeclarations = importDeclarations\n        .map((componentStoreImportDeclaration) => {\n          const componentStoreImports = getComponentStoreNamedBinding(\n            componentStoreImportDeclaration\n          );\n          if (componentStoreImports) {\n            if (\n              componentStoreImports.elements.some(\n                (element) => element.name.getText() === 'tapResponse'\n              )\n            ) {\n              return { componentStoreImports, componentStoreImportDeclaration };\n            }\n            return undefined;\n          } else {\n            return undefined;\n          }\n        })\n        .filter(Boolean);\n\n      if (componentStoreImportsAndDeclarations.length === 0) {\n        return;\n      } else if (componentStoreImportsAndDeclarations.length > 1) {\n        ctx.logger.info(\n          '[@ngrx/component-store] Skipping because of multiple `tapResponse` imports'\n        );\n        return;\n      }\n\n      const [componentStoreImportsAndDeclaration] =\n        componentStoreImportsAndDeclarations;\n      if (!componentStoreImportsAndDeclaration) {\n        return;\n      }\n      const { componentStoreImports, componentStoreImportDeclaration } =\n        componentStoreImportsAndDeclaration;\n\n      const operatorsImportDeclaration = importDeclarations.find((node) =>\n        node.moduleSpecifier.getText().includes('@ngrx/operators')\n      );\n\n      const otherComponentStoreImports = componentStoreImports.elements\n        .filter((element) => element.name.getText() !== 'tapResponse')\n        .map((element) => element.name.getText())\n        .join(', ');\n\n      const changes: Change[] = [];\n      // Remove `tapResponse` from @ngrx/component-store and leave the other imports\n      if (otherComponentStoreImports) {\n        changes.push(\n          createReplaceChange(\n            sourceFile,\n            componentStoreImportDeclaration,\n            componentStoreImportDeclaration.getText(),\n            `import { ${otherComponentStoreImports} } from '@ngrx/component-store';`\n          )\n        );\n      }\n      // Remove complete @ngrx/component-store import because it contains only `tapResponse`\n      else {\n        changes.push(\n          createRemoveChange(\n            sourceFile,\n            componentStoreImportDeclaration,\n            componentStoreImportDeclaration.getStart(),\n            componentStoreImportDeclaration.getEnd() + 1\n          )\n        );\n      }\n\n      let importAppendedInExistingDeclaration = false;\n      if (operatorsImportDeclaration?.importClause?.namedBindings) {\n        const bindings = operatorsImportDeclaration.importClause.namedBindings;\n        if (ts.isNamedImports(bindings)) {\n          // Add import to existing @ngrx/operators\n          const updatedImports = [\n            ...bindings.elements.map((element) => element.name.getText()),\n            'tapResponse',\n          ];\n          const newOperatorsImport = `import { ${updatedImports.join(\n            ', '\n          )} } from '@ngrx/operators';`;\n          changes.push(\n            createReplaceChange(\n              sourceFile,\n              operatorsImportDeclaration,\n              operatorsImportDeclaration.getText(),\n              newOperatorsImport\n            )\n          );\n          importAppendedInExistingDeclaration = true;\n        }\n      }\n\n      if (!importAppendedInExistingDeclaration) {\n        // Add new @ngrx/operators import line\n        const newOperatorsImport = `import { tapResponse } from '@ngrx/operators';`;\n        changes.push(\n          new InsertChange(\n            sourceFile.fileName,\n            componentStoreImportDeclaration.getEnd() + 1,\n            `${newOperatorsImport}\\n` // not os-independent for snapshot tests\n          )\n        );\n      }\n\n      commitChanges(tree, sourceFile.fileName, changes);\n\n      if (changes.length) {\n        ctx.logger.info(\n          `[@ngrx/component-store] Updated tapResponse to import from '@ngrx/operators'`\n        );\n      }\n    });\n  };\n}\n\nfunction getImportDeclarations(\n  node: ts.Node,\n  imports: ts.ImportDeclaration[]\n): void {\n  if (ts.isImportDeclaration(node)) {\n    imports.push(node);\n  }\n\n  ts.forEachChild(node, (childNode) =>\n    getImportDeclarations(childNode, imports)\n  );\n}\n\nfunction getComponentStoreNamedBinding(\n  node: ts.ImportDeclaration\n): ts.NamedImports | null {\n  const namedBindings = node?.importClause?.namedBindings;\n  if (\n    node.moduleSpecifier.getText().includes('@ngrx/component-store') &&\n    namedBindings &&\n    ts.isNamedImports(namedBindings)\n  ) {\n    return namedBindings;\n  }\n\n  return null;\n}\n\nexport default function (): Rule {\n  return chain([migrateTapResponseImport()]);\n}\n"
  },
  {
    "path": "modules/component-store/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"ngrx-component-store-migration-18-beta\": {\n      \"description\": \"As of NgRx v18, the `tapResponse` import has been removed from `@ngrx/component-store` in favor of the `@ngrx/operators` package.\",\n      \"version\": \"18-beta\",\n      \"factory\": \"./18_0_0-beta/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/component-store/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/component-store\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"allowedNonPeerDependencies\": [\"@ngrx/operators\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/component-store/package.json",
    "content": "{\n  \"name\": \"@ngrx/component-store\",\n  \"version\": \"21.0.1\",\n  \"description\": \"Reactive store for component state\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"Angular\",\n    \"Redux\",\n    \"NgRx\",\n    \"Schematics\",\n    \"Local State\",\n    \"Component State\",\n    \"State management\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"@angular/core\": \"^21.0.0\",\n    \"rxjs\": \"^6.5.3 || ^7.5.0\"\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"sideEffects\": false,\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"dependencies\": {\n    \"tslib\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "modules/component-store/project.json",
    "content": "{\n  \"name\": \"component-store\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/component-store/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/component-store/tsconfig.build.json\",\n        \"project\": \"modules/component-store/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package component-store\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/component-store/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/component-store\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/component-store\"\n          },\n          {\n            \"command\": \"ncp dist/modules/component-store node_modules/@ngrx/component-store\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/component-store\"\n          }\n        ]\n      },\n      \"outputs\": [\n        \"{workspaceRoot}/dist/modules/component-store\",\n        \"{workspaceRoot}/node_modules/@ngrx/component-store\"\n      ]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/component-store/*/**/*.ts\",\n          \"modules/component-store/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/component-store\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/component-store/public_api.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/component-store/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Add @ngrx/component-store to your application\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/component-store/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as SchemaOptions } from './schema';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('Component store ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/component-store',\n    path.join(\n      process.cwd(),\n      'dist/modules/component-store/schematics/collection.json'\n    )\n  );\n  const defaultOptions: SchemaOptions = {\n    skipPackageJson: false,\n  };\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/component-store']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/component-store']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "modules/component-store/schematics/ng-add/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  chain,\n  noop,\n} from '@angular-devkit/schematics';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\nimport {\n  addPackageToPackageJson,\n  platformVersion,\n} from '../../schematics-core';\nimport { Schema as SchemaOptions } from './schema';\n\nfunction addModuleToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/component-store',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nexport default function (options: SchemaOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    return chain([\n      options && options.skipPackageJson ? noop() : addModuleToPackageJson(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/component-store/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxComponentStore\",\n  \"title\": \"NgRx Component Store Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/component-store as dependency to package.json (e.g., --skipPackageJson).\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/component-store/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  skipPackageJson?: boolean;\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/component-store/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/component-store/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/component-store/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/component-store/spec/component-store.spec.ts",
    "content": "/* eslint-disable @typescript-eslint/no-non-null-assertion */\nimport {\n  computed,\n  Inject,\n  Injectable,\n  InjectionToken,\n  Injector,\n  Provider,\n  ValueEqualityFn,\n} from '@angular/core';\nimport { fakeAsync, flushMicrotasks } from '@angular/core/testing';\nimport {\n  ComponentStore,\n  OnStateInit,\n  OnStoreInit,\n  provideComponentStore,\n} from '..';\nimport { createSelector } from '@ngrx/store';\nimport {\n  asyncScheduler,\n  ConnectableObservable,\n  from,\n  interval,\n  Observable,\n  of,\n  queueScheduler,\n  scheduled,\n  Subscription,\n  throwError,\n  timer,\n} from 'rxjs';\nimport { marbles } from 'rxjs-marbles/jest';\nimport {\n  concatMap,\n  delay,\n  delayWhen,\n  finalize,\n  map,\n  publishReplay,\n  take,\n  tap,\n} from 'rxjs/operators';\nimport { vi } from 'vitest';\n\ndescribe('Component Store', () => {\n  describe('initialization', () => {\n    it(\n      'through constructor',\n      marbles((m) => {\n        const INIT_STATE = { init: 'state' };\n        const componentStore = new ComponentStore(INIT_STATE);\n\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('i', { i: INIT_STATE })\n        );\n      })\n    );\n\n    it(\n      'supports an array state',\n      marbles((m) => {\n        const INIT_STATE = [1, 2, 3];\n        const componentStore = new ComponentStore(INIT_STATE);\n\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('i', { i: INIT_STATE })\n        );\n      })\n    );\n\n    it(\n      'stays uninitialized if initial state is not provided',\n      marbles((m) => {\n        const componentStore = new ComponentStore();\n\n        // No values emitted.\n        m.expect(componentStore.state$).toBeObservable(m.hot('-'));\n      })\n    );\n\n    it(\n      'through setState method',\n      marbles((m) => {\n        const componentStore = new ComponentStore();\n        const INIT_STATE = { setState: 'passed' };\n\n        componentStore.setState(INIT_STATE);\n\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('i', { i: INIT_STATE })\n        );\n      })\n    );\n\n    it(\n      'throws an Error when setState with a function/callback is called' +\n        ' before initialization',\n      () => {\n        const componentStore = new ComponentStore();\n\n        expect(() => {\n          componentStore.setState(() => ({ setState: 'new state' }));\n        }).toThrow(\n          new Error(\n            'ComponentStore2 has not been initialized yet. ' +\n              'Please make sure it is initialized before updating/getting.'\n          )\n        );\n      }\n    );\n\n    it('throws an Error when patchState with an object is called before initialization', () => {\n      const componentStore = new ComponentStore();\n\n      expect(() => {\n        componentStore.patchState({ foo: 'bar' });\n      }).toThrow(\n        new Error(\n          'ComponentStore2 has not been initialized yet. ' +\n            'Please make sure it is initialized before updating/getting.'\n        )\n      );\n    });\n\n    it('throws an Error when patchState with Observable is called before initialization', () => {\n      const componentStore = new ComponentStore();\n\n      expect(() => {\n        componentStore.patchState(of({ foo: 'bar' }));\n      }).toThrow(\n        new Error(\n          'ComponentStore2 has not been initialized yet. ' +\n            'Please make sure it is initialized before updating/getting.'\n        )\n      );\n    });\n\n    it(\n      'throws an Error when patchState with a function/callback is called' +\n        ' before initialization',\n      () => {\n        const componentStore = new ComponentStore();\n\n        expect(() => {\n          componentStore.patchState(() => ({ foo: 'bar' }));\n        }).toThrow(\n          new Error(\n            'ComponentStore2 has not been initialized yet. ' +\n              'Please make sure it is initialized before updating/getting.'\n          )\n        );\n      }\n    );\n\n    it('throws an Error synchronously when updater is called before initialization', () => {\n      const componentStore = new ComponentStore();\n\n      expect(() => {\n        componentStore.updater((state, value: object) => value)({\n          updater: 'new state',\n        });\n      }).toThrow(\n        new Error(\n          'ComponentStore2 has not been initialized yet. ' +\n            'Please make sure it is initialized before updating/getting.'\n        )\n      );\n    });\n\n    it(\n      'throws an Error when updater is called with sync Observable' +\n        ' before initialization',\n      () => {\n        const componentStore = new ComponentStore();\n        const syncronousObservable$ = of({\n          updater: 'new state',\n        });\n\n        expect(() => {\n          componentStore.updater<object>((state, value) => value)(\n            syncronousObservable$\n          );\n        }).toThrow(\n          new Error(\n            'ComponentStore2 has not been initialized yet. ' +\n              'Please make sure it is initialized before updating/getting.'\n          )\n        );\n      }\n    );\n\n    it(\n      'throws an Error asynchronously when updater is called with async' +\n        ' Observable before initialization, however closes the subscription' +\n        ' and does not update the state',\n      marbles((m) => {\n        const componentStore = new ComponentStore();\n        const asynchronousObservable$ = m.cold('-u', {\n          u: { updater: 'new state' },\n        });\n\n        let subscription: Subscription | undefined;\n\n        expect(() => {\n          subscription = componentStore.updater(\n            (state, value: object) => value\n          )(asynchronousObservable$);\n          m.flush();\n        }).toThrow(\n          new Error(\n            'ComponentStore2 has not been initialized yet. ' +\n              'Please make sure it is initialized before updating/getting.'\n          )\n        );\n\n        expect(subscription!.closed).toBe(true);\n      })\n    );\n\n    it(\n      'does not throw an Error when updater is called with async Observable' +\n        ' before initialization, that emits the value after initialization',\n      marbles((m) => {\n        const componentStore = new ComponentStore();\n        const INIT_STATE = { initState: 'passed' };\n        const UPDATED_STATE = { updatedState: 'proccessed' };\n\n        // Record all the values that go through state$.\n        const recordedStateValues$ =\n          componentStore.state$.pipe(publishReplay());\n        // Need to \"connect\" to start getting notifications.\n        (recordedStateValues$ as ConnectableObservable<object>).connect();\n\n        const asyncronousObservable$ = of(UPDATED_STATE).pipe(\n          // Delays until the state gets the init value.\n          delayWhen(() => componentStore.state$)\n        );\n\n        expect(() => {\n          componentStore.updater<object>((state, value) => value)(\n            asyncronousObservable$\n          );\n        }).not.toThrow();\n\n        // Trigger initial state.\n        componentStore.setState(INIT_STATE);\n\n        m.expect(recordedStateValues$).toBeObservable(\n          m.hot('(iu)', { i: INIT_STATE, u: UPDATED_STATE })\n        );\n      })\n    );\n\n    it(\n      'does not throw an Error when ComponentStore initialization and' +\n        ' state update are scheduled via queueScheduler',\n      () => {\n        expect(() => {\n          queueScheduler.schedule(() => {\n            const componentStore = new ComponentStore({ foo: false });\n            componentStore.patchState({ foo: true });\n          });\n        }).not.toThrow();\n      }\n    );\n  });\n\n  describe('updates the state', () => {\n    interface State {\n      value: string;\n      updated?: boolean;\n    }\n    const INIT_STATE: State = { value: 'init' };\n    let componentStore: ComponentStore<State>;\n\n    beforeEach(() => {\n      componentStore = new ComponentStore<State>(INIT_STATE);\n    });\n\n    it(\n      'with setState to a specific value',\n      marbles((m) => {\n        const SET_STATE: State = { value: 'new state' };\n        componentStore.setState(SET_STATE);\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('s', { s: SET_STATE })\n        );\n      })\n    );\n\n    it(\n      'with setState to a value based on the previous state',\n      marbles((m) => {\n        const UPDATE_STATE: Partial<State> = { updated: true };\n        componentStore.setState((state) => ({\n          ...state,\n          ...UPDATE_STATE,\n        }));\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('u', {\n            u: {\n              value: 'init',\n              updated: true,\n            },\n          })\n        );\n      })\n    );\n\n    it(\n      'with updater to a value based on the previous state and passed values',\n      marbles((m) => {\n        const UPDATED: Partial<State> = { updated: true };\n        const UPDATE_VALUE: Partial<State> = { value: 'updated' };\n        const updater = componentStore.updater(\n          (state, value: Partial<State>) => ({\n            ...state,\n            ...value,\n          })\n        );\n\n        // Record all the values that go through state$ into an array\n        const results: object[] = [];\n        componentStore.state$.subscribe((state) => results.push(state));\n\n        // Update twice with different values\n        updater(UPDATED);\n        m.flush(); // flushed after each update\n        updater(UPDATE_VALUE);\n        m.flush(); // flushed after each update\n\n        expect(results).toEqual([\n          { value: 'init' },\n          {\n            value: 'init',\n            updated: true,\n          },\n          {\n            value: 'updated',\n            updated: true,\n          },\n        ]);\n\n        // New subsriber gets the latest value only.\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('s', {\n            s: {\n              value: 'updated',\n              updated: true,\n            },\n          })\n        );\n      })\n    );\n\n    it(\n      'with multiple values within the same microtask',\n      marbles((m) => {\n        const UPDATED: Partial<State> = { updated: true };\n        const UPDATE_VALUE: Partial<State> = { value: 'updated' };\n        const updater = componentStore.updater(\n          (state, value: Partial<State>) => ({\n            ...state,\n            ...value,\n          })\n        );\n\n        // Record all the values that go through state$ into an array\n        const results: object[] = [];\n        componentStore.state$.subscribe((state) => results.push(state));\n\n        // Update twice with different values\n        updater(UPDATED);\n        updater(UPDATE_VALUE);\n\n        // 👆👆👆\n        // Notice there is no \"flush\" until this point - all synchronous\n        m.flush();\n\n        expect(results).toEqual([\n          { value: 'init' },\n          { value: 'init', updated: true },\n          {\n            value: 'updated',\n            updated: true,\n          },\n        ]);\n\n        // New subscriber gets the latest value only.\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('s', {\n            s: {\n              value: 'updated',\n              updated: true,\n            },\n          })\n        );\n      })\n    );\n\n    it(\n      'with updater to a value based on the previous state and passed' +\n        ' Observable',\n      marbles((m) => {\n        const updater = componentStore.updater(\n          (state, value: Partial<State>) => ({\n            ...state,\n            ...value,\n          })\n        );\n\n        // Record all the values that go through state$.\n        const recordedStateValues$ =\n          componentStore.state$.pipe(publishReplay());\n        // Need to \"connect\" to start getting notifications.\n        (recordedStateValues$ as ConnectableObservable<object>).connect();\n\n        // Update with Observable.\n        updater(\n          m.cold('--u--s|', {\n            u: { updated: true },\n            s: { value: 'updated' },\n          })\n        );\n\n        m.expect(recordedStateValues$).toBeObservable(\n          m.hot('i-u--s', {\n            // First value is here due to ReplaySubject being at the heart of\n            // ComponentStore.\n            i: {\n              value: 'init',\n            },\n            u: {\n              value: 'init',\n              updated: true,\n            },\n            s: {\n              value: 'updated',\n              updated: true,\n            },\n          })\n        );\n      })\n    );\n  });\n\n  describe('cancels updater Observable', () => {\n    beforeEach(() => vi.useFakeTimers());\n    afterEach(() => vi.useRealTimers());\n\n    interface State {\n      value: string;\n      updated?: boolean;\n    }\n    const INIT_STATE: State = { value: 'init' };\n    let componentStore: ComponentStore<State>;\n\n    beforeEach(() => {\n      componentStore = new ComponentStore<State>(INIT_STATE);\n    });\n\n    it('by unsubscribing with returned Subscriber', () => {\n      const updater = componentStore.updater(\n        (state, value: Partial<State>) => ({\n          ...state,\n          ...value,\n        })\n      );\n\n      // Record all the values that go through state$ into an array\n      const results: State[] = [];\n      componentStore.state$.subscribe((state) => results.push(state));\n\n      // Update with Observable.\n      const subscription = updater(\n        interval(10).pipe(\n          map((v) => ({ value: String(v) })),\n          take(10) // just in case\n        )\n      );\n\n      // Advance for 40 fake milliseconds and unsubscribe - should capture\n      // from '0' to '3'\n      vi.advanceTimersByTime(40);\n      subscription.unsubscribe();\n\n      // Advance for 20 more fake milliseconds, to check if anything else\n      // is captured\n      vi.advanceTimersByTime(20);\n\n      expect(results).toEqual([\n        // First value is here due to ReplaySubject being at the heart of\n        // ComponentStore.\n        { value: 'init' },\n        { value: '0' },\n        { value: '1' },\n        { value: '2' },\n        { value: '3' },\n      ]);\n    });\n\n    it('and cancels the correct one', () => {\n      const updater = componentStore.updater(\n        (state, value: Partial<State>) => ({\n          ...state,\n          ...value,\n        })\n      );\n\n      // Record all the values that go through state$ into an array\n      const results: State[] = [];\n      componentStore.state$.subscribe((state) => results.push(state));\n\n      // Update with Observable.\n      const subscription = updater(\n        interval(10).pipe(\n          map((v) => ({ value: 'a' + v })),\n          take(10) // just in case\n        )\n      );\n\n      // Create the second Observable that updates the state\n      updater(\n        timer(15, 10).pipe(\n          map((v) => ({ value: 'b' + v })),\n          take(10)\n        )\n      );\n\n      // Advance for 40 fake milliseconds and unsubscribe - should capture\n      // from '0' to '3'\n      vi.advanceTimersByTime(40);\n      subscription.unsubscribe();\n\n      // Advance for 30 more fake milliseconds, to make sure that second\n      // Observable still emits\n      vi.advanceTimersByTime(30);\n\n      expect(results).toEqual([\n        // First value is here due to ReplaySubject being at the heart of\n        // ComponentStore.\n        { value: 'init' },\n        { value: 'a0' },\n        { value: 'b0' },\n        { value: 'a1' },\n        { value: 'b1' },\n        { value: 'a2' },\n        { value: 'b2' },\n        { value: 'a3' },\n        { value: 'b3' },\n        { value: 'b4' },\n        { value: 'b5' }, // second Observable continues to emit values\n      ]);\n    });\n  });\n\n  describe('patches the state', () => {\n    interface State {\n      value1: string;\n      value2: { foo: string };\n    }\n    const INIT_STATE: State = { value1: 'value1', value2: { foo: 'bar' } };\n    let componentStore: ComponentStore<State>;\n\n    beforeEach(() => {\n      componentStore = new ComponentStore(INIT_STATE);\n    });\n\n    it(\n      'with a specific value',\n      marbles((m) => {\n        componentStore.patchState({ value1: 'val1' });\n\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('s', {\n            s: { ...INIT_STATE, value1: 'val1' },\n          })\n        );\n      })\n    );\n\n    it(\n      'with the values from Observable',\n      marbles((m) => {\n        componentStore.patchState(\n          from([\n            { value1: 'foo' },\n            { value2: { foo: 'foo2' } },\n            { value1: 'baz' },\n          ]).pipe(concatMap((partialState) => of(partialState).pipe(delay(3))))\n        );\n\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('a--b--c--d', {\n            a: INIT_STATE,\n            b: { ...INIT_STATE, value1: 'foo' },\n            c: { value1: 'foo', value2: { foo: 'foo2' } },\n            d: { value1: 'baz', value2: { foo: 'foo2' } },\n          })\n        );\n      })\n    );\n\n    it(\n      'with a value based on the previous state',\n      marbles((m) => {\n        componentStore.patchState((state) => ({\n          value2: { foo: `${state.value2.foo}2` },\n        }));\n\n        m.expect(componentStore.state$).toBeObservable(\n          m.hot('s', {\n            s: { ...INIT_STATE, value2: { foo: 'bar2' } },\n          })\n        );\n      })\n    );\n  });\n\n  describe('throws an error', () => {\n    it('synchronously when synchronous error is thrown within updater', () => {\n      const componentStore = new ComponentStore({});\n      const error = new Error('ERROR!');\n      const updater = componentStore.updater(() => {\n        throw error;\n      });\n\n      expect(() => updater()).toThrow(error);\n    });\n\n    it('synchronously when synchronous error is thrown within setState callback', () => {\n      const componentStore = new ComponentStore({});\n      const error = new Error('ERROR!');\n\n      expect(() => {\n        componentStore.setState(() => {\n          throw error;\n        });\n      }).toThrow(error);\n    });\n\n    it('synchronously when synchronous error is thrown within patchState callback', () => {\n      const componentStore = new ComponentStore({});\n      const error = new Error('ERROR!');\n\n      expect(() => {\n        componentStore.patchState(() => {\n          throw error;\n        });\n      }).toThrow(error);\n    });\n\n    it('synchronously when synchronous observable throws an error with updater', () => {\n      const componentStore = new ComponentStore({});\n      const error = new Error('ERROR!');\n      const updater = componentStore.updater<unknown>(() => ({}));\n\n      expect(() => {\n        updater(throwError(() => error));\n      }).toThrow(error);\n    });\n\n    it('synchronously when synchronous observable throws an error with patchState', () => {\n      const componentStore = new ComponentStore({});\n      const error = new Error('ERROR!');\n\n      expect(() => {\n        componentStore.patchState(throwError(() => error));\n      }).toThrow(error);\n    });\n\n    it(\n      'asynchronously when asynchronous observable throws an error with updater',\n      marbles((m) => {\n        const componentStore = new ComponentStore({});\n        const error = new Error('ERROR!');\n        const updater = componentStore.updater<unknown>(() => ({}));\n        const asyncObs$ = m.cold('-#', {}, error);\n\n        expect(() => {\n          try {\n            updater(asyncObs$);\n          } catch {\n            throw new Error('updater should not throw an error synchronously');\n          }\n\n          m.flush();\n        }).toThrow(error);\n      })\n    );\n\n    it(\n      'asynchronously when asynchronous observable throws an error with patchState',\n      marbles((m) => {\n        const componentStore = new ComponentStore({});\n        const error = new Error('ERROR!');\n        const asyncObs$ = m.cold('-#', {}, error);\n\n        expect(() => {\n          try {\n            componentStore.patchState(asyncObs$);\n          } catch {\n            throw new Error(\n              'patchState should not throw an error synchronously'\n            );\n          }\n\n          m.flush();\n        }).toThrow(error);\n      })\n    );\n  });\n\n  describe('selector', () => {\n    interface State {\n      value: string;\n      updated?: boolean;\n    }\n\n    const INIT_STATE: State = { value: 'init' };\n    let componentStore: ComponentStore<State>;\n\n    beforeEach(() => {\n      componentStore = new ComponentStore<State>(INIT_STATE);\n    });\n\n    it(\n      'uninitialized Component Store does not emit values',\n      marbles((m) => {\n        const uninitializedComponentStore = new ComponentStore();\n        m.expect(uninitializedComponentStore.select((s) => s)).toBeObservable(\n          m.hot('-')\n        );\n      })\n    );\n\n    it(\n      'selects component root state',\n      marbles((m) => {\n        m.expect(componentStore.select((s) => s)).toBeObservable(\n          m.hot('i', { i: INIT_STATE })\n        );\n      })\n    );\n\n    it(\n      'selects component property from the state',\n      marbles((m) => {\n        m.expect(componentStore.select((s) => s.value)).toBeObservable(\n          m.hot('i', { i: INIT_STATE.value })\n        );\n      })\n    );\n\n    it('reads the values synchronously', () => {\n      const selector = componentStore.select((s) => s.value);\n      let result: string;\n\n      // Initial value is available\n      selector.subscribe((v) => (result = v)).unsubscribe();\n\n      expect(result!).toBe('init');\n\n      // overwritten state\n      componentStore.setState({ value: 'setState update' });\n      selector.subscribe((v) => (result = v)).unsubscribe();\n\n      expect(result!).toBe('setState update');\n\n      // with setState callback\n      componentStore.setState((state) => ({\n        value: state.value + ' adjusted',\n      }));\n      selector.subscribe((v) => (result = v)).unsubscribe();\n\n      expect(result!).toBe('setState update adjusted');\n\n      // with updater\n      componentStore.updater((state, value: string) => ({ value }))(\n        'updater value'\n      );\n      selector.subscribe((v) => (result = v)).unsubscribe();\n\n      expect(result!).toBe('updater value');\n\n      // with updater and sync Observable\n      componentStore.updater((state, value: string) => ({ value }))(\n        of('updater observable value')\n      );\n      selector.subscribe((v) => (result = v)).unsubscribe();\n\n      expect(result!).toBe('updater observable value');\n    });\n\n    it('can be combined with other selectors', () => {\n      const selector1 = componentStore.select((s) => s.value);\n      const selector2 = componentStore.select((s) => s.updated);\n      const selector3 = componentStore.select(\n        selector1,\n        selector2,\n        // Returning an object to make sure that distinctUntilChanged does\n        // not hold it\n        (s1, s2) => ({ result: s2 ? s1 : 'empty' })\n      );\n\n      const selectorResults: Array<{ result: string }> = [];\n      selector3.subscribe((s3) => {\n        selectorResults.push(s3);\n      });\n\n      componentStore.setState(() => ({ value: 'new value', updated: true }));\n\n      expect(selectorResults).toEqual([\n        { result: 'empty' },\n        { result: 'empty' }, // both \"value\" and \"updated\" are changed\n        { result: 'new value' },\n      ]);\n    });\n\n    it('can combine into an object through selectorObject', () => {\n      const selector1 = componentStore.select((s) => s.value);\n      const selector2 = componentStore.select((s) => s.updated);\n      const selector3 = componentStore.select({\n        s1: selector1,\n        s2: selector2,\n      });\n\n      const selectorResults: Array<{\n        s1: string;\n        s2: boolean | undefined;\n      }> = [];\n      selector3.subscribe((s3) => {\n        selectorResults.push(s3);\n      });\n\n      componentStore.setState(() => ({ value: 'new value', updated: true }));\n\n      expect(selectorResults).toEqual([\n        { s1: 'init', s2: undefined },\n        { s1: 'new value', s2: undefined }, // not debounced\n        { s1: 'new value', s2: true },\n      ]);\n    });\n\n    it('can combine into an object through a single selectorObject', () => {\n      const selector1 = componentStore.select((s) => s.value);\n\n      const selector2 = componentStore.select({\n        s1: selector1,\n      });\n\n      const selectorResults: Array<{\n        s1: string;\n      }> = [];\n      selector2.subscribe((s2) => {\n        selectorResults.push(s2);\n      });\n\n      componentStore.setState(() => ({ value: 'new value', updated: true }));\n\n      expect(selectorResults).toEqual([{ s1: 'init' }, { s1: 'new value' }]);\n    });\n\n    it('can combine into an object through selectorObject with debounce', fakeAsync(() => {\n      const selector1 = componentStore.select((s) => s.value);\n      const selector2 = componentStore.select((s) => s.updated);\n      const selector3 = componentStore.select(\n        {\n          s1: selector1,\n          s2: selector2,\n        },\n        { debounce: true }\n      );\n\n      const selectorResults: Array<{\n        s1: string;\n        s2: boolean | undefined;\n      }> = [];\n      selector3.subscribe((s3) => {\n        selectorResults.push(s3);\n      });\n      flushMicrotasks();\n\n      componentStore.setState(() => ({ value: 'new value', updated: true }));\n      flushMicrotasks();\n\n      expect(selectorResults).toEqual([\n        { s1: 'init', s2: undefined },\n        // debounced, so new value for both\n        { s1: 'new value', s2: true },\n      ]);\n    }));\n\n    it(\n      'can combine with other Observables',\n      marbles((m) => {\n        const observableValues = {\n          '1': 'one',\n          '2': 'two',\n          '3': 'three',\n        };\n\n        const observable$ = m.hot('      1----2---3', observableValues);\n        const updater$ = m.cold('        a-----b--c|');\n        const expectedSelector$ = m.hot('(uv)-wx--(yz)-', {\n          u: 'one init', // 👈 includes initial value\n          v: 'one a',\n          w: 'two a',\n          x: 'two b',\n          y: 'three b', // 👈 no debounce\n          z: 'three c',\n        });\n\n        const selectorValue$ = componentStore.select((s) => s.value);\n        const selector$ = componentStore.select(\n          selectorValue$,\n          observable$,\n          (s1, o) => o + ' ' + s1\n        );\n\n        scheduled(updater$, asyncScheduler).subscribe((value) => {\n          componentStore.setState({ value });\n        });\n\n        m.expect(selector$).toBeObservable(expectedSelector$);\n      })\n    );\n\n    it(\n      'would emit a value whenever any of selectors produce values',\n      marbles((m) => {\n        const s1$ = componentStore.select((s) => `fromS1(${s.value})`);\n        const s2$ = componentStore.select((s) => `fromS2(${s.value})`);\n        const s3$ = componentStore.select((s) => `fromS3(${s.value})`);\n        const s4$ = componentStore.select((s) => `fromS4(${s.value})`);\n\n        const selector$ = componentStore.select(\n          s1$,\n          s2$,\n          s3$,\n          s4$,\n          (s1, s2, s3, s4) => `${s1} & ${s2} & ${s3} & ${s4}`\n        );\n\n        const updater$ = m.cold('        -----e-|');\n        const expectedSelector$ = m.hot('i----(abcd)--', {\n          //                     initial👆    👆 emits multiple times\n          i: 'fromS1(init) & fromS2(init) & fromS3(init) & fromS4(init)',\n          a: 'fromS1(e) & fromS2(init) & fromS3(init) & fromS4(init)',\n          b: 'fromS1(e) & fromS2(e) & fromS3(init) & fromS4(init)',\n          c: 'fromS1(e) & fromS2(e) & fromS3(e) & fromS4(init)',\n          d: 'fromS1(e) & fromS2(e) & fromS3(e) & fromS4(e)',\n        });\n\n        componentStore.updater((_, newValue: string) => ({\n          value: newValue,\n        }))(updater$);\n\n        m.expect(selector$).toBeObservable(expectedSelector$);\n      })\n    );\n\n    it(\n      'can combine with Observables that complete',\n      marbles((m) => {\n        const observableValues = {\n          '1': 'one',\n          '2': 'two',\n          '3': 'three',\n        };\n\n        const observable$ = m.hot('      1----2---3|', observableValues);\n        const updater$ = m.cold('        a-----b--c|');\n        const expectedSelector$ = m.hot('(uv)-wx--(yz)-', {\n          u: 'one init', // 👈 includes initial value\n          v: 'one a',\n          w: 'two a',\n          x: 'two b',\n          y: 'three b', // 👈 no debounce\n          z: 'three c',\n        });\n\n        const selectorValue$ = componentStore.select((s) => s.value);\n        const selector$ = componentStore.select(\n          selectorValue$,\n          observable$,\n          (s1, o) => o + ' ' + s1\n        );\n\n        scheduled(updater$, asyncScheduler).subscribe((value) => {\n          componentStore.setState({ value });\n        });\n\n        m.expect(selector$).toBeObservable(expectedSelector$);\n      })\n    );\n\n    it(\n      'does not emit the same value if it did not change',\n      marbles((m) => {\n        const selector1 = componentStore.select((s) => s.value);\n        const selector2 = componentStore.select((s) => s.updated);\n        const selector3 = componentStore.select(\n          selector1,\n          selector2,\n          // returning the same value, which should be caught by\n          // distinctUntilChanged\n          () => 'selector3 result'\n        );\n\n        const selectorResults: string[] = [];\n        selector3.subscribe((s3) => {\n          selectorResults.push(s3);\n        });\n\n        m.flush();\n        componentStore.setState(() => ({ value: 'new value', updated: true }));\n\n        m.flush();\n        expect(selectorResults).toEqual(['selector3 result']);\n      })\n    );\n\n    it(\n      'are shared between subscribers',\n      marbles((m) => {\n        const projectorCallback = vi.fn((s) => s.value);\n        const selector = componentStore.select(projectorCallback);\n\n        const resultsArray: string[] = [];\n        selector.subscribe((value) =>\n          resultsArray.push('subscriber1: ' + value)\n        );\n        selector.subscribe((value) =>\n          resultsArray.push('subscriber2: ' + value)\n        );\n\n        m.flush();\n        componentStore.setState(() => ({ value: 'new value', updated: true }));\n        m.flush();\n\n        // Even though we have 2 subscribers for 2 values, the projector\n        // function is called only twice - once for each new value.\n        expect(projectorCallback.mock.calls.length).toBe(2);\n      })\n    );\n\n    it('complete when componentStore is destroyed', () =>\n      new Promise<void>((doneFn) => {\n        const selector = componentStore.select(() => ({}));\n\n        selector.subscribe({\n          complete: () => {\n            doneFn();\n          },\n        });\n\n        componentStore.ngOnDestroy();\n      }));\n\n    it('supports memoization with createSelector', () => {\n      const projectorCallback = vi.fn((str: string) => str);\n      const memoizedSelector = createSelector(\n        (s: State) => s.value,\n        projectorCallback\n      );\n      const selector = componentStore.select(memoizedSelector);\n\n      // first call to memoizedSelector\n      const subscription = selector.subscribe();\n      // second call to memoizedSelector with the same value\n      componentStore.setState(INIT_STATE);\n\n      subscription.unsubscribe();\n      expect(projectorCallback).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe('selector with debounce', () => {\n    interface State {\n      value: string;\n      updated?: boolean;\n    }\n\n    const INIT_STATE: State = { value: 'init' };\n    let componentStore: ComponentStore<State>;\n\n    beforeEach(() => {\n      componentStore = new ComponentStore<State>(INIT_STATE);\n    });\n\n    it(\n      'uninitialized Component Store does not emit values',\n      marbles((m) => {\n        const uninitializedComponentStore = new ComponentStore();\n        m.flush();\n        m.expect(\n          uninitializedComponentStore.select((s) => s, { debounce: true })\n        ).toBeObservable(m.hot('-'));\n      })\n    );\n\n    it(\n      'selects component root state',\n      marbles((m) => {\n        m.flush();\n        m.expect(\n          componentStore.select((s) => s, { debounce: true })\n        ).toBeObservable(m.hot('i', { i: INIT_STATE }));\n      })\n    );\n\n    it(\n      'selects component property from the state',\n      marbles((m) => {\n        m.flush();\n        m.expect(\n          componentStore.select((s) => s.value, { debounce: true })\n        ).toBeObservable(m.hot('i', { i: INIT_STATE.value }));\n      })\n    );\n\n    it(\n      'reads the values asynchronously',\n      marbles((m) => {\n        const selector = componentStore.select((s) => s.value, {\n          debounce: true,\n        });\n        let result: string | undefined;\n        let sub: Subscription;\n\n        // Initial value is available\n        sub = selector.subscribe((v) => (result = v));\n        expect(result).toBe(undefined);\n        m.flush();\n        sub.unsubscribe();\n\n        expect(result!).toBe('init');\n        result = undefined;\n\n        // overwritten state\n        componentStore.setState({ value: 'setState update' });\n        sub = selector.subscribe((v) => (result = v));\n        expect(result).toBe(undefined);\n        m.flush();\n        sub.unsubscribe();\n\n        expect(result).toBe('setState update');\n        result = undefined;\n\n        // with setState callback\n        componentStore.setState((state) => ({\n          value: state.value + ' adjusted',\n        }));\n        sub = selector.subscribe((v) => (result = v));\n        expect(result).toBe(undefined);\n        m.flush();\n        sub.unsubscribe();\n\n        expect(result!).toBe('setState update adjusted');\n        result = undefined;\n\n        // with updater\n        componentStore.updater((state, value: string) => ({ value }))(\n          'updater value'\n        );\n        sub = selector.subscribe((v) => (result = v));\n        expect(result).toBe(undefined);\n        m.flush();\n        sub.unsubscribe();\n\n        expect(result!).toBe('updater value');\n        result = undefined;\n\n        // with updater and sync Observable\n        componentStore.updater((state, value: string) => ({ value }))(\n          of('updater observable value')\n        );\n        sub = selector.subscribe((v) => (result = v));\n        expect(result).toBe(undefined);\n        m.flush();\n        sub.unsubscribe();\n\n        expect(result!).toBe('updater observable value');\n      })\n    );\n\n    it(\n      'can be combined with other selectors',\n      marbles((m) => {\n        const selector1 = componentStore.select((s) => s.value);\n        const selector2 = componentStore.select((s) => s.updated);\n        const selector3 = componentStore.select(\n          selector1,\n          selector2,\n          // Returning an object to make sure that distinctUntilChanged does\n          // not hold it\n          (s1, s2) => ({ result: s2 ? s1 : 'empty' }),\n          { debounce: true }\n        );\n\n        const selectorResults: Array<{ result: string }> = [];\n        selector3.subscribe((s3) => {\n          selectorResults.push(s3);\n        });\n\n        componentStore.setState(() => ({ value: 'new value', updated: true }));\n        m.flush();\n\n        expect(selectorResults).toEqual([{ result: 'new value' }]);\n      })\n    );\n\n    it(\n      'can combine with other Observables',\n      marbles((m) => {\n        const observableValues = {\n          '1': 'one',\n          '2': 'two',\n          '3': 'three',\n        };\n\n        const observable$ = m.hot('      1-2---3', observableValues);\n        const updater$ = m.cold('        a--b--c|');\n        const expectedSelector$ = m.hot('w-xy--z-', {\n          w: 'one a',\n          x: 'two a',\n          y: 'two b',\n          // 'three b', // 👈 with debounce\n          z: 'three c',\n        });\n\n        const selectorValue$ = componentStore.select((s) => s.value);\n        const selector$ = componentStore.select(\n          selectorValue$,\n          observable$,\n          (s1, o) => o + ' ' + s1,\n          { debounce: true }\n        );\n\n        scheduled(updater$, queueScheduler).subscribe((value) => {\n          componentStore.setState({ value });\n        });\n\n        m.expect(selector$).toBeObservable(expectedSelector$);\n      })\n    );\n\n    it(\n      'would emit a single value even when all 4 selectors produce values',\n      marbles((m) => {\n        const s1$ = componentStore.select((s) => `fromS1(${s.value})`);\n        const s2$ = componentStore.select((s) => `fromS2(${s.value})`);\n        const s3$ = componentStore.select((s) => `fromS3(${s.value})`);\n        const s4$ = componentStore.select((s) => `fromS4(${s.value})`);\n\n        const selector$ = componentStore.select(\n          s1$,\n          s2$,\n          s3$,\n          s4$,\n          (s1, s2, s3, s4) => `${s1} & ${s2} & ${s3} & ${s4}`,\n          { debounce: true }\n        );\n\n        const updater$ = m.cold('        -----e-|');\n        const expectedSelector$ = m.hot('i----c--', {\n          //                     initial👆   👆 combined single value\n          i: 'fromS1(init) & fromS2(init) & fromS3(init) & fromS4(init)',\n          c: 'fromS1(e) & fromS2(e) & fromS3(e) & fromS4(e)',\n        });\n\n        componentStore.updater((_, newValue: string) => ({\n          value: newValue,\n        }))(updater$);\n\n        m.expect(selector$).toBeObservable(expectedSelector$);\n      })\n    );\n\n    it(\n      'can combine with Observables that complete',\n      marbles((m) => {\n        const observableValues = {\n          '1': 'one',\n          '2': 'two',\n          '3': 'three',\n        };\n\n        const observable$ = m.hot('      1-2---3', observableValues);\n        const updater$ = m.cold('        a--b--c|');\n        const expectedSelector$ = m.hot('w-xy--z-', {\n          w: 'one a',\n          x: 'two a',\n          y: 'two b',\n          z: 'three c',\n        });\n\n        const selectorValue$ = componentStore.select((s) => s.value);\n        const selector$ = componentStore.select(\n          selectorValue$,\n          observable$,\n          (s1, o) => o + ' ' + s1,\n          { debounce: true }\n        );\n\n        scheduled(updater$, queueScheduler).subscribe((value) => {\n          componentStore.setState({ value });\n        });\n\n        m.expect(selector$).toBeObservable(expectedSelector$);\n      })\n    );\n\n    it(\n      'does not emit the same value if it did not change',\n      marbles((m) => {\n        const selector1 = componentStore.select((s) => s.value);\n        const selector2 = componentStore.select((s) => s.updated);\n        const selector3 = componentStore.select(\n          selector1,\n          selector2,\n          // returning the same value, which should be caught by\n          // distinctUntilChanged\n          () => 'selector3 result',\n          { debounce: true }\n        );\n\n        const selectorResults: string[] = [];\n        selector3.subscribe((s3) => {\n          selectorResults.push(s3);\n        });\n\n        m.flush();\n        componentStore.setState(() => ({ value: 'new value', updated: true }));\n\n        m.flush();\n        expect(selectorResults).toEqual(['selector3 result']);\n      })\n    );\n\n    it(\n      'are shared between subscribers',\n      marbles((m) => {\n        const projectorCallback = vi.fn((s) => s.value);\n        const selector = componentStore.select(projectorCallback, {\n          debounce: true,\n        });\n\n        const resultsArray: string[] = [];\n        selector.subscribe((value) =>\n          resultsArray.push('subscriber1: ' + value)\n        );\n        selector.subscribe((value) =>\n          resultsArray.push('subscriber2: ' + value)\n        );\n\n        m.flush();\n        componentStore.setState(() => ({ value: 'new value', updated: true }));\n        m.flush();\n\n        // Even though we have 2 subscribers for 2 values, the projector\n        // function is called only twice - once for each new value.\n        expect(projectorCallback.mock.calls.length).toBe(2);\n      })\n    );\n\n    it('complete when componentStore is destroyed', () =>\n      new Promise((doneFn) => {\n        const selector = componentStore.select(() => ({}), { debounce: true });\n\n        selector.subscribe({\n          complete: () => {\n            doneFn();\n          },\n        });\n\n        componentStore.ngOnDestroy();\n      }));\n  });\n\n  describe('selector with custom equal fn', () => {\n    interface State {\n      obj: StateValue;\n      updated?: boolean;\n    }\n    interface StateValue {\n      value: string;\n    }\n\n    const equal: ValueEqualityFn<StateValue> = (a, b) => a.value === b.value;\n    const INIT_STATE: State = { obj: { value: 'init' } };\n    let componentStore: ComponentStore<State>;\n\n    beforeEach(() => {\n      componentStore = new ComponentStore<State>(INIT_STATE);\n    });\n\n    it(\n      'does not emit the same value if it did not change',\n      marbles((m) => {\n        const selector = componentStore.select((s) => s.obj, {\n          equal,\n        });\n\n        const selectorResults: string[] = [];\n        selector.subscribe((value) => {\n          selectorResults.push(value.value);\n        });\n\n        m.flush();\n        componentStore.setState(() => ({ obj: { value: 'new value' } }));\n        componentStore.setState(() => ({ obj: { value: 'new value' } })); // 👈 emit twice\n\n        m.flush();\n        expect(selectorResults).toEqual(['init', 'new value']); // capture only one change\n      })\n    );\n  });\n\n  describe('selectSignal', () => {\n    it('creates a signal from the provided state projector function', () => {\n      const store = new ComponentStore<{ foo: string }>({ foo: 'bar' });\n      let projectorExecutionCount = 0;\n\n      const foo = store.selectSignal((state) => {\n        projectorExecutionCount++;\n        return state.foo;\n      });\n\n      expect(foo()).toBe('bar');\n\n      foo();\n      store.patchState({ foo: 'baz' });\n      foo();\n\n      expect(foo()).toBe('baz');\n      expect(projectorExecutionCount).toBe(2);\n    });\n\n    it('creates a signal from the provided state projector function with options', () => {\n      const store = new ComponentStore<{ arr: number[] }>({\n        arr: [10, 20, 30],\n      });\n      let projectorExecutionCount = 0;\n\n      const array = store.selectSignal(\n        (x) => {\n          projectorExecutionCount++;\n          return x.arr;\n        },\n        {\n          equal: (a, b) => a.length === b.length,\n        }\n      );\n\n      array();\n      const result1 = array();\n      expect(result1).toEqual([10, 20, 30]);\n\n      store.patchState({ arr: [30, 20, 10] });\n\n      // should be equal to the previous value because of the custom equality\n      array();\n      const result2 = array();\n      expect(result2).toEqual([10, 20, 30]);\n      expect(result2).toBe(result1);\n      expect(projectorExecutionCount).toBe(2);\n\n      store.patchState({ arr: [10] });\n      array();\n      expect(array()).toEqual([10]);\n      expect(projectorExecutionCount).toBe(3);\n    });\n\n    it('creates a signal by combining provided signals', () => {\n      const store = new ComponentStore<{ x: number; y: number; z: number }>({\n        x: 1,\n        y: 10,\n        z: 100,\n      });\n      let projectorExecutionCount = 0;\n\n      const x = store.selectSignal((s) => s.x);\n      const y = store.selectSignal((s) => s.y);\n      const xPlusY = store.selectSignal(x, y, (x, y) => {\n        projectorExecutionCount++;\n        return x + y;\n      });\n\n      expect(xPlusY()).toBe(11);\n\n      // projector should not be executed\n      store.patchState({ z: 1000 });\n      xPlusY();\n\n      store.patchState({ x: 10 });\n      xPlusY();\n\n      expect(xPlusY()).toBe(20);\n      expect(projectorExecutionCount).toBe(2);\n    });\n\n    it('creates a signal by combining provided signals with options', () => {\n      const store = new ComponentStore<{ x: number; y: number }>({\n        x: 1,\n        y: 10,\n      });\n      let projectorExecutionCount = 0;\n\n      const x = store.selectSignal((s) => s.x);\n      const y = store.selectSignal((s) => s.y);\n      const xPlusY = store.selectSignal(\n        x,\n        y,\n        (x, y) => {\n          projectorExecutionCount++;\n          return x + y;\n        },\n        {\n          equal: (a: number, b: number) => Math.round(a) === Math.round(b),\n        }\n      );\n\n      expect(xPlusY()).toBe(11);\n\n      store.patchState({ x: 1.2 });\n      xPlusY();\n\n      // should be equal to the previous value because of the custom equality\n      expect(xPlusY()).toBe(11);\n      expect(projectorExecutionCount).toBe(2);\n\n      store.patchState({ x: 1.8 });\n      xPlusY();\n      expect(xPlusY()).toBe(11.8);\n      expect(projectorExecutionCount).toBe(3);\n    });\n\n    it('uses default equality function when equality function is not provided', () => {\n      type TestState = { foo: number; bar: { baz: number } };\n\n      class TestStore extends ComponentStore<TestState> {\n        readonly foo = this.selectSignal((s) => s.foo);\n        readonly bar = this.selectSignal((s) => s.bar);\n\n        #fooExecutionCount = 0;\n        readonly fooExecutionCount = computed(() => {\n          this.foo();\n          return ++this.#fooExecutionCount;\n        });\n\n        #barExecutionCount = 0;\n        readonly barExecutionCount = computed(() => {\n          this.bar();\n          return ++this.#barExecutionCount;\n        });\n\n        constructor() {\n          super({ foo: 0, bar: { baz: 0 } });\n        }\n      }\n\n      const store = new TestStore();\n      expect(store.fooExecutionCount()).toBe(1);\n      expect(store.barExecutionCount()).toBe(1);\n\n      store.patchState({ foo: 10 });\n      expect(store.fooExecutionCount()).toBe(2);\n      // bar should not be executed\n      expect(store.barExecutionCount()).toBe(1);\n\n      store.patchState({ foo: 10 });\n      // nothing updated, so execution count should remain the same\n      expect(store.fooExecutionCount()).toBe(2);\n      expect(store.barExecutionCount()).toBe(1);\n\n      store.patchState({ bar: { baz: 100 } });\n      // foo should not be executed\n      expect(store.fooExecutionCount()).toBe(2);\n      expect(store.barExecutionCount()).toBe(2);\n\n      store.patchState(({ bar }) => ({ bar }));\n      // nothing updated, so execution count should remain the same\n      expect(store.fooExecutionCount()).toBe(2);\n      expect(store.barExecutionCount()).toBe(2);\n\n      store.patchState({ foo: 1000 });\n      expect(store.fooExecutionCount()).toBe(3);\n      // bar should not be executed\n      expect(store.barExecutionCount()).toBe(2);\n    });\n\n    it('throws an error when the signal is read before the state initialization', () => {\n      const store = new ComponentStore<{ foo: string }>();\n      const foo = store.selectSignal((s) => s.foo);\n\n      expect(() => foo()).toThrowError();\n    });\n  });\n\n  describe('effect', () => {\n    let componentStore: ComponentStore<object>;\n\n    beforeEach(() => {\n      componentStore = new ComponentStore<object>();\n    });\n\n    it(\n      'is run when value is provided',\n      marbles((m) => {\n        const results: string[] = [];\n        const mockGenerator = vi.fn((origin$: Observable<string>) =>\n          origin$.pipe(tap((v) => results.push(v)))\n        );\n        const effect = componentStore.effect(mockGenerator);\n        effect('value 1');\n        effect('value 2');\n\n        expect(results).toEqual(['value 1', 'value 2']);\n      })\n    );\n\n    it(\n      'is run when undefined value is provided',\n      marbles((m) => {\n        const results: string[] = [];\n        const mockGenerator = vi.fn((origin$: Observable<undefined>) =>\n          origin$.pipe(tap((v) => results.push(typeof v)))\n        );\n        const effect = componentStore.effect(mockGenerator);\n        effect();\n        effect();\n\n        expect(results).toEqual(['undefined', 'undefined']);\n      })\n    );\n\n    it(\n      'is run when observable is provided',\n      marbles((m) => {\n        const mockGenerator = vi.fn((origin$) => origin$);\n        const effect = componentStore.effect<string>(mockGenerator);\n\n        effect(m.cold('-a-b-c|'));\n\n        m.expect(mockGenerator.mock.calls[0][0]).toBeObservable(\n          m.hot('      -a-b-c-')\n        );\n      })\n    );\n    it(\n      'is run with multiple Observables',\n      marbles((m) => {\n        const mockGenerator = vi.fn((origin$) => origin$);\n        const effect = componentStore.effect<string>(mockGenerator);\n\n        effect(m.cold('-a-b-c|'));\n        effect(m.hot(' --d--e----f-'));\n\n        m.expect(mockGenerator.mock.calls[0][0]).toBeObservable(\n          m.hot('      -adb-(ce)-f-')\n        );\n      })\n    );\n\n    describe('cancels effect Observable', () => {\n      beforeEach(() => vi.useFakeTimers());\n      afterEach(() => vi.useRealTimers());\n      it('by unsubscribing with returned Subscription', () => {\n        const results: string[] = [];\n        const effect = componentStore.effect((origin$: Observable<string>) =>\n          origin$.pipe(tap((v) => results.push(v)))\n        );\n\n        const observable$ = interval(10).pipe(\n          map((v) => String(v)),\n          take(10) // just in case\n        );\n\n        // Update with Observable.\n        const subscription = effect(observable$);\n\n        // Advance for 40 fake milliseconds and unsubscribe - should capture\n        // from '0' to '3'\n        vi.advanceTimersByTime(40);\n        subscription.unsubscribe();\n\n        // Advance for 20 more fake milliseconds, to check if anything else\n        // is captured\n        vi.advanceTimersByTime(20);\n\n        expect(results).toEqual(['0', '1', '2', '3']);\n      });\n      it(\n        'could be unsubscribed from the specific Observable when multiple' +\n          ' are provided',\n        () => {\n          // Record all the values that go through state$ into an array\n          const results: Array<{ value: string }> = [];\n          const effect = componentStore.effect(\n            (origin$: Observable<{ value: string }>) =>\n              origin$.pipe(tap((v) => results.push(v)))\n          );\n\n          // Pass the first Observable to the effect.\n          const subscription = effect(\n            interval(10).pipe(\n              map((v) => ({ value: 'a' + v })),\n              take(10) // just in case\n            )\n          );\n\n          // Pass the second Observable that pushes values to effect\n          effect(\n            timer(15, 10).pipe(\n              map((v) => ({ value: 'b' + v })),\n              take(10)\n            )\n          );\n\n          // Advance for 40 fake milliseconds and unsubscribe - should capture\n          // from '0' to '3'\n          vi.advanceTimersByTime(40);\n          subscription.unsubscribe();\n\n          // Advance for 30 more fake milliseconds, to make sure that second\n          // Observable still emits\n          vi.advanceTimersByTime(30);\n\n          expect(results).toEqual([\n            { value: 'a0' },\n            { value: 'b0' },\n            { value: 'a1' },\n            { value: 'b1' },\n            { value: 'a2' },\n            { value: 'b2' },\n            { value: 'a3' },\n            { value: 'b3' },\n            { value: 'b4' },\n            { value: 'b5' }, // second Observable continues to emit values\n          ]);\n        }\n      );\n\n      it('completes when componentStore is destroyed', () =>\n        new Promise<void>((doneFn) => {\n          componentStore.effect((origin$: Observable<number>) =>\n            origin$.pipe(\n              finalize(() => {\n                doneFn();\n              })\n            )\n          )(interval(10));\n\n          setTimeout(() => componentStore.ngOnDestroy(), 20);\n          vi.advanceTimersByTime(20);\n        }));\n\n      it('observable argument completes when componentStore is destroyed', () =>\n        new Promise<void>((doneFn) => {\n          componentStore.effect((origin$: Observable<number>) => origin$)(\n            interval(10).pipe(\n              finalize(() => {\n                doneFn();\n              })\n            )\n          );\n\n          setTimeout(() => componentStore.ngOnDestroy(), 20);\n\n          vi.advanceTimersByTime(20);\n        }));\n    });\n  });\n\n  describe('get', () => {\n    interface State {\n      value: string;\n    }\n\n    class ExposedGetComponentStore extends ComponentStore<State> {\n      override get = super.get;\n    }\n\n    let componentStore: ExposedGetComponentStore;\n\n    it('throws an Error if called before the state is initialized', () => {\n      componentStore = new ExposedGetComponentStore();\n\n      expect(() => {\n        componentStore.get((state) => state.value);\n      }).toThrow(\n        new Error(\n          'ExposedGetComponentStore has not been initialized yet. ' +\n            'Please make sure it is initialized before updating/getting.'\n        )\n      );\n    });\n\n    it('does not throw an Error when initialized', () => {\n      componentStore = new ExposedGetComponentStore();\n      componentStore.setState({ value: 'init' });\n\n      expect(() => {\n        componentStore.get((state) => state.value);\n      }).not.toThrow();\n    });\n\n    it('provides values from the state', () => {\n      componentStore = new ExposedGetComponentStore();\n      componentStore.setState({ value: 'init' });\n\n      expect(componentStore.get((state) => state.value)).toBe('init');\n\n      componentStore.updater((state, value: string) => ({ value }))('updated');\n\n      expect(componentStore.get((state) => state.value)).toBe('updated');\n    });\n\n    it('provides the entire state when projector fn is not provided', () => {\n      componentStore = new ExposedGetComponentStore();\n      componentStore.setState({ value: 'init' });\n\n      expect(componentStore.get()).toEqual({ value: 'init' });\n\n      componentStore.updater((state, value: string) => ({ value }))('updated');\n\n      expect(componentStore.get()).toEqual({ value: 'updated' });\n    });\n  });\n\n  describe('lifecycle hooks', () => {\n    interface LifeCycle {\n      init: boolean;\n    }\n\n    const onStoreInitMessage = 'on store init called';\n    const onStateInitMessage = 'on state init called';\n\n    const INIT_STATE = new InjectionToken('Init State');\n\n    @Injectable()\n    class LifecycleStore\n      extends ComponentStore<LifeCycle>\n      implements OnStoreInit, OnStateInit\n    {\n      logs: string[] = [];\n      constructor(@Inject(INIT_STATE) state?: LifeCycle) {\n        super(state);\n      }\n\n      logEffect = this.effect(\n        tap<void>(() => {\n          this.logs.push('effect');\n        })\n      );\n\n      ngrxOnStoreInit() {\n        this.logs.push(onStoreInitMessage);\n      }\n\n      ngrxOnStateInit() {\n        this.logs.push(onStateInitMessage);\n      }\n    }\n\n    @Injectable()\n    class ExtraStore extends LifecycleStore {\n      constructor() {\n        super();\n      }\n    }\n\n    @Injectable()\n    class NonProviderStore extends ComponentStore<{}> implements OnStoreInit {\n      ngrxOnStoreInit() {}\n    }\n\n    function setup({\n      initialState,\n      providers = [],\n    }: { initialState?: LifeCycle; providers?: Provider[] } = {}) {\n      const injector = Injector.create({\n        providers: [\n          { provide: INIT_STATE, useValue: initialState },\n          provideComponentStore(LifecycleStore),\n          providers,\n        ],\n      });\n\n      return {\n        store: injector.get(LifecycleStore),\n        injector,\n      };\n    }\n\n    it('should call the OnInitStore lifecycle hook if defined', () => {\n      const state = setup({ initialState: { init: true } });\n\n      expect(state.store.logs[0]).toBe(onStoreInitMessage);\n    });\n\n    it('should only call the OnInitStore lifecycle hook once', () => {\n      const state = setup({ initialState: { init: true } });\n      expect(state.store.logs[0]).toBe(onStoreInitMessage);\n\n      state.store.logs = [];\n      state.store.setState({ init: false });\n\n      expect(state.store.logs.length).toBe(0);\n    });\n\n    it('should call the OnInitState lifecycle hook if defined and state is set eagerly', () => {\n      const state = setup({ initialState: { init: true } });\n\n      expect(state.store.logs[1]).toBe(onStateInitMessage);\n    });\n\n    it('should call the OnInitState lifecycle hook if defined and after state is set lazily', () => {\n      const state = setup();\n      expect(state.store.logs.length).toBe(1);\n\n      state.store.setState({ init: true });\n\n      expect(state.store.logs[1]).toBe(onStateInitMessage);\n    });\n\n    it('should only call the OnInitStore lifecycle hook once', () => {\n      const state = setup({ initialState: { init: true } });\n\n      expect(state.store.logs[1]).toBe(onStateInitMessage);\n      state.store.logs = [];\n      state.store.setState({ init: false });\n\n      expect(state.store.logs.length).toBe(0);\n    });\n\n    it('works with multiple stores where one extends the other', () => {\n      const state = setup({\n        providers: [provideComponentStore(ExtraStore)],\n      });\n\n      const lifecycleStore = state.store;\n      const extraStore = state.injector.get(ExtraStore);\n\n      expect(lifecycleStore).toBeDefined();\n      expect(extraStore).toBeDefined();\n    });\n\n    it('should not log a warning when a ComponentStore with hooks is provided using provideComponentStore()', fakeAsync(() => {\n      vi.spyOn(console, 'warn').mockImplementation(() => void 0);\n\n      const state = setup();\n\n      const store = state.injector.get(LifecycleStore);\n\n      flushMicrotasks();\n      expect(store.ngrxOnStoreInit).toBeDefined();\n      expect(store['ɵhasProvider']).toBeTruthy();\n      expect(console.warn).not.toHaveBeenCalled();\n    }));\n\n    it('should log a warning when a hook is implemented without using provideComponentStore()', fakeAsync(() => {\n      vi.spyOn(console, 'warn').mockImplementation(() => void 0);\n\n      const state = setup({\n        providers: [NonProviderStore],\n      });\n\n      const store = state.injector.get(NonProviderStore);\n\n      flushMicrotasks();\n      expect(store.ngrxOnStoreInit).toBeDefined();\n      expect(store['ɵhasProvider']).toBeFalsy();\n      expect(console.warn).toHaveBeenCalled();\n    }));\n  });\n});\n"
  },
  {
    "path": "modules/component-store/spec/integration.spec.ts",
    "content": "import { Component, Type, Injectable } from '@angular/core';\nimport { ComponentStore } from '../src';\nimport {\n  TestBed,\n  ComponentFixture,\n  flushMicrotasks,\n  fakeAsync,\n  tick,\n} from '@angular/core/testing';\nimport { CommonModule } from '@angular/common';\nimport { interval, Observable, of, EMPTY } from 'rxjs';\nimport { tap, concatMap, catchError } from 'rxjs/operators';\nimport { By } from '@angular/platform-browser';\nimport { Mock, vi } from 'vitest';\n\ndescribe('ComponentStore integration', () => {\n  // The same set of tests is run against different versions of how\n  // ComponentStore may be used - making sure all of them work.\n  function testWith(setup: () => Promise<SetupData<Child>>) {\n    let state: SetupData<Child>;\n    beforeEach(async () => {\n      state = await setup();\n    });\n\n    it('does not emit until state is initialized', fakeAsync(() => {\n      expect(state.parent.isChildVisible).toBe(true);\n      expect(state.hasChild()).toBe(true);\n\n      state.fixture.detectChanges();\n\n      // No values emitted,             👇 no initial state\n      expect(state.propChanges).toEqual([]);\n      expect(state.prop2Changes).toEqual([]);\n    }));\n\n    it('gets initial value when state is initialized', fakeAsync(() => {\n      state.child.init();\n      //                            init state👇\n      expect(state.propChanges).toEqual(['initial Value']);\n      expect(state.prop2Changes).toEqual([undefined]);\n\n      // clear \"Periodic timers in queue\"\n      state.destroy();\n    }));\n\n    it('effect updates values', fakeAsync(() => {\n      state.child.init();\n\n      tick(40);\n      // New value pushed every 10 ms.\n      expect(state.prop2Changes).toEqual([undefined, 0, 1, 2, 3]);\n\n      // clear \"Periodic timers in queue\"\n      state.destroy();\n    }));\n\n    it('updates values imperatively', fakeAsync(() => {\n      state.child.init();\n\n      state.child.updateProp('new value');\n      state.child.updateProp('yay!!!');\n\n      expect(state.propChanges).toContain('new value');\n      expect(state.propChanges).toContain('yay!!!');\n\n      // clear \"Periodic timers in queue\"\n      state.destroy();\n    }));\n\n    it('selector emits values on any changes', fakeAsync(() => {\n      state.child.init();\n\n      state.child.updateProp('new value'); // no flushing in between\n      state.child.updateProp('yay!!!');\n\n      expect(state.propChanges).toEqual([\n        'initial Value',\n        'new value',\n        'yay!!!',\n      ]);\n\n      // clear \"Periodic timers in queue\"\n      state.destroy();\n    }));\n\n    it('selector with debounce emits value when microtasks are flushed', fakeAsync(() => {\n      state.child.init();\n\n      state.child.updateProp('new value'); // no flushing in between\n      state.child.updateProp('yay!!!');\n\n      expect(state.propChangesDebounce).toEqual([]);\n\n      flushMicrotasks();\n      expect(state.propChangesDebounce).toEqual(['yay!!!']);\n\n      // clear \"Periodic timers in queue\"\n      state.destroy();\n    }));\n\n    it('stops observables when destroyed', fakeAsync(() => {\n      state.child.init();\n\n      tick(40);\n      // New value pushed every 10 ms.\n      expect(state.prop2Changes).toEqual([undefined, 0, 1, 2, 3]);\n\n      state.parent.isChildVisible = false;\n      state.fixture.changeDetectorRef.markForCheck();\n      state.fixture.changeDetectorRef.detectChanges();\n\n      tick(20);\n      // Still at the same values, so effect stopped running\n      expect(state.prop2Changes).toEqual([undefined, 0, 1, 2, 3]);\n    }));\n\n    it('ComponentStore is destroyed', () => {\n      state.child.init();\n\n      state.parent.isChildVisible = false;\n      state.fixture.changeDetectorRef.markForCheck();\n      state.fixture.changeDetectorRef.detectChanges();\n\n      state.destroy();\n\n      expect(state.componentStoreDestroySpy).toHaveBeenCalled();\n    });\n  }\n\n  describe('Component uses ComponentStore directly in providers', () => {\n    testWith(setupComponentProvidesComponentStore);\n  });\n\n  describe('Component uses ComponentStore directly by extending it', () => {\n    testWith(setupComponentExtendsComponentStore);\n  });\n\n  describe('Component provides a Service that extends ComponentStore', () => {\n    testWith(setupComponentProvidesService);\n  });\n\n  describe('Component extends a Service that extends ComponentStore', () => {\n    testWith(setupComponentExtendsService);\n  });\n\n  describe('ComponentStore getter', () => {\n    let state: ReturnType<typeof setupComponentProvidesService> extends Promise<\n      infer P\n    >\n      ? P\n      : never;\n    beforeEach(async () => {\n      state = await setupComponentProvidesService();\n    });\n\n    it('provides correct instant values within effect', fakeAsync(() => {\n      state.child.init();\n\n      tick(40); // Prop2 should be at value '3' now\n      state.child.call('test one:');\n\n      expect(state.serviceCallSpy).toHaveBeenCalledWith('test one:3');\n\n      tick(20); // Prop2 should be at value '5' now\n      state.child.call('test two:');\n\n      expect(state.serviceCallSpy).toHaveBeenCalledWith('test two:5');\n\n      // clear \"Periodic timers in queue\"\n      state.destroy();\n    }));\n  });\n\n  interface State {\n    prop: string;\n    prop2?: number;\n  }\n\n  interface Parent {\n    isChildVisible: boolean;\n  }\n\n  interface Child {\n    prop$: Observable<string>;\n    prop2$: Observable<number | undefined>;\n    propDebounce$: Observable<string>;\n    init: () => void;\n    updateProp(value: string): void;\n  }\n\n  interface SetupData<T extends Child> {\n    fixture: ComponentFixture<Parent>;\n    parent: Parent;\n    child: T;\n    hasChild: () => boolean;\n    propChanges: string[];\n    prop2Changes: Array<number | undefined>;\n    propChangesDebounce: Array<string | undefined>;\n    destroy: () => void;\n    componentStoreDestroySpy: Mock;\n  }\n\n  @Component({\n    selector: 'body',\n    template: ` @if (isChildVisible) {\n      <child></child>\n    }`,\n    standalone: false,\n  })\n  class ParentComponent implements Parent {\n    isChildVisible = true;\n  }\n\n  async function setupTestBed<T extends Child>(\n    childClass: Type<T>\n  ): Promise<Omit<SetupData<T>, 'componentStoreDestroySpy' | 'destroy'>> {\n    await TestBed.configureTestingModule({\n      declarations: [ParentComponent, childClass],\n      imports: [CommonModule],\n    }).compileComponents();\n\n    const fixture = TestBed.createComponent(ParentComponent);\n    fixture.detectChanges();\n\n    function getChild(): T | undefined {\n      const debugEl = fixture.debugElement.query(By.css('child'));\n      if (debugEl) {\n        return debugEl.componentInstance as T;\n      }\n      return undefined;\n    }\n\n    const propChanges: string[] = [];\n    const prop2Changes: Array<number | undefined> = [];\n    const propChangesDebounce: Array<string | undefined> = [];\n    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n    const child = getChild()!;\n    child.prop$.subscribe((v) => propChanges.push(v));\n    child.prop2$.subscribe((v) => prop2Changes.push(v));\n    child.propDebounce$.subscribe((v) => propChangesDebounce.push(v));\n\n    return {\n      fixture,\n      parent: fixture.componentInstance,\n      child,\n      hasChild: () => !!getChild(),\n      propChanges,\n      prop2Changes,\n      propChangesDebounce,\n    };\n  }\n\n  async function setupComponentProvidesComponentStore() {\n    @Component({\n      selector: 'child',\n      template: '<div>{{prop$ | async}}</div>',\n      providers: [ComponentStore],\n      standalone: false,\n    })\n    class ChildComponent implements Child {\n      prop$ = this.componentStore.select((state) => state.prop);\n      prop2$ = this.componentStore.select((state) => state.prop2);\n      propDebounce$ = this.componentStore.select((state) => state.prop, {\n        debounce: true,\n      });\n\n      intervalToProp2Effect = this.componentStore.effect(\n        (numbers$: Observable<number>) =>\n          numbers$.pipe(\n            tap((n) => {\n              this.componentStore.setState((state) => ({\n                ...state,\n                prop2: n,\n              }));\n            })\n          )\n      );\n      interval$ = interval(10);\n\n      constructor(readonly componentStore: ComponentStore<State>) {}\n\n      init() {\n        this.componentStore.setState({ prop: 'initial Value' });\n        this.intervalToProp2Effect(this.interval$);\n      }\n\n      updateProp(value: string): void {\n        this.componentStore.setState((state) => ({ ...state, prop: value }));\n      }\n    }\n\n    const setup = await setupTestBed(ChildComponent);\n    const componentStoreDestroySpy = vi.spyOn(\n      setup.child.componentStore,\n      'ngOnDestroy'\n    );\n    return {\n      ...setup,\n      destroy: () => setup.child.componentStore.ngOnDestroy(),\n      componentStoreDestroySpy,\n    };\n  }\n\n  async function setupComponentExtendsComponentStore() {\n    @Component({\n      selector: 'child',\n      template: '<div>{{prop$ | async}}</div>',\n      standalone: false,\n    })\n    class ChildComponent extends ComponentStore<State> implements Child {\n      prop$ = this.select((state) => state.prop);\n      prop2$ = this.select((state) => state.prop2);\n      propDebounce$ = this.select((state) => state.prop, { debounce: true });\n\n      intervalToProp2Effect = this.effect((numbers$: Observable<number>) =>\n        numbers$.pipe(\n          tap((n) => {\n            this.setState((state) => ({\n              ...state,\n              prop2: n,\n            }));\n          })\n        )\n      );\n      interval$ = interval(10);\n\n      init() {\n        this.setState({ prop: 'initial Value' });\n        this.intervalToProp2Effect(this.interval$);\n      }\n\n      updateProp(value: string): void {\n        this.setState((state) => ({ ...state, prop: value }));\n      }\n    }\n\n    const setup = await setupTestBed(ChildComponent);\n    const componentStoreDestroySpy = vi.spyOn(setup.child, 'ngOnDestroy');\n    return {\n      ...setup,\n      destroy: () => setup.child.ngOnDestroy(),\n      componentStoreDestroySpy,\n    };\n  }\n\n  async function setupComponentProvidesService() {\n    @Injectable({ providedIn: 'root' })\n    class Service {\n      call(arg: string) {\n        return of('result');\n      }\n    }\n\n    function getProp2(state: State): number | undefined {\n      return state.prop2;\n    }\n\n    @Injectable()\n    class PropsStore extends ComponentStore<State> {\n      prop$ = this.select((state) => state.prop);\n      // projector function 👇 reused in selector and getter\n      prop2$ = this.select(getProp2);\n      propDebounce$ = this.select((state) => state.prop, { debounce: true });\n\n      propUpdater = this.updater((state, value: string) => ({\n        ...state,\n        prop: value,\n      }));\n      prop2Updater = this.updater((state, value: number) => ({\n        ...state,\n        prop2: value,\n      }));\n\n      intervalToProp2Effect = this.effect((numbers$: Observable<number>) =>\n        numbers$.pipe(\n          tap((n) => {\n            this.prop2Updater(n);\n          })\n        )\n      );\n\n      callService = this.effect((strings$: Observable<string>) => {\n        return strings$.pipe(\n          //       getting value from State imperatively 👇\n          concatMap((str) =>\n            this.service.call(str + this.get(getProp2)).pipe(\n              tap({\n                next: (v) => this.propUpdater(v),\n                error: () => {\n                  /* handle error */\n                },\n              }),\n              // make sure to catch errors\n              catchError((e) => EMPTY)\n            )\n          )\n        );\n      });\n\n      constructor(private readonly service: Service) {\n        super();\n      }\n    }\n\n    @Component({\n      selector: 'child',\n      template: '<div>{{prop$ | async}}</div>',\n      providers: [PropsStore],\n      standalone: false,\n    })\n    class ChildComponent implements Child {\n      prop$ = this.propsStore.prop$;\n      prop2$ = this.propsStore.prop2$;\n      propDebounce$ = this.propsStore.propDebounce$;\n      interval$ = interval(10);\n\n      constructor(readonly propsStore: PropsStore) {}\n\n      init() {\n        this.propsStore.setState({ prop: 'initial Value' });\n        this.propsStore.intervalToProp2Effect(this.interval$);\n      }\n\n      updateProp(value: string): void {\n        this.propsStore.propUpdater(value);\n      }\n\n      call(str: string) {\n        this.propsStore.callService(str);\n      }\n    }\n\n    const setup = await setupTestBed(ChildComponent);\n    const componentStoreDestroySpy = vi.spyOn(\n      setup.child.propsStore,\n      'ngOnDestroy'\n    );\n\n    const serviceCallSpy = vi.spyOn(TestBed.inject(Service), 'call');\n    return {\n      ...setup,\n      destroy: () => setup.child.propsStore.ngOnDestroy(),\n      componentStoreDestroySpy,\n      serviceCallSpy,\n    };\n  }\n\n  async function setupComponentExtendsService() {\n    @Injectable()\n    class PropsStore extends ComponentStore<State> {\n      prop$ = this.select((state) => state.prop);\n      prop2$ = this.select((state) => state.prop2);\n      propDebounce$ = this.select((state) => state.prop, { debounce: true });\n\n      propUpdater = this.updater((state, value: string) => ({\n        ...state,\n        prop: value,\n      }));\n      prop2Updater = this.updater((state, value: number) => ({\n        ...state,\n        prop2: value,\n      }));\n\n      intervalToProp2Effect = this.effect((numbers$: Observable<number>) =>\n        numbers$.pipe(\n          tap((n) => {\n            this.prop2Updater(n);\n          })\n        )\n      );\n    }\n\n    @Component({\n      selector: 'child',\n      template: '<div>{{prop$ | async}}</div>',\n      standalone: false,\n    })\n    class ChildComponent extends PropsStore implements Child {\n      interval$ = interval(10);\n\n      init() {\n        this.setState({ prop: 'initial Value' });\n        this.intervalToProp2Effect(this.interval$);\n      }\n\n      updateProp(value: string): void {\n        this.propUpdater(value);\n      }\n    }\n\n    const setup = await setupTestBed(ChildComponent);\n    const componentStoreDestroySpy = vi.spyOn(setup.child, 'ngOnDestroy');\n    return {\n      ...setup,\n      destroy: () => setup.child.ngOnDestroy(),\n      componentStoreDestroySpy,\n    };\n  }\n});\n"
  },
  {
    "path": "modules/component-store/spec/integration_signals.spec.ts",
    "content": "import { ComponentStore } from '..';\n\nexport const VisibilityFilters = {\n  SHOW_ALL: 'SHOW_ALL',\n  SHOW_COMPLETED: 'SHOW_COMPLETED',\n  SHOW_ACTIVE: 'SHOW_ACTIVE',\n};\n\ninterface Todo {\n  id: number;\n  text: string;\n  completed: boolean;\n}\n\ninterface TodoState {\n  visibilityFilter: string;\n  todos: Todo[];\n}\n\ndescribe('NgRx ComponentStore and Signals Integration spec', () => {\n  let store: ComponentStore<TodoState>;\n\n  const initialState = {\n    todos: [],\n    visibilityFilter: VisibilityFilters.SHOW_ALL,\n  };\n\n  beforeEach(() => {\n    store = new ComponentStore<TodoState>(initialState);\n  });\n\n  describe('todo integration spec', function () {\n    describe('using the store.selectSignal', () => {\n      it('should use visibilityFilter to filter todos', () => {\n        store.setState((state) => {\n          return {\n            ...state,\n            todos: [\n              { id: 1, text: 'first todo', completed: false },\n              { id: 2, text: 'second todo', completed: false },\n            ],\n          };\n        });\n\n        const filterVisibleTodos = (\n          visibilityFilter: string,\n          todos: Todo[]\n        ) => {\n          let predicate;\n          if (visibilityFilter === VisibilityFilters.SHOW_ALL) {\n            predicate = () => true;\n          } else if (visibilityFilter === VisibilityFilters.SHOW_ACTIVE) {\n            predicate = (todo: any) => !todo.completed;\n          } else {\n            predicate = (todo: any) => todo.completed;\n          }\n          return todos.filter(predicate);\n        };\n\n        const filter = () => store.state().visibilityFilter;\n        const todos = store.selectSignal((state) => state.todos);\n        const currentlyVisibleTodos = () =>\n          filterVisibleTodos(filter(), todos());\n\n        expect(currentlyVisibleTodos().length).toBe(2);\n\n        store.setState((state) => {\n          const todos = state.todos.map((todo) => {\n            if (todo.id === 1) {\n              return { ...todo, completed: true };\n            }\n\n            return todo;\n          });\n\n          return {\n            ...state,\n            visibilityFilter: VisibilityFilters.SHOW_ACTIVE,\n            todos,\n          };\n        });\n\n        expect(currentlyVisibleTodos().length).toBe(1);\n        expect(currentlyVisibleTodos()[0].completed).toBe(false);\n\n        store.setState((state) => ({\n          ...state,\n          visibilityFilter: VisibilityFilters.SHOW_COMPLETED,\n        }));\n\n        expect(currentlyVisibleTodos().length).toBe(1);\n        expect(currentlyVisibleTodos()[0].completed).toBe(true);\n\n        store.setState((state) => {\n          const todos = state.todos.map((todo) => {\n            return { ...todo, completed: true };\n          });\n\n          return {\n            ...state,\n            todos,\n          };\n        });\n\n        expect(currentlyVisibleTodos().length).toBe(2);\n        expect(currentlyVisibleTodos()[0].completed).toBe(true);\n        expect(currentlyVisibleTodos()[1].completed).toBe(true);\n\n        store.setState((state) => ({\n          ...state,\n          visibilityFilter: VisibilityFilters.SHOW_ACTIVE,\n        }));\n\n        expect(currentlyVisibleTodos().length).toBe(0);\n      });\n    });\n  });\n\n  describe('context integration spec', () => {\n    it('ComponentStore.selectSignal should not throw an error if used outside in the injection context', () => {\n      let error;\n\n      try {\n        store.selectSignal((state) => state.todos);\n      } catch (e) {\n        error = `${e}`;\n      }\n\n      expect(error).toBeUndefined();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/component-store/spec/types/component-store.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('ComponentStore types', () => {\n  describe('effect', () => {\n    const expectSnippet = expecter(\n      (code) => `\n        import { ComponentStore } from '@ngrx/component-store';\n        import { of, EMPTY, Observable } from 'rxjs';\n        import { concatMap } from 'rxjs/operators';\n\n        interface Obj {\n          prop: string;\n        }\n\n        const number$: Observable<number> = of(5);\n        const string$: Observable<string> = of('string');\n\n        const componentStore = new ComponentStore();\n        ${code}\n      `,\n      compilerOptions()\n    );\n\n    describe('infers Subscription', () => {\n      it('when argument type is specified and a variable with corresponding type is passed', () => {\n        const effectTest = `const sub = componentStore.effect((e: Observable<string>) => number$)('string');`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it(\n        'when argument type is specified, returns EMPTY and ' +\n          'a variable with corresponding type is passed',\n        () => {\n          const effectTest = `const sub = componentStore.effect((e: Observable<string>) => EMPTY)('string');`;\n          expectSnippet(effectTest).toInfer('sub', 'Subscription');\n        }\n      );\n\n      it('when argument type is specified and an Observable with corresponding type is passed', () => {\n        const effectTest = `const sub = componentStore.effect((e: Observable<string>) => EMPTY)(string$);`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is specified as Observable<unknown> and any type is passed', () => {\n        const effectTest = `const sub = componentStore.effect((e: Observable<unknown>) => EMPTY)(5);`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when generic type is specified and a variable with corresponding type is passed', () => {\n        const effectTest = `const sub = componentStore.effect<string>((e) => number$)('string');`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when generic type is specified as unknown and a variable with any type is passed', () => {\n        const effectTest = `const sub = componentStore.effect<unknown>((e) => number$)('string');`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when generic type is specified as unknown and origin can still be piped', () => {\n        const effectTest = `const sub = componentStore.effect<unknown>((e) => e.pipe(concatMap(() => of())))('string');`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when generic type is specified as unknown and origin can still be piped', () => {\n        expectSnippet(\n          `const sub = componentStore.effect<unknown>((e) => e.pipe(concatMap(() => of())))('string');`\n        ).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is an interface and a variable with corresponding type is passed', () => {\n        const effectTest = `const sub = componentStore.effect((e: Observable<Obj>) => number$)({prop: 'string'});`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is a partial interface and a variable with corresponding type is passed', () => {\n        const effectTest = `const sub = componentStore.effect((e: Observable<Partial<Obj>>) => number$)({prop: 'string'});`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n    });\n\n    describe('for void types', () => {\n      it('when generic type is specified as void the argument is optional', () => {\n        const effectTest = `const sub = componentStore.effect<void>((e) => EMPTY)();`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is specified as Observable<void> the argument is optional', () => {\n        const effectTest = `const sub = componentStore.effect((e: Observable<void>) => EMPTY)();`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when type is not specified the argument is optional', () => {\n        const effectTest = `const sub = componentStore.effect((e) => EMPTY)();`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when type is specified as void the argument can be a void$', () => {\n        const effectTest = `const sub = componentStore.effect((e: Observable<void>) => EMPTY)(of<void>());`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when type is specified as void the argument can be a void', () => {\n        const effectTest = `const sub = componentStore.effect((e) => EMPTY)({} as unknown as void);`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when generic type is specified as void and origin can still be piped', () => {\n        const effectTest = `const sub = componentStore.effect<void>((e) => e.pipe(concatMap(() => number$)))();`;\n        expectSnippet(effectTest).toInfer('sub', 'Subscription');\n      });\n    });\n\n    describe('catches improper usage', () => {\n      it('when type is specified and argument is not passed', () => {\n        expectSnippet(\n          `componentStore.effect((e: Observable<string>) => of())();`\n        ).toFail(/Expected 1 arguments, but got 0/);\n      });\n\n      it('when type is specified and argument of incorrect type is passed', () => {\n        expectSnippet(\n          `componentStore.effect((e: Observable<string>) => number$)(5);`\n        ).toFail(\n          /Argument of type 'number' is not assignable to parameter of type 'string \\| Observable<string>'./\n        );\n      });\n\n      it('when type is specified and Observable argument of incorrect type is passed', () => {\n        expectSnippet(\n          `componentStore.effect((e: Observable<string>) => string$)(number$);`\n        ).toFail(\n          /Argument of type 'Observable<number>' is not assignable to parameter of type 'string \\| Observable<string>'/\n        );\n      });\n\n      it('when argument type is specified as Observable<unknown> and type is not passed', () => {\n        expectSnippet(\n          `componentStore.effect((e: Observable<unknown>) => EMPTY)();`\n        ).toFail(/Expected 1 arguments, but got 0/);\n      });\n\n      it('when generic type is specified and a variable with incorrect type is passed', () => {\n        expectSnippet(\n          `componentStore.effect<string>((e) => number$)(5);`\n        ).toFail(\n          /Argument of type 'number' is not assignable to parameter of type 'string \\| Observable<string>'/\n        );\n      });\n\n      it('when generic type is specified as void and a variable with incorrect type is passed', () => {\n        expectSnippet(`componentStore.effect<void>((e) => number$)(5);`).toFail(\n          /Argument of type 'number' is not assignable to parameter of type 'void \\| Observable<void>'/\n        );\n      });\n\n      it('when generic type is specified as unknown and a variable is not passed', () => {\n        expectSnippet(\n          `componentStore.effect<unknown>((e) => number$)();`\n        ).toFail(/Expected 1 arguments, but got 0/);\n      });\n    });\n  });\n\n  describe('updater', () => {\n    const expectSnippet = expecter(\n      (code) => `\n        import { ComponentStore } from '@ngrx/component-store';\n        import { of, EMPTY, Observable } from 'rxjs';\n        import { concatMap } from 'rxjs/operators';\n\n        export enum LoadingState {\n          INIT = 'INIT',\n          LOADING = 'LOADING',\n          LOADED = 'LOADED',\n          ERROR = 'ERROR',\n        }\n\n        interface Obj {\n          prop: string;\n        }\n\n        const number$: Observable<number> = of(5);\n        const string$: Observable<string> = of('string');\n\n        const componentStore = new ComponentStore({ prop: 'init', prop2: 'yeah!'});\n        ${code}\n      `,\n      compilerOptions()\n    );\n\n    describe('infers Subscription', () => {\n      it('when argument type is specified and a variable with corresponding type is passed', () => {\n        const updaterTest = `const sub = componentStore.updater((state, v: string) => ({...state}))('string');`;\n        expectSnippet(updaterTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is specified and an Observable with corresponding type is passed', () => {\n        const updaterTest = `const sub = componentStore.updater((state, v: string) => ({...state}))(string$);`;\n        expectSnippet(updaterTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is an interface and a variable with corresponding type is passed', () => {\n        const updaterTest = `const sub = componentStore.updater((state, v: Obj) => ({...state}))({prop: 'obj'});`;\n        expectSnippet(updaterTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is an partial interface and a variable with corresponding type is passed', () => {\n        const updaterTest = `const sub = componentStore.updater((state, v: Partial<Obj>) => ({...state}))({prop: 'obj'});`;\n        expectSnippet(updaterTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is an enum and a variable with corresponding type is passed', () => {\n        const updaterTest = `const sub = componentStore.updater((state, v: LoadingState) => ({...state}))(LoadingState.LOADED);`;\n        expectSnippet(updaterTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is a union and a variable with corresponding type is passed', () => {\n        const updaterTest = `const sub = componentStore.updater((state, v: string|number) => ({...state}))(5);`;\n        expectSnippet(updaterTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is an intersection and a variable with corresponding type is passed', () => {\n        const updaterTest = `const sub = componentStore.updater((state, v: {p: string} & {p2: number}) => ({...state}))({p: 's', p2: 3});`;\n        expectSnippet(updaterTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when argument type is unknown and any variable is passed', () => {\n        const updaterTest = `const sub = componentStore.updater((state, v: unknown) => ({...state}))({anything: 'works'});`;\n        expectSnippet(updaterTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when generic type is specified and any variable is passed', () => {\n        const updaterTest = `const sub = componentStore.updater<string>((state, v) => ({...state}))('works');`;\n        expectSnippet(updaterTest).toInfer('sub', 'Subscription');\n      });\n\n      it('when type is not specified and nothing is passed', () => {\n        const updaterTest = `const v = componentStore.updater((state) => ({...state}))();`;\n        expectSnippet(updaterTest).toInfer('v', 'void');\n      });\n\n      it('when type void is specified and nothing is passed', () => {\n        const updaterTest = `const v = componentStore.updater<void>((state) => ({...state}))();`;\n        expectSnippet(updaterTest).toInfer('v', 'void');\n      });\n    });\n\n    describe('catches improper usage', () => {\n      it('when type is specified and argument is not passed', () => {\n        expectSnippet(\n          `const sub = componentStore.updater((state, v: string) => ({...state}))();`\n        ).toFail(/Expected 1 arguments, but got 0/);\n      });\n\n      it('when argument type is unknown and nothing is passed', () => {\n        expectSnippet(\n          `const sub = componentStore.updater((state, v: unknown) => ({...state}))();`\n        ).toFail(/Expected 1 arguments, but got 0/);\n      });\n\n      it('when no argument is expected but one is passed', () => {\n        expectSnippet(\n          `const sub = componentStore.updater((state) => ({...state}))('string');`\n        ).toFail(/Expected 0 arguments, but got 1/);\n      });\n\n      it('when type is specified and Observable argument of incorrect type is passed', () => {\n        expectSnippet(\n          `const sub = componentStore.updater((state, v: string) => ({...state}))(number$);`\n        ).toFail(\n          /Argument of type 'Observable<number>' is not assignable to parameter of type 'string \\| Observable<string>'/\n        );\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/component-store/spec/types/regression.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('regression component-store', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import { ComponentStore } from '@ngrx/component-store';\n        import { of, EMPTY, Observable } from 'rxjs';\n        import { concatMap } from 'rxjs/operators';\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('https://github.com/ngrx/platform/issues/3482', () => {\n    const effectTest = `\n        interface SomeType {\n          name: string;\n          prop: string;\n        }\n\n        export abstract class MyStore<\n          QueryVariables extends SomeType\n        > extends ComponentStore<any> {\n          protected abstract readonly query$: Observable<Omit<QueryVariables, 'name'>>;\n\n          readonly load = this.effect(\n            (origin$: Observable<Omit<QueryVariables, 'name'> | null>) => origin$\n          );\n\n          protected constructor() {\n            super();\n          }\n\n          protected initializeLoad() {\n            // 👇 this should work\n            this.load(this.query$);\n          }\n        }\n      `;\n    expectSnippet(effectTest).toSucceed();\n  });\n});\n"
  },
  {
    "path": "modules/component-store/spec/types/utils.ts",
    "content": "export const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  target: 'ES2022',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  paths: {\n    '@ngrx/component-store': ['./modules/component-store'],\n    '@ngrx/operators': ['./modules/operators'],\n  },\n});\n"
  },
  {
    "path": "modules/component-store/src/component-store.ts",
    "content": "import {\n  isObservable,\n  Observable,\n  of,\n  ReplaySubject,\n  Subscription,\n  throwError,\n  combineLatest,\n  Subject,\n  queueScheduler,\n  scheduled,\n  asapScheduler,\n  EMPTY,\n  ObservedValueOf,\n} from 'rxjs';\nimport {\n  takeUntil,\n  withLatestFrom,\n  map,\n  distinctUntilChanged,\n  shareReplay,\n  take,\n  tap,\n  catchError,\n  observeOn,\n} from 'rxjs/operators';\nimport { debounceSync } from './debounce-sync';\nimport {\n  Injectable,\n  OnDestroy,\n  Optional,\n  InjectionToken,\n  Inject,\n  isDevMode,\n  Signal,\n  computed,\n  type ValueEqualityFn,\n  type CreateComputedOptions,\n} from '@angular/core';\nimport { isOnStateInitDefined, isOnStoreInitDefined } from './lifecycle_hooks';\nimport { toSignal } from '@angular/core/rxjs-interop';\n\nexport interface SelectConfig<T = unknown> {\n  debounce?: boolean;\n  equal?: ValueEqualityFn<T>;\n}\n\nexport const INITIAL_STATE_TOKEN = new InjectionToken(\n  '@ngrx/component-store Initial State'\n);\n\nexport type SelectorResults<Selectors extends Observable<unknown>[]> = {\n  [Key in keyof Selectors]: Selectors[Key] extends Observable<infer U>\n    ? U\n    : never;\n};\n\nexport type Projector<Selectors extends Observable<unknown>[], Result> = (\n  ...args: SelectorResults<Selectors>\n) => Result;\n\ntype SignalsProjector<Signals extends Signal<unknown>[], Result> = (\n  ...values: {\n    [Key in keyof Signals]: Signals[Key] extends Signal<infer Value>\n      ? Value\n      : never;\n  }\n) => Result;\n\ninterface SelectSignalOptions<T> {\n  /**\n   * A comparison function which defines equality for select results.\n   */\n  equal?: ValueEqualityFn<T>;\n}\n\n@Injectable()\nexport class ComponentStore<T extends object> implements OnDestroy {\n  // Should be used only in ngOnDestroy.\n  private readonly destroySubject$ = new ReplaySubject<void>(1);\n  // Exposed to any extending Store to be used for the teardown.\n  readonly destroy$ = this.destroySubject$.asObservable();\n\n  private readonly stateSubject$ = new ReplaySubject<T>(1);\n  private isInitialized = false;\n  // Needs to be after destroy$ is declared because it's used in select.\n  readonly state$: Observable<T> = this.select((s) => s);\n  readonly state: Signal<T> = toSignal(\n    this.stateSubject$.pipe(takeUntil(this.destroy$)),\n    { requireSync: false, manualCleanup: true }\n  ) as Signal<T>;\n  private ɵhasProvider = false;\n\n  constructor(@Optional() @Inject(INITIAL_STATE_TOKEN) defaultState?: T) {\n    // State can be initialized either through constructor or setState.\n    if (defaultState) {\n      this.initState(defaultState);\n    }\n\n    this.checkProviderForHooks();\n  }\n\n  /** Completes all relevant Observable streams. */\n  ngOnDestroy() {\n    this.stateSubject$.complete();\n    this.destroySubject$.next();\n  }\n\n  /**\n   * Creates an updater.\n   *\n   * Throws an error if updater is called with synchronous values (either\n   * imperative value or Observable that is synchronous) before ComponentStore\n   * is initialized. If called with async Observable before initialization then\n   * state will not be updated and subscription would be closed.\n   *\n   * @param updaterFn A static updater function that takes 2 parameters (the\n   * current state and an argument object) and returns a new instance of the\n   * state.\n   * @return A function that accepts one argument which is forwarded as the\n   *     second argument to `updaterFn`. Every time this function is called\n   *     subscribers will be notified of the state change.\n   */\n  updater<\n    // Allow to force-provide the type\n    ProvidedType = void,\n    // This type is derived from the `value` property, defaulting to void if it's missing\n    OriginType = ProvidedType,\n    // The Value type is assigned from the Origin\n    ValueType = OriginType,\n    // Return either an empty callback or a function requiring specific types as inputs\n    ReturnType = OriginType extends void\n      ? () => void\n      : (observableOrValue: ValueType | Observable<ValueType>) => Subscription,\n  >(updaterFn: (state: T, value: OriginType) => T): ReturnType {\n    return ((\n      observableOrValue?: OriginType | Observable<OriginType>\n    ): Subscription => {\n      // We need to explicitly throw an error if a synchronous error occurs.\n      // This is necessary to make synchronous errors catchable.\n      let isSyncUpdate = true;\n      let syncError: unknown;\n      // We can receive either the value or an observable. In case it's a\n      // simple value, we'll wrap it with `of` operator to turn it into\n      // Observable.\n      const observable$ = isObservable(observableOrValue)\n        ? observableOrValue\n        : of(observableOrValue);\n      const subscription = observable$\n        .pipe(\n          // Push the value into queueScheduler\n          observeOn(queueScheduler),\n          // If the state is not initialized yet, we'll throw an error.\n          tap(() => this.assertStateIsInitialized()),\n          withLatestFrom(this.stateSubject$),\n          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n          map(([value, currentState]) => updaterFn(currentState, value!)),\n          tap((newState) => this.stateSubject$.next(newState)),\n          catchError((error: unknown) => {\n            if (isSyncUpdate) {\n              syncError = error;\n              return EMPTY;\n            }\n\n            return throwError(error);\n          }),\n          takeUntil(this.destroy$)\n        )\n        .subscribe();\n\n      if (syncError) {\n        throw syncError;\n      }\n      isSyncUpdate = false;\n\n      return subscription;\n    }) as unknown as ReturnType;\n  }\n\n  /**\n   * Initializes state. If it was already initialized then it resets the\n   * state.\n   */\n  private initState(state: T): void {\n    scheduled([state], queueScheduler).subscribe((s) => {\n      this.isInitialized = true;\n      this.stateSubject$.next(s);\n    });\n  }\n\n  /**\n   * Sets the state specific value.\n   * @param stateOrUpdaterFn object of the same type as the state or an\n   * updaterFn, returning such object.\n   */\n  setState(stateOrUpdaterFn: T | ((state: T) => T)): void {\n    if (typeof stateOrUpdaterFn !== 'function') {\n      this.initState(stateOrUpdaterFn);\n    } else {\n      this.updater(stateOrUpdaterFn as (state: T) => T)();\n    }\n  }\n\n  /**\n   * Patches the state with provided partial state.\n   *\n   * @param partialStateOrUpdaterFn a partial state or a partial updater\n   * function that accepts the state and returns the partial state.\n   * @throws Error if the state is not initialized.\n   */\n  patchState(\n    partialStateOrUpdaterFn:\n      | Partial<T>\n      | Observable<Partial<T>>\n      | ((state: T) => Partial<T>)\n  ): void {\n    const patchedState =\n      typeof partialStateOrUpdaterFn === 'function'\n        ? partialStateOrUpdaterFn(this.get())\n        : partialStateOrUpdaterFn;\n\n    this.updater((state, partialState: Partial<T>) => ({\n      ...state,\n      ...partialState,\n    }))(patchedState);\n  }\n\n  protected get(): T;\n  protected get<R>(projector: (s: T) => R): R;\n  protected get<R>(projector?: (s: T) => R): R | T {\n    this.assertStateIsInitialized();\n    let value: R | T;\n\n    this.stateSubject$.pipe(take(1)).subscribe((state) => {\n      value = projector ? projector(state) : state;\n    });\n    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n    return value!;\n  }\n\n  /**\n   * Creates a selector.\n   *\n   * @param projector A pure projection function that takes the current state and\n   *   returns some new slice/projection of that state.\n   * @param config SelectConfig that changes the behavior of selector, including\n   *   the debouncing of the values until the state is settled.\n   * @return An observable of the projector results.\n   */\n  select<Result>(\n    projector: (s: T) => Result,\n    config?: SelectConfig<Result>\n  ): Observable<Result>;\n  select<SelectorsObject extends Record<string, Observable<unknown>>>(\n    selectorsObject: SelectorsObject,\n    config?: SelectConfig<{\n      [K in keyof SelectorsObject]: ObservedValueOf<SelectorsObject[K]>;\n    }>\n  ): Observable<{\n    [K in keyof SelectorsObject]: ObservedValueOf<SelectorsObject[K]>;\n  }>;\n  select<Selectors extends Observable<unknown>[], Result>(\n    ...selectorsWithProjector: [\n      ...selectors: Selectors,\n      projector: Projector<Selectors, Result>,\n    ]\n  ): Observable<Result>;\n  select<Selectors extends Observable<unknown>[], Result>(\n    ...selectorsWithProjectorAndConfig: [\n      ...selectors: Selectors,\n      projector: Projector<Selectors, Result>,\n      config: SelectConfig<Result>,\n    ]\n  ): Observable<Result>;\n  select<\n    Selectors extends Array<\n      Observable<unknown> | SelectConfig<Result> | ProjectorFn | SelectorsObject\n    >,\n    Result,\n    ProjectorFn extends (...a: unknown[]) => Result,\n    SelectorsObject extends Record<string, Observable<unknown>>,\n  >(...args: Selectors): Observable<Result> {\n    const { observablesOrSelectorsObject, projector, config } =\n      processSelectorArgs<Selectors, Result, ProjectorFn, SelectorsObject>(\n        args\n      );\n\n    const source$ = hasProjectFnOnly(observablesOrSelectorsObject, projector)\n      ? this.stateSubject$\n      : combineLatest(observablesOrSelectorsObject as any);\n\n    return source$.pipe(\n      config.debounce ? debounceSync() : noopOperator(),\n      (projector\n        ? map((projectorArgs) =>\n            // projectorArgs could be an Array in case where the entire state is an Array, so adding this check\n            (observablesOrSelectorsObject as Observable<unknown>[]).length >\n              0 && Array.isArray(projectorArgs)\n              ? projector(...projectorArgs)\n              : projector(projectorArgs)\n          )\n        : noopOperator()) as () => Observable<Result>,\n      distinctUntilChanged(config.equal),\n      shareReplay({\n        refCount: true,\n        bufferSize: 1,\n      }),\n      takeUntil(this.destroy$)\n    );\n  }\n\n  /**\n   * Creates a signal from the provided state projector function.\n   */\n  selectSignal<Result>(\n    projector: (state: T) => Result,\n    options?: SelectSignalOptions<Result>\n  ): Signal<Result>;\n  /**\n   * Creates a signal by combining provided signals.\n   */\n  selectSignal<Signals extends Signal<unknown>[], Result>(\n    ...args: [...signals: Signals, projector: SignalsProjector<Signals, Result>]\n  ): Signal<Result>;\n  /**\n   * Creates a signal by combining provided signals.\n   */\n  selectSignal<Signals extends Signal<unknown>[], Result>(\n    ...args: [\n      ...signals: Signals,\n      projector: SignalsProjector<Signals, Result>,\n      options: SelectSignalOptions<Result>,\n    ]\n  ): Signal<Result>;\n  selectSignal(\n    ...args:\n      | [(state: T) => unknown, SelectSignalOptions<unknown>?]\n      | [\n          ...signals: Signal<unknown>[],\n          projector: (...values: unknown[]) => unknown,\n        ]\n      | [\n          ...signals: Signal<unknown>[],\n          projector: (...values: unknown[]) => unknown,\n          options: SelectSignalOptions<unknown>,\n        ]\n  ): Signal<unknown> {\n    const selectSignalArgs = [...args];\n    const options: CreateComputedOptions<unknown> =\n      typeof selectSignalArgs[args.length - 1] === 'object'\n        ? (selectSignalArgs.pop() as SelectSignalOptions<unknown>)\n        : {};\n    const projector = selectSignalArgs.pop() as (\n      ...values: unknown[]\n    ) => unknown;\n    const signals = selectSignalArgs as Signal<unknown>[];\n\n    const computation =\n      signals.length === 0\n        ? () => projector(this.state())\n        : () => {\n            const values = signals.map((signal) => signal());\n            return projector(...values);\n          };\n\n    return computed(computation, options);\n  }\n\n  /**\n   * Creates an effect.\n   *\n   * This effect is subscribed to throughout the lifecycle of the ComponentStore.\n   * @param generator A function that takes an origin Observable input and\n   *     returns an Observable. The Observable that is returned will be\n   *     subscribed to for the life of the component.\n   * @return A function that, when called, will trigger the origin Observable.\n   */\n  effect<\n    // This type quickly became part of effect 'API'\n    ProvidedType = void,\n    // The actual origin$ type, which could be unknown, when not specified\n    OriginType extends\n      | Observable<ProvidedType>\n      | unknown = Observable<ProvidedType>,\n    // Unwrapped actual type of the origin$ Observable, after default was applied\n    ObservableType = OriginType extends Observable<infer A> ? A : never,\n    // Return either an optional callback or a function requiring specific types as inputs\n    ReturnType = ProvidedType | ObservableType extends void\n      ? (\n          observableOrValue?: ObservableType | Observable<ObservableType>\n        ) => Subscription\n      : (\n          observableOrValue: ObservableType | Observable<ObservableType>\n        ) => Subscription,\n  >(generator: (origin$: OriginType) => Observable<unknown>): ReturnType {\n    const origin$ = new Subject<ObservableType>();\n    generator(origin$ as OriginType)\n      // tied to the lifecycle 👇 of ComponentStore\n      .pipe(takeUntil(this.destroy$))\n      .subscribe();\n\n    return ((\n      observableOrValue?: ObservableType | Observable<ObservableType>\n    ): Subscription => {\n      const observable$ = isObservable(observableOrValue)\n        ? observableOrValue\n        : of(observableOrValue);\n      return observable$.pipe(takeUntil(this.destroy$)).subscribe((value) => {\n        // any new 👇 value is pushed into a stream\n        origin$.next(value as ObservableType);\n      });\n    }) as unknown as ReturnType;\n  }\n\n  /**\n   * Used to check if lifecycle hooks are defined\n   * but not used with provideComponentStore()\n   */\n  private checkProviderForHooks() {\n    asapScheduler.schedule(() => {\n      if (\n        isDevMode() &&\n        (isOnStoreInitDefined(this) || isOnStateInitDefined(this)) &&\n        !this.ɵhasProvider\n      ) {\n        const warnings = [\n          isOnStoreInitDefined(this) ? 'OnStoreInit' : '',\n          isOnStateInitDefined(this) ? 'OnStateInit' : '',\n        ].filter((defined) => defined);\n\n        console.warn(\n          `@ngrx/component-store: ${\n            this.constructor.name\n          } has the ${warnings.join(' and ')} ` +\n            'lifecycle hook(s) implemented without being provided using the ' +\n            `provideComponentStore(${this.constructor.name}) function. ` +\n            `To resolve this, provide the component store via provideComponentStore(${this.constructor.name})`\n        );\n      }\n    });\n  }\n\n  private assertStateIsInitialized(): void {\n    if (!this.isInitialized) {\n      throw new Error(\n        `${this.constructor.name} has not been initialized yet. ` +\n          `Please make sure it is initialized before updating/getting.`\n      );\n    }\n  }\n}\n\nfunction processSelectorArgs<\n  Selectors extends Array<\n    Observable<unknown> | SelectConfig<Result> | ProjectorFn | SelectorsObject\n  >,\n  Result,\n  ProjectorFn extends (...a: unknown[]) => Result,\n  SelectorsObject extends Record<string, Observable<unknown>>,\n>(\n  args: Selectors\n):\n  | {\n      observablesOrSelectorsObject: Observable<unknown>[];\n      projector: ProjectorFn;\n      config: Required<SelectConfig<Result>>;\n    }\n  | {\n      observablesOrSelectorsObject: SelectorsObject;\n      projector: undefined;\n      config: Required<SelectConfig<Result>>;\n    } {\n  const selectorArgs = Array.from(args);\n  const defaultEqualityFn: ValueEqualityFn<Result> = (previous, current) =>\n    previous === current;\n\n  // Assign default values.\n  let config: Required<SelectConfig<Result>> = {\n    debounce: false,\n    equal: defaultEqualityFn,\n  };\n\n  // Last argument is either config or projector or selectorsObject\n  if (isSelectConfig(selectorArgs[selectorArgs.length - 1])) {\n    config = { ...config, ...selectorArgs.pop() };\n  }\n\n  // At this point selectorArgs is either projector, selectors with projector or selectorsObject\n  if (selectorArgs.length === 1 && typeof selectorArgs[0] !== 'function') {\n    // this is a selectorsObject\n    return {\n      observablesOrSelectorsObject: selectorArgs[0] as SelectorsObject,\n      projector: undefined,\n      config,\n    };\n  }\n\n  const projector = selectorArgs.pop() as ProjectorFn;\n\n  // The Observables to combine, if there are any left.\n  const observables = selectorArgs as Observable<unknown>[];\n  return {\n    observablesOrSelectorsObject: observables,\n    projector,\n    config,\n  };\n}\n\nfunction isSelectConfig(\n  arg: SelectConfig<unknown> | unknown\n): arg is SelectConfig<unknown> {\n  const typedArg = arg as SelectConfig<unknown>;\n  return (\n    typeof typedArg.debounce !== 'undefined' ||\n    typeof typedArg.equal !== 'undefined'\n  );\n}\n\nfunction hasProjectFnOnly(\n  observablesOrSelectorsObject: unknown[] | Record<string, unknown>,\n  projector: unknown\n) {\n  return (\n    Array.isArray(observablesOrSelectorsObject) &&\n    observablesOrSelectorsObject.length === 0 &&\n    projector\n  );\n}\n\nfunction noopOperator(): <T>(source$: Observable<T>) => typeof source$ {\n  return (source$) => source$;\n}\n"
  },
  {
    "path": "modules/component-store/src/debounce-sync.ts",
    "content": "/**\n * @license MIT License\n *\n * Copyright (c) 2017-2020 Nicholas Jamieson and contributors\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport {\n  asapScheduler,\n  MonoTypeOperatorFunction,\n  Observable,\n  Subscription,\n} from 'rxjs';\n\nexport function debounceSync<T>(): MonoTypeOperatorFunction<T> {\n  return (source) =>\n    new Observable<T>((observer) => {\n      let actionSubscription: Subscription | undefined;\n      let actionValue: T | undefined;\n      const rootSubscription = new Subscription();\n      rootSubscription.add(\n        source.subscribe({\n          complete: () => {\n            if (actionSubscription) {\n              observer.next(actionValue);\n            }\n            observer.complete();\n          },\n          error: (error) => {\n            observer.error(error);\n          },\n          next: (value) => {\n            actionValue = value;\n            if (!actionSubscription) {\n              actionSubscription = asapScheduler.schedule(() => {\n                observer.next(actionValue);\n                actionSubscription = undefined;\n              });\n              rootSubscription.add(actionSubscription);\n            }\n          },\n        })\n      );\n      return rootSubscription;\n    });\n}\n"
  },
  {
    "path": "modules/component-store/src/index.ts",
    "content": "export * from './component-store';\nexport {\n  provideComponentStore,\n  OnStateInit,\n  OnStoreInit,\n} from './lifecycle_hooks';\n"
  },
  {
    "path": "modules/component-store/src/lifecycle_hooks.ts",
    "content": "import { Provider, InjectionToken, Type, inject } from '@angular/core';\nimport { take } from 'rxjs/operators';\nimport { ComponentStore } from './component-store';\n\n/**\n * The interface for the lifecycle hook\n * called after the ComponentStore is instantiated.\n */\nexport interface OnStoreInit {\n  readonly ngrxOnStoreInit: () => void;\n}\n\n/**\n * The interface for the lifecycle hook\n * called only once after the ComponentStore\n * state is first initialized.\n */\nexport interface OnStateInit {\n  readonly ngrxOnStateInit: () => void;\n}\n\n/**\n * Checks to see if the OnInitStore lifecycle hook\n * is defined on the ComponentStore.\n *\n * @param cs ComponentStore type\n * @returns boolean\n */\nexport function isOnStoreInitDefined(cs: unknown): cs is OnStoreInit {\n  return typeof (cs as OnStoreInit).ngrxOnStoreInit === 'function';\n}\n\n/**\n * Checks to see if the OnInitState lifecycle hook\n * is defined on the ComponentStore.\n *\n * @param cs ComponentStore type\n * @returns boolean\n */\nexport function isOnStateInitDefined(cs: unknown): cs is OnStateInit {\n  return typeof (cs as OnStateInit).ngrxOnStateInit === 'function';\n}\n\n/**\n * @description\n *\n * Function that returns the ComponentStore\n * class registered as a provider,\n * and uses a factory provider to instantiate the\n * ComponentStore and run the lifecycle hooks\n * defined on the ComponentStore.\n *\n * @param componentStoreClass The ComponentStore with lifecycle hooks\n * @returns Provider[]\n *\n * @usageNotes\n *\n * ```ts\n * @Injectable()\n * export class MyStore\n *    extends ComponentStore<{ init: boolean }>\n *    implements OnStoreInit, OnStateInit\n *   {\n *\n *   constructor() {\n *     super({ init: true });\n *   }\n *\n *   ngrxOnStoreInit() {\n *     // runs once after store has been instantiated\n *   }\n *\n *   ngrxOnStateInit() {\n *     // runs once after store state has been initialized\n *   }\n * }\n *\n * @Component({\n *   providers: [\n *     provideComponentStore(MyStore)\n *   ]\n * })\n * export class MyComponent {\n *   constructor(private myStore: MyStore) {}\n * }\n * ```\n */\nexport function provideComponentStore<T extends object>(\n  componentStoreClass: Type<ComponentStore<T>>\n): Provider[] {\n  const CS_WITH_HOOKS = new InjectionToken<ComponentStore<T>>(\n    '@ngrx/component-store ComponentStore with Hooks'\n  );\n\n  return [\n    { provide: CS_WITH_HOOKS, useClass: componentStoreClass },\n    {\n      provide: componentStoreClass,\n      useFactory: () => {\n        const componentStore = inject(CS_WITH_HOOKS);\n\n        // Set private property that CS has been provided with lifecycle hooks\n        componentStore['ɵhasProvider'] = true;\n\n        if (isOnStoreInitDefined(componentStore)) {\n          componentStore.ngrxOnStoreInit();\n        }\n\n        if (isOnStateInitDefined(componentStore)) {\n          componentStore.state$\n            .pipe(take(1))\n            .subscribe(() => componentStore.ngrxOnStateInit());\n        }\n\n        return componentStore;\n      },\n    },\n  ];\n}\n"
  },
  {
    "path": "modules/component-store/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/component-store/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../../\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmitOnError\": false,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/component-store\",\n    \"paths\": {\n      \"@ngrx/operators\": [\"./dist/modules/operators\"]\n    },\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"skipLibCheck\": true\n  },\n  \"files\": [\"public_api.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/component-store\"\n  }\n}\n"
  },
  {
    "path": "modules/component-store/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/component-store\",\n    \"paths\": {\n      \"@ngrx/component-store/schematics-core\": [\"./schematics-core\"],\n      \"@ngrx/component-store\": [\"./src\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/component-store/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"vitest/globals\", \"node\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/component-store/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [angular(), nxViteTsPaths()],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default'],\n      snapshotSerializers: [\n        'jest-preset-angular/build/serializers/html-comment',\n        'jest-preset-angular/build/serializers/ng-snapshot',\n        'jest-preset-angular/build/serializers/no-ng-attributes',\n      ],\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "modules/data/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/data/README.md",
    "content": "# @ngrx/data\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/data/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist', '**/jest.config.ts', '**/schematics-core/**/*.ts'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@typescript-eslint/no-non-null-assertion': 'off',\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-case-declarations': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/data/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/data/index.ts",
    "content": "/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\nexport * from './public_api';\n"
  },
  {
    "path": "modules/data/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {}\n}\n"
  },
  {
    "path": "modules/data/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/data\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/data/package.json",
    "content": "{\n  \"name\": \"@ngrx/data\",\n  \"version\": \"21.0.1\",\n  \"description\": \"API management for NgRx\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"Angular\",\n    \"Redux\",\n    \"NgRx\",\n    \"Schematics\",\n    \"Angular CLI\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"@angular/common\": \"^21.0.0\",\n    \"@angular/core\": \"^21.0.0\",\n    \"@ngrx/store\": \"21.0.1\",\n    \"@ngrx/effects\": \"21.0.1\",\n    \"@ngrx/entity\": \"21.0.1\",\n    \"rxjs\": \"^6.5.3 || ^7.5.0\"\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"sideEffects\": false,\n  \"dependencies\": {\n    \"tslib\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "modules/data/project.json",
    "content": "{\n  \"name\": \"data\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/data/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/data/tsconfig.build.json\",\n        \"project\": \"modules/data/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package data\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/data/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/data\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/data\"\n          },\n          {\n            \"command\": \"ncp dist/modules/data node_modules/@ngrx/data\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/data\"\n          }\n        ]\n      },\n      \"outputs\": [\n        \"{workspaceRoot}/dist/modules/data\",\n        \"{workspaceRoot}/node_modules/@ngrx/data\"\n      ]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/data/*/**/*.ts\",\n          \"modules/data/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/data\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/data/public_api.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/data/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Add @ngrx/data to your application\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/data/schematics/ng-add/__snapshots__/index.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Data ng-add Schematic > Migration of ngrx-data > Data ng-add Schematic for standalone application > provides data without effects 1`] = `\n\"import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { provideEntityData } from '@ngrx/data';\nimport { entityConfig } from './entity-metadata';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideBrowserGlobalErrorListeners(),\n    provideEntityData(entityConfig)\n]\n};\n\"\n`;\n\nexports[`Data ng-add Schematic > Migration of ngrx-data > Data ng-add Schematic for standalone application > provides data without entityConfig 1`] = `\n\"import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { provideEntityData, withEffects } from '@ngrx/data';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideBrowserGlobalErrorListeners(),\n    provideEntityData({}, withEffects())\n]\n};\n\"\n`;\n\nexports[`Data ng-add Schematic > Migration of ngrx-data > Data ng-add Schematic for standalone application > provides default data setup 1`] = `\n\"import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { provideEntityData, withEffects } from '@ngrx/data';\nimport { entityConfig } from './entity-metadata';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideBrowserGlobalErrorListeners(),\n    provideEntityData(entityConfig, withEffects())\n]\n};\n\"\n`;\n"
  },
  {
    "path": "modules/data/schematics/ng-add/files/entity-metadata.ts.template",
    "content": "import { EntityMetadataMap, EntityDataModuleConfig } from '@ngrx/data';\n\nconst entityMetadata: EntityMetadataMap = {};\n\nconst pluralNames = {  };\n\nexport const entityConfig: EntityDataModuleConfig = {\n  entityMetadata,\n  pluralNames\n};\n"
  },
  {
    "path": "modules/data/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as DataEntityOptions } from './schema';\nimport {\n  createWorkspace,\n  getTestProjectPath,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Data ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/data',\n    path.join(process.cwd(), 'dist/modules/data/schematics/collection.json')\n  );\n  const defaultOptions: DataEntityOptions = {\n    skipPackageJson: false,\n    project: 'bar',\n    module: 'app-module',\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/data']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/data']).toBeUndefined();\n  });\n\n  it('should import into a specified module', async () => {\n    const options = { ...defaultOptions, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(/import { EntityDataModule } from '@ngrx\\/data'/);\n  });\n\n  it('should fail if specified module does not exist', async () => {\n    const options = {\n      ...defaultOptions,\n      module: `${projectPath}/src/app/app-moduleXXX.ts`,\n    };\n    let thrownError: Error | null = null;\n    try {\n      await schematicRunner.runSchematic('data', options, appTree);\n    } catch (err: any) {\n      thrownError = err;\n    }\n    expect(thrownError).toBeDefined();\n  });\n\n  it('should add entity-metadata config to EntityDataModule', async () => {\n    const options = { ...defaultOptions, effects: false, entityConfig: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(\n      /import { entityConfig } from '.\\/entity-metadata'/\n    );\n    expect(content).toMatch(\n      /EntityDataModuleWithoutEffects.forRoot\\(entityConfig\\)/\n    );\n  });\n\n  it('should add entity-metadata config file', async () => {\n    const options = { ...defaultOptions, entityConfig: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/entity-metadata.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should add entity-metadata config to EntityDataModule', async () => {\n    const options = { ...defaultOptions, entityConfig: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(\n      /import { entityConfig } from '.\\/entity-metadata'/\n    );\n    expect(content).toMatch(/EntityDataModule.forRoot\\(entityConfig\\)/);\n  });\n\n  it('should import EntityDataModuleWithoutEffects into a specified module', async () => {\n    const options = {\n      ...defaultOptions,\n      module: 'app-module.ts',\n      effects: false,\n      entityConfig: false,\n    };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(\n      /import { EntityDataModuleWithoutEffects } from '@ngrx\\/data'/\n    );\n  });\n\n  it('should register EntityDataModule in the provided module', async () => {\n    const options = { ...defaultOptions, entityConfig: false };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(/EntityDataModule/);\n  });\n\n  it('should register EntityDataModuleWithoutEffects in the provided module', async () => {\n    const options = { ...defaultOptions, effects: false, entityConfig: false };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(/EntityDataModuleWithoutEffects/);\n  });\n\n  describe('Migration of ngrx-data', () => {\n    it('should remove ngrx-data from package.json', async () => {\n      const options = { ...defaultOptions, migrateNgrxData: true };\n\n      const packageJsonBefore = JSON.parse(\n        appTree.readContent('/package.json')\n      );\n      packageJsonBefore['dependencies']['ngrx-data'] = '1.0.0';\n      appTree.overwrite(\n        '/package.json',\n        JSON.stringify(packageJsonBefore, null, 2)\n      );\n\n      expect(\n        JSON.parse(appTree.readContent('/package.json'))['dependencies'][\n          'ngrx-data'\n        ]\n      ).toBeDefined();\n\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n      const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n      expect(packageJson.dependencies['ngrx-data']).not.toBeDefined();\n    });\n\n    it('should rename NgrxDataModule', async () => {\n      const options = {\n        ...defaultOptions,\n        migrateNgrxData: true,\n      };\n\n      const dataModulePath = '/data.module.ts';\n\n      const input = `\n        import {\n          DefaultDataServiceConfig,\n          EntityDataService,\n          EntityHttpResourceUrls,\n          EntityServices,\n          Logger,\n          NgrxDataModule,\n          Pluralizer\n        } from 'ngrx-data';\n\n        @NgModule({\n          imports: [\n            NgrxDataModule.forRoot({\n              entityMetadata: entityMetadata,\n              pluralNames: pluralNames\n            })\n          ],\n        })\n        export class AppModule {}\n      `;\n\n      const output = `\n        import {\n          DefaultDataServiceConfig,\n          EntityDataService,\n          EntityHttpResourceUrls,\n          EntityServices,\n          Logger,\n          EntityDataModule,\n          Pluralizer\n        } from '@ngrx/data';\n\n        @NgModule({\n          imports: [\n            EntityDataModule.forRoot({\n              entityMetadata: entityMetadata,\n              pluralNames: pluralNames\n            })\n          ],\n        })\n        export class AppModule {}\n      `;\n      appTree.create(dataModulePath, input);\n\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n      const actual = tree.readContent(dataModulePath);\n\n      expect(actual).toBe(output);\n    });\n\n    it('should rename NgrxDataModuleWithoutEffects ', async () => {\n      const options = {\n        ...defaultOptions,\n        migrateNgrxData: true,\n      };\n\n      const dataModulePath = '/data.module.ts';\n\n      const input = `\n        import {\n          DefaultDataServiceConfig,\n          EntityDataService,\n          EntityHttpResourceUrls,\n          EntityServices,\n          Logger,\n          NgrxDataModuleWithoutEffects,\n          Pluralizer\n        } from 'ngrx-data';\n\n        @NgModule({\n          imports: [\n            NgrxDataModuleWithoutEffects.forRoot({\n              entityMetadata: entityMetadata,\n              pluralNames: pluralNames\n            })\n          ],\n        })\n        export class AppModule {}\n      `;\n\n      const output = `\n        import {\n          DefaultDataServiceConfig,\n          EntityDataService,\n          EntityHttpResourceUrls,\n          EntityServices,\n          Logger,\n          EntityDataModuleWithoutEffects,\n          Pluralizer\n        } from '@ngrx/data';\n\n        @NgModule({\n          imports: [\n            EntityDataModuleWithoutEffects.forRoot({\n              entityMetadata: entityMetadata,\n              pluralNames: pluralNames\n            })\n          ],\n        })\n        export class AppModule {}\n      `;\n      appTree.create(dataModulePath, input);\n\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n      const actual = tree.readContent(dataModulePath);\n\n      expect(actual).toBe(output);\n    });\n\n    it('should rename NgrxDataModuleConfig ', async () => {\n      const options = {\n        ...defaultOptions,\n        migrateNgrxData: true,\n      };\n\n      const dataModulePath = '/data.module.ts';\n\n      const input = `\n        import {\n          DefaultDataServiceConfig,\n          EntityDataService,\n          EntityHttpResourceUrls,\n          EntityServices,\n          Logger,\n          NgrxDataModule,\n          NgrxDataModuleConfig,\n          Pluralizer\n        } from 'ngrx-data';\n\n        const customConfig: NgrxDataModuleConfig = {\n          root: 'api', // default root path to the server's web api\n          timeout: 3000, // request timeout\n        };\n\n        @NgModule({\n          imports: [\n            NgrxDataModule.forRoot({\n              entityMetadata: entityMetadata,\n              pluralNames: pluralNames\n            })\n          ],\n          providers: [\n            { provide: DefaultDataServiceConfig, useValue: customConfig },\n          ]\n        })\n        export class AppModule {}\n      `;\n\n      const output = `\n        import {\n          DefaultDataServiceConfig,\n          EntityDataService,\n          EntityHttpResourceUrls,\n          EntityServices,\n          Logger,\n          EntityDataModule,\n          EntityDataModuleConfig,\n          Pluralizer\n        } from '@ngrx/data';\n\n        const customConfig: EntityDataModuleConfig = {\n          root: 'api', // default root path to the server's web api\n          timeout: 3000, // request timeout\n        };\n\n        @NgModule({\n          imports: [\n            EntityDataModule.forRoot({\n              entityMetadata: entityMetadata,\n              pluralNames: pluralNames\n            })\n          ],\n          providers: [\n            { provide: DefaultDataServiceConfig, useValue: customConfig },\n          ]\n        })\n        export class AppModule {}\n      `;\n      appTree.create(dataModulePath, input);\n\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n      const actual = tree.readContent(dataModulePath);\n\n      expect(actual).toBe(output);\n    });\n\n    describe('Data ng-add Schematic for standalone application', () => {\n      const projectPath = getTestProjectPath(undefined, {\n        name: 'bar-standalone',\n      });\n\n      const standaloneDefaultOptions = {\n        ...defaultOptions,\n        project: 'bar-standalone',\n      };\n\n      it('provides default data setup', async () => {\n        const options = { ...standaloneDefaultOptions };\n        const tree = await schematicRunner.runSchematic(\n          'ng-add',\n          options,\n          appTree\n        );\n\n        const content = tree.readContent(\n          `${projectPath}/src/app/app.config.ts`\n        );\n\n        expect(content).toMatchSnapshot();\n      });\n\n      it('provides data without effects', async () => {\n        const options = { ...standaloneDefaultOptions, effects: false };\n        const tree = await schematicRunner.runSchematic(\n          'ng-add',\n          options,\n          appTree\n        );\n\n        const content = tree.readContent(\n          `${projectPath}/src/app/app.config.ts`\n        );\n\n        expect(content).toMatchSnapshot();\n      });\n\n      it('provides data without entityConfig', async () => {\n        const options = { ...standaloneDefaultOptions, entityConfig: false };\n        const tree = await schematicRunner.runSchematic(\n          'ng-add',\n          options,\n          appTree\n        );\n\n        const content = tree.readContent(\n          `${projectPath}/src/app/app.config.ts`\n        );\n\n        expect(content).toMatchSnapshot();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/data/schematics/ng-add/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Path } from '@angular-devkit/core';\nimport {\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  mergeWith,\n  move,\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  noop,\n  Tree,\n  url,\n} from '@angular-devkit/schematics';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\nimport {\n  addImportToModule,\n  addPackageToPackageJson,\n  commitChanges,\n  createReplaceChange,\n  findModuleFromOptions,\n  getProjectPath,\n  insertImport,\n  parseName,\n  platformVersion,\n  ReplaceChange,\n  stringUtils,\n  visitTSSourceFiles,\n} from '../../schematics-core';\nimport { Schema as EntityDataOptions } from './schema';\nimport { getProjectMainFile } from '../../schematics-core/utility/project';\nimport {\n  addFunctionalProvidersToStandaloneBootstrap,\n  callsProvidersFunction,\n} from '../../schematics-core/utility/standalone';\nimport { isStandaloneApp } from '@schematics/angular/utility/ng-ast-utils';\n\nfunction addNgRxDataToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/data',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nfunction addEntityDataToNgModule(options: EntityDataOptions): Rule {\n  return (host: Tree) => {\n    throwIfModuleNotSpecified(host, options.module);\n\n    const modulePath = options.module!;\n    const text = host.read(modulePath)!.toString();\n\n    const source = ts.createSourceFile(\n      modulePath,\n      text,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const moduleToImport = options.effects\n      ? 'EntityDataModule'\n      : 'EntityDataModuleWithoutEffects';\n\n    const effectsModuleImport = insertImport(\n      source,\n      modulePath,\n      moduleToImport,\n      '@ngrx/data'\n    );\n\n    const [dateEntityNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      options.entityConfig\n        ? [moduleToImport, 'forRoot(entityConfig)'].join('.')\n        : moduleToImport,\n      ''\n    );\n\n    const changes = [effectsModuleImport, dateEntityNgModuleImport];\n\n    if (options.entityConfig) {\n      const entityConfigImport = insertImport(\n        source,\n        modulePath,\n        'entityConfig',\n        './entity-metadata'\n      );\n      changes.push(entityConfigImport);\n    }\n\n    commitChanges(host, source.fileName, changes);\n\n    return host;\n  };\n}\n\nfunction addStandaloneConfig(options: EntityDataOptions): Rule {\n  return (host: Tree) => {\n    const mainFile = getProjectMainFile(host, options);\n    if (host.exists(mainFile)) {\n      const providerFn = 'provideEntityData';\n\n      if (callsProvidersFunction(host, mainFile, providerFn)) {\n        // exit because the store config is already provided\n        return host;\n      }\n\n      const providerOptions = [\n        ...(options.entityConfig\n          ? [ts.factory.createIdentifier(`entityConfig`)]\n          : [ts.factory.createIdentifier(`{}`)]),\n        ...(options.effects\n          ? [ts.factory.createIdentifier(`withEffects()`)]\n          : []),\n      ];\n\n      const patchedConfigFile = addFunctionalProvidersToStandaloneBootstrap(\n        host,\n        mainFile,\n        providerFn,\n        '@ngrx/data',\n        providerOptions\n      );\n\n      const configFileContent = host.read(patchedConfigFile);\n      const source = ts.createSourceFile(\n        patchedConfigFile,\n        configFileContent?.toString('utf-8') || '',\n        ts.ScriptTarget.Latest,\n        true\n      );\n\n      const recorder = host.beginUpdate(patchedConfigFile);\n\n      const changes = [];\n\n      if (options.effects) {\n        const withEffectsImport = insertImport(\n          source,\n          patchedConfigFile,\n          'withEffects',\n          '@ngrx/data'\n        );\n\n        changes.push(withEffectsImport);\n      }\n\n      if (options.entityConfig) {\n        const entityConfigImport = insertImport(\n          source,\n          patchedConfigFile,\n          'entityConfig',\n          './entity-metadata'\n        );\n\n        changes.push(entityConfigImport);\n      }\n\n      changes.forEach((change: any) => {\n        recorder.insertLeft(change.pos, change.toAdd);\n      });\n\n      host.commitUpdate(recorder);\n\n      return host;\n    }\n\n    throw new SchematicsException(\n      `Main file not found for a project ${options.project}`\n    );\n  };\n}\n\nconst renames = {\n  NgrxDataModule: 'EntityDataModule',\n  NgrxDataModuleWithoutEffects: 'EntityDataModuleWithoutEffects',\n  NgrxDataModuleConfig: 'EntityDataModuleConfig',\n};\n\nfunction removeAngularNgRxDataFromPackageJson() {\n  return (host: Tree) => {\n    if (host.exists('package.json')) {\n      const sourceText = host.read('package.json')!.toString('utf-8');\n      const json = JSON.parse(sourceText);\n\n      if (json['dependencies'] && json['dependencies']['ngrx-data']) {\n        delete json['dependencies']['ngrx-data'];\n      }\n\n      host.overwrite('package.json', JSON.stringify(json, null, 2));\n    }\n\n    return host;\n  };\n}\n\nfunction renameNgrxDataModule() {\n  return (host: Tree) => {\n    visitTSSourceFiles(host, (sourceFile) => {\n      const ngrxDataImports = sourceFile.statements\n        .filter(ts.isImportDeclaration)\n        .filter(\n          ({ moduleSpecifier }) =>\n            moduleSpecifier.getText(sourceFile) === \"'ngrx-data'\"\n        );\n\n      if (ngrxDataImports.length === 0) {\n        return;\n      }\n\n      const changes = [\n        ...findNgrxDataImports(sourceFile, ngrxDataImports),\n        ...findNgrxDataImportDeclarations(sourceFile, ngrxDataImports),\n        ...findNgrxDataReplacements(sourceFile),\n      ];\n\n      commitChanges(host, sourceFile.fileName, changes);\n    });\n  };\n}\n\nfunction findNgrxDataImports(\n  sourceFile: ts.SourceFile,\n  imports: ts.ImportDeclaration[]\n) {\n  const changes = imports.map((specifier) =>\n    createReplaceChange(\n      sourceFile,\n      specifier.moduleSpecifier,\n      \"'ngrx-data'\",\n      \"'@ngrx/data'\"\n    )\n  );\n\n  return changes;\n}\n\nfunction findNgrxDataImportDeclarations(\n  sourceFile: ts.SourceFile,\n  imports: ts.ImportDeclaration[]\n) {\n  const changes = imports\n    .map((p) => (p.importClause!.namedBindings! as ts.NamedImports).elements)\n    .reduce((imports, curr) => imports.concat(curr), [] as ts.ImportSpecifier[])\n    .map((specifier) => {\n      if (!ts.isImportSpecifier(specifier)) {\n        return { hit: false };\n      }\n\n      const ngrxDataImports = Object.keys(renames);\n      if (ngrxDataImports.includes(specifier.name.text)) {\n        return { hit: true, specifier, text: specifier.name.text };\n      }\n\n      // if import is renamed\n      if (\n        specifier.propertyName &&\n        ngrxDataImports.includes(specifier.propertyName.text)\n      ) {\n        return { hit: true, specifier, text: specifier.propertyName.text };\n      }\n\n      return { hit: false };\n    })\n    .filter(({ hit }) => hit)\n    .map(({ specifier, text }) =>\n      createReplaceChange(\n        sourceFile,\n        specifier!,\n        text!,\n        (renames as any)[text!]\n      )\n    );\n\n  return changes;\n}\n\nfunction findNgrxDataReplacements(sourceFile: ts.SourceFile) {\n  const renameKeys = Object.keys(renames);\n  const changes: ReplaceChange[] = [];\n  ts.forEachChild(sourceFile, (node) => find(node, changes));\n  return changes;\n\n  function find(node: ts.Node, changes: ReplaceChange[]) {\n    let change = undefined;\n\n    if (\n      ts.isPropertyAssignment(node) &&\n      renameKeys.includes(node.initializer.getText(sourceFile))\n    ) {\n      change = {\n        node: node.initializer,\n        text: node.initializer.getText(sourceFile),\n      };\n    }\n\n    if (\n      ts.isPropertyAccessExpression(node) &&\n      renameKeys.includes(node.expression.getText(sourceFile))\n    ) {\n      change = {\n        node: node.expression,\n        text: node.expression.getText(sourceFile),\n      };\n    }\n\n    if (\n      ts.isVariableDeclaration(node) &&\n      node.type &&\n      renameKeys.includes(node.type.getText(sourceFile))\n    ) {\n      change = {\n        node: node.type,\n        text: node.type.getText(sourceFile),\n      };\n    }\n\n    if (change) {\n      changes.push(\n        createReplaceChange(\n          sourceFile,\n          change.node,\n          change.text,\n          (renames as any)[change.text]\n        )\n      );\n    }\n\n    ts.forEachChild(node, (childNode) => find(childNode, changes));\n  }\n}\n\nfunction throwIfModuleNotSpecified(host: Tree, module?: string) {\n  if (!module) {\n    throw new Error('Module not specified');\n  }\n\n  if (!host.exists(module)) {\n    throw new Error('Specified module does not exist');\n  }\n\n  const text = host.read(module);\n  if (text === null) {\n    throw new SchematicsException(`File ${module} does not exist.`);\n  }\n}\n\nfunction createEntityConfigFile(options: EntityDataOptions, path: Path) {\n  return mergeWith(\n    apply(url('./files'), [\n      applyTemplates({\n        ...stringUtils,\n        ...options,\n      }),\n      move(path),\n    ])\n  );\n}\n\nexport default function (options: EntityDataOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    (options as any).name = '';\n    options.path = getProjectPath(host, options);\n    const mainFile = getProjectMainFile(host, options);\n    const isStandalone = isStandaloneApp(host, mainFile);\n    options.effects = options.effects === undefined ? true : options.effects;\n    options.module =\n      options.module && !isStandalone\n        ? findModuleFromOptions(host, options as any)\n        : options.module;\n\n    const parsedPath = parseName(options.path, '');\n    options.path = parsedPath.path;\n\n    const configOrModuleUpdate = isStandalone\n      ? addStandaloneConfig(options)\n      : addEntityDataToNgModule(options);\n\n    return chain([\n      options && options.skipPackageJson ? noop() : addNgRxDataToPackageJson(),\n      options.migrateNgrxData\n        ? chain([\n            removeAngularNgRxDataFromPackageJson(),\n            renameNgrxDataModule(),\n          ])\n        : branchAndMerge(chain([configOrModuleUpdate])),\n      options.entityConfig\n        ? createEntityConfigFile(options, parsedPath.path)\n        : noop(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/data/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxData\",\n  \"title\": \"NgRx Data Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"effect\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"When false use the EntityDataModuleWithoutEffects module instead of EntityDataModule.\"\n    },\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/data as dependency to package.json (e.g., --skipPackageJson).\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to the module.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"default\": \"app\",\n      \"description\": \"Allows specification of the declaring module.\",\n      \"alias\": \"m\",\n      \"subtype\": \"filepath\"\n    },\n    \"migrateNgrxData\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Migrate from ngrx-data, will rename modules.\",\n      \"alias\": \"migrate\"\n    },\n    \"entityConfig\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Create the Entity config file\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/data/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  path?: string;\n  effects?: boolean;\n  skipPackageJson?: boolean;\n  project?: string;\n  module?: string;\n  migrateNgrxData?: boolean;\n  entityConfig?: boolean;\n}\n"
  },
  {
    "path": "modules/data/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/data/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/data/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/data/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/data/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/data/spec/actions/entity-action-factory.spec.ts",
    "content": "import {\n  EntityActionOptions,\n  EntityActionPayload,\n  EntityOp,\n  EntityActionFactory,\n  MergeStrategy,\n} from '../../';\n\nclass Hero {\n  id!: number;\n  name!: string;\n}\n\ndescribe('EntityActionFactory', () => {\n  let factory: EntityActionFactory;\n\n  beforeEach(() => {\n    factory = new EntityActionFactory();\n  });\n\n  it('#create should create an EntityAction from entityName and entityOp', () => {\n    const action = factory.create('Hero', EntityOp.QUERY_ALL);\n    const { entityName, entityOp, data } = action.payload;\n    expect(entityName).toBe('Hero');\n    expect(entityOp).toBe(EntityOp.QUERY_ALL);\n    expect(data).toBeUndefined();\n  });\n\n  it('#create should create an EntityAction with the given data', () => {\n    const hero: Hero = { id: 42, name: 'Francis' };\n    const action = factory.create('Hero', EntityOp.ADD_ONE, hero);\n    const { entityName, entityOp, data } = action.payload;\n    expect(entityName).toBe('Hero');\n    expect(entityOp).toBe(EntityOp.ADD_ONE);\n    expect(data).toBe(hero);\n  });\n\n  it('#create should create an EntityAction with options', () => {\n    const options: EntityActionOptions = {\n      correlationId: 'CRID42',\n      isOptimistic: true,\n      mergeStrategy: MergeStrategy.OverwriteChanges,\n      tag: 'Foo',\n    };\n\n    // Don't forget placeholder for missing optional data!\n    const action = factory.create(\n      'Hero',\n      EntityOp.QUERY_ALL,\n      undefined,\n      options\n    );\n    const {\n      entityName,\n      entityOp,\n      data,\n      correlationId,\n      isOptimistic,\n      mergeStrategy,\n      tag,\n    } = action.payload;\n    expect(entityName).toBe('Hero');\n    expect(entityOp).toBe(EntityOp.QUERY_ALL);\n    expect(data).toBeUndefined();\n    expect(correlationId).toBe(options.correlationId);\n    expect(isOptimistic).toBe(options.isOptimistic);\n    expect(mergeStrategy).toBe(options.mergeStrategy);\n    expect(tag).toBe(options.tag);\n  });\n\n  it('#create create an EntityAction from an EntityActionPayload', () => {\n    const hero: Hero = { id: 42, name: 'Francis' };\n    const payload: EntityActionPayload = {\n      entityName: 'Hero',\n      entityOp: EntityOp.ADD_ONE,\n      data: hero,\n      correlationId: 'CRID42',\n      isOptimistic: true,\n      mergeStrategy: MergeStrategy.OverwriteChanges,\n      tag: 'Foo',\n      httpOptions: {\n        httpParams: {\n          fromString: 'extraQueryParam=CreateHeroLink',\n        },\n      },\n    };\n    const action = factory.create(payload);\n\n    const {\n      entityName,\n      entityOp,\n      data,\n      correlationId,\n      isOptimistic,\n      mergeStrategy,\n      tag,\n      httpOptions,\n    } = action.payload;\n    expect(entityName).toBe(payload.entityName);\n    expect(entityOp).toBe(payload.entityOp);\n    expect(data).toBe(payload.data);\n    expect(correlationId).toBe(payload.correlationId);\n    expect(isOptimistic).toBe(payload.isOptimistic);\n    expect(mergeStrategy).toBe(payload.mergeStrategy);\n    expect(tag).toBe(payload.tag);\n    expect(httpOptions?.httpParams?.fromString).toBe(\n      payload.httpOptions?.httpParams?.fromString\n    );\n  });\n\n  it('#createFromAction should create EntityAction from another EntityAction', () => {\n    // pessimistic save\n    const hero1: Hero = { id: undefined as any, name: 'Francis' };\n    const action1 = factory.create('Hero', EntityOp.SAVE_ADD_ONE, hero1);\n\n    // after save succeeds\n    const hero: Hero = { ...hero1, id: 42 };\n    const action = factory.createFromAction(action1, {\n      entityOp: EntityOp.SAVE_ADD_ONE_SUCCESS,\n      data: hero,\n    });\n    const { entityName, entityOp, data } = action.payload;\n\n    expect(entityName).toBe('Hero');\n    expect(entityOp).toBe(EntityOp.SAVE_ADD_ONE_SUCCESS);\n    expect(data).toBe(hero);\n    const expectedType = factory.formatActionType(\n      EntityOp.SAVE_ADD_ONE_SUCCESS,\n      'Hero'\n    );\n    expect(action.type).toEqual(expectedType);\n  });\n\n  it('#createFromAction should copy the options from the source action', () => {\n    const options: EntityActionOptions = {\n      correlationId: 'CRID42',\n      isOptimistic: true,\n      mergeStrategy: MergeStrategy.OverwriteChanges,\n      tag: 'Foo',\n    };\n    // Don't forget placeholder for missing optional data!\n    const sourceAction = factory.create(\n      'Hero',\n      EntityOp.QUERY_ALL,\n      undefined,\n      options\n    );\n\n    const queryResults: Hero[] = [\n      { id: 1, name: 'Francis' },\n      { id: 2, name: 'Alex' },\n    ];\n    const action = factory.createFromAction(sourceAction, {\n      entityOp: EntityOp.QUERY_ALL_SUCCESS,\n      data: queryResults,\n    });\n\n    const {\n      entityName,\n      entityOp,\n      data,\n      correlationId,\n      isOptimistic,\n      mergeStrategy,\n      tag,\n    } = action.payload;\n    expect(entityName).toBe('Hero');\n    expect(entityOp).toBe(EntityOp.QUERY_ALL_SUCCESS);\n    expect(data).toBe(queryResults);\n    expect(correlationId).toBe(options.correlationId);\n    expect(isOptimistic).toBe(options.isOptimistic);\n    expect(mergeStrategy).toBe(options.mergeStrategy);\n    expect(tag).toBe(options.tag);\n  });\n\n  it('#createFromAction can suppress the data property', () => {\n    const hero: Hero = { id: 42, name: 'Francis' };\n    const action1 = factory.create('Hero', EntityOp.ADD_ONE, hero);\n    const action = factory.createFromAction(action1, {\n      entityOp: EntityOp.SAVE_ADD_ONE,\n      data: undefined,\n    });\n    const { entityName, entityOp, data } = action.payload;\n    expect(entityName).toBe('Hero');\n    expect(entityOp).toBe(EntityOp.SAVE_ADD_ONE);\n    expect(data).toBeUndefined();\n  });\n\n  it('#formatActionType should format type with the entityName', () => {\n    const action = factory.create('Hero', EntityOp.QUERY_ALL);\n    const expectedFormat = factory.formatActionType(EntityOp.QUERY_ALL, 'Hero');\n    expect(action.type).toBe(expectedFormat);\n  });\n\n  it('#formatActionType should format type with given tag instead of the entity name', () => {\n    const tag = 'Hero - Tag Test';\n    const action = factory.create('Hero', EntityOp.QUERY_ALL, null, { tag });\n    expect(action.type).toContain(tag);\n  });\n\n  it('can re-format generated action.type with a custom #formatActionType()', () => {\n    factory.formatActionType = (op, entityName) =>\n      `${entityName}_${op}`.toUpperCase();\n\n    const expected = ('Hero_' + EntityOp.QUERY_ALL).toUpperCase();\n    const action = factory.create('Hero', EntityOp.QUERY_ALL);\n    expect(action.type).toBe(expected);\n  });\n\n  it('should throw if do not specify entityName', () => {\n    expect(() => factory.create(null as any)).toThrow();\n  });\n\n  it('should throw if do not specify EntityOp', () => {\n    expect(() =>\n      factory.create({ entityName: 'Hero', entityOp: null as any })\n    ).toThrow();\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/actions/entity-action-guard.spec.ts",
    "content": "import { EntityActionGuard, EntityAction, EntityOp } from '../..';\n\nclass Hero {\n  id!: number;\n  name!: string;\n  power?: string;\n}\n\ndescribe('EntityActionGuard', () => {\n  let guard: EntityActionGuard<Hero>;\n  let createAction: any;\n  let action: EntityAction<any>;\n\n  beforeEach(() => {\n    const selectId = (hero: Hero) => hero.id;\n    guard = new EntityActionGuard('Hero', selectId);\n\n    createAction = (data?: any) =>\n      (action = {\n        type: 'TEST',\n        payload: {\n          entityName: 'Hero',\n          entityOp: EntityOp.ADD_ALL, // not used\n          data,\n        },\n      });\n  });\n\n  describe('mustBeEntity', () => {\n    it('should throw if action does not contain entity', () => {\n      action = createAction();\n      expect(() => guard.mustBeEntity(action)).toThrowError(\n        /should have a single entity/\n      );\n    });\n\n    it('should throw if entity has a missing entity key', () => {\n      action = createAction({ name: 'Thor' });\n      expect(() => guard.mustBeEntity(action)).toThrowError(\n        /has a missing or invalid entity key/\n      );\n    });\n\n    it('should throw if entity has an invalid entity key', () => {\n      action = createAction({ id: { id: 1 }, name: 'Thor' });\n      expect(() => guard.mustBeEntity(action)).toThrowError(\n        /has a missing or invalid entity key/\n      );\n    });\n\n    it('should return the entity of the action', () => {\n      const data = { id: 1, name: 'Thor', power: 'Hammer' };\n      action = createAction(data);\n      expect(guard.mustBeEntity(action)).toBe(data);\n    });\n  });\n\n  describe('mustBeEntities', () => {\n    it('should throw if action does not contain an array of entities', () => {\n      action = createAction({ id: 1, name: 'Thor', power: 'Hammer' });\n      expect(() => guard.mustBeEntities(action)).toThrowError(\n        /should be an array of entities/\n      );\n    });\n\n    it('should throw if any entity has a missing entity key', () => {\n      const data = [\n        { id: 1, name: 'Thor', power: 'Hammer' },\n        { name: 'Iron Man', power: 'Nano' },\n      ];\n\n      action = createAction(data);\n      expect(() => guard.mustBeEntities(action)).toThrowError(\n        /item 2, does not have a valid entity key/\n      );\n    });\n\n    it('should throw if any entity has an invalid entity key', () => {\n      const data = [\n        { id: 1, name: 'Thor', power: 'Hammer' },\n        { id: null, name: 'Iron Man', power: 'Nano' },\n      ];\n\n      action = createAction(data);\n      expect(() => guard.mustBeEntities(action)).toThrowError(\n        /item 2, does not have a valid entity key/\n      );\n    });\n\n    it('should return the array of entities of the action', () => {\n      const data = [\n        { id: 1, name: 'Thor', power: 'Hammer' },\n        { id: 2, name: 'Iron Man', power: 'Nano' },\n      ];\n      action = createAction(data);\n      expect(guard.mustBeEntities(action)).toBe(data);\n    });\n  });\n\n  describe('mustBeKey', () => {\n    it('should throw if action does not contain a single entity key', () => {\n      action = createAction();\n      expect(() => guard.mustBeKey(action)).toThrowError(\n        /should be a single entity key/\n      );\n    });\n\n    it('should throw if action has an invalid entity key', () => {\n      action = createAction({ id: 1, name: 'Thor', power: 'Hammer' });\n      expect(() => guard.mustBeKey(action)).toThrowError(/is not a valid key/);\n    });\n\n    it('should return the entity key of the action', () => {\n      action = createAction(1);\n      expect(guard.mustBeKey(action)).toBe(1);\n    });\n\n    it('should not throw if key is 0', () => {\n      action = createAction(0);\n      expect(guard.mustBeKey(action)).toBe(0);\n    });\n  });\n\n  describe('mustBeKeys', () => {\n    it('should throw if action does not contain an array of entity keys', () => {\n      const data = { id: 1, name: 'Thor', power: 'Hammer' };\n      action = createAction(data);\n      expect(() => guard.mustBeKeys(action)).toThrowError(\n        /should be an array of entity keys/\n      );\n    });\n\n    it('should throw if any member of the array is an invalid entity key', () => {\n      const data = [1, null];\n      action = createAction(data);\n      expect(() => guard.mustBeKeys(action)).toThrowError(\n        /item 2, is not a valid entity key/\n      );\n    });\n\n    it('should return the array of entity keys of the action', () => {\n      const data = [1, 2];\n      action = createAction(data);\n      expect(guard.mustBeKeys(action)).toBe(data);\n    });\n  });\n\n  describe('mustBeUpdate', () => {\n    it('should throw if action does not contain a single entity update', () => {\n      action = createAction();\n      expect(() => guard.mustBeUpdate(action)).toThrowError(\n        /should be a single entity update/\n      );\n    });\n\n    it('should throw if entity update has a missing entity key', () => {\n      action = createAction({\n        changes: {\n          name: 'Thor',\n        },\n      });\n      expect(() => guard.mustBeUpdate(action)).toThrowError(\n        /has a missing or invalid entity key/\n      );\n    });\n\n    it('should throw if entity update has an invalid entity key', () => {\n      action = createAction({\n        changes: {\n          id: null,\n          name: 'Thor',\n        },\n      });\n      expect(() => guard.mustBeUpdate(action)).toThrowError(\n        /has a missing or invalid entity key/\n      );\n    });\n\n    it('should return the entity update of the action', () => {\n      const data = {\n        id: 1,\n        changes: { id: 1, name: 'Thor', power: 'Hammer' },\n      };\n      action = createAction(data);\n      expect(guard.mustBeUpdate(action)).toBe(data);\n    });\n  });\n\n  describe('mustBeUpdates', () => {\n    it('should throw if action does not contain an array of entity updates', () => {\n      const data = {\n        id: 1,\n        changes: { id: 1, name: 'Thor', power: 'Hammer' },\n      };\n      action = createAction(data);\n      expect(() => guard.mustBeUpdates(action)).toThrowError(\n        /should be an array of entity updates/\n      );\n    });\n\n    it('should throw if any entity update has a missing entity key', () => {\n      const data = [\n        {\n          id: 1,\n          changes: { id: 1, name: 'Thor', power: 'Hammer' },\n        },\n        {\n          changes: { name: 'Thor', power: 'Hammer' },\n        },\n      ];\n      action = createAction(data);\n      expect(() => guard.mustBeUpdates(action)).toThrowError(\n        /item 2, has a missing or invalid entity key/\n      );\n    });\n\n    it('should throw if any entity update has an invalid entity key', () => {\n      const data = [\n        {\n          id: 1,\n          changes: { id: 1, name: 'Thor', power: 'Hammer' },\n        },\n        {\n          changes: { id: null, name: 'Thor', power: 'Hammer' },\n        },\n      ];\n      action = createAction(data);\n      expect(() => guard.mustBeUpdates(action)).toThrowError(\n        /item 2, has a missing or invalid entity key/\n      );\n    });\n\n    it('should return the array of entity updates of the action', () => {\n      const data = [\n        {\n          id: 1,\n          changes: { id: 1, name: 'Thor', power: 'Hammer' },\n        },\n        {\n          id: 2,\n          changes: { id: 2, name: 'Thor', power: 'Hammer' },\n        },\n      ];\n      action = createAction(data);\n      expect(guard.mustBeUpdates(action)).toBe(data);\n    });\n  });\n\n  describe('mustBeUpdateResponse', () => {\n    it('should throw if action does not contain a single entity update', () => {\n      action = createAction();\n      expect(() => guard.mustBeUpdateResponse(action)).toThrowError(\n        /should be a single entity update/\n      );\n    });\n\n    it('should throw if entity update has a missing entity key', () => {\n      action = createAction({\n        changes: {\n          name: 'Thor',\n        },\n      });\n      expect(() => guard.mustBeUpdateResponse(action)).toThrowError(\n        /has a missing or invalid entity key/\n      );\n    });\n\n    it('should throw if entity update has an invalid entity key', () => {\n      action = createAction({\n        changes: {\n          id: null,\n          name: 'Thor',\n        },\n      });\n      expect(() => guard.mustBeUpdateResponse(action)).toThrowError(\n        /has a missing or invalid entity key/\n      );\n    });\n\n    it('should return the entity update of the action', () => {\n      const data = {\n        id: 1,\n        changes: { id: 1, name: 'Thor', power: 'Hammer' },\n      };\n      action = createAction(data);\n      expect(guard.mustBeUpdateResponse(action)).toBe(data);\n    });\n  });\n\n  describe('mustBeUpdateResponses', () => {\n    it('should throw if action does not contain an array of entity updates', () => {\n      const data = {\n        id: 1,\n        changes: { id: 1, name: 'Thor', power: 'Hammer' },\n      };\n      action = createAction(data);\n      expect(() => guard.mustBeUpdateResponses(action)).toThrowError(\n        /should be an array of entity updates/\n      );\n    });\n\n    it('should throw if any entity update has a missing entity key', () => {\n      const data = [\n        {\n          id: 1,\n          changes: { id: 1, name: 'Thor', power: 'Hammer' },\n        },\n        {\n          changes: { name: 'Thor', power: 'Hammer' },\n        },\n      ];\n      action = createAction(data);\n      expect(() => guard.mustBeUpdateResponses(action)).toThrowError(\n        /item 2, has a missing or invalid entity key/\n      );\n    });\n\n    it('should throw if any entity update has an invalid entity key', () => {\n      const data = [\n        {\n          id: 1,\n          changes: { id: 1, name: 'Thor', power: 'Hammer' },\n        },\n        {\n          changes: { id: null, name: 'Thor', power: 'Hammer' },\n        },\n      ];\n      action = createAction(data);\n      expect(() => guard.mustBeUpdateResponses(action)).toThrowError(\n        /item 2, has a missing or invalid entity key/\n      );\n    });\n\n    it('should return the array of entity updates of the action', () => {\n      const data = [\n        {\n          id: 1,\n          changes: { id: 1, name: 'Thor', power: 'Hammer' },\n        },\n        {\n          id: 2,\n          changes: { id: 2, name: 'Thor', power: 'Hammer' },\n        },\n      ];\n      action = createAction(data);\n      expect(guard.mustBeUpdateResponses(action)).toBe(data);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/actions/entity-action-operators.spec.ts",
    "content": "import { Action } from '@ngrx/store';\n\nimport { Subject } from 'rxjs';\n\nimport {\n  EntityAction,\n  EntityActionFactory,\n  EntityOp,\n  ofEntityType,\n  ofEntityOp,\n} from '../../';\n\n// Todo: consider marble testing\ndescribe('EntityAction Operators', () => {\n  // factory never changes in these tests\n  const entityActionFactory = new EntityActionFactory();\n\n  let results: any[];\n  let actions: Subject<EntityAction>;\n\n  const testActions = {\n    FOO: <Action>{ type: 'Foo' },\n    HERO_QUERY_ALL: entityActionFactory.create('Hero', EntityOp.QUERY_ALL),\n    VILLAIN_QUERY_MANY: entityActionFactory.create(\n      'Villain',\n      EntityOp.QUERY_MANY\n    ),\n    HERO_DELETE: entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_DELETE_ONE,\n      42\n    ),\n    BAR: <Action>(<any>{ type: 'Bar', payload: 'bar' }),\n  };\n\n  function dispatchTestActions() {\n    Object.keys(testActions).forEach((a) =>\n      actions.next((<any>testActions)[a])\n    );\n  }\n\n  beforeEach(() => {\n    actions = new Subject<EntityAction>();\n    results = [];\n  });\n\n  ///////////////\n\n  it('#ofEntityType()', () => {\n    // EntityActions of any kind\n    actions.pipe(ofEntityType()).subscribe((ea) => results.push(ea));\n\n    const expectedActions = [\n      testActions.HERO_QUERY_ALL,\n      testActions.VILLAIN_QUERY_MANY,\n      testActions.HERO_DELETE,\n    ];\n    dispatchTestActions();\n    expect(results).toEqual(expectedActions);\n  });\n\n  it(`#ofEntityType('SomeType')`, () => {\n    // EntityActions of one type\n    actions.pipe(ofEntityType('Hero')).subscribe((ea) => results.push(ea));\n\n    const expectedActions = [\n      testActions.HERO_QUERY_ALL,\n      testActions.HERO_DELETE,\n    ];\n    dispatchTestActions();\n    expect(results).toEqual(expectedActions);\n  });\n\n  it(`#ofEntityType('Type1', 'Type2', 'Type3')`, () => {\n    // n.b. 'Bar' is not an EntityType even though it is an action type\n    actions\n      .pipe(ofEntityType('Hero', 'Villain', 'Bar'))\n      .subscribe((ea) => results.push(ea));\n\n    ofEntityTypeTest();\n  });\n\n  it('#ofEntityType(...arrayOfTypeNames)', () => {\n    const types = ['Hero', 'Villain', 'Bar'];\n\n    actions.pipe(ofEntityType(...types)).subscribe((ea) => results.push(ea));\n    ofEntityTypeTest();\n  });\n\n  it('#ofEntityType(arrayOfTypeNames)', () => {\n    const types = ['Hero', 'Villain', 'Bar'];\n\n    actions.pipe(ofEntityType(types)).subscribe((ea) => results.push(ea));\n    ofEntityTypeTest();\n  });\n\n  function ofEntityTypeTest() {\n    const expectedActions = [\n      testActions.HERO_QUERY_ALL,\n      testActions.VILLAIN_QUERY_MANY,\n      testActions.HERO_DELETE,\n      // testActions.bar, // 'Bar' is not an EntityType\n    ];\n    dispatchTestActions();\n    expect(results).toEqual(expectedActions);\n  }\n\n  it('#ofEntityType(...) is case sensitive', () => {\n    // EntityActions of the 'hero' type, but it's lowercase so shouldn't match\n    actions.pipe(ofEntityType('hero')).subscribe((ea) => results.push(ea));\n\n    dispatchTestActions();\n    expect(results).toEqual([]);\n  });\n\n  ///////////////\n\n  it('#ofEntityOp with string args', () => {\n    actions\n      .pipe(ofEntityOp(EntityOp.QUERY_ALL, EntityOp.QUERY_MANY))\n      .subscribe((ea) => results.push(ea));\n\n    ofEntityOpTest();\n  });\n\n  it('#ofEntityOp with ...rest args', () => {\n    const ops = [EntityOp.QUERY_ALL, EntityOp.QUERY_MANY];\n\n    actions.pipe(ofEntityOp(...ops)).subscribe((ea) => results.push(ea));\n    ofEntityOpTest();\n  });\n\n  it('#ofEntityOp with array args', () => {\n    const ops = [EntityOp.QUERY_ALL, EntityOp.QUERY_MANY];\n\n    actions.pipe(ofEntityOp(ops)).subscribe((ea) => results.push(ea));\n    ofEntityOpTest();\n  });\n\n  it('#ofEntityOp()', () => {\n    // EntityOps of any kind\n    actions.pipe(ofEntityOp()).subscribe((ea) => results.push(ea));\n\n    const expectedActions = [\n      testActions.HERO_QUERY_ALL,\n      testActions.VILLAIN_QUERY_MANY,\n      testActions.HERO_DELETE,\n    ];\n    dispatchTestActions();\n    expect(results).toEqual(expectedActions);\n  });\n\n  function ofEntityOpTest() {\n    const expectedActions = [\n      testActions.HERO_QUERY_ALL,\n      testActions.VILLAIN_QUERY_MANY,\n    ];\n    dispatchTestActions();\n    expect(results).toEqual(expectedActions);\n  }\n});\n"
  },
  {
    "path": "modules/data/spec/actions/entity-cache-changes-set.spec.ts",
    "content": "import { ChangeSetOperation, changeSetItemFactory as cif } from '../../';\n\ndescribe('changeSetItemFactory', () => {\n  const hero = { id: 1, name: 'Hero 1' };\n  const villains = [\n    { id: 2, name: 'Villain 2' },\n    { id: 3, name: 'Villain 3' },\n  ];\n\n  it('should create an Add item with array of entities from single entity', () => {\n    const heroItem = cif.add('Hero', hero);\n    expect(heroItem.op).toBe(ChangeSetOperation.Add);\n    expect(heroItem.entityName).toBe('Hero');\n    expect(heroItem.entities).toEqual([hero]);\n  });\n\n  it('should create a Delete item from an array entity keys', () => {\n    const ids = villains.map((v) => v.id);\n    const heroItem = cif.delete('Villain', ids);\n    expect(heroItem.op).toBe(ChangeSetOperation.Delete);\n    expect(heroItem.entityName).toBe('Villain');\n    expect(heroItem.entities).toEqual(ids);\n  });\n\n  it('should create an Add item with empty array when given no entities', () => {\n    const heroItem = cif.add('Hero', null);\n    expect(heroItem.op).toBe(ChangeSetOperation.Add);\n    expect(heroItem.entityName).toBe('Hero');\n    expect(heroItem.entities).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/dataservices/data-service-error.spec.ts",
    "content": "import { HttpErrorResponse } from '@angular/common/http';\nimport { DataServiceError } from '../../';\n\ndescribe('DataServiceError', () => {\n  describe('#message', () => {\n    it('should define message when ctor error is string', () => {\n      const expected = 'The error';\n      const dse = new DataServiceError(expected, null);\n      expect(dse.message).toBe(expected);\n    });\n\n    it('should define message when ctor error is new Error(\"message\")', () => {\n      const expected = 'The error';\n      const dse = new DataServiceError(new Error(expected), null);\n      expect(dse.message).toBe(expected);\n    });\n\n    it('should define message when ctor error is typical HttpResponseError', () => {\n      const expected = 'The error';\n      const body = expected; // server error is typically in the body of the server response\n      const httpErr = new HttpErrorResponse({\n        status: 400,\n        statusText: 'Bad Request',\n        url: 'http://foo.com/bad',\n        error: body,\n      });\n      const dse = new DataServiceError(httpErr, null);\n      expect(dse.message).toBe(expected);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/dataservices/default-data.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport {\n  HttpClient,\n  HttpContext,\n  HttpContextToken,\n  HttpParams,\n} from '@angular/common/http';\nimport {\n  HttpClientTestingModule,\n  HttpTestingController,\n} from '@angular/common/http/testing';\n\nimport { Observable, of } from 'rxjs';\n\nimport { Update } from '@ngrx/entity';\n\nimport {\n  DefaultDataService,\n  DefaultDataServiceFactory,\n  DefaultHttpUrlGenerator,\n  HttpUrlGenerator,\n  DefaultDataServiceConfig,\n  DataServiceError,\n  HttpMethods,\n  QueryParams,\n} from '../../';\nimport { HttpOptions } from '../../src/dataservices/interfaces';\nimport { vi } from 'vitest';\n\nclass Hero {\n  id!: number;\n  name!: string;\n  version?: number;\n}\n\nexport const IS_CACHE_ENABLED = new HttpContextToken<boolean>(() => false);\n/**\n * This is to test that we don't inadvertently delete http options when users override methods on DefaultDataService.\n */\nclass CustomDataService<T> extends DefaultDataService<T> {\n  override getWithQuery(\n    queryParams: QueryParams | string | undefined,\n    options?: HttpOptions\n  ): Observable<T[]> {\n    const qParams =\n      typeof queryParams === 'string'\n        ? { fromString: queryParams }\n        : { fromObject: queryParams };\n    const params = new HttpParams(qParams);\n\n    return this.execute(\n      'GET',\n      this.entitiesUrl,\n      undefined,\n      {\n        params,\n        observe: 'body',\n        context: new HttpContext().set(IS_CACHE_ENABLED, true),\n      },\n      options\n    );\n  }\n}\n\n////////  Tests  /////////////\ndescribe('DefaultDataService', () => {\n  let httpClient: HttpClient;\n  let httpTestingController: HttpTestingController;\n  const heroUrl = 'api/hero/';\n  const heroesUrl = 'api/heroes/';\n  let httpUrlGenerator: HttpUrlGenerator;\n  let service: DefaultDataService<Hero>;\n  let customService: CustomDataService<Hero>;\n\n  //// HttpClient testing boilerplate\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [HttpClientTestingModule],\n    });\n    httpClient = TestBed.inject(HttpClient);\n    httpTestingController = TestBed.inject(HttpTestingController);\n\n    httpUrlGenerator = new DefaultHttpUrlGenerator(null as any);\n    httpUrlGenerator.registerHttpResourceUrls({\n      Hero: {\n        entityResourceUrl: heroUrl,\n        collectionResourceUrl: heroesUrl,\n      },\n    });\n\n    service = new DefaultDataService('Hero', httpClient, httpUrlGenerator);\n    customService = new CustomDataService<Hero>(\n      'Hero',\n      httpClient,\n      httpUrlGenerator\n    );\n  });\n\n  afterEach(() => {\n    // After every test, assert that there are no pending requests.\n    httpTestingController.verify();\n  });\n  ///////////////////\n\n  describe('property inspection', () => {\n    // Test wrapper exposes protected properties\n    class TestService<T> extends DefaultDataService<T> {\n      properties = {\n        entityUrl: this.entityUrl,\n        entitiesUrl: this.entitiesUrl,\n        getDelay: this.getDelay,\n        saveDelay: this.saveDelay,\n        timeout: this.timeout,\n      };\n    }\n\n    // eslint-disable-next-line @typescript-eslint/no-shadow\n    let service: TestService<Hero>;\n\n    beforeEach(() => {\n      // use test wrapper class to get to protected properties\n      service = new TestService('Hero', httpClient, httpUrlGenerator);\n    });\n\n    it('has expected name', () => {\n      expect(service.name).toBe('Hero DefaultDataService');\n    });\n\n    it('has expected single-entity url', () => {\n      expect(service.properties.entityUrl).toBe(heroUrl);\n    });\n\n    it('has expected multiple-entities url', () => {\n      expect(service.properties.entitiesUrl).toBe(heroesUrl);\n    });\n  });\n\n  describe('#getAll', () => {\n    let expectedHeroes: Hero[];\n\n    beforeEach(() => {\n      expectedHeroes = [\n        { id: 1, name: 'A' },\n        { id: 2, name: 'B' },\n      ] as Hero[];\n    });\n\n    it('should return expected heroes (called once)', () =>\n      new Promise<void>((done, fail) => {\n        service.getAll().subscribe((heroes) => {\n          expect(heroes).toEqual(expectedHeroes);\n          done();\n        }, fail);\n\n        // HeroService should have made one request to GET heroes from expected URL\n        const req = httpTestingController.expectOne(heroesUrl);\n        expect(req.request.method).toEqual('GET');\n\n        expect(req.request.body).toBeNull();\n\n        // Respond with the mock heroes\n        req.flush(expectedHeroes);\n      }));\n\n    it('should be OK returning no heroes', () =>\n      new Promise<void>((done, fail) => {\n        service.getAll().subscribe((heroes) => {\n          expect(heroes.length).toEqual(0);\n          done();\n        }, fail);\n\n        const req = httpTestingController.expectOne(heroesUrl);\n        req.flush([]); // Respond with no heroes\n      }));\n\n    it('should return expected heroes (called multiple times)', () =>\n      new Promise<void>((done, fail) => {\n        service.getAll().subscribe();\n        service.getAll().subscribe();\n        service.getAll().subscribe((heroes) => {\n          expect(heroes).toEqual(expectedHeroes);\n          done();\n        }, fail);\n\n        const requests = httpTestingController.match(heroesUrl);\n        expect(requests.length).toEqual(3);\n\n        // Respond to each request with different mock hero results\n        requests[0].flush([]);\n        requests[1].flush([{ id: 1, name: 'bob' }]);\n        requests[2].flush(expectedHeroes);\n      }));\n\n    it('should turn 404 into Observable<DataServiceError>', () =>\n      new Promise<void>((done, fail) => {\n        const msg = 'deliberate 404 error';\n\n        service.getAll().subscribe(\n          () => fail('getAll succeeded when expected it to fail with a 404'),\n          (err) => {\n            expect(err).toBeDefined();\n            expect(err instanceof DataServiceError).toBe(true);\n            expect(err.error.status).toEqual(404);\n            expect(err.message).toEqual(msg);\n            done();\n          }\n        );\n\n        const req = httpTestingController.expectOne(heroesUrl);\n\n        const errorEvent = {\n          // Source of the service's not-so-friendly user-facing message\n          message: msg,\n\n          // The rest of this is optional and not used. Just showing that you could.\n          filename: 'DefaultDataService.ts',\n          lineno: 42,\n          colno: 21,\n        } as ErrorEvent;\n\n        req.error(errorEvent, { status: 404, statusText: 'Not Found' });\n      }));\n\n    it('should pass httpOptions', () =>\n      new Promise<void>((done, fail) => {\n        const expectedParams = new HttpParams({ fromObject: { test: 123 } });\n        service\n          .getAll({ httpParams: { fromObject: { test: 123 } } })\n          .subscribe((heroes) => {\n            expect(heroes.length).toEqual(0);\n            done();\n          }, fail);\n\n        const req = httpTestingController.expectOne(heroesUrl + '?test=123');\n\n        expect(req.request.params).toEqual(expectedParams);\n\n        req.flush([]); // Respond with no heroes\n      }));\n  });\n\n  describe('#getById', () => {\n    let expectedHero: Hero;\n    const heroUrlId1 = heroUrl + '1';\n\n    it('should return expected hero when id is found', () =>\n      new Promise<void>((done, fail) => {\n        expectedHero = { id: 1, name: 'A' };\n\n        service.getById(1).subscribe((hero) => {\n          expect(hero).toEqual(expectedHero);\n          done();\n        }, fail);\n\n        // One request to GET hero from expected URL\n        const req = httpTestingController.expectOne(heroUrlId1);\n\n        expect(req.request.body).toBeNull();\n\n        // Respond with the expected hero\n        req.flush(expectedHero);\n      }));\n\n    it('should turn 404 when id not found', () =>\n      new Promise<void>((done, fail) => {\n        service.getById(1).subscribe(\n          (heroes) =>\n            fail('getById succeeded when expected it to fail with a 404'),\n          (err) => {\n            expect(err instanceof DataServiceError).toBe(true);\n            done();\n          }\n        );\n\n        const req = httpTestingController.expectOne(heroUrlId1);\n        const errorEvent = { message: 'boom!' } as ErrorEvent;\n        req.error(errorEvent, { status: 404, statusText: 'Not Found' });\n      }));\n\n    it('should throw when no id given', () =>\n      new Promise<void>((done, fail) => {\n        service.getById(undefined as any).subscribe(\n          () => fail('getById succeeded when expected it to fail'),\n          (err) => {\n            expect(err.message).toMatch(/No \"Hero\" key/);\n            done();\n          }\n        );\n      }));\n  });\n\n  describe('#getWithQuery', () => {\n    let expectedHeroes: Hero[];\n\n    beforeEach(() => {\n      expectedHeroes = [\n        { id: 1, name: 'BA' },\n        { id: 2, name: 'BB' },\n      ] as Hero[];\n    });\n\n    it('should return expected selected heroes w/ object params', () =>\n      new Promise<void>((done, fail) => {\n        service.getWithQuery({ name: 'B' }).subscribe((heroes) => {\n          expect(heroes).toEqual(expectedHeroes);\n          done();\n        }, fail);\n\n        // HeroService should have made one request to GET heroes\n        // from expected URL with query params\n        const req = httpTestingController.expectOne(heroesUrl + '?name=B');\n        expect(req.request.method).toEqual('GET');\n\n        expect(req.request.body).toBeNull();\n\n        // Respond with the mock heroes\n        req.flush(expectedHeroes);\n      }));\n\n    it('should return expected selected heroes w/ string params', () =>\n      new Promise<void>((done, fail) => {\n        service.getWithQuery('name=B').subscribe((heroes) => {\n          expect(heroes).toEqual(expectedHeroes);\n          done();\n        }, fail);\n\n        // HeroService should have made one request to GET heroes\n        // from expected URL with query params\n        const req = httpTestingController.expectOne(heroesUrl + '?name=B');\n        expect(req.request.method).toEqual('GET');\n\n        // Respond with the mock heroes\n        req.flush(expectedHeroes);\n      }));\n\n    it('should return expected selected heroes w/ string params and a custom header', () =>\n      new Promise<void>((done, fail) => {\n        const httpOptions: HttpOptions = {\n          httpHeaders: { MyHeader: 'MyHeaderValue' },\n        } as HttpOptions;\n        service.getWithQuery('name=B', httpOptions).subscribe((heroes) => {\n          expect(heroes).toEqual(expectedHeroes);\n          done();\n        }, fail);\n\n        // HeroService should have made one request to GET heroes\n        // from expected URL with query params\n        const req = httpTestingController.expectOne(heroesUrl + '?name=B');\n        expect(req.request.method).toEqual('GET');\n        expect(req.request.headers.has('MyHeader')).toEqual(true);\n        expect(req.request.headers.get('MyHeader')).toEqual('MyHeaderValue');\n\n        // Respond with the mock heroes\n        req.flush(expectedHeroes);\n      }));\n\n    it('should return expected selected heroes w/ httpOption string params', () =>\n      new Promise<void>((done, fail) => {\n        const httpOptions: HttpOptions = {\n          httpParams: { fromString: 'name=B' },\n        } as HttpOptions;\n\n        service.getWithQuery(undefined, httpOptions).subscribe((heroes) => {\n          expect(heroes).toEqual(expectedHeroes);\n          done();\n        }, fail);\n\n        // HeroService should have made one request to GET heroes\n        // from expected URL with query params\n        const req = httpTestingController.expectOne(heroesUrl + '?name=B');\n        expect(req.request.method).toEqual('GET');\n\n        // Respond with the mock heroes\n        req.flush(expectedHeroes);\n      }));\n\n    it('should return expected selected heroes w/ httpOption option params', () =>\n      new Promise<void>((done, fail) => {\n        const httpOptions: HttpOptions = {\n          httpParams: {\n            fromObject: {\n              name: 'B',\n            },\n          },\n        } as HttpOptions;\n\n        service.getWithQuery(undefined, httpOptions).subscribe((heroes) => {\n          expect(heroes).toEqual(expectedHeroes);\n          done();\n        }, fail);\n\n        // HeroService should have made one request to GET heroes\n        // from expected URL with query params\n        const req = httpTestingController.expectOne(heroesUrl + '?name=B');\n        expect(req.request.method).toEqual('GET');\n\n        // Respond with the mock heroes\n        req.flush(expectedHeroes);\n      }));\n\n    it('should support custom data services that provide their own http options to execute', () =>\n      new Promise<void>((done, fail) => {\n        const httpOptions: HttpOptions = {\n          httpParams: { fromString: 'name=B' },\n        } as HttpOptions;\n\n        customService\n          .getWithQuery(undefined, httpOptions)\n          .subscribe((heroes) => {\n            expect(heroes).toEqual(expectedHeroes);\n            done();\n          }, fail);\n\n        // HeroService should have made one request to GET heroes\n        // from expected URL with query params\n        const req = httpTestingController.expectOne(heroesUrl + '?name=B');\n        expect(req.request.method).toEqual('GET');\n        expect(req.request.context.get(IS_CACHE_ENABLED)).toEqual(true);\n\n        // Respond with the mock heroes\n        req.flush(expectedHeroes);\n      }));\n\n    it('should be OK returning no heroes', () =>\n      new Promise<void>((done, fail) => {\n        service.getWithQuery({ name: 'B' }).subscribe((heroes) => {\n          expect(heroes.length).toEqual(0);\n          done();\n        }, fail);\n\n        const req = httpTestingController.expectOne(heroesUrl + '?name=B');\n        req.flush([]); // Respond with no heroes\n      }));\n\n    it('should turn 404 into Observable<DataServiceError>', () =>\n      new Promise<void>((done, fail) => {\n        const msg = 'deliberate 404 error';\n\n        service.getWithQuery({ name: 'B' }).subscribe(\n          () =>\n            fail('getWithQuery succeeded when expected it to fail with a 404'),\n          (err) => {\n            expect(err).toBeDefined();\n            expect(err instanceof DataServiceError).toBe(true);\n            expect(err.error.status).toEqual(404);\n            expect(err.message).toEqual(msg);\n            done();\n          }\n        );\n\n        const req = httpTestingController.expectOne(heroesUrl + '?name=B');\n\n        const errorEvent = { message: msg } as ErrorEvent;\n\n        req.error(errorEvent, { status: 404, statusText: 'Not Found' });\n      }));\n  });\n\n  describe('#add', () => {\n    let expectedHero: Hero;\n\n    it('should return expected hero with id', () =>\n      new Promise<void>((done, fail) => {\n        expectedHero = { id: 42, name: 'A' };\n        const heroData: Hero = { id: undefined, name: 'A' } as any;\n\n        service.add(heroData).subscribe((hero) => {\n          expect(hero).toEqual(expectedHero);\n          done();\n        }, fail);\n\n        // One request to POST hero from expected URL\n        const req = httpTestingController.expectOne(\n          (r) => r.method === 'POST' && r.url === heroUrl\n        );\n\n        expect(req.request.body).toEqual(heroData);\n\n        // Respond with the expected hero\n        req.flush(expectedHero);\n      }));\n\n    it('should throw when no entity given', () =>\n      new Promise<void>((done, fail) => {\n        service.add(undefined as any).subscribe(\n          () => fail('add succeeded when expected it to fail'),\n          (err) => {\n            expect(err.message).toMatch(/No \"Hero\" entity/);\n            done();\n          }\n        );\n      }));\n  });\n\n  describe('#delete', () => {\n    const heroUrlId1 = heroUrl + '1';\n\n    it('should delete by hero id', () =>\n      new Promise<void>((done, fail) => {\n        service.delete(1).subscribe((result) => {\n          expect(result).toEqual(1);\n          done();\n        }, fail);\n\n        // One request to DELETE hero from expected URL\n        const req = httpTestingController.expectOne(\n          (r) => r.method === 'DELETE' && r.url === heroUrlId1\n        );\n\n        expect(req.request.body).toBeNull();\n\n        // Respond with empty nonsense object\n        req.flush({});\n      }));\n\n    it('should return successfully when id not found and delete404OK is true (default)', () =>\n      new Promise<void>((done, fail) => {\n        service.delete(1).subscribe((result) => {\n          expect(result).toEqual(1);\n          done();\n        }, fail);\n\n        // One request to DELETE hero from expected URL\n        const req = httpTestingController.expectOne(\n          (r) => r.method === 'DELETE' && r.url === heroUrlId1\n        );\n\n        // Respond with empty nonsense object\n        req.flush({});\n      }));\n\n    it('should return 404 when id not found and delete404OK is false', () =>\n      new Promise<void>((done, fail) => {\n        service = new DefaultDataService('Hero', httpClient, httpUrlGenerator, {\n          delete404OK: false,\n        });\n        service.delete(1).subscribe(\n          (heroes) =>\n            fail('delete succeeded when expected it to fail with a 404'),\n          (err) => {\n            expect(err instanceof DataServiceError).toBe(true);\n            done();\n          }\n        );\n\n        const req = httpTestingController.expectOne(heroUrlId1);\n        const errorEvent = { message: 'boom!' } as ErrorEvent;\n        req.error(errorEvent, { status: 404, statusText: 'Not Found' });\n      }));\n\n    it('should throw when no id given', () =>\n      new Promise<void>((done, fail) => {\n        service.delete(undefined as any).subscribe(\n          (heroes) => fail('delete succeeded when expected it to fail'),\n          (err) => {\n            expect(err.error.message).toMatch(/No \"Hero\" key/);\n            done();\n          }\n        );\n      }));\n  });\n\n  describe('#update', () => {\n    const heroUrlId1 = heroUrl + '1';\n\n    it('should return expected hero with id', () =>\n      new Promise<void>((done, fail) => {\n        // Call service.update with an Update<T> arg\n        const updateArg: Update<Hero> = {\n          id: 1,\n          changes: { id: 1, name: 'B' },\n        };\n\n        // The server makes the update AND updates the version concurrency property.\n        const expectedHero: Hero = { id: 1, name: 'B', version: 2 };\n\n        service.update(updateArg).subscribe((updated) => {\n          expect(updated).toEqual(expectedHero);\n          done();\n        }, fail);\n\n        // One request to PUT hero from expected URL\n        const req = httpTestingController.expectOne(\n          (r) => r.method === 'PUT' && r.url === heroUrlId1\n        );\n\n        expect(req.request.body).toEqual(updateArg.changes);\n\n        // Respond with the expected hero\n        req.flush(expectedHero);\n      }));\n\n    it('should return 404 when id not found', () =>\n      new Promise<void>((done, fail) => {\n        service.update({ id: 1, changes: { id: 1, name: 'B' } }).subscribe(\n          (update) =>\n            fail('update succeeded when expected it to fail with a 404'),\n          (err) => {\n            expect(err instanceof DataServiceError).toBe(true);\n            done();\n          }\n        );\n\n        const req = httpTestingController.expectOne(heroUrlId1);\n        const errorEvent = { message: 'boom!' } as ErrorEvent;\n        req.error(errorEvent, { status: 404, statusText: 'Not Found' });\n      }));\n\n    it('should throw when no update given', () =>\n      new Promise<void>((done, fail) => {\n        service.update(undefined as any).subscribe(\n          (heroes) => fail('update succeeded when expected it to fail'),\n          (err) => {\n            expect(err.error.message).toMatch(/No \"Hero\" update data/);\n            done();\n          }\n        );\n      }));\n  });\n\n  describe('#upsert', () => {\n    let expectedHero: Hero;\n\n    it('should return expected hero with id', () =>\n      new Promise<void>((done, fail) => {\n        expectedHero = { id: 42, name: 'A' };\n        const heroData: Hero = { id: undefined, name: 'A' } as any;\n\n        service.upsert(heroData).subscribe((hero) => {\n          expect(hero).toEqual(expectedHero);\n          done();\n        }, fail);\n\n        // One request to POST hero from expected URL\n        const req = httpTestingController.expectOne(\n          (r) => r.method === 'POST' && r.url === heroUrl\n        );\n\n        expect(req.request.body).toEqual(heroData);\n\n        // Respond with the expected hero\n        req.flush(expectedHero);\n      }));\n\n    it('should throw when no entity given', () =>\n      new Promise<void>((done, fail) => {\n        service.upsert(undefined as any).subscribe(\n          (heroes) => fail('add succeeded when expected it to fail'),\n          (err) => {\n            expect(err.error.message).toMatch(/No \"Hero\" entity/);\n            done();\n          }\n        );\n      }));\n  });\n});\n\ndescribe('DefaultDataServiceFactory', () => {\n  const heroUrl = 'api/hero';\n  const heroesUrl = 'api/heroes';\n\n  let http: any;\n  let httpUrlGenerator: HttpUrlGenerator;\n\n  beforeEach(() => {\n    httpUrlGenerator = new DefaultHttpUrlGenerator(null as any);\n    httpUrlGenerator.registerHttpResourceUrls({\n      Hero: {\n        entityResourceUrl: heroUrl,\n        collectionResourceUrl: heroesUrl,\n      },\n    });\n    http = {\n      get: vi.fn(() => of([])).mockName('get'),\n      delete: vi.fn().mockName('delete'),\n      post: vi.fn().mockName('post'),\n      put: vi.fn().mockName('put'),\n    };\n  });\n\n  describe('(no config)', () => {\n    it('can create factory', () => {\n      const factory = new DefaultDataServiceFactory(http, httpUrlGenerator);\n      const heroDS = factory.create<Hero>('Hero');\n      expect(heroDS.name).toBe('Hero DefaultDataService');\n    });\n\n    it('should produce hero data service that gets all heroes with expected URL', () => {\n      const factory = new DefaultDataServiceFactory(http, httpUrlGenerator);\n      const heroDS = factory.create<Hero>('Hero');\n      heroDS.getAll();\n      expect(http.get).toHaveBeenCalledWith('api/heroes', undefined);\n    });\n  });\n\n  describe('(with config)', () => {\n    it('can create factory', () => {\n      const config: DefaultDataServiceConfig = { root: 'api' };\n      const factory = new DefaultDataServiceFactory(\n        http,\n        httpUrlGenerator,\n        config\n      );\n      const heroDS = factory.create<Hero>('Hero');\n      expect(heroDS.name).toBe('Hero DefaultDataService');\n    });\n\n    it('should produce hero data service that gets heroes via hero HttpResourceUrls', () => {\n      const newHeroesUrl = 'some/other/api/heroes';\n      const config: DefaultDataServiceConfig = {\n        root: 'api',\n        entityHttpResourceUrls: {\n          Hero: {\n            entityResourceUrl: heroUrl,\n            collectionResourceUrl: newHeroesUrl,\n          },\n        },\n      };\n      const factory = new DefaultDataServiceFactory(\n        http,\n        httpUrlGenerator,\n        config\n      );\n      const heroDS = factory.create<Hero>('Hero');\n      heroDS.getAll();\n      expect(http.get).toHaveBeenCalledWith(newHeroesUrl, undefined);\n    });\n\n    it('should keep trailing slash', () => {\n      const newHeroesUrl = 'some/other/api/heroes/';\n      const config: DefaultDataServiceConfig = {\n        root: '//example.com/api/',\n        entityHttpResourceUrls: {\n          Hero: {\n            entityResourceUrl: '/api/hero/',\n            collectionResourceUrl: newHeroesUrl,\n          },\n        },\n        trailingSlashEndpoints: true,\n      };\n      const factory = new DefaultDataServiceFactory(\n        http,\n        httpUrlGenerator,\n        config\n      );\n      const heroDS = factory.create<Hero>('Hero');\n      heroDS.getAll();\n      expect(http.get).toHaveBeenCalledWith(newHeroesUrl, undefined);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/dataservices/entity-data.service.spec.ts",
    "content": "import { Injectable, NgModule, Optional } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { HttpClient } from '@angular/common/http';\n\nimport { Observable } from 'rxjs';\n\nimport { Update } from '@ngrx/entity';\n\nimport {\n  DefaultDataService,\n  DefaultDataServiceFactory,\n  HttpUrlGenerator,\n  EntityHttpResourceUrls,\n  EntityDataService,\n  EntityCollectionDataService,\n  QueryParams,\n} from '../../';\n\n// region Test Helpers\n///// Test Helpers /////\n\nexport class CustomDataService {\n  name: string;\n  constructor(name: string) {\n    this.name = name + ' CustomDataService';\n  }\n}\n\nexport class Bazinga {\n  id!: number;\n  wow!: string;\n}\n\n@Injectable()\nexport class BazingaDataService\n  implements EntityCollectionDataService<Bazinga>\n{\n  name: string;\n\n  // TestBed bug requires `@Optional` even though http is always provided.\n  constructor(@Optional() private http: HttpClient) {\n    if (!http) {\n      throw new Error('Where is HttpClient?');\n    }\n    this.name = 'Bazinga custom data service';\n  }\n\n  add(entity: Bazinga): Observable<Bazinga> {\n    return this.bazinga();\n  }\n  delete(id: any): Observable<number | string> {\n    return this.bazinga();\n  }\n  getAll(): Observable<Bazinga[]> {\n    return this.bazinga();\n  }\n  getById(id: any): Observable<Bazinga> {\n    return this.bazinga();\n  }\n  getWithQuery(params: string | QueryParams): Observable<Bazinga[]> {\n    return this.bazinga();\n  }\n  update(update: Update<Bazinga>): Observable<Bazinga> {\n    return this.bazinga();\n  }\n  upsert(entity: Bazinga): Observable<Bazinga> {\n    return this.bazinga();\n  }\n\n  private bazinga(): any {\n    bazingaFail();\n    return undefined;\n  }\n}\n\n@NgModule({\n  providers: [BazingaDataService],\n})\nexport class CustomDataServiceModule {\n  constructor(\n    entityDataService: EntityDataService,\n    bazingaService: BazingaDataService\n  ) {\n    entityDataService.registerService('Bazinga', bazingaService);\n  }\n}\n\nfunction bazingaFail() {\n  throw new Error('Bazinga! This method is not implemented.');\n}\n\n/** Test version always returns canned Hero resource base URLs  */\nclass TestHttpUrlGenerator implements HttpUrlGenerator {\n  entityResource(entityName: string, root: string): string {\n    return 'api/hero/';\n  }\n  collectionResource(entityName: string, root: string): string {\n    return 'api/heroes/';\n  }\n  registerHttpResourceUrls(\n    entityHttpResourceUrls: EntityHttpResourceUrls\n  ): void {}\n}\n\n// endregion\n\n///// Tests begin ////\ndescribe('EntityDataService', () => {\n  const nullHttp = {};\n  let entityDataService: EntityDataService;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [CustomDataServiceModule],\n      providers: [\n        DefaultDataServiceFactory,\n        EntityDataService,\n        { provide: HttpClient, useValue: nullHttp },\n        { provide: HttpUrlGenerator, useClass: TestHttpUrlGenerator },\n      ],\n    });\n    entityDataService = TestBed.inject(EntityDataService);\n  });\n\n  describe('#getService', () => {\n    it('can create a data service for \"Hero\" entity', () => {\n      const service = entityDataService.getService('Hero');\n      expect(service).toBeDefined();\n    });\n\n    it('data service should be a DefaultDataService by default', () => {\n      const service = entityDataService.getService('Hero');\n      expect(service instanceof DefaultDataService).toBe(true);\n    });\n\n    it('gets the same service every time you ask for it', () => {\n      const service1 = entityDataService.getService('Hero');\n      const service2 = entityDataService.getService('Hero');\n      expect(service1).toBe(service2);\n    });\n  });\n\n  describe('#register...', () => {\n    it('can register a custom service for \"Hero\"', () => {\n      const customService: any = new CustomDataService('Hero');\n      entityDataService.registerService('Hero', customService);\n\n      const service = entityDataService.getService('Hero');\n      expect(service).toBe(customService);\n    });\n\n    it('can register multiple custom services at the same time', () => {\n      const customHeroService: any = new CustomDataService('Hero');\n      const customVillainService: any = new CustomDataService('Villain');\n      entityDataService.registerServices({\n        Hero: customHeroService,\n        Villain: customVillainService,\n      });\n\n      let service = entityDataService.getService('Hero');\n      expect(service).toBe(customHeroService);\n      expect(service.name).toBe('Hero CustomDataService');\n\n      service = entityDataService.getService('Villain');\n      expect(service).toBe(customVillainService);\n\n      // Other services are still DefaultDataServices\n      service = entityDataService.getService('Foo');\n      expect(service.name).toBe('Foo DefaultDataService');\n    });\n\n    it('can register a custom service using a module import', () => {\n      const service = entityDataService.getService('Bazinga');\n      expect(service instanceof BazingaDataService).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/dispatchers/entity-dispatcher.spec.ts",
    "content": "import { Action } from '@ngrx/store';\nimport { Update } from '@ngrx/entity';\n\nimport { Subject } from 'rxjs';\n\nimport {\n  EntityDispatcherDefaultOptions,\n  CorrelationIdGenerator,\n  EntityActionFactory,\n  createEntityCacheSelector,\n  defaultSelectId,\n  EntityDispatcherBase,\n  EntityDispatcher,\n  EntityAction,\n  EntityOp,\n  MergeStrategy,\n} from '../..';\nimport { Mock, vi } from 'vitest';\n\nclass Hero {\n  id!: number;\n  name!: string;\n  saying?: string;\n}\n\n/** Store stub */\nclass TestStore {\n  // only interested in calls to store.dispatch()\n  dispatch() {}\n  select() {}\n}\n\nconst defaultDispatcherOptions = new EntityDispatcherDefaultOptions();\n\ndescribe('EntityDispatcher', () => {\n  commandDispatchTest(entityDispatcherSetup);\n\n  function entityDispatcherSetup() {\n    const correlationIdGenerator = new CorrelationIdGenerator();\n    const entityActionFactory = new EntityActionFactory();\n    const entityCacheSelector = createEntityCacheSelector();\n    const scannedActions$ = new Subject<Action>();\n    const selectId = defaultSelectId;\n    const store: any = new TestStore();\n\n    const dispatcher = new EntityDispatcherBase<Hero>(\n      'Hero',\n      entityActionFactory,\n      store,\n      selectId,\n      defaultDispatcherOptions,\n      scannedActions$, // scannedActions$ not used in these tests\n      entityCacheSelector, // entityCacheSelector not used in these tests\n      correlationIdGenerator\n    );\n    return { dispatcher, store };\n  }\n});\n\n///// Tests /////\n\n/**\n * Test that implementer of EntityCommands dispatches properly\n * @param setup Function that sets up the EntityDispatcher before each test (called in a BeforeEach()).\n */\nexport function commandDispatchTest(\n  setup: () => { dispatcher: EntityDispatcher<Hero>; store: any }\n) {\n  let dispatcher: EntityDispatcher<Hero>;\n  let testStore: { dispatch: Mock };\n\n  function dispatchedAction() {\n    return <EntityAction>testStore.dispatch.mock.calls.at(0)?.[0];\n  }\n\n  beforeEach(() => {\n    const s = setup();\n    vi.spyOn(s.store, 'dispatch');\n    dispatcher = s.dispatcher;\n    testStore = s.store;\n  });\n\n  it('#entityName is the expected name of the entity type', () => {\n    expect(dispatcher.entityName).toBe('Hero');\n  });\n\n  it('#cancel(correlationId) can dispatch CANCEL_PERSIST', () => {\n    dispatcher.cancel('CRID007', 'Test cancel');\n    const { entityOp, correlationId, data } = dispatchedAction().payload;\n    expect(entityOp).toBe(EntityOp.CANCEL_PERSIST);\n    expect(correlationId).toBe('CRID007');\n    expect(data).toBe('Test cancel');\n  });\n\n  describe('Save actions', () => {\n    // By default add and update are pessimistic and delete is optimistic.\n    // Tests override in the dispatcher method calls as necessary.\n\n    describe('(optimistic)', () => {\n      it('#add(hero) can dispatch SAVE_ADD_ONE optimistically', () => {\n        const hero: Hero = { id: 42, name: 'test' };\n        dispatcher.add(hero, { isOptimistic: true });\n        const { entityOp, isOptimistic, data } = dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_ADD_ONE);\n        expect(isOptimistic).toBe(true);\n        expect(data).toBe(hero);\n      });\n\n      it('#delete(42) dispatches SAVE_DELETE_ONE optimistically for the id:42', () => {\n        dispatcher.delete(42); // optimistic by default\n        const { entityOp, isOptimistic, data } = dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_DELETE_ONE);\n        expect(isOptimistic).toBe(true);\n        expect(data).toBe(42);\n      });\n\n      it('#delete(42) with a query param dispatches SAVE_DELETE_ONE optimistically for the id:42', () => {\n        dispatcher.delete(42, {\n          httpOptions: { httpParams: { fromObject: { queryParam1: 1 } } },\n        }); // optimistic by default\n        const { entityOp, isOptimistic, data, httpOptions } =\n          dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_DELETE_ONE);\n        expect(isOptimistic).toBe(true);\n        expect(data).toBe(42);\n        expect(httpOptions?.httpParams?.fromObject?.queryParam1).toBe(1);\n      });\n\n      it('#delete(hero) dispatches SAVE_DELETE_ONE optimistically for the hero.id', () => {\n        const id = 42;\n        const hero: Hero = { id, name: 'test' };\n        dispatcher.delete(hero); // optimistic by default\n        const { entityOp, isOptimistic, data } = dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_DELETE_ONE);\n        expect(isOptimistic).toBe(true);\n        expect(data).toBe(42);\n      });\n\n      it('#update(hero) can dispatch SAVE_UPDATE_ONE optimistically with an update payload', () => {\n        const hero: Hero = { id: 42, name: 'test' };\n        const expectedUpdate: Update<Hero> = { id: 42, changes: hero };\n\n        dispatcher.update(hero, { isOptimistic: true });\n        const { entityOp, isOptimistic, data } = dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_UPDATE_ONE);\n        expect(isOptimistic).toBe(true);\n        expect(data).toEqual(expectedUpdate);\n      });\n    });\n\n    describe('(pessimistic)', () => {\n      it('#add(hero) dispatches SAVE_ADD pessimistically', () => {\n        const hero: Hero = { id: 42, name: 'test' };\n        dispatcher.add(hero); // pessimistic by default\n        const { entityOp, isOptimistic, data } = dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_ADD_ONE);\n        expect(isOptimistic).toBe(false);\n        expect(data).toBe(hero);\n      });\n\n      it('#add(hero) dispatches SAVE_ADD pessimistically with partial hero', () => {\n        const hero: Partial<Hero> = { name: 'test' };\n        dispatcher.add(hero);\n        const { entityOp, isOptimistic, data } = dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_ADD_ONE);\n        expect(isOptimistic).toBe(false);\n        expect(data).toBe(hero);\n\n        testStore.dispatch.mockClear();\n\n        dispatcher.add(hero, { isOptimistic: false });\n        const specificallyPessimistic = dispatchedAction().payload;\n        expect(specificallyPessimistic.entityOp).toBe(EntityOp.SAVE_ADD_ONE);\n        expect(specificallyPessimistic.isOptimistic).toBe(false);\n        expect(specificallyPessimistic.data).toBe(hero);\n      });\n\n      it('#delete(42) can dispatch SAVE_DELETE pessimistically for the id:42', () => {\n        dispatcher.delete(42, { isOptimistic: false }); // optimistic by default\n        const { entityOp, isOptimistic, data } = dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_DELETE_ONE);\n        expect(isOptimistic).toBe(false);\n        expect(data).toBe(42);\n      });\n\n      it('#delete(hero) can dispatch SAVE_DELETE pessimistically for the hero.id', () => {\n        const id = 42;\n        const hero: Hero = { id, name: 'test' };\n\n        dispatcher.delete(hero, { isOptimistic: false }); // optimistic by default\n        const { entityOp, isOptimistic, data } = dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_DELETE_ONE);\n        expect(isOptimistic).toBe(false);\n        expect(data).toBe(42);\n      });\n\n      it('#update(hero) dispatches SAVE_UPDATE with an update payload', () => {\n        const hero: Hero = { id: 42, name: 'test' };\n        const expectedUpdate: Update<Hero> = { id: 42, changes: hero };\n\n        dispatcher.update(hero); // pessimistic by default\n        const { entityOp, isOptimistic, data } = dispatchedAction().payload;\n        expect(entityOp).toBe(EntityOp.SAVE_UPDATE_ONE);\n        expect(isOptimistic).toBe(false);\n        expect(data).toEqual(expectedUpdate);\n      });\n    });\n  });\n\n  describe('Query actions', () => {\n    it('#getAll() dispatches QUERY_ALL', () => {\n      dispatcher.getAll();\n\n      const { entityOp, entityName, mergeStrategy } =\n        dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.QUERY_ALL);\n      expect(entityName).toBe('Hero');\n      expect(mergeStrategy).toBeUndefined();\n    });\n\n    it('#getAll({mergeStrategy}) dispatches QUERY_ALL with a MergeStrategy', () => {\n      dispatcher.getAll({ mergeStrategy: MergeStrategy.PreserveChanges });\n\n      const { entityOp, entityName, mergeStrategy } =\n        dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.QUERY_ALL);\n      expect(entityName).toBe('Hero');\n      expect(mergeStrategy).toBe(MergeStrategy.PreserveChanges);\n    });\n\n    it('#getByKey(42) dispatches QUERY_BY_KEY for the id:42', () => {\n      dispatcher.getByKey(42);\n\n      const { entityOp, data, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.QUERY_BY_KEY);\n      expect(data).toBe(42);\n      expect(mergeStrategy).toBeUndefined();\n    });\n\n    it('#getByKey(42, {mergeStrategy}) dispatches QUERY_BY_KEY with a MergeStrategy', () => {\n      dispatcher.getByKey(42, {\n        mergeStrategy: MergeStrategy.OverwriteChanges,\n      });\n\n      const { entityOp, data, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.QUERY_BY_KEY);\n      expect(data).toBe(42);\n      expect(mergeStrategy).toBe(MergeStrategy.OverwriteChanges);\n    });\n\n    it('#getWithQuery(QueryParams) dispatches QUERY_MANY', () => {\n      dispatcher.getWithQuery({ name: 'B' });\n\n      const { entityOp, data, entityName, mergeStrategy } =\n        dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.QUERY_MANY);\n      expect(entityName).toBe('Hero');\n      expect(data).toEqual({ name: 'B' });\n      expect(mergeStrategy).toBeUndefined();\n    });\n\n    it('#getWithQuery(string) dispatches QUERY_MANY', () => {\n      dispatcher.getWithQuery('name=B');\n\n      const { entityOp, data, entityName, mergeStrategy } =\n        dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.QUERY_MANY);\n      expect(entityName).toBe('Hero');\n      expect(data).toEqual('name=B');\n      expect(mergeStrategy).toBeUndefined();\n    });\n\n    it('#getWithQuery(string) dispatches QUERY_MANY with a MergeStrategy', () => {\n      dispatcher.getWithQuery('name=B', {\n        mergeStrategy: MergeStrategy.PreserveChanges,\n      });\n\n      const { entityOp, data, entityName, mergeStrategy } =\n        dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.QUERY_MANY);\n      expect(entityName).toBe('Hero');\n      expect(data).toEqual('name=B');\n      expect(mergeStrategy).toBe(MergeStrategy.PreserveChanges);\n    });\n\n    it('#load() dispatches QUERY_LOAD', () => {\n      dispatcher.load();\n\n      const { entityOp, entityName, mergeStrategy } =\n        dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.QUERY_LOAD);\n      expect(entityName).toBe('Hero');\n      expect(mergeStrategy).toBeUndefined();\n    });\n\n    it('#loadWithQuery() dispatches QUERY_MANY', () => {\n      dispatcher.loadWithQuery('name=B');\n\n      const { entityOp, data, entityName, mergeStrategy } =\n        dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.QUERY_MANY);\n      expect(entityName).toBe('Hero');\n      expect(data).toEqual('name=B');\n      expect(mergeStrategy).toBeUndefined(); //?\n    });\n  });\n\n  /*** Cache-only operations ***/\n  describe('Cache-only actions', () => {\n    it('#addAllToCache dispatches ADD_ALL', () => {\n      const heroes: Hero[] = [\n        { id: 42, name: 'test 42' },\n        { id: 84, name: 'test 84', saying: 'howdy' },\n      ];\n      dispatcher.addAllToCache(heroes);\n      const { entityOp, data } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.ADD_ALL);\n      expect(data).toBe(heroes);\n    });\n\n    it('#addOneToCache dispatches ADD_ONE', () => {\n      const hero: Hero = { id: 42, name: 'test' };\n      dispatcher.addOneToCache(hero);\n      const { entityOp, data, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.ADD_ONE);\n      expect(data).toBe(hero);\n      expect(mergeStrategy).toBeUndefined();\n    });\n\n    it('#addOneToCache can dispatch ADD_ONE and MergeStrategy.IgnoreChanges', () => {\n      const hero: Hero = { id: 42, name: 'test' };\n      dispatcher.addOneToCache(hero, {\n        mergeStrategy: MergeStrategy.IgnoreChanges,\n      });\n      const { entityOp, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.ADD_ONE);\n      expect(mergeStrategy).toBe(MergeStrategy.IgnoreChanges);\n    });\n\n    it('#addManyToCache dispatches ADD_MANY', () => {\n      const heroes: Hero[] = [\n        { id: 42, name: 'test 42' },\n        { id: 84, name: 'test 84', saying: 'howdy' },\n      ];\n      dispatcher.addManyToCache(heroes);\n      const { entityOp, data } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.ADD_MANY);\n      expect(data).toBe(heroes);\n    });\n\n    it('#addManyToCache can dispatch ADD_MANY and MergeStrategy.IgnoreChanges', () => {\n      const heroes: Hero[] = [\n        { id: 42, name: 'test 42' },\n        { id: 84, name: 'test 84', saying: 'howdy' },\n      ];\n      dispatcher.addManyToCache(heroes, {\n        mergeStrategy: MergeStrategy.IgnoreChanges,\n      });\n      const { entityOp, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.ADD_MANY);\n      expect(mergeStrategy).toBe(MergeStrategy.IgnoreChanges);\n    });\n\n    it('#clearCache() dispatches REMOVE_ALL for the Hero collection', () => {\n      dispatcher.clearCache();\n      const { entityOp, entityName } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.REMOVE_ALL);\n      expect(entityName).toBe('Hero');\n    });\n\n    it('#clearCache() can dispatch REMOVE_ALL with options', () => {\n      dispatcher.clearCache({ mergeStrategy: MergeStrategy.IgnoreChanges });\n      const { entityOp, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.REMOVE_ALL);\n      expect(mergeStrategy).toBe(MergeStrategy.IgnoreChanges);\n    });\n\n    it('#removeOneFromCache(key) dispatches REMOVE_ONE', () => {\n      const id = 42;\n      dispatcher.removeOneFromCache(id);\n      const { entityOp, data } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.REMOVE_ONE);\n      expect(data).toBe(id);\n    });\n\n    it('#removeOneFromCache(key) can dispatch REMOVE_ONE and MergeStrategy.IgnoreChanges', () => {\n      const id = 42;\n      dispatcher.removeOneFromCache(id, {\n        mergeStrategy: MergeStrategy.IgnoreChanges,\n      });\n      const { entityOp, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.REMOVE_ONE);\n      expect(mergeStrategy).toBe(MergeStrategy.IgnoreChanges);\n    });\n\n    it('#removeManyFromCache(keys) dispatches REMOVE_MANY', () => {\n      const keys = [42, 84];\n      dispatcher.removeManyFromCache(keys);\n      const { entityOp, data } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.REMOVE_MANY);\n      expect(data).toBe(keys);\n    });\n\n    it('#removeManyFromCache(keys) can dispatch REMOVE_MANY and MergeStrategy.IgnoreChanges', () => {\n      const keys = [42, 84];\n      dispatcher.removeManyFromCache(keys, {\n        mergeStrategy: MergeStrategy.IgnoreChanges,\n      });\n      const { entityOp, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.REMOVE_MANY);\n      expect(mergeStrategy).toBe(MergeStrategy.IgnoreChanges);\n    });\n\n    it('#removeManyFromCache(entities) dispatches REMOVE_MANY', () => {\n      const heroes: Hero[] = [\n        { id: 42, name: 'test 42' },\n        { id: 84, name: 'test 84', saying: 'howdy' },\n      ];\n      const keys = heroes.map((h) => h.id);\n      dispatcher.removeManyFromCache(heroes);\n      const { entityOp, data } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.REMOVE_MANY);\n      expect(data).toEqual(keys);\n    });\n\n    it('#toUpdate() helper method creates Update<T>', () => {\n      const hero: Partial<Hero> = { id: 42, name: 'test' };\n      const expected = { id: 42, changes: hero };\n      const update = dispatcher.toUpdate(hero);\n      expect(update).toEqual(expected);\n    });\n\n    it('#updateOneInCache dispatches UPDATE_ONE', () => {\n      const hero: Partial<Hero> = { id: 42, name: 'test' };\n      const update = { id: 42, changes: hero };\n      dispatcher.updateOneInCache(hero);\n      const { entityOp, data } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.UPDATE_ONE);\n      expect(data).toEqual(update);\n    });\n\n    it('#updateOneInCache can dispatch UPDATE_ONE and MergeStrategy.IgnoreChanges', () => {\n      const hero: Partial<Hero> = { id: 42, name: 'test' };\n      const update = { id: 42, changes: hero };\n      dispatcher.updateOneInCache(hero, {\n        mergeStrategy: MergeStrategy.IgnoreChanges,\n      });\n      const { entityOp, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.UPDATE_ONE);\n      expect(mergeStrategy).toBe(MergeStrategy.IgnoreChanges);\n    });\n\n    it('#updateManyInCache dispatches UPDATE_MANY', () => {\n      const heroes: Partial<Hero>[] = [\n        { id: 42, name: 'test 42' },\n        { id: 84, saying: 'ho ho ho' },\n      ];\n      const updates = [\n        { id: 42, changes: heroes[0] },\n        { id: 84, changes: heroes[1] },\n      ];\n      dispatcher.updateManyInCache(heroes);\n      const { entityOp, data } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.UPDATE_MANY);\n      expect(data).toEqual(updates);\n    });\n\n    it('#updateManyInCache can dispatch UPDATE_MANY and MergeStrategy.IgnoreChanges', () => {\n      const heroes: Partial<Hero>[] = [\n        { id: 42, name: 'test 42' },\n        { id: 84, saying: 'ho ho ho' },\n      ];\n      const updates = [\n        { id: 42, changes: heroes[0] },\n        { id: 84, changes: heroes[1] },\n      ];\n      dispatcher.updateManyInCache(heroes, {\n        mergeStrategy: MergeStrategy.IgnoreChanges,\n      });\n      const { entityOp, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.UPDATE_MANY);\n      expect(mergeStrategy).toBe(MergeStrategy.IgnoreChanges);\n    });\n\n    it('#upsertOneInCache dispatches UPSERT_ONE', () => {\n      const hero = { id: 42, name: 'test' };\n      dispatcher.upsertOneInCache(hero);\n      const { entityOp, data } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.UPSERT_ONE);\n      expect(data).toEqual(hero);\n    });\n\n    it('#upsertOneInCache can dispatch UPSERT_ONE and MergeStrategy.IgnoreChanges', () => {\n      const hero = { id: 42, name: 'test' };\n      dispatcher.upsertOneInCache(hero, {\n        mergeStrategy: MergeStrategy.IgnoreChanges,\n      });\n      const { entityOp, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.UPSERT_ONE);\n      expect(mergeStrategy).toBe(MergeStrategy.IgnoreChanges);\n    });\n\n    it('#upsertManyInCache dispatches UPSERT_MANY', () => {\n      const heroes = [\n        { id: 42, name: 'test 42' },\n        { id: 84, saying: 'ho ho ho' },\n      ];\n      dispatcher.upsertManyInCache(heroes);\n      const { entityOp, data } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.UPSERT_MANY);\n      expect(data).toEqual(heroes);\n    });\n\n    it('#upsertManyInCache can dispatch UPSERT_MANY and MergeStrategy.IgnoreChanges', () => {\n      const heroes = [\n        { id: 42, name: 'test 42' },\n        { id: 84, saying: 'ho ho ho' },\n      ];\n      dispatcher.upsertManyInCache(heroes, {\n        mergeStrategy: MergeStrategy.IgnoreChanges,\n      });\n      const { entityOp, mergeStrategy } = dispatchedAction().payload;\n      expect(entityOp).toBe(EntityOp.UPSERT_MANY);\n      expect(mergeStrategy).toBe(MergeStrategy.IgnoreChanges);\n    });\n  });\n}\n"
  },
  {
    "path": "modules/data/spec/effects/entity-cache-effects.spec.ts",
    "content": "// Not using marble testing\nimport { TestBed } from '@angular/core/testing';\nimport { Action } from '@ngrx/store';\nimport { Actions } from '@ngrx/effects';\nimport { observeOn } from 'rxjs/operators';\nimport { asapScheduler, ReplaySubject, Subject } from 'rxjs';\n\nimport {\n  EntityCacheEffects,\n  EntityActionFactory,\n  EntityCacheDataService,\n  SaveEntities,\n  SaveEntitiesSuccess,\n  SaveEntitiesCancel,\n  SaveEntitiesCanceled,\n  SaveEntitiesError,\n  HttpMethods,\n  DataServiceError,\n  ChangeSet,\n  ChangeSetItem,\n  ChangeSetOperation,\n  Logger,\n  MergeStrategy,\n} from '../..';\nimport { vi } from 'vitest';\n\ndescribe('EntityCacheEffects (normal testing)', () => {\n  let actions$: ReplaySubject<Action>;\n  let correlationId: string;\n  let dataService: TestEntityCacheDataService;\n  let effects: EntityCacheEffects;\n  let logger: Logger;\n  let mergeStrategy: MergeStrategy | undefined;\n  let options: {\n    correlationId: typeof correlationId;\n    mergeStrategy: typeof mergeStrategy;\n  };\n\n  function expectCompletion(completion: any, done: any, fail: any) {\n    effects.saveEntities$.subscribe({\n      next: (result) => {\n        expect(result).toEqual(expect.objectContaining(completion));\n        done();\n      },\n      error: fail,\n    });\n  }\n\n  beforeEach(() => {\n    actions$ = new ReplaySubject<Action>(1);\n    correlationId = 'CORID42';\n    logger = {\n      error: vi.fn().mockName('error'),\n      log: vi.fn().mockName('log'),\n      warn: vi.fn().mockName('warn'),\n    };\n    mergeStrategy = undefined;\n    options = { correlationId, mergeStrategy };\n\n    const eaFactory = new EntityActionFactory(); // doesn't change.\n\n    TestBed.configureTestingModule({\n      providers: [\n        EntityCacheEffects,\n        { provide: EntityActionFactory, useValue: eaFactory },\n        { provide: Actions, useValue: actions$ },\n        /* eslint-disable-next-line @typescript-eslint/no-use-before-define */\n        {\n          provide: EntityCacheDataService,\n          useClass: TestEntityCacheDataService,\n        },\n        { provide: Logger, useValue: logger },\n      ],\n    });\n\n    actions$ = TestBed.inject(Actions) as ReplaySubject<Action>;\n    effects = TestBed.inject(EntityCacheEffects);\n    dataService = TestBed.inject<unknown>(\n      EntityCacheDataService\n    ) as TestEntityCacheDataService;\n  });\n\n  it('should return a SAVE_ENTITIES_SUCCESS with the expected ChangeSet on success', () =>\n    new Promise<void>((done, fail) => {\n      const cs = createChangeSet();\n      const action = new SaveEntities(cs, 'test/save', options);\n      const completion = new SaveEntitiesSuccess(cs, 'test/save', options);\n\n      expectCompletion(completion, done, fail);\n\n      actions$.next(action);\n      dataService.setResponse(cs);\n    }));\n\n  it('should not emit SAVE_ENTITIES_SUCCESS if cancel arrives in time', () =>\n    new Promise<void>((done, fail) => {\n      const cs = createChangeSet();\n      const action = new SaveEntities(cs, 'test/save', options);\n      const cancel = new SaveEntitiesCancel(correlationId, 'Test Cancel');\n\n      effects.saveEntities$.subscribe({\n        next: (result) => {\n          expect(result instanceof SaveEntitiesSuccess).toBe(false);\n          expect(result instanceof SaveEntitiesCanceled).toBe(true); // instead\n          done();\n        },\n        error: fail,\n      });\n\n      actions$.next(action);\n      actions$.next(cancel);\n      dataService.setResponse(cs);\n    }));\n\n  it('should emit SAVE_ENTITIES_SUCCESS if cancel arrives too late', () =>\n    new Promise<void>((done, fail) => {\n      vi.useFakeTimers();\n      const cs = createChangeSet();\n      const action = new SaveEntities(cs, 'test/save', options);\n      const cancel = new SaveEntitiesCancel(correlationId, 'Test Cancel');\n\n      effects.saveEntities$.subscribe({\n        next: (result) => {\n          expect(result).toBeInstanceOf(SaveEntitiesSuccess);\n          done();\n          vi.useRealTimers();\n        },\n        error: fail,\n      });\n\n      actions$.next(action);\n      dataService.setResponse(cs);\n\n      vi.advanceTimersByTime(1);\n      actions$.next(cancel);\n    }));\n\n  it('should emit SAVE_ENTITIES_SUCCESS immediately if no changes to save', () =>\n    new Promise<void>((done, fail) => {\n      const action = new SaveEntities({ changes: [] }, 'test/save', options);\n      effects.saveEntities$.subscribe({\n        next: (result) => {\n          expect(result instanceof SaveEntitiesSuccess).toBe(true);\n          expect(dataService.saveEntities).not.toHaveBeenCalled();\n          done();\n        },\n        error: fail,\n      });\n      actions$.next(action);\n    }));\n\n  it('should return a SAVE_ENTITIES_ERROR when data service fails', () =>\n    new Promise<void>((done, fail) => {\n      const cs = createChangeSet();\n      const action = new SaveEntities(cs, 'test/save', options);\n      const httpError = { error: new Error('Test Failure'), status: 501 };\n      const error = makeDataServiceError('POST', httpError);\n      const completion = new SaveEntitiesError(error, action);\n\n      expectCompletion(completion, done, fail);\n\n      actions$.next(action);\n      dataService.setErrorResponse(error);\n    }));\n});\n\n// #region test helpers\nexport class TestEntityCacheDataService {\n  response$ = new Subject<any>();\n\n  saveEntities = vi\n    .fn()\n    .mockName('saveEntities')\n    .mockReturnValue(this.response$);\n\n  setResponse(data: any) {\n    this.response$.next(data);\n  }\n\n  setErrorResponse(error: any) {\n    this.response$.error(error);\n  }\n}\n\n/** make error produced by the EntityDataService */\nfunction makeDataServiceError(\n  /** Http method for that action */\n  method: HttpMethods,\n  /** Http error from the web api */\n  httpError?: any,\n  /** Options sent with the request */\n  options?: any\n) {\n  let url = 'test/save';\n  if (httpError) {\n    url = httpError.url || url;\n  } else {\n    httpError = { error: new Error('Test error'), status: 500, url };\n  }\n  return new DataServiceError(httpError, { method, url, options });\n}\n\nfunction createChangeSet(): ChangeSet {\n  const changes: ChangeSetItem[] = [\n    {\n      op: ChangeSetOperation.Add,\n      entityName: 'Hero',\n      entities: [{ id: 1, name: 'A1 Add' }],\n    },\n    {\n      op: ChangeSetOperation.Delete,\n      entityName: 'Hero',\n      entities: [2, 3],\n    },\n    {\n      op: ChangeSetOperation.Update,\n      entityName: 'Villain',\n      entities: [\n        { id: 4, changes: { id: 4, name: 'V4 Update' } },\n        { id: 5, changes: { id: 5, name: 'V5 Update' } },\n        { id: 6, changes: { id: 6, name: 'V6 Update' } },\n      ],\n    },\n    {\n      op: ChangeSetOperation.Upsert,\n      entityName: 'Villain',\n      entities: [\n        { id: 7, name: 'V7 Upsert new' },\n        { id: 4, name: 'V4 Upsert existing' },\n      ],\n    },\n  ];\n\n  return {\n    changes,\n    extras: { foo: 'anything' },\n    tag: 'Test',\n  };\n}\n// #endregion test helpers\n"
  },
  {
    "path": "modules/data/spec/effects/entity-effects.marbles.spec.ts",
    "content": "// Using marble testing\nimport { TestBed } from '@angular/core/testing';\n\nimport { cold, hot, getTestScheduler } from 'jasmine-marbles';\nimport { Observable } from 'rxjs';\n\nimport { Actions } from '@ngrx/effects';\nimport { Update } from '@ngrx/entity';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport {\n  EntityEffects,\n  EntityActionFactory,\n  EntityDataService,\n  PersistenceResultHandler,\n  DefaultPersistenceResultHandler,\n  EntityOp,\n  HttpMethods,\n  DataServiceError,\n  EntityAction,\n  makeErrorOp,\n  EntityActionDataServiceError,\n  Logger,\n} from '../..';\nimport { ENTITY_EFFECTS_SCHEDULER } from '../../src/effects/entity-effects-scheduler';\nimport { Mock, vi } from 'vitest';\n\n//////// Tests begin ////////\ndescribe('EntityEffects (marble testing)', () => {\n  let effects: EntityEffects;\n  let entityActionFactory: EntityActionFactory;\n  let dataService: TestDataService;\n  let actions: Observable<any>;\n  let logger: Logger;\n\n  beforeEach(() => {\n    logger = {\n      error: vi.fn().mockName('error'),\n      log: vi.fn().mockName('log'),\n      warn: vi.fn().mockName('warn'),\n    };\n\n    TestBed.configureTestingModule({\n      providers: [\n        EntityEffects,\n        provideMockActions(() => actions),\n        EntityActionFactory,\n        // See https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md\n        { provide: ENTITY_EFFECTS_SCHEDULER, useFactory: getTestScheduler },\n        /* eslint-disable-next-line @typescript-eslint/no-use-before-define */\n        { provide: EntityDataService, useClass: TestDataService },\n        { provide: Logger, useValue: logger },\n        {\n          provide: PersistenceResultHandler,\n          useClass: DefaultPersistenceResultHandler,\n        },\n      ],\n    });\n    actions = TestBed.inject(Actions);\n    dataService = TestBed.inject<unknown>(EntityDataService) as TestDataService;\n    entityActionFactory = TestBed.inject(EntityActionFactory);\n    effects = TestBed.inject(EntityEffects);\n  });\n\n  it('should return a QUERY_ALL_SUCCESS with the heroes on success', () => {\n    const hero1 = { id: 1, name: 'A' } as Hero;\n    const hero2 = { id: 2, name: 'B' } as Hero;\n    const heroes = [hero1, hero2];\n\n    const action = entityActionFactory.create('Hero', EntityOp.QUERY_ALL);\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.QUERY_ALL_SUCCESS,\n      heroes\n    );\n\n    const x = hot('-a---', { a: action });\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: heroes });\n    const expected = cold('----b', { b: completion });\n    dataService.getAll.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a QUERY_ALL_ERROR when data service fails', () => {\n    const action = entityActionFactory.create('Hero', EntityOp.QUERY_ALL);\n    const httpError = { error: new Error('Test Failure'), status: 501 };\n    const error = makeDataServiceError('GET', httpError);\n    const completion = makeEntityErrorCompletion(action, error);\n\n    actions = hot('-a---', { a: action });\n    const response = cold('----#|', {}, error);\n    const expected = cold('------b', { b: completion });\n    dataService.getAll.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n    expect(completion.payload.entityOp).toEqual(EntityOp.QUERY_ALL_ERROR);\n  });\n\n  it('should return a QUERY_BY_KEY_SUCCESS with a hero on success', () => {\n    const hero = { id: 1, name: 'A' } as Hero;\n    const action = entityActionFactory.create('Hero', EntityOp.QUERY_BY_KEY, 1);\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.QUERY_BY_KEY_SUCCESS,\n      hero\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: hero });\n    const expected = cold('----b', { b: completion });\n    dataService.getById.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a QUERY_BY_KEY_ERROR when data service fails', () => {\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.QUERY_BY_KEY,\n      42\n    );\n    const httpError = { error: new Error('Entity not found'), status: 404 };\n    const error = makeDataServiceError('GET', httpError);\n    const completion = makeEntityErrorCompletion(action, error);\n\n    actions = hot('-a---', { a: action });\n    const response = cold('----#|', {}, error);\n    const expected = cold('------b', { b: completion });\n    dataService.getById.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a QUERY_MANY_SUCCESS with selected heroes on success', () => {\n    const hero1 = { id: 1, name: 'BA' } as Hero;\n    const hero2 = { id: 2, name: 'BB' } as Hero;\n    const heroes = [hero1, hero2];\n\n    const action = entityActionFactory.create('Hero', EntityOp.QUERY_MANY, {\n      name: 'B',\n    });\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.QUERY_MANY_SUCCESS,\n      heroes\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: heroes });\n    const expected = cold('----b', { b: completion });\n    dataService.getWithQuery.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a QUERY_MANY_ERROR when data service fails', () => {\n    const action = entityActionFactory.create('Hero', EntityOp.QUERY_MANY, {\n      name: 'B',\n    });\n    const httpError = { error: new Error('Resource not found'), status: 404 };\n    const error = makeDataServiceError('GET', httpError, {\n      name: 'B',\n    });\n    const completion = makeEntityErrorCompletion(action, error);\n\n    actions = hot('-a---', { a: action });\n    const response = cold('----#|', {}, error);\n    const expected = cold('------b', { b: completion });\n    dataService.getWithQuery.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n    expect(completion.payload.entityOp).toEqual(EntityOp.QUERY_MANY_ERROR);\n  });\n\n  it('should return a SAVE_ADD_ONE_SUCCESS (optimistic) with the hero on success', () => {\n    const hero = { id: 1, name: 'A' } as Hero;\n\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_ADD_ONE,\n      hero,\n      { isOptimistic: true }\n    );\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_ADD_ONE_SUCCESS,\n      hero,\n      { isOptimistic: true }\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: hero });\n    const expected = cold('----b', { b: completion });\n    dataService.add.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_ADD_ONE_SUCCESS (pessimistic) with the hero on success', () => {\n    const hero = { id: 1, name: 'A' } as Hero;\n\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_ADD_ONE,\n      hero\n    );\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_ADD_ONE_SUCCESS,\n      hero\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: hero });\n    const expected = cold('----b', { b: completion });\n    dataService.add.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_ADD_ONE_ERROR when data service fails', () => {\n    const hero = { id: 1, name: 'A' } as Hero;\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_ADD_ONE,\n      hero\n    );\n    const httpError = { error: new Error('Test Failure'), status: 501 };\n    const error = makeDataServiceError('PUT', httpError);\n    const completion = makeEntityErrorCompletion(action, error);\n\n    actions = hot('-a---', { a: action });\n    const response = cold('----#|', {}, error);\n    const expected = cold('------b', { b: completion });\n    dataService.add.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_DELETE_ONE_SUCCESS (Optimistic) with the delete id on success', () => {\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_DELETE_ONE,\n      42,\n      { isOptimistic: true }\n    );\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_DELETE_ONE_SUCCESS,\n      42,\n      { isOptimistic: true }\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: 42 });\n    const expected = cold('----b', { b: completion });\n    dataService.delete.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_DELETE_ONE_SUCCESS (Pessimistic) on success', () => {\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_DELETE_ONE,\n      42\n    );\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_DELETE_ONE_SUCCESS,\n      42\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: 42 });\n    const expected = cold('----b', { b: completion });\n    dataService.delete.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_DELETE_ONE_ERROR when data service fails', () => {\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_DELETE_ONE,\n      42\n    );\n    const httpError = { error: new Error('Test Failure'), status: 501 };\n    const error = makeDataServiceError('DELETE', httpError);\n    const completion = makeEntityErrorCompletion(action, error);\n\n    actions = hot('-a---', { a: action });\n    const response = cold('----#|', {}, error);\n    const expected = cold('------b', { b: completion });\n    dataService.delete.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_UPDATE_ONE_SUCCESS (Optimistic) with the hero on success', () => {\n    const updateEntity = { id: 1, name: 'A' };\n    const update = { id: 1, changes: updateEntity } as Update<Hero>;\n    const updateResponse = { ...update, changed: true };\n\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPDATE_ONE,\n      update,\n      { isOptimistic: true }\n    );\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPDATE_ONE_SUCCESS,\n      updateResponse,\n      { isOptimistic: true }\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: updateEntity });\n    const expected = cold('----b', { b: completion });\n    dataService.update.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_UPDATE_ONE_SUCCESS (Pessimistic) with the hero on success', () => {\n    const updateEntity = { id: 1, name: 'A' };\n    const update = { id: 1, changes: updateEntity } as Update<Hero>;\n    const updateResponse = { ...update, changed: true };\n\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPDATE_ONE,\n      update\n    );\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPDATE_ONE_SUCCESS,\n      updateResponse\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: updateEntity });\n    const expected = cold('----b', { b: completion });\n    dataService.update.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_UPDATE_ONE_ERROR when data service fails', () => {\n    const update = { id: 1, changes: { id: 1, name: 'A' } } as Update<Hero>;\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPDATE_ONE,\n      update\n    );\n    const httpError = { error: new Error('Test Failure'), status: 501 };\n    const error = makeDataServiceError('PUT', httpError);\n    const completion = makeEntityErrorCompletion(action, error);\n\n    actions = hot('-a---', { a: action });\n    const response = cold('----#|', {}, error);\n    const expected = cold('------b', { b: completion });\n    dataService.update.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_UPSERT_ONE_SUCCESS (optimistic) with the hero on success', () => {\n    const hero = { id: 1, name: 'A' } as Hero;\n\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPSERT_ONE,\n      hero,\n      { isOptimistic: true }\n    );\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPSERT_ONE_SUCCESS,\n      hero,\n      { isOptimistic: true }\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: hero });\n    const expected = cold('----b', { b: completion });\n    dataService.upsert.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_UPSERT_ONE_SUCCESS (pessimistic) with the hero on success', () => {\n    const hero = { id: 1, name: 'A' } as Hero;\n\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPSERT_ONE,\n      hero\n    );\n    const completion = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPSERT_ONE_SUCCESS,\n      hero\n    );\n\n    actions = hot('-a---', { a: action });\n    // delay the response 3 frames\n    const response = cold('---a|', { a: hero });\n    const expected = cold('----b', { b: completion });\n    dataService.upsert.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it('should return a SAVE_UPSERT_ONE_ERROR when data service fails', () => {\n    const hero = { id: 1, name: 'A' } as Hero;\n    const action = entityActionFactory.create(\n      'Hero',\n      EntityOp.SAVE_UPSERT_ONE,\n      hero\n    );\n    const httpError = { error: new Error('Test Failure'), status: 501 };\n    const error = makeDataServiceError('POST', httpError);\n    const completion = makeEntityErrorCompletion(action, error);\n\n    actions = hot('-a---', { a: action });\n    const response = cold('----#|', {}, error);\n    const expected = cold('------b', { b: completion });\n    dataService.upsert.mockReturnValue(response);\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n\n  it(`should not do anything with an irrelevant action`, () => {\n    // Would clear the cached collection\n    const action = entityActionFactory.create('Hero', EntityOp.REMOVE_ALL);\n\n    actions = hot('-a---', { a: action });\n    const expected = cold('---');\n\n    expect(effects.persist$).toBeObservable(expected);\n  });\n});\n\n// #region test helpers\nexport class Hero {\n  id!: number;\n  name!: string;\n}\n\n/** make error produced by the EntityDataService */\nfunction makeDataServiceError(\n  /** Http method for that action */\n  method: HttpMethods,\n  /** Http error from the web api */\n  httpError?: any,\n  /** Options sent with the request */\n  options?: any\n) {\n  let url = 'api/heroes';\n  if (httpError) {\n    url = httpError.url || url;\n  } else {\n    httpError = { error: new Error('Test error'), status: 500, url };\n  }\n  return new DataServiceError(httpError, { method, url, options });\n}\n\n/** Make an EntityDataService error */\nfunction makeEntityErrorCompletion(\n  /** The action that initiated the data service call */\n  originalAction: EntityAction,\n  /** error produced by the EntityDataService */\n  error: DataServiceError\n) {\n  const errOp = makeErrorOp(originalAction.payload.entityOp);\n\n  // Entity Error Action\n  const eaFactory = new EntityActionFactory();\n  return eaFactory.create<EntityActionDataServiceError>('Hero', errOp, {\n    originalAction,\n    error,\n  });\n}\n\nexport interface TestDataServiceMethod {\n  add: Mock;\n  delete: Mock;\n  getAll: Mock;\n  getById: Mock;\n  getWithQuery: Mock;\n  update: Mock;\n  upsert: Mock;\n}\n\nexport class TestDataService {\n  add = vi.fn().mockName('add');\n  delete = vi.fn().mockName('delete');\n  getAll = vi.fn().mockName('getAll');\n  getById = vi.fn().mockName('getById');\n  getWithQuery = vi.fn().mockName('getWithQuery');\n  update = vi.fn().mockName('update');\n  upsert = vi.fn().mockName('upsert');\n\n  getService(): TestDataServiceMethod {\n    return this;\n  }\n\n  setResponse(methodName: keyof TestDataServiceMethod, data$: Observable<any>) {\n    this[methodName].mockReturnValue(data$);\n  }\n}\n// #endregion test helpers\n"
  },
  {
    "path": "modules/data/spec/effects/entity-effects.spec.ts",
    "content": "// Not using marble testing\nimport { TestBed } from '@angular/core/testing';\nimport { Action } from '@ngrx/store';\nimport { Actions } from '@ngrx/effects';\nimport { Update } from '@ngrx/entity';\n\nimport { of, merge, ReplaySubject, throwError, timer } from 'rxjs';\nimport { delay, first, mergeMap } from 'rxjs/operators';\n\nimport {\n  EntityActionFactory,\n  EntityEffects,\n  EntityAction,\n  EntityDataService,\n  PersistenceResultHandler,\n  DefaultPersistenceResultHandler,\n  EntityOp,\n  HttpMethods,\n  DataServiceError,\n  makeErrorOp,\n  EntityActionDataServiceError,\n  Logger,\n} from '../..';\nimport { Mock, vi } from 'vitest';\n\ndescribe('EntityEffects (normal testing)', () => {\n  // factory never changes in these tests\n  const entityActionFactory = new EntityActionFactory();\n\n  let actions$: ReplaySubject<Action>;\n  let effects: EntityEffects;\n  let logger: Logger;\n  let dataService: TestDataService;\n\n  function expectCompletion(completion: EntityAction, done: any, fail: any) {\n    effects.persist$.subscribe(\n      (result) => {\n        expect(result).toEqual(completion);\n        done();\n      },\n      (error) => {\n        fail(error);\n      }\n    );\n  }\n\n  beforeEach(() => {\n    logger = {\n      error: vi.fn().mockName('error'),\n      log: vi.fn().mockName('log'),\n      warn: vi.fn().mockName('warn'),\n    };\n    actions$ = new ReplaySubject<Action>(1);\n\n    TestBed.configureTestingModule({\n      providers: [\n        EntityEffects,\n        { provide: Actions, useValue: actions$ },\n        { provide: EntityActionFactory, useValue: entityActionFactory },\n        /* eslint-disable-next-line @typescript-eslint/no-use-before-define */\n        { provide: EntityDataService, useClass: TestDataService },\n        { provide: Logger, useValue: logger },\n        {\n          provide: PersistenceResultHandler,\n          useClass: DefaultPersistenceResultHandler,\n        },\n      ],\n    });\n\n    actions$ = TestBed.inject<unknown>(Actions) as ReplaySubject<Action>;\n    effects = TestBed.inject(EntityEffects);\n    dataService = TestBed.inject<unknown>(EntityDataService) as TestDataService;\n  });\n\n  it('cancel$ should emit correlation id for CANCEL_PERSIST', () =>\n    new Promise<void>((done) => {\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.CANCEL_PERSIST,\n        undefined,\n        { correlationId: 42 }\n      );\n      effects.cancel$.subscribe((crid: any) => {\n        expect(crid).toBe(42);\n        done();\n      });\n      actions$.next(action);\n    }));\n\n  it('should return a QUERY_ALL_SUCCESS with the heroes on success', () =>\n    new Promise<void>((done, fail) => {\n      const hero1 = { id: 1, name: 'A' } as Hero;\n      const hero2 = { id: 2, name: 'B' } as Hero;\n      const heroes = [hero1, hero2];\n      dataService.setResponse('getAll', heroes);\n\n      const action = entityActionFactory.create('Hero', EntityOp.QUERY_ALL);\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.QUERY_ALL_SUCCESS,\n        heroes\n      );\n\n      actions$.next(action);\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should perform QUERY_ALL when dispatch custom tagged action', () =>\n    new Promise<void>((done, fail) => {\n      const hero1 = { id: 1, name: 'A' } as Hero;\n      const hero2 = { id: 2, name: 'B' } as Hero;\n      const heroes = [hero1, hero2];\n      dataService.setResponse('getAll', heroes);\n\n      const action = entityActionFactory.create({\n        entityName: 'Hero',\n        entityOp: EntityOp.QUERY_ALL,\n        tag: 'Custom Hero Tag',\n      });\n\n      const completion = entityActionFactory.createFromAction(action, {\n        entityOp: EntityOp.QUERY_ALL_SUCCESS,\n        data: heroes,\n      });\n\n      actions$.next(action);\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should perform QUERY_ALL when dispatch custom action w/ that entityOp', () =>\n    new Promise<void>((done, fail) => {\n      const hero1 = { id: 1, name: 'A' } as Hero;\n      const hero2 = { id: 2, name: 'B' } as Hero;\n      const heroes = [hero1, hero2];\n      dataService.setResponse('getAll', heroes);\n\n      const action = {\n        type: 'some/arbitrary/type/text',\n        payload: {\n          entityName: 'Hero',\n          entityOp: EntityOp.QUERY_ALL,\n        },\n      };\n\n      const completion = entityActionFactory.createFromAction(action, {\n        entityOp: EntityOp.QUERY_ALL_SUCCESS,\n        data: heroes,\n      });\n\n      actions$.next(action);\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a QUERY_ALL_ERROR when data service fails', () =>\n    new Promise<void>((done, fail) => {\n      const action = entityActionFactory.create('Hero', EntityOp.QUERY_ALL);\n      const httpError = { error: new Error('Test Failure'), status: 501 };\n      const error = makeDataServiceError('GET', httpError);\n      const completion = makeEntityErrorCompletion(action, error);\n\n      actions$.next(action);\n      dataService.setErrorResponse('getAll', error);\n\n      expectCompletion(completion, done, fail);\n      expect(completion.payload.entityOp).toEqual(EntityOp.QUERY_ALL_ERROR);\n    }));\n\n  it('should return a QUERY_BY_KEY_SUCCESS with a hero on success', () =>\n    new Promise<void>((done, fail) => {\n      const hero = { id: 1, name: 'A' } as Hero;\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.QUERY_BY_KEY,\n        1\n      );\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.QUERY_BY_KEY_SUCCESS,\n        hero\n      );\n\n      actions$.next(action);\n      dataService.setResponse('getById', hero);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a QUERY_BY_KEY_ERROR when data service fails', () =>\n    new Promise<void>((done, fail) => {\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.QUERY_BY_KEY,\n        42\n      );\n      const httpError = { error: new Error('Entity not found'), status: 404 };\n      const error = makeDataServiceError('GET', httpError);\n      const completion = makeEntityErrorCompletion(action, error);\n\n      actions$.next(action);\n      dataService.setErrorResponse('getById', error);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a QUERY_MANY_SUCCESS with selected heroes on success', () =>\n    new Promise<void>((done, fail) => {\n      const hero1 = { id: 1, name: 'BA' } as Hero;\n      const hero2 = { id: 2, name: 'BB' } as Hero;\n      const heroes = [hero1, hero2];\n\n      const action = entityActionFactory.create('Hero', EntityOp.QUERY_MANY, {\n        name: 'B',\n      });\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.QUERY_MANY_SUCCESS,\n        heroes\n      );\n\n      actions$.next(action);\n      dataService.setResponse('getWithQuery', heroes);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a QUERY_MANY_ERROR when data service fails', () =>\n    new Promise<void>((done, fail) => {\n      const action = entityActionFactory.create('Hero', EntityOp.QUERY_MANY, {\n        name: 'B',\n      });\n      const httpError = { error: new Error('Resource not found'), status: 404 };\n      const error = makeDataServiceError('GET', httpError, {\n        name: 'B',\n      });\n      const completion = makeEntityErrorCompletion(action, error);\n\n      actions$.next(action);\n      dataService.setErrorResponse('getWithQuery', error);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_ADD_ONE_SUCCESS (Optimistic) with the hero on success', () =>\n    new Promise<void>((done, fail) => {\n      const hero = { id: 1, name: 'A' } as Hero;\n\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_ADD_ONE,\n        hero,\n        { isOptimistic: true }\n      );\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_ADD_ONE_SUCCESS,\n        hero,\n        { isOptimistic: true }\n      );\n\n      actions$.next(action);\n      dataService.setResponse('add', hero);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_ADD_ONE_SUCCESS (Pessimistic) with the hero on success', () =>\n    new Promise<void>((done, fail) => {\n      const hero = { id: 1, name: 'A' } as Hero;\n\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_ADD_ONE,\n        hero\n      );\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_ADD_ONE_SUCCESS,\n        hero\n      );\n\n      actions$.next(action);\n      dataService.setResponse('add', hero);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_ADD_ONE_ERROR when data service fails', () =>\n    new Promise<void>((done, fail) => {\n      const hero = { id: 1, name: 'A' } as Hero;\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_ADD_ONE,\n        hero\n      );\n      const httpError = { error: new Error('Test Failure'), status: 501 };\n      const error = makeDataServiceError('PUT', httpError);\n      const completion = makeEntityErrorCompletion(action, error);\n\n      actions$.next(action);\n      dataService.setErrorResponse('add', error);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_DELETE_ONE_SUCCESS (Optimistic) on success with delete id', () =>\n    new Promise<void>((done, fail) => {\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE,\n        42,\n        { isOptimistic: true }\n      );\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE_SUCCESS,\n        42,\n        { isOptimistic: true }\n      );\n\n      actions$.next(action);\n      dataService.setResponse('delete', 42);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_DELETE_ONE_SUCCESS (Pessimistic) on success', () =>\n    new Promise<void>((done, fail) => {\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE,\n        42\n      );\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE_SUCCESS,\n        42\n      );\n\n      actions$.next(action);\n      dataService.setResponse('delete', 42);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_DELETE_ONE_ERROR when data service fails', () =>\n    new Promise<void>((done, fail) => {\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE,\n        42\n      );\n      const httpError = { error: new Error('Test Failure'), status: 501 };\n      const error = makeDataServiceError('DELETE', httpError);\n      const completion = makeEntityErrorCompletion(action, error);\n\n      actions$.next(action);\n      dataService.setErrorResponse('delete', error);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_UPDATE_ONE_SUCCESS (Optimistic) with the hero on success', () =>\n    new Promise<void>((done, fail) => {\n      const updateEntity = { id: 1, name: 'A' };\n      const update = { id: 1, changes: updateEntity } as Update<Hero>;\n      const updateResponse = { ...update, changed: true };\n\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPDATE_ONE,\n        update,\n        { isOptimistic: true }\n      );\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPDATE_ONE_SUCCESS,\n        updateResponse,\n        { isOptimistic: true }\n      );\n\n      actions$.next(action);\n      dataService.setResponse('update', updateEntity);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_UPDATE_ONE_SUCCESS (Pessimistic) with the hero on success', () =>\n    new Promise<void>((done, fail) => {\n      const updateEntity = { id: 1, name: 'A' };\n      const update = { id: 1, changes: updateEntity } as Update<Hero>;\n      const updateResponse = { ...update, changed: true };\n\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPDATE_ONE,\n        update\n      );\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPDATE_ONE_SUCCESS,\n        updateResponse\n      );\n\n      actions$.next(action);\n      dataService.setResponse('update', updateEntity);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_UPDATE_ONE_ERROR when data service fails', () =>\n    new Promise<void>((done, fail) => {\n      const update = { id: 1, changes: { id: 1, name: 'A' } } as Update<Hero>;\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPDATE_ONE,\n        update\n      );\n      const httpError = { error: new Error('Test Failure'), status: 501 };\n      const error = makeDataServiceError('PUT', httpError);\n      const completion = makeEntityErrorCompletion(action, error);\n\n      actions$.next(action);\n      dataService.setErrorResponse('update', error);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_UPSERT_ONE_SUCCESS (Optimistic) with the hero on success', () =>\n    new Promise<void>((done, fail) => {\n      const hero = { id: 1, name: 'A' } as Hero;\n\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPSERT_ONE,\n        hero,\n        { isOptimistic: true }\n      );\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPSERT_ONE_SUCCESS,\n        hero,\n        { isOptimistic: true }\n      );\n\n      actions$.next(action);\n      dataService.setResponse('upsert', hero);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_UPSERT_ONE_SUCCESS (Pessimistic) with the hero on success', () =>\n    new Promise<void>((done, fail) => {\n      const hero = { id: 1, name: 'A' } as Hero;\n\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPSERT_ONE,\n        hero\n      );\n      const completion = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPSERT_ONE_SUCCESS,\n        hero\n      );\n\n      actions$.next(action);\n      dataService.setResponse('upsert', hero);\n\n      expectCompletion(completion, done, fail);\n    }));\n\n  it('should return a SAVE_UPSERT_ONE_ERROR when data service fails', () =>\n    new Promise<void>((done, fail) => {\n      const hero = { id: 1, name: 'A' } as Hero;\n      const action = entityActionFactory.create(\n        'Hero',\n        EntityOp.SAVE_UPSERT_ONE,\n        hero\n      );\n      const httpError = { error: new Error('Test Failure'), status: 501 };\n      const error = makeDataServiceError('POST', httpError);\n      const completion = makeEntityErrorCompletion(action, error);\n\n      actions$.next(action);\n      dataService.setErrorResponse('upsert', error);\n\n      expectCompletion(completion, done, fail);\n    }));\n  it(`should not do anything with an irrelevant action`, () =>\n    new Promise<void>((done, fail) => {\n      // Would clear the cached collection\n      const action = entityActionFactory.create('Hero', EntityOp.REMOVE_ALL);\n\n      actions$.next(action);\n      const sentinel = 'no persist$ effect';\n\n      merge(\n        effects.persist$,\n        of(sentinel).pipe(delay(1))\n        // of(entityActionFactory.create('Hero', EntityOp.QUERY_ALL)) // will cause test to fail\n      )\n        .pipe(first())\n        .subscribe(\n          (result) => expect(result).toEqual(sentinel),\n          (err) => {\n            fail(err);\n            done();\n          },\n          done\n        );\n    }));\n});\n\n// #region test helpers\nexport class Hero {\n  id!: number;\n  name!: string;\n}\n\n/** make error produced by the EntityDataService */\nfunction makeDataServiceError(\n  /** Http method for that action */\n  method: HttpMethods,\n  /** Http error from the web api */\n  httpError?: any,\n  /** Options sent with the request */\n  options?: any\n) {\n  let url = 'api/heroes';\n  if (httpError) {\n    url = httpError.url || url;\n  } else {\n    httpError = { error: new Error('Test error'), status: 500, url };\n  }\n  return new DataServiceError(httpError, { method, url, options });\n}\n\n/** Make an EntityDataService error */\nfunction makeEntityErrorCompletion(\n  /** The action that initiated the data service call */\n  originalAction: EntityAction,\n  /** error produced by the EntityDataService */\n  error: DataServiceError\n) {\n  const errOp = makeErrorOp(originalAction.payload.entityOp);\n\n  // Entity Error Action\n  const eaFactory = new EntityActionFactory();\n  return eaFactory.create<EntityActionDataServiceError>('Hero', errOp, {\n    originalAction,\n    error,\n  });\n}\n\nexport interface TestDataServiceMethod {\n  add: Mock;\n  delete: Mock;\n  getAll: Mock;\n  getById: Mock;\n  getWithQuery: Mock;\n  update: Mock;\n  upsert: Mock;\n}\nexport class TestDataService {\n  add = vi.fn().mockName('add');\n  delete = vi.fn().mockName('delete');\n  getAll = vi.fn().mockName('getAll');\n  getById = vi.fn().mockName('getById');\n  getWithQuery = vi.fn().mockName('getWithQuery');\n  update = vi.fn().mockName('update');\n  upsert = vi.fn().mockName('upsert');\n\n  getService(): TestDataServiceMethod {\n    return this;\n  }\n\n  setResponse(methodName: keyof TestDataServiceMethod, data: any) {\n    this[methodName].mockReturnValue(of(data).pipe(delay(1)));\n  }\n\n  setErrorResponse(methodName: keyof TestDataServiceMethod, error: any) {\n    // Following won't quite work because delay does not appear to delay an error\n    // this[methodName].mockReturnValue(throwError(() => error).pipe(delay(1)));\n    // Use timer instead\n    this[methodName].mockReturnValue(\n      timer(1).pipe(mergeMap(() => throwError(() => error)))\n    );\n  }\n}\n// #endregion test helpers\n"
  },
  {
    "path": "modules/data/spec/entity-data.module.spec.ts",
    "content": "import { Injectable, InjectionToken } from '@angular/core';\nimport {\n  Action,\n  ActionReducer,\n  MetaReducer,\n  Store,\n  StoreModule,\n} from '@ngrx/store';\nimport { Actions, EffectsModule, createEffect } from '@ngrx/effects';\n\n// Not using marble testing\nimport { TestBed } from '@angular/core/testing';\n\nimport { Observable } from 'rxjs';\nimport { map, skip } from 'rxjs/operators';\n\nimport {\n  EntityCache,\n  ofEntityOp,\n  persistOps,\n  EntityAction,\n  EntityActionFactory,\n  EntityDataModule,\n  EntityCacheEffects,\n  EntityEffects,\n  EntityOp,\n  EntityCollectionCreator,\n  EntityCollection,\n} from '..';\n\nimport { vi } from 'vitest';\n\nconst TEST_ACTION = 'test/get-everything-succeeded';\nconst EC_METAREDUCER_TOKEN = new InjectionToken<\n  MetaReducer<EntityCache, Action>\n>('EC MetaReducer');\n\n@Injectable()\nclass TestEntityEffects {\n  test$: Observable<Action> = createEffect(() =>\n    this.actions.pipe(\n      // tap(action => console.log('test$ effect', action)),\n      ofEntityOp(persistOps),\n      map(this.testHook)\n    )\n  );\n\n  testHook(action: EntityAction) {\n    return {\n      type: 'test-action',\n      payload: action, // the incoming action\n      entityName: action.payload.entityName,\n    };\n  }\n\n  constructor(private actions: Actions<EntityAction>) {}\n}\n\nclass Hero {\n  id!: number;\n  name!: string;\n  power?: string;\n}\n\nclass Villain {\n  id!: string;\n  name!: string;\n}\n\nconst entityMetadata = {\n  Hero: {},\n  Villain: {},\n};\n\n//////// Tests begin ////////\n\ndescribe('EntityDataModule', () => {\n  describe('with replaced EntityEffects', () => {\n    // factory never changes in these tests\n    const entityActionFactory = new EntityActionFactory();\n\n    let actions$: Actions;\n    let store: Store<EntityCache>;\n    let testEffects: TestEntityEffects;\n\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot({}),\n          EffectsModule.forRoot([]),\n          EntityDataModule.forRoot({\n            entityMetadata: entityMetadata,\n          }),\n        ],\n        providers: [\n          { provide: EntityCacheEffects, useValue: {} },\n          { provide: EntityEffects, useClass: TestEntityEffects },\n        ],\n      });\n\n      actions$ = TestBed.inject(Actions);\n      store = TestBed.inject(Store);\n\n      testEffects = TestBed.inject<unknown>(EntityEffects) as TestEntityEffects;\n      vi.spyOn(testEffects, 'testHook');\n    });\n\n    it('should invoke test effect with an EntityAction', () => {\n      const actions: Action[] = [];\n\n      // listen for actions after the next dispatched action\n      actions$\n        .pipe(\n          // tap(act => console.log('test action', act)),\n          skip(1) // Skip QUERY_ALL\n        )\n        .subscribe((act) => actions.push(act));\n\n      const action = entityActionFactory.create('Hero', EntityOp.QUERY_ALL);\n      store.dispatch(action);\n      expect(actions.length).toBe(1);\n      expect(actions[0].type).toBe('test-action');\n    });\n\n    it('should not invoke test effect with non-EntityAction', () => {\n      const actions: Action[] = [];\n\n      // listen for actions after the next dispatched action\n      actions$.pipe(skip(1)).subscribe((act) => actions.push(act));\n\n      store.dispatch({ type: 'not-an-entity-action' });\n      expect(actions.length).toBe(0);\n    });\n  });\n\n  describe('with EntityCacheMetaReducer', () => {\n    let cacheSelector$: Observable<EntityCache>;\n    let eaFactory: EntityActionFactory;\n    let metaReducerLog: string[];\n    let store: Store<{ entityCache: EntityCache }>;\n\n    function loggingEntityCacheMetaReducer(\n      reducer: ActionReducer<EntityCache>\n    ): ActionReducer<EntityCache> {\n      return (state, action) => {\n        metaReducerLog.push(`MetaReducer saw \"${action.type}\"`);\n        return reducer(state, action);\n      };\n    }\n\n    beforeEach(() => {\n      metaReducerLog = [];\n\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot({}),\n          EffectsModule.forRoot([]),\n          EntityDataModule.forRoot({\n            entityMetadata: entityMetadata,\n            entityCacheMetaReducers: [\n              loggingEntityCacheMetaReducer,\n              EC_METAREDUCER_TOKEN,\n            ],\n          }),\n        ],\n        providers: [\n          { provide: EntityCacheEffects, useValue: {} },\n          { provide: EntityEffects, useValue: {} },\n          {\n            // Here's how you add an EntityCache metareducer with an injected service\n            provide: EC_METAREDUCER_TOKEN,\n            useFactory: entityCacheMetaReducerFactory,\n            deps: [EntityCollectionCreator],\n          },\n        ],\n      });\n\n      store = TestBed.inject(Store);\n      cacheSelector$ = <any>store.select((state) => state.entityCache);\n      eaFactory = TestBed.inject(EntityActionFactory);\n    });\n\n    it('should log an ordinary entity action', () => {\n      const action = eaFactory.create('Hero', EntityOp.SET_LOADING);\n      store.dispatch(action);\n      expect(metaReducerLog.join('|')).toContain(EntityOp.SET_LOADING);\n    });\n\n    it('should respond to action handled by custom EntityCacheMetaReducer', () =>\n      new Promise<void>((done, fail) => {\n        const data = {\n          Hero: [\n            { id: 2, name: 'B', power: 'Fast' },\n            { id: 1, name: 'A', power: 'invisible' },\n          ],\n          Villain: [{ id: 30, name: 'Dr. Evil' }],\n        };\n        const action = {\n          type: TEST_ACTION,\n          payload: data,\n        };\n        store.dispatch(action);\n        cacheSelector$.subscribe({\n          next: (cache) => {\n            try {\n              expect(cache.Hero.entities[1]).toEqual(data.Hero[1]);\n              expect(cache.Villain.entities[30]).toEqual(data.Villain[0]);\n              expect(metaReducerLog.join('|')).toContain(TEST_ACTION);\n              done();\n            } catch (error: any) {\n              fail(error);\n            }\n          },\n          error: fail,\n        });\n      }));\n  });\n});\n\n// #region helpers\n\n/** Create the test entityCacheMetaReducer, injected in tests */\nfunction entityCacheMetaReducerFactory(\n  collectionCreator: EntityCollectionCreator\n) {\n  return (reducer: ActionReducer<EntityCache, Action>) => {\n    return (state: EntityCache, action: { type: string; payload?: any }) => {\n      switch (action.type) {\n        case TEST_ACTION: {\n          const mergeState = {\n            Hero: createCollection<Hero>('Hero', action.payload['Hero'] || []),\n            Villain: createCollection<Villain>(\n              'Villain',\n              action.payload['Villain'] || []\n            ),\n          };\n          return { ...state, ...mergeState };\n        }\n      }\n      return reducer(state, action);\n    };\n  };\n\n  function createCollection<T extends { id: any }>(\n    entityName: string,\n    data: T[]\n  ) {\n    return {\n      ...collectionCreator.create<T>(entityName),\n      ids: data.map((e) => e.id),\n      entities: data.reduce((acc, e) => {\n        acc[e.id] = e;\n        return acc;\n      }, {} as any),\n    } as EntityCollection<T>;\n  }\n}\n// #endregion\n"
  },
  {
    "path": "modules/data/spec/entity-metadata/entity-definition.service.spec.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\n\nimport {\n  createEntityDefinition,\n  EntityDefinitionService,\n  EntityMetadataMap,\n  ENTITY_METADATA_TOKEN,\n} from '../..';\n\n@NgModule({})\nclass LazyModule {\n  lazyMetadataMap = {\n    Sidekick: {},\n  };\n\n  constructor(entityDefinitionService: EntityDefinitionService) {\n    entityDefinitionService.registerMetadataMap(this.lazyMetadataMap);\n  }\n}\n\ndescribe('EntityDefinitionService', () => {\n  let service: EntityDefinitionService;\n  let metadataMap: EntityMetadataMap;\n\n  beforeEach(() => {\n    metadataMap = {\n      Hero: {},\n      Villain: {},\n    };\n\n    TestBed.configureTestingModule({\n      // Not actually lazy but demonstrates a module that registers metadata\n      imports: [LazyModule],\n      providers: [\n        EntityDefinitionService,\n        { provide: ENTITY_METADATA_TOKEN, multi: true, useValue: metadataMap },\n      ],\n    });\n    service = TestBed.inject(EntityDefinitionService);\n  });\n\n  describe('#getDefinition', () => {\n    it('returns definition for known entity', () => {\n      const def = service.getDefinition('Hero');\n      expect(def).toBeDefined();\n    });\n\n    it('throws if request definition for unknown entity', () => {\n      expect(() => service.getDefinition('Foo')).toThrowError(/no entity/i);\n    });\n\n    it('returns undefined if request definition for unknown entity and `shouldThrow` is false', () => {\n      const def = service.getDefinition('foo', /* shouldThrow */ false);\n      expect(def).not.toBeDefined();\n    });\n  });\n\n  describe('#registerMetadata(Map)', () => {\n    it('can register a new definition by metadata', () => {\n      service.registerMetadata({ entityName: 'Foo' });\n\n      let def = service.getDefinition('Foo');\n      expect(def).toBeDefined();\n      // Hero is still defined after registering Foo\n      def = service.getDefinition('Hero');\n      expect(def).toBeDefined();\n    });\n\n    it('can register new definitions by metadata map', () => {\n      service.registerMetadataMap({\n        Foo: {},\n        Bar: {},\n      });\n\n      let def = service.getDefinition('Foo');\n      expect(def).toBeDefined();\n      def = service.getDefinition('Bar');\n      expect(def).toBeDefined();\n      def = service.getDefinition('Hero');\n      expect(def).toBeDefined();\n    });\n\n    it('entityName property should trump map key', () => {\n      service.registerMetadataMap({\n        1: { entityName: 'Foo' }, // key and entityName differ\n      });\n\n      let def = service.getDefinition('Foo');\n      expect(def).toBeDefined();\n      def = service.getDefinition('Hero');\n      expect(def).toBeDefined();\n    });\n\n    it('a (lazy-loaded) module can register metadata with its constructor', () => {\n      // The `Sidekick` metadata are registered by LazyModule's ctor\n      // Although LazyModule is actually eagerly-loaded in this test,\n      // the registration technique is the important thing.\n      const def = service.getDefinition('Sidekick');\n      expect(def).toBeDefined();\n    });\n  });\n\n  describe('#registerDefinition(s)', () => {\n    it('can register a new definition', () => {\n      const newDef = createEntityDefinition({ entityName: 'Foo' });\n      service.registerDefinition(newDef);\n\n      let def = service.getDefinition('Foo');\n      expect(def).toBeDefined();\n      // Hero is still defined after registering Foo\n      def = service.getDefinition('Hero');\n      expect(def).toBeDefined();\n    });\n\n    it('can register a map of several definitions', () => {\n      const newDefMap = {\n        Foo: createEntityDefinition({ entityName: 'Foo' }),\n        Bar: createEntityDefinition({ entityName: 'Bar' }),\n      };\n      service.registerDefinitions(newDefMap);\n\n      let def = service.getDefinition('Foo');\n      expect(def).toBeDefined();\n      def = service.getDefinition('Bar');\n      expect(def).toBeDefined();\n      def = service.getDefinition('Hero');\n      expect(def).toBeDefined();\n    });\n\n    it('can re-register an existing definition', () => {\n      const testSelectId = (entity: any) => 'test-id';\n      const newDef = createEntityDefinition({\n        entityName: 'Hero',\n        selectId: testSelectId,\n      });\n      service.registerDefinition(newDef);\n\n      const def = service.getDefinition('Hero');\n      expect(def).toBeDefined();\n      expect(def.selectId).toBe(testSelectId);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/entity-metadata/entity-definition.spec.ts",
    "content": "import { EntityMetadata, createEntityDefinition } from '../..';\n\ninterface Hero {\n  id: number;\n  name: string;\n}\n\ninterface NonIdClass {\n  key: string;\n  something: any;\n}\n\nconst sorter = <T>(a: T, b: T) => 1;\n\nconst filter = <T>(entities: T[], pattern?: any) => entities;\n\nconst selectIdForNonId = (entity: any) => entity.key;\n\nconst HERO_METADATA: EntityMetadata<Hero> = {\n  entityName: 'Hero',\n  sortComparer: sorter,\n  filterFn: filter,\n};\n\ndescribe('EntityDefinition', () => {\n  let heroMetadata: EntityMetadata<Hero>;\n\n  describe('#createEntityDefinition', () => {\n    beforeEach(() => {\n      heroMetadata = { ...HERO_METADATA };\n    });\n\n    it('generates expected `initialState`', () => {\n      const def = createEntityDefinition(heroMetadata);\n      const initialState = def.initialState;\n      expect(initialState).toEqual({\n        entityName: 'Hero',\n        ids: [],\n        entities: {},\n        filter: '',\n        loaded: false,\n        loading: false,\n        changeState: {},\n      });\n    });\n\n    it('generates expected `initialState` when `additionalCollectionState`', () => {\n      // extend Hero collection metadata with more collection state\n      const metadata = {\n        ...heroMetadata,\n        additionalCollectionState: { foo: 'foo' },\n      };\n      const def = createEntityDefinition(metadata);\n      const initialState = def.initialState;\n      expect(initialState).toEqual(<any>{\n        entityName: 'Hero',\n        ids: [],\n        entities: {},\n        filter: '',\n        loaded: false,\n        loading: false,\n        changeState: {},\n        foo: 'foo',\n      });\n    });\n\n    it('creates default `selectId` on the definition when no metadata.selectId', () => {\n      const def = createEntityDefinition(heroMetadata);\n      expect(def.selectId({ id: 42 } as Hero)).toBe(42);\n    });\n\n    it('creates expected `selectId` on the definition when  metadata.selectId exists', () => {\n      const metadata: EntityMetadata = {\n        entityName: 'NonIdClass',\n        selectId: selectIdForNonId,\n      };\n      const def = createEntityDefinition(metadata);\n      expect(def.selectId({ key: 'foo' })).toBe('foo');\n    });\n\n    it('sets `sortComparer` to false if not in metadata', () => {\n      delete heroMetadata.sortComparer;\n      const def = createEntityDefinition(heroMetadata);\n      expect(def.metadata.sortComparer).toBe(false);\n    });\n\n    it('sets `entityDispatchOptions to {} if not in metadata', () => {\n      const def = createEntityDefinition(heroMetadata);\n      expect(def.entityDispatcherOptions).toEqual({});\n    });\n\n    it('passes `metadata.entityDispatchOptions` thru', () => {\n      const options = {\n        optimisticAdd: false,\n        optimisticUpdate: false,\n      };\n      heroMetadata.entityDispatcherOptions = options;\n      const def = createEntityDefinition(heroMetadata);\n      expect(def.entityDispatcherOptions).toBe(options);\n    });\n\n    it('throws error if missing `entityName`', () => {\n      const metadata: EntityMetadata = <any>{};\n      expect(() => createEntityDefinition(metadata)).toThrowError(/entityName/);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/entity-metadata/entity-filters.spec.ts",
    "content": "import { PropsFilterFnFactory } from '../..';\n\nclass Hero {\n  id!: number;\n  name!: string;\n  saying?: string;\n}\n\ndescribe('EntityFilterFn - PropsFilter', () => {\n  it('can match entity on full text of a target prop', () => {\n    const entity1: Hero = { id: 42, name: 'Foo' };\n    const entity2: Hero = { id: 21, name: 'Bar' };\n    const entities: Hero[] = [entity1, entity2];\n    const filter = PropsFilterFnFactory<Hero>(['name']);\n    expect(filter(entities, 'Foo')).toEqual([entity1]);\n  });\n\n  it('can match entity on regex of a target prop', () => {\n    const entity1: Hero = { id: 42, name: 'Foo' };\n    const entity2: Hero = { id: 21, name: 'Bar' };\n    const entities: Hero[] = [entity1, entity2];\n    const filter = PropsFilterFnFactory<Hero>(['name']);\n    expect(filter(entities, /fo/i)).toEqual([entity1]);\n  });\n\n  it('can match entity on regex of two target props', () => {\n    const entity1: Hero = { id: 42, name: 'Foo' };\n    const entity2: Hero = { id: 21, name: 'Bar', saying: 'Foo is not Bar' };\n    const entities: Hero[] = [entity1, entity2];\n    const filter = PropsFilterFnFactory<Hero>(['name', 'saying']);\n    expect(filter(entities, /fo/i)).toEqual([entity1, entity2]);\n  });\n\n  it('returns empty array when no matches', () => {\n    const entity1: Hero = { id: 42, name: 'Foo' };\n    const entity2: Hero = { id: 21, name: 'Bar' };\n    const entities: Hero[] = [entity1, entity2];\n    const filter = PropsFilterFnFactory<Hero>(['name']);\n    expect(filter(entities, 'Baz')).toEqual([]);\n  });\n\n  it('returns empty array for empty input entities array', () => {\n    const entities: Hero[] = [];\n    const filter = PropsFilterFnFactory<Hero>(['name']);\n    expect(filter(entities, 'Foo')).toEqual([]);\n  });\n\n  it('returns empty array for null input entities array', () => {\n    const filter = PropsFilterFnFactory<Hero>(['name']);\n    expect(filter(null as any, 'Foo')).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/entity-services/entity-collection-service.spec.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { Action, StoreModule, Store } from '@ngrx/store';\nimport { Actions, EffectsModule } from '@ngrx/effects';\n\nimport { Observable, of, throwError, timer } from 'rxjs';\nimport { delay, filter, mergeMap, tap, withLatestFrom } from 'rxjs/operators';\n\nimport { commandDispatchTest } from '../dispatchers/entity-dispatcher.spec';\nimport {\n  EntityCollectionService,\n  EntityActionOptions,\n  PersistanceCanceled,\n  EntityDispatcherDefaultOptions,\n  EntityAction,\n  EntityActionFactory,\n  EntityCache,\n  EntityOp,\n  EntityMetadataMap,\n  EntityDataModule,\n  EntityCacheEffects,\n  EntityDataService,\n  EntityDispatcherFactory,\n  EntityServices,\n  OP_SUCCESS,\n  HttpMethods,\n  DataServiceError,\n  Logger,\n} from '../..';\nimport { Mock, vi } from 'vitest';\n\ndescribe('EntityCollectionService', () => {\n  describe('Command dispatching', () => {\n    // Borrowing the dispatcher tests from entity-dispatcher.spec.\n    // The critical difference: those test didn't invoke the reducers; they do when run here.\n    commandDispatchTest(getDispatcher);\n\n    function getDispatcher() {\n      const { heroCollectionService, store } = entityServicesSetup();\n      const dispatcher = heroCollectionService.dispatcher;\n      return { dispatcher, store };\n    }\n  });\n\n  // TODO: test the effect of MergeStrategy when there are entities in cache with changes\n  // This concern is largely met by EntityChangeTracker tests but integration tests would be reassuring.\n  describe('queries', () => {\n    let heroCollectionService: EntityCollectionService<Hero>;\n    let dataService: TestDataService;\n    let reducedActions$Snoop: () => void;\n\n    beforeEach(() => {\n      ({ heroCollectionService, reducedActions$Snoop, dataService } =\n        entityServicesSetup());\n    });\n\n    // Compare to next test which subscribes to getAll() result\n    it('can use loading$ to learn when getAll() succeeds', () =>\n      new Promise<void>((done, fail) => {\n        const hero1 = { id: 1, name: 'A' } as Hero;\n        const hero2 = { id: 2, name: 'B' } as Hero;\n        const heroes = [hero1, hero2];\n        dataService.setResponse('getAll', heroes);\n        heroCollectionService.getAll();\n\n        // N.B.: This technique does not detect errors\n        heroCollectionService.loading$\n          .pipe(\n            filter((loading) => !loading),\n            withLatestFrom(heroCollectionService.entities$)\n          )\n          .subscribe(([loading, data]) => {\n            expect(data).toEqual(heroes);\n            done();\n          });\n      }));\n\n    // Compare to previous test the waits for loading$ flag to flip\n    it('getAll observable should emit heroes on success', () =>\n      new Promise<void>((done, fail) => {\n        const hero1 = { id: 1, name: 'A' } as Hero;\n        const hero2 = { id: 2, name: 'B' } as Hero;\n        const heroes = [hero1, hero2];\n        dataService.setResponse('getAll', heroes);\n        heroCollectionService\n          .getAll()\n          .subscribe(expectDataToBe(heroes, { done, fail }));\n\n        // reducedActions$Snoop(); // diagnostic\n      }));\n\n    it('getAll observable should emit expected error when data service fails', () =>\n      new Promise<void>((done, fail) => {\n        const httpError = { error: new Error('Test Failure'), status: 501 };\n        const error = makeDataServiceError('GET', httpError);\n        dataService.setErrorResponse('getAll', error);\n        heroCollectionService\n          .getAll()\n          .subscribe(expectErrorToBe(error, { done, fail }));\n      }));\n\n    it('getByKey observable should emit a hero on success', () =>\n      new Promise<void>((done, fail) => {\n        const hero = { id: 1, name: 'A' } as Hero;\n        dataService.setResponse('getById', hero);\n        heroCollectionService\n          .getByKey(1)\n          .subscribe(expectDataToBe(hero, { done, fail }));\n      }));\n\n    it('getByKey observable should emit expected error when data service fails', () =>\n      new Promise<void>((done, fail) => {\n        // Simulate HTTP 'Not Found' response\n        const httpError = new HttpErrorResponse({\n          error: 'Entity not found',\n          status: 404,\n          statusText: 'Not Found',\n          url: 'bad/location',\n        });\n\n        // For test purposes, the following would have been effectively the same thing\n        // const httpError = { error: new Error('Entity not found'), status: 404 };\n\n        const error = makeDataServiceError('GET', httpError);\n        dataService.setErrorResponse('getById', error);\n        heroCollectionService\n          .getByKey(42)\n          .subscribe(expectErrorToBe(error, { done, fail }));\n      }));\n\n    it('getWithQuery observable should emit heroes on success', () =>\n      new Promise<void>((done, fail) => {\n        const hero1 = { id: 1, name: 'A' } as Hero;\n        const hero2 = { id: 2, name: 'B' } as Hero;\n        const heroes = [hero1, hero2];\n        dataService.setResponse('getWithQuery', heroes);\n        heroCollectionService\n          .getWithQuery({ name: 'foo' })\n          .subscribe(expectDataToBe(heroes, { done, fail }));\n\n        // reducedActions$Snoop(); // diagnostic\n      }));\n\n    it('getWithQuery observable should emit expected error when data service fails', () =>\n      new Promise<void>((done, fail) => {\n        const httpError = { error: new Error('Test Failure'), status: 501 };\n        const error = makeDataServiceError('GET', httpError);\n        dataService.setErrorResponse('getWithQuery', error);\n        heroCollectionService\n          .getWithQuery({ name: 'foo' })\n          .subscribe(expectErrorToBe(error, { done, fail }));\n      }));\n\n    it('load observable should emit heroes on success', () =>\n      new Promise<void>((done, fail) => {\n        const hero1 = { id: 1, name: 'A' } as Hero;\n        const hero2 = { id: 2, name: 'B' } as Hero;\n        const heroes = [hero1, hero2];\n        dataService.setResponse('getAll', heroes);\n        heroCollectionService\n          .load()\n          .subscribe(expectDataToBe(heroes, { done, fail }));\n      }));\n\n    it('load observable should emit expected error when data service fails', () =>\n      new Promise<void>((done, fail) => {\n        const httpError = { error: new Error('Test Failure'), status: 501 };\n        const error = makeDataServiceError('GET', httpError);\n        dataService.setErrorResponse('getAll', error);\n        heroCollectionService\n          .load()\n          .subscribe(expectErrorToBe(error, { done, fail }));\n      }));\n\n    it('loadWithQuery observable should emit heroes on success', () =>\n      new Promise<void>((done, fail) => {\n        const hero1 = { id: 1, name: 'A' } as Hero;\n        const hero2 = { id: 2, name: 'B' } as Hero;\n        const heroes = [hero1, hero2];\n        dataService.setResponse('getWithQuery', heroes);\n        heroCollectionService\n          .loadWithQuery({ name: 'foo' })\n          .subscribe(expectDataToBe(heroes, { done, fail }));\n      }));\n\n    it('loadWithQuery observable should emit expected error when data service fails', () =>\n      new Promise<void>((done, fail) => {\n        const httpError = { error: new Error('Test Failure'), status: 501 };\n        const error = makeDataServiceError('GET', httpError);\n        dataService.setErrorResponse('getWithQuery', error);\n        heroCollectionService\n          .loadWithQuery({ name: 'foo' })\n          .subscribe(expectErrorToBe(error, { done, fail }));\n      }));\n  });\n\n  describe('cancel', () => {\n    const hero1 = { id: 1, name: 'A' } as Hero;\n    const hero2 = { id: 2, name: 'B' } as Hero;\n    const heroes = [hero1, hero2];\n\n    let heroCollectionService: EntityCollectionService<Hero>;\n    let dataService: TestDataService;\n    let reducedActions$Snoop: () => void;\n\n    beforeEach(() => {\n      ({ dataService, heroCollectionService, reducedActions$Snoop } =\n        entityServicesSetup());\n    });\n\n    it('can cancel a long running query', () =>\n      new Promise<void>((done, fail) => {\n        const responseDelay = 4;\n        dataService['getAll'].mockReturnValue(\n          of(heroes).pipe(delay(responseDelay))\n        );\n\n        // Create the correlation id yourself to know which action to cancel.\n        const correlationId = 'CRID007';\n        const options: EntityActionOptions = { correlationId };\n        heroCollectionService.getAll(options).subscribe({\n          next: (data) => fail('should not have data but got data'),\n          error: (error) => {\n            expect(error instanceof PersistanceCanceled).toBe(true);\n            expect(error.message).toBe('Test cancel');\n            done();\n          },\n        });\n\n        heroCollectionService.cancel(correlationId, 'Test cancel');\n      }));\n\n    it('has no effect on action with different correlationId', () =>\n      new Promise<void>((done, fail) => {\n        const responseDelay = 4;\n        dataService['getAll'].mockReturnValue(\n          of(heroes).pipe(delay(responseDelay))\n        );\n\n        const correlationId = 'CRID007';\n        const options: EntityActionOptions = { correlationId };\n        heroCollectionService.getAll(options).subscribe({\n          next: (data) => {\n            expect(data).toEqual(heroes);\n            done();\n          },\n          error: fail,\n        });\n\n        heroCollectionService.cancel('not-the-crid');\n      }));\n\n    it('has no effect when too late', () =>\n      new Promise<void>((done, fail) => {\n        const responseDelay = 4;\n        dataService['getAll'].mockReturnValue(\n          of(heroes).pipe(delay(responseDelay))\n        );\n\n        const correlationId = 'CRID007';\n        const options: EntityActionOptions = { correlationId };\n        heroCollectionService.getAll(options).subscribe({\n          next: (data) => expect(data).toEqual(heroes),\n          error: fail,\n        });\n\n        setTimeout(\n          () => heroCollectionService.cancel(correlationId),\n          responseDelay + 2\n        );\n        setTimeout(done, responseDelay + 4); // wait for all to complete\n      }));\n  });\n\n  describe.skip('saves (optimistic)', () => {\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        /* eslint-disable-next-line @typescript-eslint/no-use-before-define */\n        providers: [\n          {\n            provide: EntityDispatcherDefaultOptions,\n            useClass: OptimisticDispatcherDefaultOptions,\n          },\n        ],\n      });\n    });\n\n    combinedSaveTests(true);\n  });\n\n  describe.skip('saves (pessimistic)', () => {\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        /* eslint-disable-next-line @typescript-eslint/no-use-before-define */\n        providers: [\n          {\n            provide: EntityDispatcherDefaultOptions,\n            useClass: PessimisticDispatcherDefaultOptions,\n          },\n        ],\n      });\n    });\n\n    combinedSaveTests(false);\n  });\n\n  /** Save tests to be run both optimistically and pessimistically */\n  function combinedSaveTests(isOptimistic: boolean) {\n    let heroCollectionService: EntityCollectionService<Hero>;\n    let dataService: TestDataService;\n    let expectOptimisticSuccess: (expect: boolean) => () => void;\n    let reducedActions$Snoop: () => void;\n    let successActions$: Observable<EntityAction>;\n\n    beforeEach(() => {\n      ({\n        dataService,\n        expectOptimisticSuccess,\n        heroCollectionService,\n        reducedActions$Snoop,\n        successActions$,\n      } = entityServicesSetup());\n    });\n\n    it('add() should save a new entity and return it', () =>\n      new Promise<void>((done, fail) => {\n        const extra = expectOptimisticSuccess(isOptimistic);\n        const hero = { id: 1, name: 'A' } as Hero;\n        dataService.setResponse('add', hero);\n        heroCollectionService\n          .add(hero)\n          .subscribe(expectDataToBe(hero, { done, fail }, undefined, extra));\n      }));\n\n    it('add() observable should emit expected error when data service fails', () =>\n      new Promise<void>((done, fail) => {\n        const hero = { id: 1, name: 'A' } as Hero;\n        const httpError = { error: new Error('Test Failure'), status: 501 };\n        const error = makeDataServiceError('PUT', httpError);\n        dataService.setErrorResponse('add', error);\n        heroCollectionService\n          .add(hero)\n          .subscribe(expectErrorToBe(error, { done, fail }));\n      }));\n\n    it('delete() should send delete for entity not in cache and return its id', () =>\n      new Promise<void>((done, fail) => {\n        const extra = expectOptimisticSuccess(isOptimistic);\n        dataService.setResponse('delete', 42);\n        heroCollectionService\n          .delete(42)\n          .subscribe(expectDataToBe(42, { done, fail }, undefined, extra));\n      }));\n\n    it('delete() should skip delete for added entity cache', () =>\n      new Promise<void>((done, fail) => {\n        // reducedActions$Snoop();\n        let wasSkipped: boolean;\n        successActions$.subscribe(\n          (act: EntityAction) => (wasSkipped = act.payload.skip === true)\n        );\n        const extra = () => expect(wasSkipped).toBe(true);\n\n        const hero = { id: 1, name: 'A' } as Hero;\n        heroCollectionService.addOneToCache(hero);\n        dataService.setResponse('delete', 1);\n        heroCollectionService\n          .delete(1)\n          .subscribe(expectDataToBe(1, { done, fail }, undefined, extra));\n      }));\n\n    it('delete() observable should emit expected error when data service fails', () =>\n      new Promise<void>((done, fail) => {\n        const httpError = { error: new Error('Test Failure'), status: 501 };\n        const error = makeDataServiceError('DELETE', httpError);\n        dataService.setErrorResponse('delete', error);\n        heroCollectionService\n          .delete(42)\n          .subscribe(expectErrorToBe(error, { done, fail }));\n      }));\n\n    it('update() should save updated entity and return it', () =>\n      new Promise<void>((done, fail) => {\n        const extra = expectOptimisticSuccess(isOptimistic);\n        const preUpdate = { id: 1, name: 'A' } as Hero;\n        heroCollectionService.addAllToCache([preUpdate]); // populate cache\n        const update = { ...preUpdate, name: 'Updated A' };\n        dataService.setResponse('update', null); // server returns nothing after update\n        heroCollectionService\n          .update(update)\n          .subscribe(expectDataToBe(update, { done, fail }, undefined, extra));\n      }));\n\n    it('update() should save updated entity and return server-changed version', () =>\n      new Promise<void>((done, fail) => {\n        const extra = expectOptimisticSuccess(isOptimistic);\n        const preUpdate = { id: 1, name: 'A' } as Hero;\n        heroCollectionService.addAllToCache([preUpdate]); // populate cache\n        const update = { ...preUpdate, name: 'Updated A' };\n        const postUpdate = {\n          ...preUpdate,\n          name: 'Updated A',\n          saying: 'Server set this',\n        };\n        dataService.setResponse('update', postUpdate); // server returns entity with server-side changes\n        heroCollectionService\n          .update(update)\n          .subscribe(\n            expectDataToBe(postUpdate, { done, fail }, undefined, extra)\n          );\n      }));\n\n    it('update() observable should emit expected error when data service fails', () =>\n      new Promise<void>((done, fail) => {\n        const preUpdate = { id: 1, name: 'A' } as Hero;\n        heroCollectionService.addAllToCache([preUpdate]); // populate cache\n        const update = { ...preUpdate, name: 'Updated A' };\n        const httpError = { error: new Error('Test Failure'), status: 501 };\n        const error = makeDataServiceError('PUT', httpError);\n        dataService.setErrorResponse('update', error);\n        heroCollectionService\n          .update(update)\n          .subscribe(expectErrorToBe(error, { done, fail }));\n      }));\n\n    it('can handle out-of-order save results', () =>\n      new Promise<void>((done) => {\n        const hero1 = { id: 1, name: 'A' } as Hero;\n        const hero2 = { id: 2, name: 'B' } as Hero;\n        let successActionCount = 0;\n        const delayMs = 5;\n        let responseDelay = delayMs;\n        const savedHeroes: Hero[] = [];\n\n        successActions$.pipe(delay(1)).subscribe((act) => {\n          successActionCount += 1;\n          if (successActionCount === 2) {\n            // Confirm hero2 actually saved before hero1\n            expect(savedHeroes).toEqual([hero2, hero1]);\n            done();\n          }\n        });\n\n        // dataService.add returns odd responses later than even responses\n        // so add of hero2 should complete before add of hero1\n        dataService['add'].mockImplementation((data: Hero) => {\n          const result = of(data).pipe(\n            delay(responseDelay),\n            tap((h) => savedHeroes.push(h))\n          );\n          responseDelay = delayMs === responseDelay ? 1 : responseDelay;\n          return result;\n        });\n\n        // Save hero1 before hero2\n        // Confirm that each add returns with its own hero\n        heroCollectionService\n          .add(hero1)\n          .subscribe((data) => expect(data).toEqual(hero1));\n\n        heroCollectionService\n          .add(hero2)\n          .subscribe((data) => expect(data).toEqual(hero2));\n      }));\n  }\n\n  describe('selectors$', () => {\n    let entityActionFactory: EntityActionFactory;\n    let heroCollectionService: EntityCollectionService<Hero>;\n    let store: Store<EntityCache>;\n\n    function dispatchedAction() {\n      return <EntityAction>(<Mock>store.dispatch).mock.calls.at(0)?.[0];\n    }\n\n    beforeEach(() => {\n      const setup = entityServicesSetup();\n      ({ entityActionFactory, heroCollectionService, store } = setup);\n      vi.spyOn(store, 'dispatch');\n    });\n\n    it('can get collection from collection$', () => {\n      const action = entityActionFactory.create('Hero', EntityOp.ADD_ALL, [\n        { id: 1, name: 'A' },\n      ]);\n      store.dispatch(action);\n      heroCollectionService.collection$.subscribe((collection) => {\n        expect(collection.ids).toEqual([1]);\n      });\n    });\n  });\n});\n\n// #region test helpers\nclass Hero {\n  id!: number;\n  name!: string;\n  saying?: string;\n}\nclass Villain {\n  key!: string;\n  name!: string;\n}\n\nconst entityMetadata: EntityMetadataMap = {\n  Hero: {},\n  Villain: { selectId: (villain) => villain.key },\n};\n\nfunction entityServicesSetup() {\n  const logger = {\n    error: vi.fn().mockName('error'),\n    log: vi.fn().mockName('log'),\n    warn: vi.fn().mockName('warn'),\n  };\n\n  TestBed.configureTestingModule({\n    imports: [\n      StoreModule.forRoot({}),\n      EffectsModule.forRoot([]),\n      EntityDataModule.forRoot({\n        entityMetadata: entityMetadata,\n      }),\n    ],\n    providers: [\n      { provide: EntityCacheEffects, useValue: {} },\n      /* eslint-disable-next-line @typescript-eslint/no-use-before-define */\n      { provide: EntityDataService, useClass: TestDataService },\n      { provide: Logger, useValue: logger },\n    ],\n  });\n\n  const actions$: Observable<Action> = TestBed.inject(Actions);\n  const dataService: TestDataService = TestBed.inject<unknown>(\n    EntityDataService\n  ) as TestDataService;\n  const entityActionFactory: EntityActionFactory =\n    TestBed.inject(EntityActionFactory);\n  const entityDispatcherFactory: EntityDispatcherFactory = TestBed.inject(\n    EntityDispatcherFactory\n  );\n  const entityServices: EntityServices = TestBed.inject(EntityServices);\n  const heroCollectionService =\n    entityServices.getEntityCollectionService<Hero>('Hero');\n  const reducedActions$: Observable<Action> =\n    entityDispatcherFactory.reducedActions$;\n  const store: Store<EntityCache> = TestBed.inject(Store);\n  const successActions$: Observable<EntityAction> = reducedActions$.pipe(\n    filter(\n      (act: any) => act.payload && act.payload.entityOp.endsWith(OP_SUCCESS)\n    )\n  );\n\n  /** Returns fn that confirms EntityAction was (or was not Optimistic) after success */\n  function expectOptimisticSuccess(expected: boolean) {\n    let wasOptimistic: boolean;\n    const msg = `${expected ? 'Optimistic' : 'Pessimistic'} save `;\n    successActions$.subscribe(\n      (act: EntityAction) => (wasOptimistic = act.payload.isOptimistic === true)\n    );\n    return () => expect(wasOptimistic).toBe(expected);\n  }\n\n  /** Snoop on reducedActions$ while debugging a test */\n  function reducedActions$Snoop() {\n    reducedActions$.subscribe((act) => {\n      console.log('scannedActions$', act);\n    });\n  }\n\n  return {\n    actions$,\n    dataService,\n    entityActionFactory,\n    entityServices,\n    expectOptimisticSuccess,\n    heroCollectionService,\n    reducedActions$,\n    reducedActions$Snoop,\n    store,\n    successActions$,\n  };\n}\n\nfunction expectDataToBe(\n  expected: any,\n  { done, fail }: { done: any; fail: any },\n  message?: string,\n  extra?: () => void\n) {\n  return {\n    next: (data: any) => {\n      expect(data).toEqual(expected);\n      if (extra) {\n        extra(); // extra expectations before done\n      }\n      done();\n    },\n    error: fail,\n  };\n}\n\nfunction expectErrorToBe(\n  expected: any,\n  { done, fail }: { done: any; fail: any },\n  message?: string\n) {\n  return {\n    next: (data: any) => {\n      fail(`Expected error response but got data: '${JSON.stringify(data)}'`);\n      done();\n    },\n    error: (error: any) => {\n      expect(error).toEqual(expected);\n      done();\n    },\n  };\n}\n\n/** make error produced by the EntityDataService */\nfunction makeDataServiceError(\n  /** Http method for that action */\n  method: HttpMethods,\n  /** Http error from the web api */\n  httpError?: any,\n  /** Options sent with the request */\n  options?: any\n) {\n  let url = 'api/heroes';\n  if (httpError) {\n    url = httpError.url || url;\n  } else {\n    httpError = { error: new Error('Test error'), status: 500, url };\n  }\n  return new DataServiceError(httpError, { method, url, options });\n}\n\n@Injectable()\nexport class OptimisticDispatcherDefaultOptions {\n  optimisticAdd = true;\n  optimisticDelete = true;\n  optimisticUpdate = true;\n}\n\n@Injectable()\nexport class PessimisticDispatcherDefaultOptions {\n  optimisticAdd = false;\n  optimisticDelete = false;\n  optimisticUpdate = false;\n}\n\nexport interface TestDataServiceMethod {\n  add: Mock;\n  delete: Mock;\n  getAll: Mock;\n  getById: Mock;\n  getWithQuery: Mock;\n  update: Mock;\n}\n\nexport class TestDataService {\n  add = vi.fn().mockName('add');\n  delete = vi.fn().mockName('delete');\n  getAll = vi.fn().mockName('getAll');\n  getById = vi.fn().mockName('getById');\n  getWithQuery = vi.fn().mockName('getWithQuery');\n  update = vi.fn().mockName('update');\n\n  getService(): TestDataServiceMethod {\n    return this;\n  }\n\n  setResponse(methodName: keyof TestDataServiceMethod, data: any) {\n    this[methodName].mockReturnValue(of(data).pipe(delay(1)));\n  }\n\n  setErrorResponse(methodName: keyof TestDataServiceMethod, error: any) {\n    // Following won't quite work because delay does not appear to delay an error\n    // this[methodName].mockReturnValue(throwError(() => error).pipe(delay(1)));\n    // Use timer instead\n    this[methodName].mockReturnValue(\n      timer(1).pipe(mergeMap(() => throwError(() => error)))\n    );\n  }\n}\n// #endregion test helpers\n"
  },
  {
    "path": "modules/data/spec/entity-services/entity-services.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { Action, StoreModule, Store } from '@ngrx/store';\nimport { Actions, EffectsModule } from '@ngrx/effects';\nimport { Observable } from 'rxjs';\nimport { first, skip } from 'rxjs/operators';\n\nimport {\n  EntityAction,\n  EntityOp,\n  EntityCacheQuerySet,\n  MergeQuerySet,\n  EntityMetadataMap,\n  EntityDataModule,\n  EntityCacheEffects,\n  EntityDataService,\n  EntityActionFactory,\n  EntityDispatcherFactory,\n  EntityServices,\n  EntityCache,\n  HttpMethods,\n  DataServiceError,\n  Logger,\n} from '../..';\nimport { vi } from 'vitest';\n\ndescribe('EntityServices', () => {\n  describe('entityActionErrors$', () => {\n    it('should emit EntityAction errors for multiple entity types', () => {\n      const errors: EntityAction[] = [];\n      const { entityActionFactory, entityServices } = entityServicesSetup();\n      entityServices.entityActionErrors$.subscribe((error) =>\n        errors.push(error)\n      );\n\n      entityServices.dispatch({ type: 'not-an-entity-action' });\n      entityServices.dispatch(\n        entityActionFactory.create('Hero', EntityOp.QUERY_ALL)\n      ); // not an error\n      entityServices.dispatch(\n        entityActionFactory.create(\n          'Hero',\n          EntityOp.QUERY_ALL_ERROR,\n          makeDataServiceError('GET', new Error('Bad hero news'))\n        )\n      );\n      entityServices.dispatch(\n        entityActionFactory.create('Villain', EntityOp.QUERY_ALL)\n      ); // not an error\n      entityServices.dispatch(\n        entityActionFactory.create(\n          'Villain',\n          EntityOp.SAVE_ADD_ONE_ERROR,\n          makeDataServiceError('PUT', new Error('Bad villain news'))\n        )\n      );\n\n      expect(errors.length).toBe(2);\n    });\n  });\n\n  describe('entityCache$', () => {\n    it('should observe the entire entity cache', () => {\n      const entityCacheValues: any = [];\n\n      const { entityActionFactory, entityServices, store } =\n        entityServicesSetup();\n\n      // entityCache$.subscribe() callback invoked immediately. The cache is empty at first.\n      entityServices.entityCache$.subscribe((ec) => entityCacheValues.push(ec));\n\n      // This first action to go through the Hero's EntityCollectionReducer\n      // creates the collection in the EntityCache as a side-effect,\n      // triggering the second entityCache$.subscribe() callback\n      const heroAction = entityActionFactory.create(\n        'Hero',\n        EntityOp.SET_FILTER,\n        'test'\n      );\n      store.dispatch(heroAction);\n\n      expect(entityCacheValues.length).toEqual(2);\n      expect(entityCacheValues[0]).toEqual({});\n      expect(entityCacheValues[1].Hero).toBeDefined();\n    });\n  });\n\n  describe('dispatch(MergeQuerySet)', () => {\n    // using async test to guard against false test pass.\n    it('should update entityCache$ twice after merging two individual collections', () =>\n      new Promise<void>((done) => {\n        const hero1 = { id: 1, name: 'A' } as Hero;\n        const hero2 = { id: 2, name: 'B' } as Hero;\n        const heroes = [hero1, hero2];\n\n        const villain = { key: 'DE', name: 'Dr. Evil' } as Villain;\n\n        const { entityServices } = entityServicesSetup();\n        const heroCollectionService =\n          entityServices.getEntityCollectionService<Hero>('Hero');\n        const villainCollectionService =\n          entityServices.getEntityCollectionService<Villain>('Villain');\n\n        const entityCacheValues: any = [];\n        entityServices.entityCache$.subscribe((cache) => {\n          entityCacheValues.push(cache);\n          if (entityCacheValues.length === 3) {\n            expect(entityCacheValues[0]).toEqual({});\n            expect(entityCacheValues[1]['Hero'].ids).toEqual([1, 2]);\n            expect(entityCacheValues[1]['Villain']).toBeUndefined();\n            expect(entityCacheValues[2]['Villain'].entities['DE']).toEqual(\n              villain\n            );\n            done();\n          }\n        });\n\n        // Emulate what would happen if had queried collections separately\n        heroCollectionService.createAndDispatch(\n          EntityOp.QUERY_MANY_SUCCESS,\n          heroes\n        );\n        villainCollectionService.createAndDispatch(\n          EntityOp.QUERY_BY_KEY_SUCCESS,\n          villain\n        );\n      }));\n\n    // using async test to guard against false test pass.\n    it('should update entityCache$ once when MergeQuerySet multiple collections', () =>\n      new Promise<void>((done) => {\n        const hero1 = { id: 1, name: 'A' } as Hero;\n        const hero2 = { id: 2, name: 'B' } as Hero;\n        const heroes = [hero1, hero2];\n        const villain = { key: 'DE', name: 'Dr. Evil' } as Villain;\n        const querySet: EntityCacheQuerySet = {\n          Hero: heroes,\n          Villain: [villain],\n        };\n        const action = new MergeQuerySet(querySet);\n\n        const { entityServices } = entityServicesSetup();\n\n        // Skip initial value. Want the first one after merge is dispatched\n        entityServices.entityCache$\n          .pipe(skip(1), first())\n          .subscribe((cache) => {\n            expect(cache['Hero'].ids).toEqual([1, 2]);\n            expect(cache['Villain'].entities['DE']).toEqual(villain);\n            done();\n          });\n        entityServices.dispatch(action);\n      }));\n  });\n});\n\n// #region test helpers\nclass Hero {\n  id!: number;\n  name!: string;\n  saying?: string;\n}\nclass Villain {\n  key!: string;\n  name!: string;\n}\n\nconst entityMetadata: EntityMetadataMap = {\n  Hero: {},\n  Villain: { selectId: (villain) => villain.key },\n};\n\nfunction entityServicesSetup() {\n  const logger = {\n    error: vi.fn().mockName('error'),\n    log: vi.fn().mockName('log'),\n    warn: vi.fn().mockName('warn'),\n  };\n\n  TestBed.configureTestingModule({\n    imports: [\n      StoreModule.forRoot({}),\n      EffectsModule.forRoot([]),\n      EntityDataModule.forRoot({\n        entityMetadata: entityMetadata,\n      }),\n    ],\n    /* eslint-disable-next-line @typescript-eslint/no-use-before-define */\n    providers: [\n      { provide: EntityCacheEffects, useValue: {} },\n      { provide: EntityDataService, useValue: null },\n      { provide: Logger, useValue: logger },\n    ],\n  });\n\n  const actions$: Observable<Action> = TestBed.inject(Actions);\n  const entityActionFactory: EntityActionFactory =\n    TestBed.inject(EntityActionFactory);\n  const entityDispatcherFactory: EntityDispatcherFactory = TestBed.inject(\n    EntityDispatcherFactory\n  );\n  const entityServices: EntityServices = TestBed.inject(EntityServices);\n  const store: Store<EntityCache> = TestBed.inject(Store);\n\n  return {\n    actions$,\n    entityActionFactory,\n    entityServices,\n    store,\n  };\n}\n\n/** make error produced by the EntityDataService */\nfunction makeDataServiceError(\n  /** Http method for that action */\n  method: HttpMethods,\n  /** Http error from the web api */\n  httpError?: any,\n  /** Options sent with the request */\n  options?: any\n) {\n  let url = 'api/heroes';\n  if (httpError) {\n    url = httpError.url || url;\n  } else {\n    httpError = { error: new Error('Test error'), status: 500, url };\n  }\n  return new DataServiceError(httpError, { method, url, options });\n}\n// #endregion test helpers\n"
  },
  {
    "path": "modules/data/spec/reducers/entity-cache-reducer.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { Action, ActionReducer } from '@ngrx/store';\nimport { IdSelector } from '@ngrx/entity';\n\nimport {\n  EntityMetadataMap,\n  EntityCollectionCreator,\n  EntityActionFactory,\n  EntityCache,\n  EntityCacheReducerFactory,\n  EntityCollectionReducerMethodsFactory,\n  EntityCollectionReducerFactory,\n  EntityCollectionReducerRegistry,\n  EntityDefinitionService,\n  ENTITY_METADATA_TOKEN,\n  EntityOp,\n  ClearCollections,\n  EntityCacheQuerySet,\n  LoadCollections,\n  MergeQuerySet,\n  SetEntityCache,\n  SaveEntities,\n  SaveEntitiesCancel,\n  SaveEntitiesSuccess,\n  DataServiceError,\n  SaveEntitiesError,\n  EntityCollection,\n  ChangeSet,\n  ChangeSetOperation,\n  Logger,\n  MergeStrategy,\n  ChangeType,\n} from '../..';\nimport { vi } from 'vitest';\n\nclass Hero {\n  id!: number;\n  name!: string;\n  power?: string;\n}\nclass Villain {\n  key!: string;\n  name!: string;\n}\n\nconst metadata: EntityMetadataMap = {\n  Fool: {},\n  Hero: {},\n  Knave: {},\n  Villain: { selectId: (villain) => villain.key },\n};\n\ndescribe('EntityCacheReducer', () => {\n  let collectionCreator: EntityCollectionCreator;\n  let entityActionFactory: EntityActionFactory;\n  let entityCacheReducer: ActionReducer<EntityCache, Action>;\n\n  beforeEach(() => {\n    entityActionFactory = new EntityActionFactory();\n    const logger = {\n      error: vi.fn().mockName('error'),\n      log: vi.fn().mockName('log'),\n      warn: vi.fn().mockName('warn'),\n    };\n\n    TestBed.configureTestingModule({\n      providers: [\n        EntityCacheReducerFactory,\n        EntityCollectionCreator,\n        {\n          provide: EntityCollectionReducerMethodsFactory,\n          useClass: EntityCollectionReducerMethodsFactory,\n        },\n        EntityCollectionReducerFactory,\n        EntityCollectionReducerRegistry,\n        EntityDefinitionService,\n        { provide: ENTITY_METADATA_TOKEN, multi: true, useValue: metadata },\n        { provide: Logger, useValue: logger },\n      ],\n    });\n\n    collectionCreator = TestBed.inject(EntityCollectionCreator);\n    const entityCacheReducerFactory = TestBed.inject(EntityCacheReducerFactory);\n    entityCacheReducer = entityCacheReducerFactory.create();\n  });\n\n  describe('#create', () => {\n    it('creates a default hero reducer when QUERY_ALL for hero', () => {\n      const hero: Hero = { id: 42, name: 'Bobby' };\n      const action = entityActionFactory.create<Hero>(\n        'Hero',\n        EntityOp.ADD_ONE,\n        hero\n      );\n\n      const state = entityCacheReducer({}, action);\n      const collection = state['Hero'];\n      expect(collection.ids.length).toBe(1);\n      expect(collection.entities[42]).toEqual(hero);\n    });\n\n    it('throws when ask for reducer of unknown entity type', () => {\n      const action = entityActionFactory.create('Foo', EntityOp.QUERY_ALL);\n      expect(() => entityCacheReducer({}, action)).toThrowError(\n        /no EntityDefinition/i\n      );\n    });\n  });\n\n  /**\n   * Test the EntityCache-level actions, SET and MERGE, which can\n   * be used to restore the entity cache from a know state such as\n   * re-hydrating from browser storage.\n   * Useful for an offline-capable app.\n   */\n  describe('EntityCache-level actions', () => {\n    let initialHeroes: Hero[];\n    let initialCache: EntityCache;\n\n    beforeEach(() => {\n      initialHeroes = [\n        { id: 2, name: 'B', power: 'Fast' },\n        { id: 1, name: 'A', power: 'invisible' },\n      ];\n      initialCache = createInitialCache({ Hero: initialHeroes });\n    });\n\n    describe('CLEAR_COLLECTIONS', () => {\n      beforeEach(() => {\n        const heroes = [\n          { id: 2, name: 'B', power: 'Fast' },\n          { id: 1, name: 'A', power: 'invisible' },\n        ];\n        const villains = [{ key: 'DE', name: 'Dr. Evil' }];\n        const fools = [{ id: 66, name: 'Fool 66' }];\n\n        initialCache = createInitialCache({\n          Hero: heroes,\n          Villain: villains,\n          Fool: fools,\n        });\n      });\n\n      it('should clear an existing cached collection', () => {\n        const collections = ['Hero'];\n        const action = new ClearCollections(collections);\n        const state = entityCacheReducer(initialCache, action);\n        expect(state['Hero'].ids).toEqual([]);\n        expect(state['Fool'].ids.length).toBeGreaterThan(0);\n        expect(state['Villain'].ids.length).toBeGreaterThan(0);\n      });\n\n      it('should clear multiple existing cached collections', () => {\n        const collections = ['Hero', 'Villain'];\n        const action = new ClearCollections(collections);\n        const state = entityCacheReducer(initialCache, action);\n        expect(state['Hero'].ids).toEqual([]);\n        expect(state['Villain'].ids).toEqual([]);\n        expect(state['Fool'].ids.length).toBeGreaterThan(0);\n      });\n\n      it('should initialize an empty cache with the collections', () => {\n        // because ANY call to a reducer creates the collection!\n        const collections = ['Hero', 'Villain'];\n        const action = new ClearCollections(collections);\n\n        const state = entityCacheReducer({}, action);\n        expect(Object.keys(state)).toEqual(['Hero', 'Villain']);\n        expect(state['Villain'].ids).toEqual([]);\n        expect(state['Hero'].ids).toEqual([]);\n      });\n\n      it('should return cache matching existing cache for empty collections array', () => {\n        const collections: string[] = [];\n        const action = new ClearCollections(collections);\n        const state = entityCacheReducer(initialCache, action);\n        expect(state).toEqual(initialCache);\n      });\n\n      it('should clear every collection in an existing cache when collections is falsy', () => {\n        const action = new ClearCollections(undefined);\n        const state = entityCacheReducer(initialCache, action);\n        expect(Object.keys(state).sort()).toEqual(['Fool', 'Hero', 'Villain']);\n        expect(state['Fool'].ids).toEqual([]);\n        expect(state['Hero'].ids).toEqual([]);\n        expect(state['Villain'].ids).toEqual([]);\n      });\n    });\n\n    describe('LOAD_COLLECTIONS', () => {\n      function shouldHaveExpectedHeroes(entityCache: EntityCache) {\n        const heroCollection = entityCache['Hero'];\n        expect(heroCollection.ids).toEqual([2, 1]);\n        expect(heroCollection.entities).toEqual({\n          1: initialHeroes[1],\n          2: initialHeroes[0],\n        });\n        expect(heroCollection.loaded).toBe(true);\n      }\n\n      it('should initialize an empty cache with the collections', () => {\n        const collections: EntityCacheQuerySet = {\n          Hero: initialHeroes,\n          Villain: [{ key: 'DE', name: 'Dr. Evil' }],\n        };\n\n        const action = new LoadCollections(collections);\n\n        const state = entityCacheReducer({}, action);\n        shouldHaveExpectedHeroes(state);\n        expect(state['Villain'].ids).toEqual(['DE']);\n        expect(state['Villain'].loaded).toBe(true);\n      });\n\n      it('should return cache matching existing cache when collections set is empty', () => {\n        const action = new LoadCollections({});\n        const state = entityCacheReducer(initialCache, action);\n        expect(state).toEqual(initialCache);\n      });\n\n      it('should add a new collection to existing cache', () => {\n        const collections: EntityCacheQuerySet = {\n          Knave: [{ id: 96, name: 'Sneaky Pete' }],\n        };\n        const action = new LoadCollections(collections);\n        const state = entityCacheReducer(initialCache, action);\n        expect(state['Knave'].ids).toEqual([96]);\n        expect(state['Knave'].loaded).toBe(true);\n      });\n\n      it('should replace an existing cached collection', () => {\n        const collections: EntityCacheQuerySet = {\n          Hero: [{ id: 42, name: 'Bobby' }],\n        };\n        const action = new LoadCollections(collections);\n        const state = entityCacheReducer(initialCache, action);\n        const heroCollection = state['Hero'];\n        expect(heroCollection.ids).toEqual([42]);\n        expect(heroCollection.entities[42]).toEqual({ id: 42, name: 'Bobby' });\n      });\n    });\n\n    describe('MERGE_QUERY_SET', () => {\n      function shouldHaveExpectedHeroes(entityCache: EntityCache) {\n        expect(entityCache['Hero'].ids).toEqual([2, 1]);\n        expect(entityCache['Hero'].entities).toEqual({\n          1: initialHeroes[1],\n          2: initialHeroes[0],\n        });\n      }\n\n      it('should initialize an empty cache with query set', () => {\n        const querySet: EntityCacheQuerySet = {\n          Hero: initialHeroes,\n          Villain: [{ key: 'DE', name: 'Dr. Evil' }],\n        };\n\n        const action = new MergeQuerySet(querySet);\n\n        const state = entityCacheReducer({}, action);\n        shouldHaveExpectedHeroes(state);\n        expect(state['Villain'].ids).toEqual(['DE']);\n      });\n\n      it('should return cache matching existing cache when query set is empty', () => {\n        const action = new MergeQuerySet({});\n        const state = entityCacheReducer(initialCache, action);\n        shouldHaveExpectedHeroes(state);\n      });\n\n      it('should add a new collection to existing cache', () => {\n        const querySet: EntityCacheQuerySet = {\n          Villain: [{ key: 'DE', name: 'Dr. Evil' }],\n        };\n        const action = new MergeQuerySet(querySet);\n        const state = entityCacheReducer(initialCache, action);\n        shouldHaveExpectedHeroes(state);\n        expect(state['Villain'].ids).toEqual(['DE']);\n      });\n\n      it('should merge into an existing cached collection', () => {\n        const querySet: EntityCacheQuerySet = {\n          Hero: [{ id: 42, name: 'Bobby' }],\n        };\n        const action = new MergeQuerySet(querySet);\n        const state = entityCacheReducer(initialCache, action);\n        const heroCollection = state['Hero'];\n        const expectedIds = initialHeroes.map((h) => h.id).concat(42);\n        expect(heroCollection.ids).toEqual(expectedIds);\n        expect(heroCollection.entities[42]).toEqual({ id: 42, name: 'Bobby' });\n      });\n\n      it('should use default preserve changes merge strategy', () => {\n        const {\n          unchangedHero,\n          unchangedHeroServerUpdated,\n          updatedHero,\n          locallyUpdatedHero,\n          serverUpdatedHero,\n          initialCache,\n        } = createInitialCacheForMerges();\n        const querySet: EntityCacheQuerySet = {\n          Hero: [unchangedHeroServerUpdated, serverUpdatedHero],\n        };\n        const action = new MergeQuerySet(querySet);\n\n        const state = entityCacheReducer(initialCache, action);\n        expect(state['Hero'].entities[unchangedHero.id]).toEqual(\n          unchangedHeroServerUpdated\n        );\n        expect(state['Hero'].entities[updatedHero.id]).toEqual(\n          locallyUpdatedHero\n        );\n        expect(\n          state['Hero'].changeState[updatedHero.id]!.originalValue\n        ).toEqual(serverUpdatedHero);\n      });\n\n      it('should be able to use ignore changes merge strategy', () => {\n        const { updatedHero, serverUpdatedHero, initialCache } =\n          createInitialCacheForMerges();\n        const querySet: EntityCacheQuerySet = {\n          Hero: [serverUpdatedHero],\n        };\n        const action = new MergeQuerySet(querySet, MergeStrategy.IgnoreChanges);\n\n        const state = entityCacheReducer(initialCache, action);\n        expect(state['Hero'].entities[updatedHero.id]).toEqual(\n          serverUpdatedHero\n        );\n        expect(\n          state['Hero'].changeState[updatedHero.id]!.originalValue\n        ).toEqual(updatedHero);\n      });\n\n      it('should be able to use preserve changes merge strategy', () => {\n        const {\n          unchangedHero,\n          unchangedHeroServerUpdated,\n          updatedHero,\n          locallyUpdatedHero,\n          serverUpdatedHero,\n          initialCache,\n        } = createInitialCacheForMerges();\n        const querySet: EntityCacheQuerySet = {\n          Hero: [unchangedHeroServerUpdated, serverUpdatedHero],\n        };\n        const action = new MergeQuerySet(\n          querySet,\n          MergeStrategy.PreserveChanges\n        );\n\n        const state = entityCacheReducer(initialCache, action);\n        expect(state['Hero'].entities[unchangedHero.id]).toEqual(\n          unchangedHeroServerUpdated\n        );\n        expect(state['Hero'].entities[updatedHero.id]).toEqual(\n          locallyUpdatedHero\n        );\n        expect(\n          state['Hero'].changeState[updatedHero.id]!.originalValue\n        ).toEqual(serverUpdatedHero);\n      });\n\n      it('should be able to use overwrite changes merge strategy', () => {\n        const {\n          unchangedHero,\n          unchangedHeroServerUpdated,\n          updatedHero,\n          serverUpdatedHero,\n          initialCache,\n        } = createInitialCacheForMerges();\n        const querySet: EntityCacheQuerySet = {\n          Hero: [unchangedHeroServerUpdated, serverUpdatedHero],\n        };\n        const action = new MergeQuerySet(\n          querySet,\n          MergeStrategy.OverwriteChanges\n        );\n\n        const state = entityCacheReducer(initialCache, action);\n        expect(state['Hero'].entities[unchangedHero.id]).toEqual(\n          unchangedHeroServerUpdated\n        );\n        expect(state['Hero'].changeState[unchangedHero.id]).toBeUndefined();\n        expect(state['Hero'].entities[updatedHero.id]).toEqual(\n          serverUpdatedHero\n        );\n        expect(state['Hero'].changeState[updatedHero.id]).toBeUndefined();\n      });\n    });\n\n    describe('SET_ENTITY_CACHE', () => {\n      it('should initialize cache', () => {\n        const cache = createInitialCache({\n          Hero: initialHeroes,\n          Villain: [{ key: 'DE', name: 'Dr. Evil' }],\n        });\n\n        const action = new SetEntityCache(cache);\n        // const action = {  // equivalent\n        //   type: SET_ENTITY_CACHE,\n        //   payload: cache\n        // };\n\n        const state = entityCacheReducer(cache, action);\n        expect(state['Hero'].ids).toEqual([2, 1]);\n        expect(state['Hero'].entities).toEqual({\n          1: initialHeroes[1],\n          2: initialHeroes[0],\n        });\n        expect(state['Villain'].ids).toEqual(['DE']);\n      });\n\n      it('should clear the cache when set with empty object', () => {\n        const action = new SetEntityCache({});\n        const state = entityCacheReducer(initialCache, action);\n        expect(Object.keys(state)).toEqual([]);\n      });\n\n      it('should replace prior cache with new cache', () => {\n        const priorCache = createInitialCache({\n          Hero: initialHeroes,\n          Villain: [{ key: 'DE', name: 'Dr. Evil' }],\n        });\n\n        const newHeroes = [{ id: 42, name: 'Bobby' }];\n        const newCache = createInitialCache({ Hero: newHeroes });\n\n        const action = new SetEntityCache(newCache);\n        const state = entityCacheReducer(priorCache, action);\n        expect(state['Villain']).toBeUndefined();\n\n        const heroCollection = state['Hero'];\n        expect(heroCollection.ids).toEqual([42]);\n        expect(heroCollection.entities[42]).toEqual(newHeroes[0]);\n      });\n    });\n\n    describe('SAVE_ENTITIES', () => {\n      it('should turn on loading flags for affected collections and nothing more when pessimistic', () => {\n        const changeSet = createTestChangeSet();\n        const action = new SaveEntities(changeSet, 'api/save', {\n          isOptimistic: false,\n        });\n\n        const entityCache = entityCacheReducer({}, action);\n\n        expect(entityCache['Fool'].ids).toEqual([]);\n        expect(entityCache['Hero'].ids).toEqual([]);\n        expect(entityCache['Knave'].ids).toEqual([]);\n        expect(entityCache['Villain'].ids).toEqual([]);\n        expectLoadingFlags(entityCache, true);\n      });\n\n      it('should initialize an empty cache with entities when optimistic and turn on loading flags', () => {\n        const changeSet = createTestChangeSet();\n        const action = new SaveEntities(changeSet, 'api/save', {\n          isOptimistic: true,\n        });\n\n        const entityCache = entityCacheReducer({}, action);\n\n        expect(entityCache['Fool'].ids).toEqual([]);\n        expect(entityCache['Hero'].ids).toEqual([42, 43]);\n        expect(entityCache['Knave'].ids).toEqual([6, 66]);\n        expect(entityCache['Villain'].ids).toEqual(['44', '45']);\n        expectLoadingFlags(entityCache, true);\n      });\n\n      it('should modify existing cache with entities when optimistic and turn on loading flags', () => {\n        const initialEntities = createInitialSaveTestEntities();\n        let entityCache = createInitialCache(initialEntities);\n\n        const changeSet = createTestChangeSet();\n        const action = new SaveEntities(changeSet, 'api/save', {\n          isOptimistic: true,\n        });\n\n        entityCache = entityCacheReducer(entityCache, action);\n\n        expect(entityCache['Fool'].ids).toEqual([1, 2]);\n        expect(entityCache['Fool'].entities[1].skill).toEqual(\n          'Updated Skill 1'\n        );\n\n        expect(entityCache['Hero'].ids).toEqual([4, 42, 43]);\n\n        expect(entityCache['Knave'].ids).toEqual([6, 66]);\n        expect(entityCache['Knave'].entities[6].name).toEqual(\n          'Upsert Update Knave 6'\n        );\n        expect(entityCache['Knave'].entities[66].name).toEqual(\n          'Upsert Add Knave 66'\n        );\n\n        expect(entityCache['Villain'].ids).toEqual([\n          '7',\n          '8',\n          '10',\n          '44',\n          '45',\n        ]);\n\n        expectLoadingFlags(entityCache, true);\n      });\n    });\n\n    describe('SAVE_ENTITIES_CANCEL', () => {\n      const corid = 'CORID42';\n\n      it('should not turn off loading flags if you do not specify collections', () => {\n        const changeSet = createTestChangeSet();\n        let action: Action = new SaveEntities(changeSet, 'api/save', {\n          correlationId: corid,\n          isOptimistic: false,\n        });\n\n        // Pessimistic save turns on loading flags\n        let entityCache = entityCacheReducer({}, action);\n        expectLoadingFlags(entityCache, true);\n\n        action = new SaveEntitiesCancel(corid, 'Test Cancel'); // no names so no flags turned off.\n        entityCache = entityCacheReducer(entityCache, action);\n        expectLoadingFlags(entityCache, true);\n      });\n\n      it('should turn off loading flags for collections that you specify', () => {\n        const changeSet = createTestChangeSet();\n        let action: Action = new SaveEntities(changeSet, 'api/save', {\n          correlationId: corid,\n          isOptimistic: false,\n        });\n\n        // Pessimistic save turns on loading flags\n        let entityCache = entityCacheReducer({}, action);\n        expectLoadingFlags(entityCache, true);\n\n        action = new SaveEntitiesCancel(corid, 'Test Cancel', ['Hero', 'Fool']);\n        entityCache = entityCacheReducer(entityCache, action);\n        expectLoadingFlags(entityCache, false, ['Hero', 'Fool']);\n        expectLoadingFlags(entityCache, true, ['Knave', 'Villain']);\n      });\n    });\n\n    describe('SAVE_ENTITIES_SUCCESS', () => {\n      it('should initialize an empty cache with entities when pessimistic', () => {\n        const changeSet = createTestChangeSet();\n        const action = new SaveEntitiesSuccess(changeSet, 'api/save', {\n          isOptimistic: false,\n        });\n\n        const entityCache = entityCacheReducer({}, action);\n\n        expect(entityCache['Fool'].ids).toEqual([]);\n        expect(entityCache['Hero'].ids).toEqual([42, 43]);\n        expect(entityCache['Knave'].ids).toEqual([6, 66]);\n        expect(entityCache['Villain'].ids).toEqual(['44', '45']);\n        expectLoadingFlags(entityCache, false);\n      });\n\n      it('should modify existing cache with entities when pessimistic', () => {\n        const initialEntities = createInitialSaveTestEntities();\n        let entityCache = createInitialCache(initialEntities);\n\n        const changeSet = createTestChangeSet();\n        const action = new SaveEntitiesSuccess(changeSet, 'api/save', {\n          isOptimistic: false,\n        });\n\n        entityCache = entityCacheReducer(entityCache, action);\n\n        expect(entityCache['Fool'].ids).toEqual([1, 2]);\n        expect(entityCache['Fool'].entities[1].skill).toEqual(\n          'Updated Skill 1'\n        );\n\n        expect(entityCache['Hero'].ids).toEqual([4, 42, 43]);\n\n        expect(entityCache['Knave'].ids).toEqual([6, 66]);\n        expect(entityCache['Knave'].entities[6].name).toEqual(\n          'Upsert Update Knave 6'\n        );\n        expect(entityCache['Knave'].entities[66].name).toEqual(\n          'Upsert Add Knave 66'\n        );\n\n        expect(entityCache['Villain'].ids).toEqual([\n          '7',\n          '8',\n          '10',\n          '44',\n          '45',\n        ]);\n\n        expectLoadingFlags(entityCache, false);\n      });\n\n      it('should modify existing cache with entities when optimistic', () => {\n        const initialEntities = createInitialSaveTestEntities();\n        let entityCache = createInitialCache(initialEntities);\n\n        const changeSet = createTestChangeSet();\n        const action = new SaveEntitiesSuccess(changeSet, 'api/save', {\n          isOptimistic: true,\n        });\n\n        entityCache = entityCacheReducer(entityCache, action);\n\n        expect(entityCache['Fool'].ids).toEqual([1, 2]);\n        expect(entityCache['Fool'].entities[1].skill).toEqual(\n          'Updated Skill 1'\n        );\n\n        expect(entityCache['Hero'].ids).toEqual([4, 42, 43]);\n\n        expect(entityCache['Knave'].ids).toEqual([6, 66]);\n        expect(entityCache['Knave'].entities[6].name).toEqual(\n          'Upsert Update Knave 6'\n        );\n        expect(entityCache['Knave'].entities[66].name).toEqual(\n          'Upsert Add Knave 66'\n        );\n\n        expect(entityCache['Villain'].ids).toEqual([\n          '7',\n          '8',\n          '10',\n          '44',\n          '45',\n        ]);\n\n        expectLoadingFlags(entityCache, false);\n      });\n    });\n\n    describe('SAVE_ENTITIES_ERROR', () => {\n      it('should turn loading flags off', () => {\n        // Begin as if saving optimistically\n        const changeSet = createTestChangeSet();\n        const saveAction = new SaveEntities(changeSet, 'api/save', {\n          isOptimistic: true,\n        });\n        let entityCache = entityCacheReducer({}, saveAction);\n\n        expectLoadingFlags(entityCache, true);\n\n        const dsError = new DataServiceError(new Error('Test Error'), {\n          url: 'api/save',\n        } as any);\n        const errorAction = new SaveEntitiesError(dsError, saveAction);\n        entityCache = entityCacheReducer(entityCache, errorAction);\n\n        expectLoadingFlags(entityCache, false);\n\n        // Added entities remain in cache (if not on the server), with pending changeState\n        expect(entityCache['Hero'].ids).toEqual([42, 43]);\n        const heroChangeState = entityCache['Hero'].changeState;\n        expect(heroChangeState[42]).toBeDefined();\n        expect(heroChangeState[43]).toBeDefined();\n      });\n    });\n  });\n\n  // #region helpers\n  function createCollection<T = any>(\n    entityName: string,\n    data: T[],\n    selectId: IdSelector<any>\n  ) {\n    return {\n      ...collectionCreator.create<T>(entityName),\n      ids: data.map((e) => selectId(e)) as string[] | number[],\n      entities: data.reduce((acc, e) => {\n        acc[selectId(e)] = e;\n        return acc;\n      }, {} as any),\n    } as EntityCollection<T>;\n  }\n\n  function createInitialCache(entityMap: { [entityName: string]: any[] }) {\n    const cache: EntityCache = {};\n    // eslint-disable-next-line guard-for-in\n    for (const entityName in entityMap) {\n      const selectId =\n        metadata[entityName].selectId || ((entity: any) => entity.id);\n      cache[entityName] = createCollection(\n        entityName,\n        entityMap[entityName],\n        selectId\n      );\n    }\n\n    return cache;\n  }\n\n  function createInitialCacheForMerges() {\n    // general test data for testing mergeStrategy\n    const unchangedHero = { id: 1, name: 'Unchanged', power: 'Hammer' };\n    const unchangedHeroServerUpdated = {\n      id: 1,\n      name: 'UnchangedUpdated',\n      power: 'Bish',\n    };\n    const deletedHero = { id: 2, name: 'Deleted', power: 'Bash' };\n    const addedHero = { id: 3, name: 'Added', power: 'Tiny' };\n    const updatedHero = { id: 4, name: 'Pre Updated', power: 'Tech' };\n    const locallyUpdatedHero = {\n      id: 4,\n      name: 'Locally Updated',\n      power: 'Suit',\n    };\n    const serverUpdatedHero = { id: 4, name: 'Server Updated', power: 'Nano' };\n    const ids = [unchangedHero.id, addedHero.id, updatedHero.id];\n    const initialCache = {\n      Hero: {\n        ids,\n        entities: {\n          [unchangedHero.id]: unchangedHero,\n          [addedHero.id]: addedHero,\n          [updatedHero.id]: locallyUpdatedHero,\n        },\n        entityName: 'Hero',\n        filter: '',\n        loaded: true,\n        loading: false,\n        changeState: {\n          [deletedHero.id]: {\n            changeType: ChangeType.Deleted,\n            originalValue: deletedHero,\n          },\n          [updatedHero.id]: {\n            changeType: ChangeType.Updated,\n            originalValue: updatedHero,\n          },\n          [addedHero.id]: { changeType: ChangeType.Added },\n        },\n      },\n    };\n    return {\n      unchangedHero,\n      unchangedHeroServerUpdated,\n      deletedHero,\n      addedHero,\n      updatedHero,\n      locallyUpdatedHero,\n      serverUpdatedHero,\n      initialCache,\n    };\n  }\n\n  function createInitialSaveTestEntities() {\n    const entities: { [entityName: string]: any[] } = {\n      Fool: [\n        { id: 1, name: 'Fool 1', skill: 'Skill 1' },\n        { id: 2, name: 'Fool 2', skill: 'Skill 2' },\n      ],\n      Hero: [\n        { id: 3, name: 'Hero 3', power: 'Power 3' },\n        { id: 4, name: 'Hero 4', power: 'Power 4' },\n        { id: 5, name: 'Hero 5', power: 'Power 5' },\n      ],\n      Knave: [{ id: 6, name: 'Knave 1', weakness: 'Weakness 6' }],\n      Villain: [\n        { key: '7', name: 'Villain 7', sin: 'Sin 7' },\n        { key: '8', name: 'Villain 8', sin: 'Sin 8' },\n        { key: '9', name: 'Villain 9', sin: 'Sin 9' },\n        { key: '10', name: 'Villain 10', sin: 'Sin 10' },\n      ],\n    };\n    return entities;\n  }\n\n  function createTestChangeSet() {\n    const changeSet: ChangeSet = {\n      changes: [\n        {\n          entityName: 'Hero',\n          op: ChangeSetOperation.Add,\n          entities: [\n            { id: 42, name: 'Hero 42' },\n            { id: 43, name: 'Hero 43', power: 'Power 43' },\n          ] as Hero[],\n        },\n        { entityName: 'Hero', op: ChangeSetOperation.Delete, entities: [3, 5] },\n        {\n          entityName: 'Villain',\n          op: ChangeSetOperation.Delete,\n          entities: ['9'],\n        },\n        {\n          entityName: 'Villain',\n          op: ChangeSetOperation.Add,\n          entities: [\n            { key: '44', name: 'Villain 44' },\n            { key: '45', name: 'Villain 45', sin: 'Sin 45' },\n          ] as Villain[],\n        },\n        {\n          entityName: 'Fool',\n          op: ChangeSetOperation.Update,\n          entities: [{ id: 1, changes: { id: 1, skill: 'Updated Skill 1' } }],\n        },\n        {\n          entityName: 'Knave',\n          op: ChangeSetOperation.Upsert,\n          entities: [\n            { id: 6, name: 'Upsert Update Knave 6' },\n            { id: 66, name: 'Upsert Add Knave 66' },\n          ],\n        },\n      ],\n    };\n    return changeSet;\n  }\n\n  /**\n   * Expect the loading flags of the named EntityCache collections to be in the `flag` state.\n   * @param entityCache cache to check\n   * @param flag True if should be loading; false if should not be loading\n   * @param entityNames names of collections to check; if undefined, check all collections\n   */\n  function expectLoadingFlags(\n    entityCache: EntityCache,\n    flag: boolean,\n    entityNames?: string[]\n  ) {\n    entityNames = entityNames ? [] : Object.keys(entityCache);\n    entityNames.forEach((name) => {\n      expect(entityCache[name].loading).toBe(flag);\n    });\n  }\n  // #endregion helpers\n});\n"
  },
  {
    "path": "modules/data/spec/reducers/entity-change-tracker-base.spec.ts",
    "content": "/* eslint-disable prefer-const */\nimport { EntityAdapter, createEntityAdapter } from '@ngrx/entity';\nimport {\n  EntityCollection,\n  EntityChangeTracker,\n  createEmptyEntityCollection,\n  EntityChangeTrackerBase,\n  defaultSelectId,\n  ChangeType,\n  ChangeState,\n  MergeStrategy,\n} from '../..';\n\ninterface Hero {\n  id: number;\n  name: string;\n  power?: string;\n}\n\nfunction sortByName(a: { name: string }, b: { name: string }): number {\n  return a.name.localeCompare(b.name);\n}\n\n/** Test version of toUpdate that assumes entity has key named 'id' */\nfunction toUpdate(entity: any) {\n  return { id: entity.id, changes: entity };\n}\n\nconst adapter: EntityAdapter<Hero> = createEntityAdapter<Hero>({\n  sortComparer: sortByName,\n});\n\ndescribe('EntityChangeTrackerBase', () => {\n  let origCollection: EntityCollection<Hero>;\n  let tracker: EntityChangeTracker<Hero>;\n\n  beforeEach(() => {\n    origCollection = createEmptyEntityCollection<Hero>('Hero');\n    origCollection.entities = {\n      1: { id: 1, name: 'Alice', power: 'Strong' },\n      2: { id: 2, name: 'Gail', power: 'Loud' },\n      7: { id: 7, name: 'Bob', power: 'Swift' },\n    };\n    origCollection.ids = [1, 7, 2];\n    tracker = new EntityChangeTrackerBase(adapter, defaultSelectId);\n  });\n\n  describe('#commitAll', () => {\n    it('should clear all tracked changes', () => {\n      let { collection } = createTestTrackedEntities();\n      expect(Object.keys(collection.changeState).length).toBe(3);\n\n      collection = tracker.commitAll(collection);\n      expect(Object.keys(collection.changeState).length).toBe(0);\n    });\n  });\n\n  describe('#commitOne', () => {\n    it('should clear current tracking of the given entity', () => {\n      let { collection, deletedEntity, addedEntity, updatedEntity } =\n        createTestTrackedEntities();\n      collection = tracker.commitMany([updatedEntity], collection);\n      expect(collection.changeState[updatedEntity.id]).toBeUndefined();\n      expect(collection.changeState[deletedEntity!.id]).toBeDefined();\n      expect(collection.changeState[addedEntity.id]).toBeDefined();\n    });\n  });\n\n  describe('#commitMany', () => {\n    it('should clear current tracking of the given entities', () => {\n      let { collection, deletedEntity, addedEntity, updatedEntity } =\n        createTestTrackedEntities();\n      collection = tracker.commitMany([addedEntity, updatedEntity], collection);\n      expect(collection.changeState[addedEntity.id]).toBeUndefined();\n      expect(collection.changeState[updatedEntity.id]).toBeUndefined();\n      expect(collection.changeState[deletedEntity!.id]).toBeDefined();\n    });\n  });\n\n  describe('#mergeQueryResults', () => {\n    it('should use default preserve changes strategy', () => {\n      let {\n        unchangedHero,\n        unchangedHeroServerUpdated,\n        updatedHero,\n        serverUpdatedHero,\n        locallyUpdatedHero,\n        initialCache,\n      } = createInitialCacheForMerges();\n      const collection = tracker.mergeQueryResults(\n        [unchangedHeroServerUpdated, serverUpdatedHero],\n        initialCache.Hero\n      );\n\n      expect(collection.entities[unchangedHero.id]).toEqual(\n        unchangedHeroServerUpdated\n      );\n      expect(collection.entities[updatedHero.id]).toEqual(locallyUpdatedHero);\n      expect(collection.changeState[updatedHero.id]!.originalValue).toEqual(\n        serverUpdatedHero\n      );\n    });\n\n    it('should be able to use ignore changes strategy', () => {\n      const { updatedHero, serverUpdatedHero, initialCache } =\n        createInitialCacheForMerges();\n\n      const collection = tracker.mergeQueryResults(\n        [serverUpdatedHero],\n        initialCache.Hero,\n        MergeStrategy.IgnoreChanges // manually provide strategy\n      );\n\n      expect(collection.entities[updatedHero.id]).toEqual(serverUpdatedHero);\n      expect(collection.changeState[updatedHero.id]!.originalValue).toEqual(\n        updatedHero\n      );\n    });\n\n    it('should be able to use preserve changes strategy', () => {\n      const {\n        unchangedHero,\n        unchangedHeroServerUpdated,\n        updatedHero,\n        serverUpdatedHero,\n        locallyUpdatedHero,\n        initialCache,\n      } = createInitialCacheForMerges();\n\n      const collection = tracker.mergeQueryResults(\n        [unchangedHeroServerUpdated, serverUpdatedHero],\n        initialCache.Hero,\n        MergeStrategy.PreserveChanges // manually provide strategy\n      );\n\n      expect(collection.entities[unchangedHero.id]).toEqual(\n        unchangedHeroServerUpdated\n      );\n      expect(collection.entities[updatedHero.id]).toEqual(locallyUpdatedHero);\n      expect(collection.changeState[updatedHero.id]!.originalValue).toEqual(\n        serverUpdatedHero\n      );\n    });\n\n    it('should be able to use overwrite changes strategy', () => {\n      const {\n        unchangedHero,\n        unchangedHeroServerUpdated,\n        updatedHero,\n        serverUpdatedHero,\n        initialCache,\n      } = createInitialCacheForMerges();\n\n      const collection = tracker.mergeQueryResults(\n        [unchangedHeroServerUpdated, serverUpdatedHero],\n        initialCache.Hero,\n        MergeStrategy.OverwriteChanges // manually provide strategy\n      );\n\n      expect(collection.entities[unchangedHero.id]).toEqual(\n        unchangedHeroServerUpdated\n      );\n      expect(collection.changeState[unchangedHero.id]).toBeUndefined();\n      expect(collection.entities[updatedHero.id]).toEqual(serverUpdatedHero);\n      expect(collection.changeState[updatedHero.id]).toBeUndefined();\n    });\n  });\n\n  describe('#mergeSaveAdds', () => {\n    it('should use default overwrite changes strategy', () => {\n      let {\n        unchangedHero,\n        unchangedHeroServerUpdated,\n        updatedHero,\n        serverUpdatedHero,\n        initialCache,\n      } = createInitialCacheForMerges();\n      const collection = tracker.mergeSaveAdds(\n        [unchangedHeroServerUpdated, serverUpdatedHero],\n        initialCache.Hero\n      );\n\n      expect(collection.entities[unchangedHero.id]).toEqual(\n        unchangedHeroServerUpdated\n      );\n      expect(collection.changeState[unchangedHero.id]).toBeUndefined();\n      expect(collection.entities[updatedHero.id]).toEqual(serverUpdatedHero);\n      expect(collection.changeState[updatedHero.id]).toBeUndefined();\n    });\n\n    it('should be able to use ignore changes strategy', () => {\n      const { updatedHero, serverUpdatedHero, initialCache } =\n        createInitialCacheForMerges();\n\n      const collection = tracker.mergeSaveAdds(\n        [serverUpdatedHero],\n        initialCache.Hero,\n        MergeStrategy.IgnoreChanges // manually provide strategy\n      );\n\n      expect(collection.entities[updatedHero.id]).toEqual(serverUpdatedHero);\n      expect(collection.changeState[updatedHero.id]!.originalValue).toEqual(\n        updatedHero\n      );\n    });\n\n    it('should be able to use preserve changes strategy', () => {\n      const {\n        unchangedHero,\n        unchangedHeroServerUpdated,\n        updatedHero,\n        serverUpdatedHero,\n        locallyUpdatedHero,\n        initialCache,\n      } = createInitialCacheForMerges();\n\n      const collection = tracker.mergeSaveAdds(\n        [unchangedHeroServerUpdated, serverUpdatedHero],\n        initialCache.Hero,\n        MergeStrategy.PreserveChanges // manually provide strategy\n      );\n\n      expect(collection.entities[unchangedHero.id]).toEqual(\n        unchangedHeroServerUpdated\n      );\n      expect(collection.entities[updatedHero.id]).toEqual(locallyUpdatedHero);\n      expect(collection.changeState[updatedHero.id]!.originalValue).toEqual(\n        serverUpdatedHero\n      );\n    });\n\n    it('should be able to use overwrite changes strategy', () => {\n      const {\n        unchangedHero,\n        unchangedHeroServerUpdated,\n        updatedHero,\n        serverUpdatedHero,\n        initialCache,\n      } = createInitialCacheForMerges();\n\n      const collection = tracker.mergeSaveAdds(\n        [unchangedHeroServerUpdated, serverUpdatedHero],\n        initialCache.Hero,\n        MergeStrategy.OverwriteChanges // manually provide strategy\n      );\n\n      expect(collection.entities[unchangedHero.id]).toEqual(\n        unchangedHeroServerUpdated\n      );\n      expect(collection.changeState[unchangedHero.id]).toBeUndefined();\n      expect(collection.entities[updatedHero.id]).toEqual(serverUpdatedHero);\n      expect(collection.changeState[updatedHero.id]).toBeUndefined();\n    });\n  });\n\n  describe.skip('#mergeSaveDeletes', () => {\n    // TODO: add some tests\n  });\n\n  describe.skip('#mergeSaveUpdates', () => {\n    // TODO: add some tests\n  });\n\n  describe('#mergeSaveUpserts', () => {\n    it('should use default overwrite changes strategy', () => {\n      let {\n        unchangedHero,\n        unchangedHeroServerUpdated,\n        updatedHero,\n        serverUpdatedHero,\n        initialCache,\n      } = createInitialCacheForMerges();\n      const collection = tracker.mergeSaveUpserts(\n        [unchangedHeroServerUpdated, serverUpdatedHero],\n        initialCache.Hero\n      );\n\n      expect(collection.entities[unchangedHero.id]).toEqual(\n        unchangedHeroServerUpdated\n      );\n      expect(collection.changeState[unchangedHero.id]).toBeUndefined();\n      expect(collection.entities[updatedHero.id]).toEqual(serverUpdatedHero);\n      expect(collection.changeState[updatedHero.id]).toBeUndefined();\n    });\n\n    it('should be able to use ignore changes strategy', () => {\n      const { updatedHero, serverUpdatedHero, initialCache } =\n        createInitialCacheForMerges();\n\n      const collection = tracker.mergeSaveUpserts(\n        [serverUpdatedHero],\n        initialCache.Hero,\n        MergeStrategy.IgnoreChanges // manually provide strategy\n      );\n\n      expect(collection.entities[updatedHero.id]).toEqual(serverUpdatedHero);\n      expect(collection.changeState[updatedHero.id]!.originalValue).toEqual(\n        updatedHero\n      );\n    });\n\n    it('should be able to use preserve changes strategy', () => {\n      const {\n        unchangedHero,\n        unchangedHeroServerUpdated,\n        updatedHero,\n        serverUpdatedHero,\n        locallyUpdatedHero,\n        initialCache,\n      } = createInitialCacheForMerges();\n\n      const collection = tracker.mergeSaveUpserts(\n        [unchangedHeroServerUpdated, serverUpdatedHero],\n        initialCache.Hero,\n        MergeStrategy.PreserveChanges // manually provide strategy\n      );\n\n      expect(collection.entities[unchangedHero.id]).toEqual(\n        unchangedHeroServerUpdated\n      );\n      expect(collection.entities[updatedHero.id]).toEqual(locallyUpdatedHero);\n      expect(collection.changeState[updatedHero.id]!.originalValue).toEqual(\n        serverUpdatedHero\n      );\n    });\n\n    it('should be able to use overwrite changes strategy', () => {\n      const {\n        unchangedHero,\n        unchangedHeroServerUpdated,\n        updatedHero,\n        serverUpdatedHero,\n        initialCache,\n      } = createInitialCacheForMerges();\n\n      const collection = tracker.mergeSaveUpserts(\n        [unchangedHeroServerUpdated, serverUpdatedHero],\n        initialCache.Hero,\n        MergeStrategy.OverwriteChanges // manually provide strategy\n      );\n\n      expect(collection.entities[unchangedHero.id]).toEqual(\n        unchangedHeroServerUpdated\n      );\n      expect(collection.changeState[unchangedHero.id]).toBeUndefined();\n      expect(collection.entities[updatedHero.id]).toEqual(serverUpdatedHero);\n      expect(collection.changeState[updatedHero.id]).toBeUndefined();\n    });\n  });\n\n  describe('#trackAddOne', () => {\n    it('should return a new collection with tracked new entity', () => {\n      const addedEntity = { id: 42, name: 'Ted', power: 'Chatty' };\n      const collection = tracker.trackAddOne(addedEntity, origCollection);\n\n      expect(collection).not.toBe(origCollection);\n      const change = collection.changeState[addedEntity.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Added);\n      expect(change!.originalValue).toBeUndefined();\n    });\n\n    it('should leave added entity tracked as added when entity is updated', () => {\n      const addedEntity = { id: 42, name: 'Ted', power: 'Chatty' };\n      let collection = tracker.trackAddOne(addedEntity, origCollection);\n\n      const updatedEntity = { ...addedEntity, name: 'Double Test' };\n      collection = tracker.trackUpdateOne(toUpdate(updatedEntity), collection);\n      // simulate the collection update\n      collection.entities[addedEntity.id] = updatedEntity;\n\n      const change = collection.changeState[updatedEntity.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Added);\n      expect(change!.originalValue).toBeUndefined();\n    });\n\n    it('should return same collection if called with null entity', () => {\n      const collection = tracker.trackAddOne(null as any, origCollection);\n      expect(collection).toBe(origCollection);\n    });\n\n    it('should return the same collection if MergeStrategy.IgnoreChanges', () => {\n      const addedEntity = { id: 42, name: 'Ted', power: 'Chatty' };\n      const collection = tracker.trackAddOne(\n        addedEntity,\n        origCollection,\n        MergeStrategy.IgnoreChanges\n      );\n\n      expect(collection).toBe(origCollection);\n      const change = collection.changeState[addedEntity.id];\n      expect(change).toBeUndefined();\n    });\n  });\n\n  describe('#trackAddMany', () => {\n    const newEntities = [\n      { id: 42, name: 'Ted', power: 'Chatty' },\n      { id: 84, name: 'Sally', power: 'Laughter' },\n    ];\n\n    it('should return a new collection with tracked new entities', () => {\n      const collection = tracker.trackAddMany(newEntities, origCollection);\n      expect(collection).not.toBe(origCollection);\n      const trackKeys = Object.keys(collection.changeState);\n      expect(trackKeys).toEqual(['42', '84']);\n\n      trackKeys.forEach((key, ix) => {\n        const change = collection.changeState[key];\n        expect(change).toBeDefined();\n        expectChangeType(\n          change,\n          ChangeType.Added,\n          `tracking ${key} as a new entity`\n        );\n        expect(change!.originalValue).toBeUndefined();\n      });\n    });\n\n    it('should return same collection if called with empty array', () => {\n      const collection = tracker.trackAddMany([] as any, origCollection);\n      expect(collection).toBe(origCollection);\n    });\n  });\n\n  describe('#trackDeleteOne', () => {\n    it('should return a new collection with tracked \"deleted\" entity', () => {\n      const existingEntity = getFirstExistingEntity();\n      const collection = tracker.trackDeleteOne(\n        existingEntity!.id,\n        origCollection\n      );\n      expect(collection).not.toBe(origCollection);\n      const change = collection.changeState[existingEntity!.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Deleted);\n      expect(change!.originalValue).toBe(existingEntity);\n    });\n\n    it('should return a new collection with tracked \"deleted\" entity, deleted by key', () => {\n      const existingEntity = getFirstExistingEntity();\n      const collection = tracker.trackDeleteOne(\n        existingEntity!.id,\n        origCollection\n      );\n      expect(collection).not.toBe(origCollection);\n      const change = collection.changeState[existingEntity!.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Deleted);\n      expect(change!.originalValue).toBe(existingEntity);\n    });\n\n    it('should untrack (commit) an added entity when it is removed', () => {\n      const addedEntity = { id: 42, name: 'Ted', power: 'Chatty' };\n      let collection = tracker.trackAddOne(addedEntity, origCollection);\n\n      // Add it to the collection as the reducer would\n      collection = {\n        ...collection,\n        entities: { ...collection.entities, 42: addedEntity },\n        ids: (collection.ids as number[]).concat(42),\n      };\n\n      let change = collection.changeState[addedEntity.id];\n      expect(change).toBeDefined();\n\n      collection = tracker.trackDeleteOne(addedEntity.id, collection);\n      change = collection.changeState[addedEntity.id];\n      expect(change).not.toBeDefined();\n    });\n\n    it('should switch an updated entity to a deleted entity when it is removed', () => {\n      const existingEntity = getFirstExistingEntity();\n      const updatedEntity = toUpdate({\n        ...existingEntity,\n        name: 'test update',\n      });\n\n      let collection = tracker.trackUpdateOne(\n        toUpdate(updatedEntity),\n        origCollection\n      );\n\n      let change = collection.changeState[updatedEntity.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Updated, 'updated at first');\n\n      collection = tracker.trackDeleteOne(updatedEntity.id, collection);\n      change = collection.changeState[updatedEntity.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Deleted, 'after delete');\n      expect(change!.originalValue).toEqual(existingEntity);\n    });\n\n    it('should leave deleted entity tracked as deleted when try to update', () => {\n      const existingEntity = getFirstExistingEntity();\n      let collection = tracker.trackDeleteOne(\n        existingEntity!.id,\n        origCollection\n      );\n\n      let change = collection.changeState[existingEntity!.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Deleted);\n\n      // This shouldn't be possible but let's try it.\n      const updatedEntity: any = { ...existingEntity, name: 'Double Test' };\n      collection.entities[existingEntity!.id] = updatedEntity;\n\n      collection = tracker.trackUpdateOne(toUpdate(updatedEntity), collection);\n      change = collection.changeState[updatedEntity.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Deleted);\n      expect(change!.originalValue).toEqual(existingEntity);\n    });\n\n    it('should return same collection if called with null entity', () => {\n      const collection = tracker.trackDeleteOne(null as any, origCollection);\n      expect(collection).toBe(origCollection);\n    });\n\n    it('should return same collection if called with a key not found', () => {\n      const collection = tracker.trackDeleteOne('1234', origCollection);\n      expect(collection).toBe(origCollection);\n    });\n\n    it('should return same collection if MergeStrategy.IgnoreChanges', () => {\n      const existingEntity = getFirstExistingEntity();\n      const collection = tracker.trackDeleteOne(\n        existingEntity!.id,\n        origCollection,\n        MergeStrategy.IgnoreChanges\n      );\n      expect(collection).toBe(origCollection);\n      const change = collection.changeState[existingEntity!.id];\n      expect(change).toBeUndefined();\n    });\n  });\n\n  describe('#trackDeleteMany', () => {\n    it('should return a new collection with tracked \"deleted\" entities', () => {\n      const existingEntities = getSomeExistingEntities(2);\n      const collection = tracker.trackDeleteMany(\n        existingEntities.map((e) => e!.id),\n        origCollection\n      );\n      expect(collection).not.toBe(origCollection);\n      existingEntities.forEach((entity, ix) => {\n        const change = collection.changeState[existingEntities[ix]!.id];\n        expect(change).toBeDefined();\n        expectChangeType(change, ChangeType.Deleted, `entity #${ix}`);\n        expect(change!.originalValue).toBe(existingEntities[ix]);\n      });\n    });\n\n    it('should return same collection if called with empty array', () => {\n      const collection = tracker.trackDeleteMany([], origCollection);\n      expect(collection).toBe(origCollection);\n    });\n\n    it('should return same collection if called with a key not found', () => {\n      const collection = tracker.trackDeleteMany(['1234', 456], origCollection);\n      expect(collection).toBe(origCollection);\n    });\n\n    it('should not mutate changeState when called on a tracked \"updated\" entity', () => {\n      const existingEntity = getFirstExistingEntity();\n      const updatedEntity = toUpdate({\n        ...existingEntity,\n        name: 'test update',\n      });\n      const collection = tracker.trackUpdateOne(updatedEntity, origCollection);\n      const change = collection.changeState[existingEntity!.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Updated);\n      Object.freeze(change);\n      expect(() => {\n        tracker.trackDeleteMany([existingEntity!.id], collection);\n      }).not.toThrowError();\n    });\n  });\n\n  describe('#trackUpdateOne', () => {\n    it('should return a new collection with tracked updated entity', () => {\n      const existingEntity = getFirstExistingEntity();\n      const updatedEntity = toUpdate({\n        ...existingEntity,\n        name: 'test update',\n      });\n      const collection = tracker.trackUpdateOne(updatedEntity, origCollection);\n      expect(collection).not.toBe(origCollection);\n      const change = collection.changeState[existingEntity!.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Updated);\n      expect(change!.originalValue).toBe(existingEntity);\n    });\n\n    it('should return a new collection with tracked updated entity, updated by key', () => {\n      const existingEntity = getFirstExistingEntity();\n      const updatedEntity = toUpdate({\n        ...existingEntity,\n        name: 'test update',\n      });\n      const collection = tracker.trackUpdateOne(updatedEntity, origCollection);\n      expect(collection).not.toBe(origCollection);\n      const change = collection.changeState[existingEntity!.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Updated);\n      expect(change!.originalValue).toBe(existingEntity);\n    });\n\n    it('should leave updated entity tracked as updated if try to add', () => {\n      const existingEntity = getFirstExistingEntity();\n      const updatedEntity = toUpdate({\n        ...existingEntity,\n        name: 'test update',\n      });\n      let collection = tracker.trackUpdateOne(updatedEntity, origCollection);\n\n      let change = collection.changeState[existingEntity!.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Updated);\n\n      // This shouldn't be possible but let's try it.\n      const addedEntity: any = { ...existingEntity, name: 'Double Test' };\n      collection.entities[existingEntity!.id] = addedEntity;\n\n      collection = tracker.trackAddOne(addedEntity, collection);\n      change = collection.changeState[addedEntity.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Updated);\n      expect(change!.originalValue).toEqual(existingEntity);\n    });\n\n    it('should return same collection if called with null entity', () => {\n      const collection = tracker.trackUpdateOne(null as any, origCollection);\n      expect(collection).toBe(origCollection);\n    });\n\n    it('should return same collection if called with a key not found', () => {\n      const updateEntity = toUpdate({ id: '1234', name: 'Mr. 404' });\n      const collection = tracker.trackUpdateOne(updateEntity, origCollection);\n      expect(collection).toBe(origCollection);\n    });\n\n    it('should return same collection if MergeStrategy.IgnoreChanges', () => {\n      const existingEntity = getFirstExistingEntity();\n      const updatedEntity = toUpdate({\n        ...existingEntity,\n        name: 'test update',\n      });\n      const collection = tracker.trackUpdateOne(\n        updatedEntity,\n        origCollection,\n        MergeStrategy.IgnoreChanges\n      );\n      expect(collection).toBe(origCollection);\n      const change = collection.changeState[existingEntity!.id];\n      expect(change).toBeUndefined();\n    });\n  });\n\n  describe('#trackUpdateMany', () => {\n    it('should return a new collection with tracked updated entities', () => {\n      const existingEntities = getSomeExistingEntities(2);\n      const updateEntities = existingEntities.map((e) =>\n        toUpdate({ ...e, name: e!.name + ' updated' })\n      );\n      const collection = tracker.trackUpdateMany(\n        updateEntities,\n        origCollection\n      );\n      expect(collection).not.toBe(origCollection);\n      existingEntities.forEach((entity, ix) => {\n        const change = collection.changeState[existingEntities[ix]!.id];\n        expect(change).toBeDefined();\n        expectChangeType(change, ChangeType.Updated, `entity #${ix}`);\n        expect(change!.originalValue).toBe(existingEntities[ix]);\n      });\n    });\n\n    it('should return same collection if called with empty array', () => {\n      const collection = tracker.trackUpdateMany([], origCollection);\n      expect(collection).toBe(origCollection);\n    });\n\n    it('should return same collection if called with entities whose keys are not found', () => {\n      const updateEntities = [\n        toUpdate({ id: '1234', name: 'Mr. 404' }),\n        toUpdate({ id: 456, name: 'Ms. 404' }),\n      ];\n      const collection = tracker.trackUpdateMany(\n        updateEntities,\n        origCollection\n      );\n      expect(collection).toBe(origCollection);\n    });\n  });\n\n  describe('#trackUpsertOne', () => {\n    it('should return a new collection with tracked added entity', () => {\n      const addedEntity = { id: 42, name: 'Ted', power: 'Chatty' };\n      const collection = tracker.trackUpsertOne(addedEntity, origCollection);\n      expect(collection).not.toBe(origCollection);\n      const change = collection.changeState[addedEntity.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Added);\n      expect(change!.originalValue).toBeUndefined();\n    });\n\n    it('should return a new collection with tracked updated entity', () => {\n      const existingEntity = getFirstExistingEntity();\n      const collection = tracker.trackUpsertOne(\n        existingEntity as Hero,\n        origCollection\n      );\n      expect(collection).not.toBe(origCollection);\n      const change = collection.changeState[existingEntity!.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Updated);\n      expect(change!.originalValue).toBe(existingEntity);\n    });\n\n    it('should not change orig value of updated entity that is updated again', () => {\n      const existingEntity = getFirstExistingEntity();\n      let collection = tracker.trackUpsertOne(\n        existingEntity as Hero,\n        origCollection\n      );\n\n      let change = collection.changeState[existingEntity!.id];\n      expect(change).toBeDefined();\n      expectChangeType(change, ChangeType.Updated, 'first updated');\n\n      const updatedAgainEntity = {\n        ...existingEntity,\n        name: 'Double Test',\n      } as Hero;\n\n      collection = tracker.trackUpsertOne(\n        updatedAgainEntity as Hero,\n        collection\n      );\n      change = collection.changeState[updatedAgainEntity.id];\n      expect(change).toBeDefined();\n      expectChangeType(\n        change,\n        ChangeType.Updated,\n        'still updated after attempted add'\n      );\n      expect(change!.originalValue).toEqual(existingEntity);\n    });\n\n    it('should return same collection if called with null entity', () => {\n      const collection = tracker.trackUpsertOne(null as any, origCollection);\n      expect(collection).toBe(origCollection);\n    });\n\n    it('should return same collection if MergeStrategy.IgnoreChanges', () => {\n      const existingEntity = getFirstExistingEntity();\n      const updatedEntity = { ...existingEntity, name: 'test update' };\n      const collection = tracker.trackUpsertOne(\n        updatedEntity as Hero,\n        origCollection,\n        MergeStrategy.IgnoreChanges\n      );\n      expect(collection).toBe(origCollection);\n      const change = collection.changeState[existingEntity!.id];\n      expect(change).toBeUndefined();\n    });\n  });\n\n  describe('#trackUpsertMany', () => {\n    it('should return a new collection with tracked upserted entities', () => {\n      const addedEntity = { id: 42, name: 'Ted', power: 'Chatty' };\n      const exitingEntities = getSomeExistingEntities(2);\n      const updatedEntities = exitingEntities.map((e) => ({\n        ...e,\n        name: e!.name + 'test',\n      }));\n      const upsertEntities = updatedEntities.concat(addedEntity);\n      const collection = tracker.trackUpsertMany(\n        upsertEntities as Hero[],\n        origCollection\n      );\n      expect(collection).not.toBe(origCollection);\n      updatedEntities.forEach((entity, ix) => {\n        const change = collection.changeState[(updatedEntities[ix] as Hero).id];\n        expect(change).toBeDefined();\n        // first two should be updated, the 3rd is added\n        expectChangeType(\n          change,\n          ix === 2 ? ChangeType.Added : ChangeType.Updated,\n          `entity #${ix}`\n        );\n        if (change!.changeType === ChangeType.Updated) {\n          expect(change!.originalValue).toBe(exitingEntities[ix]);\n        } else {\n          expect(change!.originalValue).toBeUndefined();\n        }\n      });\n    });\n\n    it('should return same collection if called with empty array', () => {\n      const collection = tracker.trackUpsertMany([], origCollection);\n      expect(collection).toBe(origCollection);\n    });\n  });\n\n  describe('#undoAll', () => {\n    it('should clear all tracked changes', () => {\n      let { collection } = createTestTrackedEntities();\n      expect(Object.keys(collection.changeState).length).toBe(3);\n\n      collection = tracker.undoAll(collection);\n      expect(Object.keys(collection.changeState).length).toBe(0);\n    });\n\n    it('should restore the collection to the pre-change state', () => {\n      let {\n        collection,\n        addedEntity,\n        deletedEntity,\n        preUpdatedEntity,\n        updatedEntity,\n      } = createTestTrackedEntities();\n\n      // Before undo\n      expect(collection.entities[addedEntity.id]).toBeDefined();\n      expect(collection.entities[deletedEntity!.id]).toBeUndefined();\n      expect(updatedEntity.name).not.toEqual(preUpdatedEntity!.name);\n\n      collection = tracker.undoAll(collection);\n\n      // After undo\n      expect(collection.entities[addedEntity.id]).toBeUndefined();\n      expect(collection.entities[deletedEntity!.id]).toBeDefined();\n      const revertedUpdate = collection.entities[updatedEntity.id];\n      expect(revertedUpdate!.name).toEqual(preUpdatedEntity!.name);\n    });\n  });\n\n  describe('#undoOne', () => {\n    it('should clear one tracked change', () => {\n      let { collection, deletedEntity } = createTestTrackedEntities();\n\n      expect(Object.keys(collection.changeState).length).toBe(3);\n\n      collection = tracker.undoOne(deletedEntity as Hero, collection);\n\n      expect(Object.keys(collection.changeState).length).toBe(2);\n    });\n\n    it('should restore the collection to the pre-change state for the given entity', () => {\n      let {\n        collection,\n        addedEntity,\n        deletedEntity,\n        preUpdatedEntity,\n        updatedEntity,\n      } = createTestTrackedEntities();\n\n      collection = tracker.undoOne(deletedEntity as Hero, collection);\n\n      expect(collection.entities[deletedEntity!.id]).toBeDefined();\n      expect(collection.entities[addedEntity.id]).toBeDefined();\n      expect(updatedEntity.name).not.toEqual(preUpdatedEntity!.name);\n    });\n\n    it('should do nothing when the given entity is null', () => {\n      let {\n        collection,\n        addedEntity,\n        deletedEntity,\n        preUpdatedEntity,\n        updatedEntity,\n      } = createTestTrackedEntities();\n\n      collection = tracker.undoOne(null as any, collection);\n      expect(collection.entities[addedEntity.id]).toBeDefined();\n      expect(collection.entities[deletedEntity!.id]).toBeUndefined();\n      expect(updatedEntity.name).not.toEqual(preUpdatedEntity!.name);\n    });\n  });\n\n  describe('#undoMany', () => {\n    it('should clear many tracked changes', () => {\n      let {\n        collection,\n        addedEntity,\n        deletedEntity,\n        preUpdatedEntity,\n        updatedEntity,\n      } = createTestTrackedEntities();\n\n      expect(Object.keys(collection.changeState).length).toBe(3);\n\n      collection = tracker.undoMany(\n        [addedEntity, deletedEntity, updatedEntity],\n        collection\n      );\n\n      expect(Object.keys(collection.changeState).length).toBe(0);\n    });\n\n    it('should restore the collection to the pre-change state for the given entities', () => {\n      let {\n        collection,\n        addedEntity,\n        deletedEntity,\n        preUpdatedEntity,\n        updatedEntity,\n      } = createTestTrackedEntities();\n\n      collection = tracker.undoMany(\n        [addedEntity, deletedEntity, updatedEntity],\n        collection\n      );\n      expect(collection.entities[addedEntity.id]).toBeUndefined();\n      expect(collection.entities[deletedEntity!.id]).toBeDefined();\n      const revertedUpdate = collection.entities[updatedEntity.id];\n      expect(revertedUpdate!.name).toEqual(preUpdatedEntity!.name);\n    });\n\n    it('should do nothing when there are no entities to undo', () => {\n      let {\n        collection,\n        addedEntity,\n        deletedEntity,\n        preUpdatedEntity,\n        updatedEntity,\n      } = createTestTrackedEntities();\n\n      collection = tracker.undoMany([], collection);\n      expect(collection.entities[addedEntity.id]).toBeDefined();\n      expect(collection.entities[deletedEntity!.id]).toBeUndefined();\n      expect(updatedEntity.name).not.toEqual(preUpdatedEntity!.name);\n    });\n  });\n\n  /// helpers ///\n\n  /** Simulate the state of the collection after some test changes */\n  function createTestTrackedEntities() {\n    const addedEntity = { id: 42, name: 'Ted', power: 'Chatty' };\n    const [deletedEntity, preUpdatedEntity] = getSomeExistingEntities(2);\n    const updatedEntity: any = { ...preUpdatedEntity, name: 'Test Me' };\n\n    let collection = tracker.trackAddOne(addedEntity, origCollection);\n    collection = tracker.trackDeleteOne(deletedEntity!.id, collection);\n    collection = tracker.trackUpdateOne(toUpdate(updatedEntity), collection);\n\n    // Make the collection match these changes\n    collection.ids = (\n      collection.ids.slice(1, collection.ids.length) as number[]\n    ).concat(42);\n    const entities: { [id: number]: Hero } = {\n      ...collection.entities,\n      42: addedEntity,\n      [updatedEntity.id]: updatedEntity,\n    };\n    delete entities[deletedEntity!.id];\n    collection.entities = entities;\n    return {\n      collection,\n      addedEntity,\n      deletedEntity,\n      preUpdatedEntity,\n      updatedEntity,\n    };\n  }\n\n  function createInitialCacheForMerges() {\n    // general test data for testing mergeStrategy\n    const unchangedHero = { id: 1, name: 'Unchanged', power: 'Hammer' };\n    const unchangedHeroServerUpdated = {\n      id: 1,\n      name: 'UnchangedUpdated',\n      power: 'Bish',\n    };\n    const deletedHero = { id: 2, name: 'Deleted', power: 'Bash' };\n    const addedHero = { id: 3, name: 'Added', power: 'Tiny' };\n    const updatedHero = { id: 4, name: 'Pre Updated', power: 'Tech' };\n    const locallyUpdatedHero = {\n      id: 4,\n      name: 'Locally Updated',\n      power: 'Suit',\n    };\n    const serverUpdatedHero = { id: 4, name: 'Server Updated', power: 'Nano' };\n    const ids = [unchangedHero.id, addedHero.id, updatedHero.id];\n    const initialCache = {\n      Hero: {\n        ids,\n        entities: {\n          [unchangedHero.id]: unchangedHero,\n          [addedHero.id]: addedHero,\n          [updatedHero.id]: locallyUpdatedHero,\n        },\n        entityName: 'Hero',\n        filter: '',\n        loaded: true,\n        loading: false,\n        changeState: {\n          [deletedHero.id]: {\n            changeType: ChangeType.Deleted,\n            originalValue: deletedHero,\n          },\n          [updatedHero.id]: {\n            changeType: ChangeType.Updated,\n            originalValue: updatedHero,\n          },\n          [addedHero.id]: { changeType: ChangeType.Added },\n        },\n      },\n    };\n    return {\n      unchangedHero,\n      unchangedHeroServerUpdated,\n      deletedHero,\n      addedHero,\n      updatedHero,\n      locallyUpdatedHero,\n      serverUpdatedHero,\n      initialCache,\n    };\n  }\n\n  /** Test for ChangeState with expected ChangeType */\n  function expectChangeType(\n    change: ChangeState<any> | undefined,\n    expectedChangeType: ChangeType,\n    msg?: string\n  ) {\n    expect(ChangeType[change!.changeType]).toEqual(\n      ChangeType[expectedChangeType]\n    );\n  }\n\n  /** Get the first entity in `originalCollection`  */\n  function getFirstExistingEntity() {\n    return getExistingEntityById(origCollection.ids[0]);\n  }\n\n  /**\n   * Get the first 'n' existing entities from `originalCollection`\n   * @param n Number of them to get\n   */\n  function getSomeExistingEntities(n: number) {\n    const ids = (origCollection.ids as string[]).slice(0, n);\n    return getExistingEntitiesById(ids);\n  }\n\n  function getExistingEntityById(id: number | string) {\n    return getExistingEntitiesById([id as string])[0];\n  }\n\n  function getExistingEntitiesById(ids: string[]) {\n    return ids.map((id) => origCollection.entities[id]);\n  }\n});\n"
  },
  {
    "path": "modules/data/spec/reducers/entity-collection-creator.spec.ts",
    "content": "import {\n  EntityMetadata,\n  EntityCollectionCreator,\n  EntityDefinitionService,\n  createEntityDefinition,\n  EntityCollection,\n} from '../..';\n\n/** HeroMetadata identifies extra collection state properties */\nconst heroMetadata: EntityMetadata<Hero> = {\n  entityName: 'Hero',\n  additionalCollectionState: {\n    foo: 'Foo',\n    bar: 3.14,\n  },\n};\n\ndescribe('EntityCollectionCreator', () => {\n  let creator: EntityCollectionCreator;\n  let eds: EntityDefinitionService;\n\n  beforeEach(() => {\n    eds = new EntityDefinitionService(null as any);\n    const hdef = createEntityDefinition(heroMetadata);\n    hdef.initialState.filter = 'super';\n    eds.registerDefinition(hdef);\n\n    creator = new EntityCollectionCreator(eds);\n  });\n\n  it('should create collection with the definitions initial state', () => {\n    const collection = creator.create<Hero, HeroCollection>('Hero');\n    expect(collection.foo).toBe('Foo');\n    expect(collection.filter).toBe('super');\n  });\n\n  it('should create empty collection even when no initial state', () => {\n    const hdef = eds.getDefinition('Hero');\n    hdef.initialState = undefined as any; // ZAP!\n    const collection = creator.create<Hero, HeroCollection>('Hero');\n    expect(collection.foo).toBeUndefined();\n    expect(collection.ids).toBeDefined();\n  });\n\n  it('should create empty collection even when no def for entity type', () => {\n    const collection = creator.create('Bazinga');\n    expect(collection.ids).toBeDefined();\n  });\n});\n\n/////// Test values and helpers /////////\n\n/// Hero\ninterface Hero {\n  id: number;\n  name: string;\n}\n\n/** HeroCollection is EntityCollection<Hero> with extra collection properties */\ninterface HeroCollection extends EntityCollection<Hero> {\n  foo: string;\n  bar: number;\n}\n"
  },
  {
    "path": "modules/data/spec/reducers/entity-collection-reducer-registry.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { Action, ActionReducer, MetaReducer } from '@ngrx/store';\nimport { IdSelector } from '@ngrx/entity';\n\nimport {\n  EntityMetadataMap,\n  EntityCollectionCreator,\n  EntityActionFactory,\n  EntityCache,\n  EntityCollectionReducerRegistry,\n  EntityCacheReducerFactory,\n  EntityCollectionReducerMethodsFactory,\n  EntityCollectionReducerFactory,\n  EntityDefinitionService,\n  ENTITY_METADATA_TOKEN,\n  EntityOp,\n  EntityCollectionReducers,\n  EntityCollection,\n  EntityAction,\n  ENTITY_COLLECTION_META_REDUCERS,\n  Logger,\n} from '../..';\nimport { vi } from 'vitest';\n\nclass Bar {\n  id!: number;\n  bar!: string;\n}\nclass Foo {\n  id!: string;\n  foo!: string;\n}\nclass Hero {\n  id!: number;\n  name!: string;\n  power?: string;\n}\nclass Villain {\n  key!: string;\n  name!: string;\n}\n\nconst metadata: EntityMetadataMap = {\n  Hero: {},\n  Villain: { selectId: (villain) => villain.key },\n};\ndescribe('EntityCollectionReducerRegistry', () => {\n  let collectionCreator: EntityCollectionCreator;\n  let entityActionFactory: EntityActionFactory;\n  let entityCacheReducer: ActionReducer<EntityCache, Action>;\n  let entityCollectionReducerRegistry: EntityCollectionReducerRegistry;\n  let logger: Logger;\n\n  beforeEach(() => {\n    entityActionFactory = new EntityActionFactory();\n    const logger = {\n      error: vi.fn().mockName('error'),\n      log: vi.fn().mockName('log'),\n      warn: vi.fn().mockName('warn'),\n    };\n\n    TestBed.configureTestingModule({\n      providers: [\n        EntityCacheReducerFactory,\n        EntityCollectionCreator,\n        {\n          provide: EntityCollectionReducerMethodsFactory,\n          useClass: EntityCollectionReducerMethodsFactory,\n        },\n        EntityCollectionReducerFactory,\n        EntityCollectionReducerRegistry,\n        EntityDefinitionService,\n        { provide: ENTITY_METADATA_TOKEN, multi: true, useValue: metadata },\n        { provide: Logger, useValue: logger },\n      ],\n    });\n  });\n\n  /** Sets the test variables with injected values. Closes TestBed configuration. */\n  function setup() {\n    collectionCreator = TestBed.inject(EntityCollectionCreator);\n    const entityCacheReducerFactory = TestBed.inject(EntityCacheReducerFactory);\n    entityCacheReducer = entityCacheReducerFactory.create();\n    entityCollectionReducerRegistry = TestBed.inject(\n      EntityCollectionReducerRegistry\n    );\n  }\n\n  describe('#registerReducer', () => {\n    beforeEach(setup);\n\n    it('can register a new reducer', () => {\n      const reducer = createNoopReducer();\n      entityCollectionReducerRegistry.registerReducer('Foo', reducer);\n      const action = entityActionFactory.create<Foo>('Foo', EntityOp.ADD_ONE, {\n        id: 'forty-two',\n        foo: 'fooz',\n      });\n      // Must initialize the state by hand\n      const state = entityCacheReducer({}, action);\n      const collection = state['Foo'];\n      expect(collection.ids.length).toBe(0);\n    });\n\n    it('can replace existing reducer by registering with same name', () => {\n      // Just like ADD_ONE test above with default reducer\n      // but this time should not add the hero.\n      const hero: Hero = { id: 42, name: 'Bobby' };\n      const reducer = createNoopReducer();\n      entityCollectionReducerRegistry.registerReducer('Hero', reducer);\n      const action = entityActionFactory.create<Hero>(\n        'Hero',\n        EntityOp.ADD_ONE,\n        hero\n      );\n      const state = entityCacheReducer({}, action);\n      const collection = state['Hero'];\n      expect(collection.ids.length).toBe(0);\n    });\n  });\n\n  describe('#registerReducers', () => {\n    beforeEach(setup);\n\n    it('can register several reducers at the same time.', () => {\n      const reducer = createNoopReducer();\n      const reducers: EntityCollectionReducers = {\n        Foo: reducer,\n        Bar: reducer,\n      };\n      entityCollectionReducerRegistry.registerReducers(reducers);\n\n      const fooAction = entityActionFactory.create<Foo>(\n        'Foo',\n        EntityOp.ADD_ONE,\n        { id: 'forty-two', foo: 'fooz' }\n      );\n      const barAction = entityActionFactory.create<Bar>(\n        'Bar',\n        EntityOp.ADD_ONE,\n        { id: 84, bar: 'baz' }\n      );\n\n      let state = entityCacheReducer({}, fooAction);\n      state = entityCacheReducer(state, barAction);\n\n      expect(state['Foo'].ids.length).toBe(0);\n      expect(state['Bar'].ids.length).toBe(0);\n    });\n\n    it('can register several reducers that may override.', () => {\n      const reducer = createNoopReducer();\n      const reducers: EntityCollectionReducers = {\n        Foo: reducer,\n        Hero: reducer,\n      };\n      entityCollectionReducerRegistry.registerReducers(reducers);\n\n      const fooAction = entityActionFactory.create<Foo>(\n        'Foo',\n        EntityOp.ADD_ONE,\n        { id: 'forty-two', foo: 'fooz' }\n      );\n      const heroAction = entityActionFactory.create<Hero>(\n        'Hero',\n        EntityOp.ADD_ONE,\n        { id: 84, name: 'Alex' }\n      );\n\n      let state = entityCacheReducer({}, fooAction);\n      state = entityCacheReducer(state, heroAction);\n\n      expect(state['Foo'].ids.length).toBe(0);\n      expect(state['Hero'].ids.length).toBe(0);\n    });\n  });\n\n  describe('with EntityCollectionMetadataReducers', () => {\n    let metaReducerA: MetaReducer<EntityCollection, EntityAction>;\n    let metaReducerB: MetaReducer<EntityCollection, EntityAction>;\n    let metaReducerOutput: any[];\n\n    // Create MetaReducer that reports how it was called on the way in and out\n    function testMetadataReducerFactory(name: string) {\n      // Return the MetaReducer\n      return (r: ActionReducer<EntityCollection, EntityAction>) => {\n        // Return the wrapped reducer\n        return (state: EntityCollection, action: EntityAction) => {\n          // entered\n          metaReducerOutput.push({ metaReducer: name, inOut: 'in', action });\n          // called reducer\n          const newState = r(state, action);\n          // exited\n          metaReducerOutput.push({ metaReducer: name, inOut: 'out', action });\n          return newState;\n        };\n      };\n    }\n\n    let addOneAction: EntityAction<Hero>;\n    let hero: Hero;\n\n    beforeEach(() => {\n      metaReducerOutput = [];\n      metaReducerA = vi\n        .fn(testMetadataReducerFactory('A'))\n        .mockName('metaReducerA') as MetaReducer<\n        EntityCollection,\n        EntityAction\n      >;\n      metaReducerB = vi\n        .fn(testMetadataReducerFactory('B'))\n        .mockName('metaReducerB') as MetaReducer<\n        EntityCollection,\n        EntityAction\n      >;\n      const metaReducers = [metaReducerA, metaReducerB];\n\n      TestBed.configureTestingModule({\n        providers: [\n          EntityCacheReducerFactory,\n          EntityCollectionCreator,\n          {\n            provide: EntityCollectionReducerMethodsFactory,\n            useClass: EntityCollectionReducerMethodsFactory,\n          },\n          EntityCollectionReducerFactory,\n          EntityCollectionReducerRegistry,\n          EntityDefinitionService,\n          { provide: ENTITY_METADATA_TOKEN, multi: true, useValue: metadata },\n          { provide: ENTITY_COLLECTION_META_REDUCERS, useValue: metaReducers },\n          { provide: Logger, useValue: logger! },\n        ],\n      });\n\n      setup();\n\n      hero = { id: 42, name: 'Bobby' };\n      addOneAction = entityActionFactory.create<Hero>(\n        'Hero',\n        EntityOp.ADD_ONE,\n        hero\n      );\n    });\n\n    it('should run inner default reducer as expected', () => {\n      const state = entityCacheReducer({}, addOneAction);\n\n      // inner default reducer worked as expected\n      const collection = state['Hero'];\n      expect(collection.ids.length).toBe(1);\n      expect(collection.entities[42]).toEqual(hero);\n    });\n\n    it('should call meta reducers for inner default reducer as expected', () => {\n      const expected = [\n        { metaReducer: 'A', inOut: 'in', action: addOneAction },\n        { metaReducer: 'B', inOut: 'in', action: addOneAction },\n        { metaReducer: 'B', inOut: 'out', action: addOneAction },\n        { metaReducer: 'A', inOut: 'out', action: addOneAction },\n      ];\n\n      const state = entityCacheReducer({}, addOneAction);\n      expect(metaReducerA).toHaveBeenCalled();\n      expect(metaReducerB).toHaveBeenCalled();\n      expect(metaReducerOutput).toEqual(expected);\n    });\n\n    it('should call meta reducers for custom registered reducer', () => {\n      const reducer = createNoopReducer();\n      entityCollectionReducerRegistry.registerReducer('Foo', reducer);\n      const action = entityActionFactory.create<Foo>('Foo', EntityOp.ADD_ONE, {\n        id: 'forty-two',\n        foo: 'fooz',\n      });\n\n      const state = entityCacheReducer({}, action);\n      expect(metaReducerA).toHaveBeenCalled();\n      expect(metaReducerB).toHaveBeenCalled();\n    });\n\n    it('should call meta reducers for multiple registered reducers', () => {\n      const reducer = createNoopReducer();\n      const reducers: EntityCollectionReducers = {\n        Foo: reducer,\n        Hero: reducer,\n      };\n      entityCollectionReducerRegistry.registerReducers(reducers);\n\n      const fooAction = entityActionFactory.create<Foo>(\n        'Foo',\n        EntityOp.ADD_ONE,\n        { id: 'forty-two', foo: 'fooz' }\n      );\n\n      entityCacheReducer({}, fooAction);\n      expect(metaReducerA).toHaveBeenCalled();\n      expect(metaReducerB).toHaveBeenCalled();\n\n      const heroAction = entityActionFactory.create<Hero>(\n        'Hero',\n        EntityOp.ADD_ONE,\n        { id: 84, name: 'Alex' }\n      );\n\n      entityCacheReducer({}, heroAction);\n      expect(metaReducerA).toHaveBeenCalledTimes(2);\n      expect(metaReducerB).toHaveBeenCalledTimes(2);\n    });\n  });\n\n  // #region helpers\n  function createCollection<T = any>(\n    entityName: string,\n    data: T[],\n    selectId: IdSelector<any>\n  ) {\n    return {\n      ...collectionCreator.create<T>(entityName),\n      ids: data.map((e) => selectId(e)) as string[] | number[],\n      entities: data.reduce((acc, e) => {\n        acc[selectId(e)] = e;\n        return acc;\n      }, {} as any),\n    } as EntityCollection<T>;\n  }\n\n  function createInitialCache(entityMap: { [entityName: string]: any[] }) {\n    const cache: EntityCache = {};\n    // eslint-disable-next-line guard-for-in\n    for (const entityName in entityMap) {\n      const selectId =\n        metadata[entityName].selectId || ((entity: any) => entity.id);\n      cache[entityName] = createCollection(\n        entityName,\n        entityMap[entityName],\n        selectId\n      );\n    }\n\n    return cache;\n  }\n\n  function createNoopReducer<T>() {\n    return function NoopReducer(\n      collection: EntityCollection<T>,\n      action: EntityAction\n    ): EntityCollection<T> {\n      return collection;\n    };\n  }\n  // #endregion helpers\n});\n"
  },
  {
    "path": "modules/data/spec/reducers/entity-collection-reducer.spec.ts",
    "content": "// EntityCollectionReducer tests - tests of reducers for entity collections in the entity cache\n// Tests for EntityCache-level reducers (e.g., SET_ENTITY_CACHE) are in `entity-cache-reducer.spec.ts`\nimport { Action } from '@ngrx/store';\nimport { EntityAdapter, Update, IdSelector } from '@ngrx/entity';\n\nimport {\n  EntityMetadataMap,\n  EntityActionFactory,\n  EntityOp,\n  EntityActionOptions,\n  EntityAction,\n  toUpdateFactory,\n  EntityCollectionReducerRegistry,\n  EntityCache,\n  EntityCollectionCreator,\n  EntityDefinitionService,\n  EntityCollectionReducerMethodsFactory,\n  EntityCollectionReducerFactory,\n  EntityCacheReducerFactory,\n  EntityCollection,\n  ChangeStateMap,\n  EntityActionDataServiceError,\n  DataServiceError,\n  ChangeType,\n  ChangeState,\n  Logger,\n} from '../../';\nimport { vi } from 'vitest';\n\nclass Foo {\n  id!: string;\n  foo!: string;\n}\nclass Hero {\n  id!: number;\n  name!: string;\n  power?: string;\n}\nclass Villain {\n  key!: string;\n  name!: string;\n}\n\nconst metadata: EntityMetadataMap = {\n  Hero: {},\n  Villain: { selectId: (villain) => villain.key },\n};\n\ndescribe('EntityCollectionReducer', () => {\n  // action factory never changes in these tests\n  const entityActionFactory = new EntityActionFactory();\n  const createAction: (\n    entityName: string,\n    op: EntityOp,\n    data?: any,\n    options?: EntityActionOptions\n  ) => EntityAction = entityActionFactory.create.bind(entityActionFactory);\n\n  const toHeroUpdate = toUpdateFactory<Hero>();\n\n  let entityReducerRegistry: EntityCollectionReducerRegistry;\n  let entityReducer: (state: EntityCache, action: Action) => EntityCache;\n\n  let initialHeroes: Hero[];\n  let initialCache: EntityCache;\n  let logger: Logger;\n  let collectionCreator: EntityCollectionCreator;\n\n  beforeEach(() => {\n    const eds = new EntityDefinitionService([metadata]);\n    collectionCreator = new EntityCollectionCreator(eds);\n    const collectionReducerMethodsFactory =\n      new EntityCollectionReducerMethodsFactory(eds);\n    const collectionReducerFactory = new EntityCollectionReducerFactory(\n      collectionReducerMethodsFactory\n    );\n    logger = {\n      error: vi.fn().mockName('error'),\n      log: vi.fn().mockName('log'),\n      warn: vi.fn().mockName('warn'),\n    };\n\n    entityReducerRegistry = new EntityCollectionReducerRegistry(\n      collectionReducerFactory\n    );\n    const entityCacheReducerFactory = new EntityCacheReducerFactory(\n      collectionCreator,\n      entityReducerRegistry,\n      logger\n    );\n    entityReducer = entityCacheReducerFactory.create();\n\n    initialHeroes = [\n      { id: 2, name: 'B', power: 'Fast' },\n      { id: 1, name: 'A', power: 'Invisible' },\n    ];\n    initialCache = createInitialCache({ Hero: initialHeroes });\n  });\n\n  it('should ignore an action without an EntityOp', () => {\n    // should not throw\n    const action = {\n      type: 'does-not-matter',\n      payload: {\n        entityName: 'Hero',\n        entityOp: undefined,\n      },\n    };\n    const newCache = entityReducer(initialCache, action);\n    expect(newCache).toBe(initialCache);\n  });\n\n  // #region queries\n  describe('QUERY_ALL', () => {\n    const queryAction = createAction('Hero', EntityOp.QUERY_ALL);\n\n    it('QUERY_ALL sets loading flag but does not fill collection', () => {\n      const state = entityReducer({}, queryAction);\n      const collection = state['Hero'];\n      expect(collection.ids.length).toBe(0);\n      expect(collection.loaded).toBe(false);\n      expect(collection.loading).toBe(true);\n    });\n\n    it('QUERY_ALL_SUCCESS can create the initial collection', () => {\n      let state = entityReducer({}, queryAction);\n      const heroes: Hero[] = [\n        { id: 2, name: 'B' },\n        { id: 1, name: 'A' },\n      ];\n      const action = createAction('Hero', EntityOp.QUERY_ALL_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities['1']).toBe(heroes[1]);\n      expect(collection.entities['2']).toBe(heroes[0]);\n    });\n\n    it('QUERY_ALL_SUCCESS sets the loaded flag and clears loading flag', () => {\n      let state = entityReducer({}, queryAction);\n      const heroes: Hero[] = [\n        { id: 2, name: 'B' },\n        { id: 1, name: 'A' },\n      ];\n      const action = createAction('Hero', EntityOp.QUERY_ALL_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n      expect(collection.loaded).toBe(true);\n      expect(collection.loading).toBe(false);\n    });\n\n    it('QUERY_ALL_ERROR clears loading flag and does not fill collection', () => {\n      let state = entityReducer({}, queryAction);\n      const action = createAction('Hero', EntityOp.QUERY_ALL_ERROR);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n      expect(collection.loading).toBe(false);\n      expect(collection.loaded).toBe(false);\n      expect(collection.ids.length).toBe(0);\n    });\n\n    it('QUERY_ALL_SUCCESS works for \"Villain\" entity with non-id primary key', () => {\n      let state = entityReducer({}, queryAction);\n      const villains: Villain[] = [\n        { key: '2', name: 'B' },\n        { key: '1', name: 'A' },\n      ];\n      const action = createAction(\n        'Villain',\n        EntityOp.QUERY_ALL_SUCCESS,\n        villains\n      );\n      state = entityReducer(state, action);\n      const collection = state['Villain'];\n      expect(collection.ids).toEqual(['2', '1']);\n      expect(collection.entities['1']).toBe(villains[1]);\n      expect(collection.entities['2']).toBe(villains[0]);\n      expect(collection.loaded).toBe(true);\n      expect(collection.loading).toBe(false);\n    });\n\n    it('QUERY_ALL_SUCCESS can add to existing collection', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const heroes: Hero[] = [{ id: 3, name: 'C' }];\n      const action = createAction('Hero', EntityOp.QUERY_ALL_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 3]);\n    });\n\n    it('QUERY_ALL_SUCCESS can update existing collection', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const heroes: Hero[] = [{ id: 1, name: 'A+' }];\n      const action = createAction('Hero', EntityOp.QUERY_ALL_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities['1'].name).toBe('A+');\n    });\n\n    it('QUERY_ALL_SUCCESS can add and update existing collection', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const heroes: Hero[] = [\n        { id: 3, name: 'C' },\n        { id: 1, name: 'A+' },\n      ];\n      const action = createAction('Hero', EntityOp.QUERY_ALL_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 3]);\n      expect(collection.entities['1'].name).toBe('A+');\n    });\n\n    it('QUERY_ALL_SUCCESS overwrites changeState.originalValue for updated entity', () => {\n      const { entityCache, preUpdatedEntity, updatedEntity } =\n        createTestTrackedEntities();\n      const queriedUpdate = { ...updatedEntity, name: 'Queried update' };\n\n      // a new entity and yet another version of the entity that is currently updated but not saved.\n      const queryResults: Hero[] = [{ id: 100, name: 'X' }, queriedUpdate];\n      const action = createAction(\n        'Hero',\n        EntityOp.QUERY_ALL_SUCCESS,\n        queryResults\n      );\n      const collection = entityReducer(entityCache, action)['Hero'];\n      const originalValue =\n        collection.changeState[updatedEntity.id]!.originalValue;\n\n      expect(collection.entities[updatedEntity.id]).toEqual(updatedEntity);\n      expect(originalValue).toBeDefined();\n      expect(originalValue).not.toEqual(preUpdatedEntity);\n      expect(originalValue).not.toEqual(updatedEntity);\n      expect(originalValue).toEqual(queriedUpdate);\n    });\n\n    it('QUERY_ALL_SUCCESS works when the query results are empty', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const action = createAction('Hero', EntityOp.QUERY_ALL_SUCCESS, []);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.entities).toBe(initialCache['Hero'].entities);\n      expect(collection.ids).toBe(initialCache['Hero'].ids);\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection).not.toBe(initialCache['Hero']);\n    });\n  });\n\n  describe('QUERY_BY_KEY', () => {\n    const queryAction = createAction('Hero', EntityOp.QUERY_BY_KEY);\n\n    it('QUERY_BY_KEY sets loading flag but does not touch the collection', () => {\n      const state = entityReducer({}, queryAction);\n      const collection = state['Hero'];\n      expect(collection.ids.length).toBe(0);\n      expect(collection.loaded).toBe(false);\n      expect(collection.loading).toBe(true);\n    });\n\n    it('QUERY_BY_KEY_SUCCESS can create the initial collection', () => {\n      let state = entityReducer({}, queryAction);\n      const hero: Hero = { id: 3, name: 'C' };\n      const action = createAction('Hero', EntityOp.QUERY_BY_KEY_SUCCESS, hero);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([3]);\n      expect(collection.loaded).toBe(false);\n      expect(collection.loading).toBe(false);\n    });\n\n    it('QUERY_BY_KEY_SUCCESS can add to existing collection', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const hero: Hero = { id: 3, name: 'C' };\n      const action = createAction('Hero', EntityOp.QUERY_BY_KEY_SUCCESS, hero);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 3]);\n    });\n\n    it('QUERY_BY_KEY_SUCCESS can update existing collection', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const hero: Hero = { id: 1, name: 'A+' };\n      const action = createAction('Hero', EntityOp.QUERY_BY_KEY_SUCCESS, hero);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities['1'].name).toBe('A+');\n    });\n\n    it('QUERY_BY_KEY_SUCCESS updates the originalValue of a pending update', () => {\n      const { entityCache, preUpdatedEntity, updatedEntity } =\n        createTestTrackedEntities();\n      const queriedUpdate = { ...updatedEntity, name: 'Queried update' };\n      const action = createAction(\n        'Hero',\n        EntityOp.QUERY_BY_KEY_SUCCESS,\n        queriedUpdate\n      );\n      const collection = entityReducer(entityCache, action)['Hero'];\n      const originalValue =\n        collection.changeState[updatedEntity.id]!.originalValue;\n\n      expect(collection.entities[updatedEntity.id]).toEqual(updatedEntity);\n      expect(originalValue).toBeDefined();\n      expect(originalValue).not.toEqual(preUpdatedEntity);\n      expect(originalValue).not.toEqual(updatedEntity);\n      expect(originalValue).toEqual(queriedUpdate);\n    });\n\n    // Normally would 404 but maybe this API just returns an empty result.\n    it('QUERY_BY_KEY_SUCCESS works when the query results are empty', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const action = createAction(\n        'Hero',\n        EntityOp.QUERY_BY_KEY_SUCCESS,\n        undefined\n      );\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.entities).toBe(initialCache['Hero'].entities);\n      expect(collection.ids).toBe(initialCache['Hero'].ids);\n      expect(collection.ids).toEqual([2, 1]);\n    });\n  });\n\n  describe('QUERY_MANY', () => {\n    const queryAction = createAction('Hero', EntityOp.QUERY_MANY);\n\n    it('QUERY_MANY sets loading flag but does not touch the collection', () => {\n      const state = entityReducer({}, queryAction);\n      const collection = state['Hero'];\n      expect(collection.loaded).toBe(false);\n      expect(collection.loading).toBe(true);\n      expect(collection.ids.length).toBe(0);\n    });\n\n    it('QUERY_MANY_SUCCESS can create the initial collection', () => {\n      let state = entityReducer({}, queryAction);\n      const heroes: Hero[] = [{ id: 3, name: 'C' }];\n      const action = createAction('Hero', EntityOp.QUERY_MANY_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([3]);\n      expect(collection.loaded).toBe(true);\n      expect(collection.loading).toBe(false);\n    });\n\n    it('QUERY_MANY_SUCCESS can add to existing collection', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const heroes: Hero[] = [{ id: 3, name: 'C' }];\n      const action = createAction('Hero', EntityOp.QUERY_MANY_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 3]);\n    });\n\n    it('QUERY_MANY_SUCCESS can update existing collection', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const heroes: Hero[] = [{ id: 1, name: 'A+' }];\n      const action = createAction('Hero', EntityOp.QUERY_MANY_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities['1'].name).toBe('A+');\n    });\n\n    it('QUERY_MANY_SUCCESS can add and update existing collection', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const heroes: Hero[] = [\n        { id: 3, name: 'C' },\n        { id: 1, name: 'A+' },\n      ];\n      const action = createAction('Hero', EntityOp.QUERY_MANY_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 3]);\n      expect(collection.entities['1'].name).toBe('A+');\n    });\n\n    it('QUERY_MANY_SUCCESS overwrites changeState.originalValue for updated entity', () => {\n      const { entityCache, preUpdatedEntity, updatedEntity } =\n        createTestTrackedEntities();\n      const queriedUpdate = { ...updatedEntity, name: 'Queried update' };\n\n      // a new entity and yet another version of the entity that is currently updated but not saved.\n      const queryResults: Hero[] = [{ id: 100, name: 'X' }, queriedUpdate];\n      const action = createAction(\n        'Hero',\n        EntityOp.QUERY_MANY_SUCCESS,\n        queryResults\n      );\n      const collection = entityReducer(entityCache, action)['Hero'];\n      const originalValue =\n        collection.changeState[updatedEntity.id]!.originalValue;\n\n      expect(collection.entities[updatedEntity.id]).toEqual(updatedEntity);\n      expect(originalValue).toBeDefined();\n      expect(originalValue).not.toEqual(preUpdatedEntity);\n      expect(originalValue).not.toEqual(updatedEntity);\n      expect(originalValue).toEqual(queriedUpdate);\n    });\n\n    it('QUERY_MANY_SUCCESS works when the query results are empty', () => {\n      let state = entityReducer(initialCache, queryAction);\n      const action = createAction('Hero', EntityOp.QUERY_MANY_SUCCESS, []);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n\n      expect(collection.entities).toBe(initialCache['Hero'].entities);\n      expect(collection.ids).toBe(initialCache['Hero'].ids);\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection).not.toBe(initialCache['Hero']);\n    });\n  });\n\n  describe('CANCEL_PERSIST', () => {\n    it('should only clear the loading flag', () => {\n      const { entityCache } = createTestTrackedEntities();\n      let cache = entityReducer(\n        entityCache,\n        createAction('Hero', EntityOp.SET_LOADING, true)\n      );\n      expect(cache['Hero'].loading).toBe(true);\n      cache = entityReducer(\n        cache,\n        createAction('Hero', EntityOp.CANCEL_PERSIST, undefined, {\n          correlationId: 42,\n        })\n      );\n      expect(cache['Hero'].loading).toBe(false);\n      expect(cache).toEqual(entityCache);\n    });\n  });\n\n  describe('QUERY_LOAD', () => {\n    const queryAction = createAction('Hero', EntityOp.QUERY_LOAD);\n\n    it('QUERY_LOAD sets loading flag but does not fill collection', () => {\n      const state = entityReducer({}, queryAction);\n      const collection = state['Hero'];\n      expect(collection.ids.length).toBe(0);\n      expect(collection.loaded).toBe(false);\n      expect(collection.loading).toBe(true);\n    });\n\n    it('QUERY_LOAD_SUCCESS fills collection, clears loading flag, and sets loaded flag', () => {\n      let state = entityReducer({}, queryAction);\n      const heroes: Hero[] = [\n        { id: 2, name: 'B' },\n        { id: 1, name: 'A' },\n      ];\n      const action = createAction('Hero', EntityOp.QUERY_LOAD_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities['1']).toBe(heroes[1]);\n      expect(collection.entities['2']).toBe(heroes[0]);\n      expect(collection.loaded).toBe(true);\n      expect(collection.loading).toBe(false);\n    });\n\n    it('QUERY_LOAD_SUCCESS clears changeState', () => {\n      const { entityCache, preUpdatedEntity, updatedEntity } =\n        createTestTrackedEntities();\n\n      // Completely replaces existing Hero entities\n      const heroes: Hero[] = [\n        { id: 1000, name: 'X' },\n        { ...updatedEntity, name: 'Queried update' },\n      ];\n      const action = createAction('Hero', EntityOp.QUERY_LOAD_SUCCESS, heroes);\n      const collection: EntityCollection<Hero> = entityReducer(\n        entityCache,\n        action\n      )['Hero'];\n      const { ids, changeState } = collection;\n      expect(changeState).toEqual({} as ChangeStateMap<Hero>);\n      expect(ids).toEqual([1000, updatedEntity.id]); // no sort so in load order\n    });\n\n    it('QUERY_LOAD_SUCCESS replaces collection contents with queried entities', () => {\n      let state: EntityCache = {\n        Hero: {\n          entityName: 'Hero',\n          ids: [42],\n          entities: { 42: { id: 42, name: 'Fribit' } },\n          filter: 'xxx',\n          loaded: true,\n          loading: false,\n          changeState: {},\n        },\n      };\n      state = entityReducer(state, queryAction);\n      const heroes: Hero[] = [\n        { id: 2, name: 'B' },\n        { id: 1, name: 'A' },\n      ];\n      const action = createAction('Hero', EntityOp.QUERY_LOAD_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities['1']).toBe(heroes[1]);\n      expect(collection.entities['2']).toBe(heroes[0]);\n    });\n\n    it('QUERY_LOAD_ERROR clears loading flag and does not fill collection', () => {\n      let state = entityReducer({}, queryAction);\n      const action = createAction('Hero', EntityOp.QUERY_LOAD_ERROR);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n      expect(collection.loading).toBe(false);\n      expect(collection.loaded).toBe(false);\n      expect(collection.ids.length).toBe(0);\n    });\n\n    it('QUERY_LOAD_SUCCESS works for \"Villain\" entity with non-id primary key', () => {\n      let state = entityReducer({}, queryAction);\n      const villains: Villain[] = [\n        { key: '2', name: 'B' },\n        { key: '1', name: 'A' },\n      ];\n      const action = createAction(\n        'Villain',\n        EntityOp.QUERY_LOAD_SUCCESS,\n        villains\n      );\n      state = entityReducer(state, action);\n      const collection = state['Villain'];\n      expect(collection.ids).toEqual(['2', '1']);\n      expect(collection.entities['1']).toBe(villains[1]);\n      expect(collection.entities['2']).toBe(villains[0]);\n      expect(collection.loaded).toBe(true);\n      expect(collection.loading).toBe(false);\n    });\n  });\n  // #endregion queries\n\n  // #region saves\n  describe('SAVE_ADD_ONE (Optimistic)', () => {\n    function createTestAction(hero: Hero) {\n      return createAction('Hero', EntityOp.SAVE_ADD_ONE, hero, {\n        isOptimistic: true,\n      });\n    }\n\n    it('should add a new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const hero = { name: 'New One', power: 'Strong' };\n      // bad add, no id.\n      const action = createTestAction(<any>hero);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /missing or invalid entity key/\n      );\n    });\n\n    it('should NOT update an existing entity in collection', () => {\n      const hero: Hero = { id: 2, name: 'B+' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n  });\n\n  describe('SAVE_ADD_ONE (Pessimistic)', () => {\n    it('should only set the loading flag', () => {\n      const addedEntity = { id: 42, name: 'New Guy' };\n      const action = createAction('Hero', EntityOp.SAVE_ADD_ONE, addedEntity);\n      expectOnlySetLoadingFlag(action, initialCache);\n    });\n  });\n\n  describe('SAVE_ADD_ONE_SUCCESS (Optimistic)', () => {\n    function createTestAction(hero: Hero) {\n      return createAction('Hero', EntityOp.SAVE_ADD_ONE_SUCCESS, hero, {\n        isOptimistic: true,\n      });\n    }\n\n    // server returned a hero with different id; not good\n    it('should NOT add a new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n    });\n\n    // The hero was already added to the collection by SAVE_ADD_ONE\n    // You cannot change the key with SAVE_ADD_ONE_SUCCESS\n    // You'd have to do it with SAVE_UPDATE_ONE...\n    it('should NOT change the id of a newly added hero', () => {\n      // pretend this hero was added by SAVE_ADD_ONE and returned by server with new ID\n      const hero = initialHeroes[0];\n      hero.id = 13;\n\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const hero = { name: 'New One', power: 'Strong' };\n      // bad add, no id.\n      const action = createTestAction(<any>hero);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /missing or invalid entity key/\n      );\n    });\n\n    // because the hero was already added to the collection by SAVE_ADD_ONE\n    // should update values (but not id) if the server changed them\n    // as it might with a concurrency property.\n    it('should update an existing entity with that ID in collection', () => {\n      // This example simulates the server updating the name and power\n      const hero: Hero = { id: 2, name: 'Updated Name', power: 'Test Power' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('Updated Name');\n      // unmentioned property updated too\n      expect(collection.entities[2].power).toBe('Test Power');\n    });\n  });\n\n  describe('SAVE_ADD_ONE_SUCCESS (Pessimistic)', () => {\n    function createTestAction(hero: Hero) {\n      return createAction('Hero', EntityOp.SAVE_ADD_ONE_SUCCESS, hero);\n    }\n\n    it('should add a new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(hero);\n      const collection = entityReducer(initialCache, action)['Hero'];\n      expect(collection.ids).toEqual([2, 1, 13]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const hero = { name: 'New One', power: 'Strong' };\n      // bad add, no id.\n      const action = createTestAction(<any>hero);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /missing or invalid entity key/\n      );\n    });\n\n    it('should update an existing entity in collection', () => {\n      // ... because reducer calls mergeServerUpserts()\n      const hero: Hero = { id: 2, name: 'B+' };\n      const action = createTestAction(hero);\n      const collection = entityReducer(initialCache, action)['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B+');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n  });\n\n  describe('SAVE_ADD_ONE_ERROR', () => {\n    it('should only clear the loading flag', () => {\n      const { entityCache, addedEntity } = createTestTrackedEntities();\n      const originalAction = createAction(\n        'Hero',\n        EntityOp.SAVE_ADD_ONE,\n        addedEntity\n      );\n      const error: EntityActionDataServiceError = {\n        error: new DataServiceError(new Error('Test Error'), {\n          method: 'POST',\n          url: 'foo',\n        }),\n        originalAction,\n      };\n      const action = createAction('Hero', EntityOp.SAVE_ADD_MANY_ERROR, error);\n      expectOnlySetLoadingFlag(action, entityCache);\n    });\n  });\n\n  describe('SAVE_ADD_MANY (Optimistic)', () => {\n    function createTestAction(heroes: Hero[]) {\n      return createAction('Hero', EntityOp.SAVE_ADD_MANY, heroes, {\n        isOptimistic: true,\n      });\n    }\n\n    it('should add new heroes to collection', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n    });\n\n    it('should error if one of new heroes lacks its pkey', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: undefined as any, name: 'New B', power: 'Swift' }, // missing its id\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /does not have a valid entity key/\n      );\n    });\n\n    it('should NOT update an existing entity in collection', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 2, name: 'B+' },\n        { id: 14, name: 'New B', power: 'Swift' }, // missing its id\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n      expect(collection.entities[2].name).toBe('B');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n  });\n\n  describe('SAVE_ADD_MANY (Pessimistic)', () => {\n    it('should only set the loading flag', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createAction('Hero', EntityOp.SAVE_ADD_MANY, heroes);\n      expectOnlySetLoadingFlag(action, initialCache);\n    });\n  });\n\n  describe('SAVE_ADD_MANY_SUCCESS (Optimistic)', () => {\n    function createTestAction(heroes: Hero[]) {\n      return createAction('Hero', EntityOp.SAVE_ADD_MANY_SUCCESS, heroes, {\n        isOptimistic: true,\n      });\n    }\n\n    // Server returned heroes with ids that are new and were not sent to the server.\n    // This could be correct or it could be bad (e.g. server changed the id of a new entity)\n    // Regardless, SAVE_ADD_MANY_SUCCESS (optimistic) will add them because it upserts.\n    it('should add heroes that were not previously in the collection', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const heroes: Hero[] = [\n        { id: undefined as any, name: 'New A', power: 'Strong' }, // missing its id\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /does not have a valid entity key/\n      );\n    });\n\n    // because the hero was already added to the collection by SAVE_ADD_MANY\n    // should update values (but not id) if the server changed them\n    // as it might with a concurrency property.\n    it('should update an existing entity with that ID in collection', () => {\n      // This example simulates the server updating the name and power\n      const heroes: Hero[] = [\n        { id: 1, name: 'Updated name A', power: 'Test Power A' },\n        { id: 2, name: 'Updated name B', power: 'Test Power B' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[1].name).toBe('Updated name A');\n      expect(collection.entities[2].name).toBe('Updated name B');\n      // unmentioned property updated too\n      expect(collection.entities[1].power).toBe('Test Power A');\n      expect(collection.entities[2].power).toBe('Test Power B');\n    });\n  });\n\n  describe('SAVE_ADD_MANY_SUCCESS (Pessimistic)', () => {\n    function createTestAction(heroes: Hero[]) {\n      return createAction('Hero', EntityOp.SAVE_ADD_MANY_SUCCESS, heroes, {\n        isOptimistic: false,\n      });\n    }\n\n    it('should add new heroes to collection', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const collection = entityReducer(initialCache, action)['Hero'];\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const heroes: Hero[] = [\n        { id: undefined as any, name: 'New A', power: 'Strong' }, // missing id\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload!.error!.message).toMatch(\n        /does not have a valid entity key/\n      );\n    });\n\n    it('should update an existing entity in collection', () => {\n      // This example simulates the server updating the name and power\n      const heroes: Hero[] = [\n        { id: 1, name: 'Updated name A' },\n        { id: 2, name: 'Updated name B' },\n      ];\n      const action = createTestAction(heroes);\n      const collection = entityReducer(initialCache, action)['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[1].name).toBe('Updated name A');\n      expect(collection.entities[2].name).toBe('Updated name B');\n      // unmentioned property stays the same\n      expect(collection.entities[1].power).toBe(initialHeroes[1].power);\n      expect(collection.entities[2].power).toBe(initialHeroes[0].power);\n    });\n  });\n\n  describe('SAVE_ADD_MANY_ERROR', () => {\n    it('should only clear the loading flag', () => {\n      const { entityCache, addedEntity } = createTestTrackedEntities();\n      const originalAction = createAction('Hero', EntityOp.SAVE_ADD_MANY, [\n        addedEntity,\n      ]);\n      const error: EntityActionDataServiceError = {\n        error: new DataServiceError(new Error('Test Error'), {\n          method: 'POST',\n          url: 'foo',\n        }),\n        originalAction,\n      };\n      const action = createAction('Hero', EntityOp.SAVE_ADD_MANY_ERROR, error);\n      expectOnlySetLoadingFlag(action, entityCache);\n    });\n  });\n\n  describe('SAVE_DELETE_ONE (Optimistic)', () => {\n    it('should immediately remove the existing hero', () => {\n      const hero = initialHeroes[0];\n      expect(initialCache['Hero'].entities[hero.id]).toBe(hero);\n\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, hero, {\n        isOptimistic: true,\n      });\n\n      const collection = entityReducer(initialCache, action)['Hero'];\n      expect(collection.entities[hero.id]).toBeUndefined();\n      expect(collection.loading).toBe(true);\n    });\n\n    it('should immediately remove the hero by id ', () => {\n      const hero = initialHeroes[0];\n      expect(initialCache['Hero'].entities[hero.id]).toBe(hero);\n\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, hero.id, {\n        isOptimistic: true,\n      });\n\n      const collection = entityReducer(initialCache, action)['Hero'];\n      expect(collection.entities[hero.id]).toBeUndefined();\n      expect(collection.loading).toBe(true);\n    });\n\n    it('should immediately remove an unsaved added hero', () => {\n      const { entityCache, addedEntity } = createTestTrackedEntities();\n      const id = addedEntity.id;\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, id, {\n        isOptimistic: true,\n      });\n      const { entities, changeState } = entityReducer(entityCache, action)[\n        'Hero'\n      ];\n      expect(entities[id]).toBeUndefined();\n      expect(changeState[id]).toBeUndefined();\n      expect(action.payload.skip).toBe(true);\n    });\n\n    it('should reclassify change of an unsaved updated hero to \"deleted\"', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const id = updatedEntity.id;\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, id, {\n        isOptimistic: true,\n      });\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      expect(collection.entities[id]).toBeUndefined();\n      const entityChangeState = collection.changeState[id];\n      expect(entityChangeState).toBeDefined();\n      expect(entityChangeState!.changeType).toBe(ChangeType.Deleted);\n    });\n\n    it('should be ok when the id is not in the collection', () => {\n      expect(initialCache['Hero'].entities[1000]).toBeUndefined();\n\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE,\n        1000, // id of entity that is not in the collection\n        { isOptimistic: true }\n      );\n\n      const collection = entityReducer(initialCache, action)['Hero'];\n      expect(collection.entities[1000]).toBeUndefined();\n      expect(collection.loading).toBe(true);\n    });\n  });\n\n  describe('SAVE_DELETE_ONE (Pessimistic)', () => {\n    it('should NOT remove the existing hero', () => {\n      const hero = initialHeroes[0];\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, hero);\n\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n      expect(collection.entities[hero.id]).toBe(hero);\n      expect(collection.loading).toBe(true);\n    });\n\n    it('should immediately remove an unsaved added hero', () => {\n      const { entityCache, addedEntity } = createTestTrackedEntities();\n      const id = addedEntity.id;\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, id);\n      const { entities, changeState } = entityReducer(entityCache, action)[\n        'Hero'\n      ];\n      expect(entities[id]).toBeUndefined();\n      expect(changeState[id]).toBeUndefined();\n      expect(action.payload.skip).toBe(true);\n    });\n\n    it('should reclassify change of an unsaved updated hero to \"deleted\"', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const id = updatedEntity.id;\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, id);\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      expect(collection.entities[id]).toBeDefined();\n      const entityChangeState = collection.changeState[id];\n      expect(entityChangeState).toBeDefined();\n      expect(entityChangeState!.changeType).toBe(ChangeType.Deleted);\n    });\n  });\n\n  describe('SAVE_DELETE_ONE_SUCCESS (Optimistic)', () => {\n    it('should turn loading flag off and clear change tracking for existing entity', () => {\n      const { entityCache, removedEntity } = createTestTrackedEntities();\n\n      // the action that would have saved the delete\n      const saveAction = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE,\n        removedEntity.id,\n        { isOptimistic: true }\n      );\n\n      const { entities: initialEntities, changeState: initialChangeState } =\n        entityCache['Hero'];\n      expect(initialChangeState[removedEntity.id]).toBeDefined();\n\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE_SUCCESS,\n        removedEntity.id, // Pretend optimistically deleted this hero\n        { isOptimistic: true }\n      );\n\n      const collection = entityReducer(entityCache, action)['Hero'];\n      expect(collection.entities).toBe(initialEntities);\n      expect(collection.loading).toBe(false);\n      expect(collection.changeState[removedEntity.id]).toBeUndefined();\n    });\n\n    it('should be ok when the id is not in the collection', () => {\n      expect(initialCache['Hero'].entities[1000]).toBeUndefined();\n\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE_SUCCESS,\n        1000, // id of entity that is not in the collection\n        { isOptimistic: true }\n      );\n\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.entities[1000]).toBeUndefined();\n      expect(collection.loading).toBe(false);\n    });\n  });\n\n  describe('SAVE_DELETE_ONE_SUCCESS (Pessimistic)', () => {\n    it('should remove the hero by id', () => {\n      const hero = initialHeroes[0];\n      expect(initialCache['Hero'].entities[hero.id]).toBe(hero);\n\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE_SUCCESS,\n        hero.id\n      );\n\n      const collection = entityReducer(initialCache, action)['Hero'];\n\n      expect(collection.entities[hero.id]).toBeUndefined();\n      expect(collection.loading).toBe(false);\n    });\n\n    it('should be ok when the id is not in the collection', () => {\n      expect(initialCache['Hero'].entities[1000]).toBeUndefined();\n\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE_SUCCESS,\n        1000\n      );\n\n      const collection = entityReducer(initialCache, action)['Hero'];\n\n      expect(collection.entities[1000]).toBeUndefined();\n      expect(collection.loading).toBe(false);\n    });\n  });\n\n  describe('SAVE_DELETE_ONE_ERROR', () => {\n    it('should only clear the loading flag', () => {\n      const { entityCache, removedEntity } = createTestTrackedEntities();\n      const originalAction = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE,\n        removedEntity.id\n      );\n      const error: EntityActionDataServiceError = {\n        error: new DataServiceError(new Error('Test Error'), {\n          method: 'DELETE',\n          url: 'foo',\n        }),\n        originalAction,\n      };\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE_ERROR,\n        error\n      );\n      expectOnlySetLoadingFlag(action, entityCache);\n    });\n\n    // No compensating action on error (yet)\n    it('should NOT restore the hero after optimistic save', () => {\n      const initialEntities = initialCache['Hero'].entities;\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_ONE_ERROR,\n        { id: 13, name: 'Deleted' }, // Pretend optimistically deleted this hero\n        { isOptimistic: true }\n      );\n\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n      expect(collection.entities).toBe(initialEntities);\n      expect(collection.loading).toBe(false);\n    });\n\n    it('should NOT remove the hero', () => {\n      const hero = initialHeroes[0];\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE_ERROR, hero);\n\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n      expect(collection.entities[hero.id]).toBe(hero);\n      expect(collection.loading).toBe(false);\n    });\n  });\n\n  describe('SAVE_DELETE_MANY (Optimistic)', () => {\n    it('should immediately remove the heroes by id ', () => {\n      const ids = initialHeroes.map((h) => h.id);\n      expect(initialCache['Hero'].entities[ids[0]]).toBe(initialHeroes[0]);\n      expect(initialCache['Hero'].entities[ids[1]]).toBe(initialHeroes[1]);\n\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_MANY, ids, {\n        isOptimistic: true,\n      });\n\n      const collection = entityReducer(initialCache, action)['Hero'];\n      expect(collection.entities[ids[0]]).toBeUndefined();\n      expect(collection.entities[ids[1]]).toBeUndefined();\n      expect(collection.loading).toBe(true);\n    });\n\n    it('should immediately remove an unsaved added hero', () => {\n      const { entityCache, addedEntity } = createTestTrackedEntities();\n      const id = addedEntity.id;\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_MANY, [id], {\n        isOptimistic: true,\n      });\n      const { entities, changeState } = entityReducer(entityCache, action)[\n        'Hero'\n      ];\n      expect(entities[id]).toBeUndefined();\n      expect(changeState[id]).toBeUndefined();\n    });\n\n    it('should reclassify change of an unsaved updated hero to \"deleted\"', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const id = updatedEntity.id;\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_MANY, [id], {\n        isOptimistic: true,\n      });\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      expect(collection.entities[id]).toBeUndefined();\n      const entityChangeState = collection.changeState[id];\n      expect(entityChangeState).toBeDefined();\n      expect(entityChangeState!.changeType).toBe(ChangeType.Deleted);\n    });\n\n    it('should be ok when the id is not in the collection', () => {\n      expect(initialCache['Hero'].entities[1000]).toBeUndefined();\n\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_MANY,\n        [1000], // id of entity that is not in the collection\n        { isOptimistic: true }\n      );\n\n      const collection = entityReducer(initialCache, action)['Hero'];\n      expect(collection.entities[1000]).toBeUndefined();\n      expect(collection.loading).toBe(true);\n    });\n  });\n\n  describe('SAVE_DELETE_MANY (Pessimistic)', () => {\n    it('should NOT remove the existing hero', () => {\n      const hero = initialHeroes[0];\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, hero);\n\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n      expect(collection.entities[hero.id]).toBe(hero);\n      expect(collection.loading).toBe(true);\n    });\n\n    it('should immediately remove an unsaved added hero', () => {\n      const { entityCache, addedEntity } = createTestTrackedEntities();\n      const id = addedEntity.id;\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, id);\n      const { entities, changeState } = entityReducer(entityCache, action)[\n        'Hero'\n      ];\n      expect(entities[id]).toBeUndefined();\n      expect(changeState[id]).toBeUndefined();\n      expect(action.payload.skip).toBe(true);\n    });\n\n    it('should reclassify change of an unsaved updated hero to \"deleted\"', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const id = updatedEntity.id;\n      const action = createAction('Hero', EntityOp.SAVE_DELETE_ONE, id);\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      expect(collection.entities[id]).toBeDefined();\n      const entityChangeState = collection.changeState[id];\n      expect(entityChangeState).toBeDefined();\n      expect(entityChangeState!.changeType).toBe(ChangeType.Deleted);\n    });\n  });\n\n  describe('SAVE_DELETE_MANY_SUCCESS (Optimistic)', () => {\n    it('should turn loading flag off and clear change tracking for existing entities', () => {\n      const { entityCache, removedEntity, updatedEntity } =\n        createTestTrackedEntities();\n      const ids = [removedEntity.id, updatedEntity.id];\n\n      let action = createAction('Hero', EntityOp.SAVE_DELETE_MANY, ids, {\n        isOptimistic: true,\n      });\n\n      let collection = entityReducer(entityCache, action)['Hero'];\n      let changeState = collection.changeState;\n      expect(collection.loading).toBe(true);\n      expect(changeState[ids[0]]).toBeDefined();\n      expect(changeState[ids[1]]).toBeDefined();\n\n      action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_MANY_SUCCESS,\n        ids, // After optimistically deleted this hero\n        { isOptimistic: true }\n      );\n\n      collection = entityReducer(entityCache, action)['Hero'];\n      changeState = collection.changeState;\n      expect(collection.loading).toBe(false);\n      expect(changeState[ids[0]]).toBeUndefined();\n      expect(changeState[ids[1]]).toBeUndefined();\n    });\n\n    it('should be ok when the id is not in the collection', () => {\n      expect(initialCache['Hero'].entities[1000]).toBeUndefined();\n\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_MANY_SUCCESS,\n        [1000], // id of entity that is not in the collection\n        { isOptimistic: true }\n      );\n\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.entities[1000]).toBeUndefined();\n      expect(collection.loading).toBe(false);\n    });\n  });\n\n  describe('SAVE_DELETE_MANY_SUCCESS (Pessimistic)', () => {\n    it('should remove heroes by id', () => {\n      const heroes = initialHeroes;\n      const ids = heroes.map((h) => h.id);\n\n      expect(initialCache['Hero'].entities[ids[0]]).toBe(heroes[0]);\n      expect(initialCache['Hero'].entities[ids[1]]).toBe(heroes[1]);\n\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_MANY_SUCCESS,\n        ids,\n        { isOptimistic: false }\n      );\n\n      const collection = entityReducer(initialCache, action)['Hero'];\n\n      expect(collection.entities[ids[0]]).toBeUndefined();\n      expect(collection.entities[ids[1]]).toBeUndefined();\n      expect(collection.loading).toBe(false);\n    });\n\n    it('should be ok when an id is not in the collection', () => {\n      const ids = [initialHeroes[0].id, 1000];\n      expect(initialCache['Hero'].entities[1000]).toBeUndefined();\n\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_MANY_SUCCESS,\n        ids,\n        { isOptimistic: false }\n      );\n\n      const collection = entityReducer(initialCache, action)['Hero'];\n\n      expect(collection.entities[ids[0]]).toBeUndefined();\n      expect(collection.entities[1000]).toBeUndefined();\n      expect(collection.loading).toBe(false);\n    });\n  });\n\n  describe('SAVE_DELETE_MANY_ERROR', () => {\n    it('should only clear the loading flag', () => {\n      const { entityCache, removedEntity } = createTestTrackedEntities();\n      const originalAction = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_MANY,\n        removedEntity.id\n      );\n      const error: EntityActionDataServiceError = {\n        error: new DataServiceError(new Error('Test Error'), {\n          method: 'DELETE',\n          url: 'foo',\n        }),\n        originalAction,\n      };\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_MANY_ERROR,\n        error\n      );\n      expectOnlySetLoadingFlag(action, entityCache);\n    });\n\n    // No compensating action on error (yet)\n    it('should NOT restore the hero after optimistic save', () => {\n      const initialEntities = initialCache['Hero'].entities;\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_MANY_ERROR,\n        { id: 13, name: 'Deleted' }, // Pretend optimistically deleted this hero\n        { isOptimistic: true }\n      );\n\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n      expect(collection.entities).toBe(initialEntities);\n      expect(collection.loading).toBe(false);\n    });\n\n    it('should NOT remove the hero', () => {\n      const hero = initialHeroes[0];\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_DELETE_MANY_ERROR,\n        hero\n      );\n\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n      expect(collection.entities[hero.id]).toBe(hero);\n      expect(collection.loading).toBe(false);\n    });\n  });\n\n  describe('SAVE_UPDATE_ONE (Optimistic)', () => {\n    function createTestAction(hero: Update<Hero>) {\n      return createAction('Hero', EntityOp.SAVE_UPDATE_ONE, hero, {\n        isOptimistic: true,\n      });\n    }\n\n    it('should update existing entity in collection', () => {\n      const hero: Hero = { id: 2, name: 'B+' };\n      const action = createTestAction(toHeroUpdate(hero));\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B+');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n\n    it('can update existing entity key in collection', () => {\n      // Change the pkey (id) and the name of former hero:2\n      const hero: Hero = { id: 42, name: 'Super' };\n      const update = { id: 2, changes: hero };\n      const action = createTestAction(update);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([42, 1]);\n      expect(collection.entities[42].name).toBe('Super');\n      // unmentioned property stays the same\n      expect(collection.entities[42].power).toBe('Fast');\n    });\n\n    // Changed in v6. It used to add a new entity.\n    it('should NOT add new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(toHeroUpdate(hero));\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n    });\n  });\n\n  describe('SAVE_UPDATE_ONE (Pessimistic)', () => {\n    it('should only set the loading flag', () => {\n      const updatedEntity = { ...initialHeroes[0], name: 'Updated' };\n      const update = { id: updatedEntity.id, changes: updatedEntity };\n      const action = createAction('Hero', EntityOp.SAVE_UPDATE_ONE, update);\n      expectOnlySetLoadingFlag(action, initialCache);\n    });\n  });\n\n  describe('SAVE_UPDATE_ONE_SUCCESS (Optimistic)', () => {\n    function createTestAction(update: Update<Hero>, changed: boolean) {\n      return createAction(\n        'Hero',\n        EntityOp.SAVE_UPDATE_ONE_SUCCESS,\n        { ...update, changed },\n        { isOptimistic: true }\n      );\n    }\n\n    it('should leave updated entity alone if server did not change the update (changed: false)', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const ids = entityCache['Hero'].ids;\n      const id = updatedEntity.id;\n      const action = createTestAction(toHeroUpdate(updatedEntity), false);\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      expect(collection.ids).toEqual(ids);\n      expect(collection.entities[id].name).toBe(updatedEntity.name);\n      expect(collection.entities[id].power).toBe(updatedEntity.power);\n    });\n\n    it('should update existing entity when server adds its own changes (changed: true)', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const ids = entityCache['Hero'].ids;\n      const id = updatedEntity.id;\n      // Server changed the name\n      const serverEntity = { id: updatedEntity.id, name: 'Server Update Name' };\n      const action = createTestAction(toHeroUpdate(serverEntity), true);\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      expect(collection.ids).toEqual(ids);\n      expect(collection.entities[id].name).toBe(serverEntity.name);\n      // unmentioned property stays the same\n      expect(collection.entities[id].power).toBe(updatedEntity.power);\n    });\n\n    it('can update existing entity key', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const ids = entityCache['Hero'].ids as number[];\n      const id = updatedEntity.id;\n      // Server changed the pkey (id) and the name\n      const serverEntity = { id: 13, name: 'Server Update Name' };\n      const update = { id: updatedEntity.id, changes: serverEntity };\n      const action = createTestAction(update, true);\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      // Should have replaced updatedEntity.id with 13\n      const newIds = ids.map((i) => (i === id ? 13 : i));\n\n      expect(collection.ids).toEqual(newIds);\n      expect(collection.entities[13].name).toBe(serverEntity.name);\n      // unmentioned property stays the same\n      expect(collection.entities[13].power).toBe(updatedEntity.power);\n    });\n\n    // Changed in v6. It used to add a new entity.\n    it('should NOT add new hero to collection', () => {\n      const { entityCache } = createTestTrackedEntities();\n      const ids = entityCache['Hero'].ids;\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(toHeroUpdate(hero), true);\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      expect(collection.ids).toEqual(ids);\n    });\n  });\n\n  describe('SAVE_UPDATE_ONE_SUCCESS (Pessimistic)', () => {\n    function createTestAction(update: Update<Hero>, changed: boolean) {\n      return createAction('Hero', EntityOp.SAVE_UPDATE_ONE_SUCCESS, {\n        ...update,\n        changed,\n      });\n    }\n\n    it('should update existing entity in collection', () => {\n      const hero: Hero = { id: 2, name: 'B+' };\n      const action = createTestAction(toHeroUpdate(hero), false);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B+');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n\n    it('can update existing entity key in collection', () => {\n      // Change the pkey (id) and the name of former hero:2\n      const hero: Hero = { id: 42, name: 'Super' };\n      const update = { id: 2, changes: hero };\n      const action = createTestAction(update, true);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([42, 1]);\n      expect(collection.entities[42].name).toBe('Super');\n      // unmentioned property stays the same\n      expect(collection.entities[42].power).toBe('Fast');\n    });\n\n    // Changed in v6. It used to add a new entity.\n    it('should NOT add new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(toHeroUpdate(hero), false);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n    });\n  });\n\n  describe('SAVE_UPDATE_ONE_ERROR', () => {\n    it('should only clear the loading flag', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const originalAction = createAction(\n        'Hero',\n        EntityOp.SAVE_UPDATE_ONE,\n        updatedEntity\n      );\n      const error: EntityActionDataServiceError = {\n        error: new DataServiceError(new Error('Test Error'), {\n          method: 'PUT',\n          url: 'foo',\n        }),\n        originalAction,\n      };\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_UPDATE_ONE_ERROR,\n        error\n      );\n      expectOnlySetLoadingFlag(action, entityCache);\n    });\n  });\n\n  describe('SAVE_UPDATE_MANY (Optimistic)', () => {\n    function createTestAction(heroes: Update<Hero>[]) {\n      return createAction('Hero', EntityOp.SAVE_UPDATE_MANY, heroes, {\n        isOptimistic: true,\n      });\n    }\n\n    it('should update existing entities in collection', () => {\n      const heroes: Partial<Hero>[] = [\n        { id: 2, name: 'B+' },\n        { id: 1, power: 'Updated Power' },\n      ];\n      const action = createTestAction(heroes.map((h) => toHeroUpdate(h)));\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[1].name).toBe('A');\n      expect(collection.entities[1].power).toBe('Updated Power');\n      expect(collection.entities[2].name).toBe('B+');\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n\n    it('can update existing entity key in collection', () => {\n      // Change the pkey (id) and the name of former hero:2\n      const hero: Hero = { id: 42, name: 'Super' };\n      const update = { id: 2, changes: hero };\n      const action = createTestAction([update]);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([42, 1]);\n      expect(collection.entities[42].name).toBe('Super');\n      // unmentioned property stays the same\n      expect(collection.entities[42].power).toBe('Fast');\n    });\n\n    // Changed in v6. It used to add a new entity.\n    it('should NOT add new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction([toHeroUpdate(hero)]);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n    });\n  });\n\n  describe('SAVE_UPDATE_MANY (Pessimistic)', () => {\n    it('should only set the loading flag', () => {\n      const updatedEntity = { ...initialHeroes[0], name: 'Updated' };\n      const update = { id: updatedEntity.id, changes: updatedEntity };\n      const action = createAction('Hero', EntityOp.SAVE_UPDATE_MANY, [update]);\n      expectOnlySetLoadingFlag(action, initialCache);\n    });\n  });\n\n  describe('SAVE_UPDATE_MANY_SUCCESS (Optimistic)', () => {\n    function createInitialAction(updates: Update<Hero>[]) {\n      return createAction('Hero', EntityOp.SAVE_UPDATE_MANY, updates, {\n        isOptimistic: true,\n      });\n    }\n    function createTestAction(updates: Update<Hero>[]) {\n      return createAction('Hero', EntityOp.SAVE_UPDATE_MANY_SUCCESS, updates, {\n        isOptimistic: true,\n      });\n    }\n\n    it('should update existing entities when server adds its own changes', () => {\n      const updates = initialHeroes.map((h) => {\n        return { id: h.id, changes: { ...h, name: 'Updated ' + h.name } };\n      });\n\n      let action = createInitialAction(updates);\n      let entityCache = entityReducer(initialCache, action);\n      let collection = entityCache['Hero'];\n\n      let name0 = updates[0].changes.name;\n      expect(name0).toContain('Updated');\n\n      const id0 = updates[0].id;\n      name0 = 'Re-' + name0; // server's own change\n      updates[0] = { id: id0, changes: { ...updates[0].changes, name: name0 } };\n      action = createTestAction(updates);\n      entityCache = entityReducer(entityCache, action);\n      collection = entityCache['Hero'];\n\n      expect(collection.entities[id0].name).toBe(name0);\n    });\n\n    it('can update existing entity key', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const ids = entityCache['Hero'].ids as number[];\n      const id = updatedEntity.id;\n      // Server changed the pkey (id) and the name\n      const serverEntity = { id: 13, name: 'Server Update Name' };\n      const update = { id: updatedEntity.id, changes: serverEntity };\n      const action = createTestAction([update]);\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      // Should have replaced updatedEntity.id with 13\n      const newIds = ids.map((i) => (i === id ? 13 : i));\n\n      expect(collection.ids).toEqual(newIds);\n      expect(collection.entities[13].name).toBe(serverEntity.name);\n      // unmentioned property stays the same\n      expect(collection.entities[13].power).toBe(updatedEntity.power);\n    });\n\n    // Changed in v6. It used to add a new entity.\n    it('should NOT add new hero to collection', () => {\n      const { entityCache } = createTestTrackedEntities();\n      const ids = entityCache['Hero'].ids;\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction([toHeroUpdate(hero)]);\n      const collection = entityReducer(entityCache, action)['Hero'];\n\n      expect(collection.ids).toEqual(ids);\n    });\n  });\n\n  describe('SAVE_UPDATE_MANY_SUCCESS (Pessimistic)', () => {\n    function createTestAction(updates: Update<Hero>[]) {\n      return createAction('Hero', EntityOp.SAVE_UPDATE_MANY_SUCCESS, updates, {\n        isOptimistic: false,\n      });\n    }\n\n    it('should update existing entities in collection', () => {\n      const updates = initialHeroes.map((h) => {\n        return { id: h.id, changes: { ...h, name: 'Updated ' + h.name } };\n      });\n\n      const action = createTestAction(updates);\n      const collection = entityReducer(initialCache, action)['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[1].name).toContain('Updated');\n      expect(collection.entities[2].name).toContain('Updated');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n\n    it('can update existing entity key in collection', () => {\n      // Change the pkey (id) and the name of former hero:2\n      const hero: Hero = { id: 42, name: 'Super' };\n      const update = { id: 2, changes: hero };\n      const action = createTestAction([update]);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([42, 1]);\n      expect(collection.entities[42].name).toBe('Super');\n      // unmentioned property stays the same\n      expect(collection.entities[42].power).toBe('Fast');\n    });\n\n    // Changed in v6. It used to add a new entity.\n    it('should NOT add new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction([toHeroUpdate(hero)]);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n    });\n  });\n\n  describe('SAVE_UPDATE_MANY_ERROR', () => {\n    it('should only clear the loading flag', () => {\n      const { entityCache, updatedEntity } = createTestTrackedEntities();\n      const originalAction = createAction('Hero', EntityOp.SAVE_UPDATE_MANY, [\n        updatedEntity,\n      ]);\n      const error: EntityActionDataServiceError = {\n        error: new DataServiceError(new Error('Test Error'), {\n          method: 'PUT',\n          url: 'foo',\n        }),\n        originalAction,\n      };\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_UPDATE_MANY_ERROR,\n        error\n      );\n      expectOnlySetLoadingFlag(action, entityCache);\n    });\n  });\n\n  describe('SAVE_UPSERT_ONE (Optimistic)', () => {\n    function createTestAction(hero: Hero) {\n      return createAction('Hero', EntityOp.SAVE_UPSERT_ONE, hero, {\n        isOptimistic: true,\n      });\n    }\n\n    it('should add a new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const hero = { name: 'New One', power: 'Strong' };\n      // bad add, no id.\n      const action = createTestAction(<any>hero);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /missing or invalid entity key/\n      );\n    });\n\n    it('should update an existing entity in collection', () => {\n      const hero: Hero = { id: 2, name: 'B+' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B+');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n  });\n\n  describe('SAVE_UPSERT_ONE (Pessimistic)', () => {\n    it('should only set the loading flag', () => {\n      const addedEntity = { id: 42, name: 'New Guy' };\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_UPSERT_ONE,\n        addedEntity\n      );\n      expectOnlySetLoadingFlag(action, initialCache);\n    });\n  });\n\n  describe('SAVE_UPSERT_ONE_SUCCESS (Optimistic)', () => {\n    function createTestAction(hero: Hero) {\n      return createAction('Hero', EntityOp.SAVE_UPSERT_ONE_SUCCESS, hero, {\n        isOptimistic: true,\n      });\n    }\n\n    it('should add a new hero to collection, even if it was not among the saved upserted entities', () => {\n      // pretend this new hero was returned by the server instead of the one added by SAVE_UPSERT_ONE\n      const hero: Hero = { id: 13, name: 'Different New One', power: 'Strong' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const hero = { name: 'New One', power: 'Strong' };\n      // bad add, no id.\n      const action = createTestAction(<any>hero);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /missing or invalid entity key/\n      );\n    });\n\n    // because the hero was already upserted to the collection by SAVE_UPSERT_ONE\n    // should update values (but not id) if the server changed them\n    // as it might with a concurrency property.\n    it('should update an existing entity with that ID in collection', () => {\n      // This example simulates the server updating the name and power\n      const hero: Hero = { id: 2, name: 'Updated Name', power: 'Test Power' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('Updated Name');\n      // unmentioned property updated too\n      expect(collection.entities[2].power).toBe('Test Power');\n    });\n\n    // You cannot change the key with SAVE_UPSERT_MANY_SUCCESS\n    // You'd have to do it with SAVE_UPDATE_ONE...\n    it('should NOT change the id of an existing entity hero (will add instead)', () => {\n      const hero = initialHeroes[0];\n      hero.id = 13;\n\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13]);\n      expect(collection.entities[13].name).toEqual(collection.entities[2].name);\n    });\n  });\n\n  describe('SAVE_UPSERT_ONE_SUCCESS (Pessimistic)', () => {\n    function createTestAction(heroes: Hero) {\n      return createAction('Hero', EntityOp.SAVE_UPSERT_ONE_SUCCESS, heroes, {\n        isOptimistic: false,\n      });\n    }\n\n    it('should add new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New A', power: 'Strong' };\n      const action = createTestAction(hero);\n      const collection = entityReducer(initialCache, action)['Hero'];\n      expect(collection.ids).toEqual([2, 1, 13]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const hero: Hero = {\n        id: undefined as any,\n        name: 'New A',\n        power: 'Strong',\n      }; // missing id\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /missing or invalid entity key/\n      );\n    });\n\n    it('should update an existing entity in collection', () => {\n      // This example simulates the server updating the name and power\n      const hero: Hero = {\n        id: 1,\n        name: 'Updated name A',\n        power: 'Updated power A',\n      };\n      const action = createTestAction(hero);\n      const collection = entityReducer(initialCache, action)['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[1].name).toBe('Updated name A');\n      expect(collection.entities[1].power).toBe('Updated power A');\n    });\n  });\n\n  describe('SAVE_UPSERT_ONE_ERROR', () => {\n    it('should only clear the loading flag', () => {\n      const { entityCache, addedEntity } = createTestTrackedEntities();\n      const originalAction = createAction(\n        'Hero',\n        EntityOp.SAVE_UPSERT_ONE,\n        addedEntity\n      );\n      const error: EntityActionDataServiceError = {\n        error: new DataServiceError(new Error('Test Error'), {\n          method: 'POST',\n          url: 'foo',\n        }),\n        originalAction,\n      };\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_UPSERT_ONE_ERROR,\n        error\n      );\n      expectOnlySetLoadingFlag(action, entityCache);\n    });\n  });\n\n  describe('SAVE_UPSERT_MANY (Optimistic)', () => {\n    function createTestAction(heroes: Hero[]) {\n      return createAction('Hero', EntityOp.SAVE_UPSERT_MANY, heroes, {\n        isOptimistic: true,\n      });\n    }\n\n    it('should add new heroes to collection', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n    });\n\n    it('should error if one of new heroes lacks its pkey', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: undefined as any, name: 'New B', power: 'Swift' }, // missing its id\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /does not have a valid entity key/\n      );\n    });\n\n    it('should update an existing entity in collection while adding new ones', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 2, name: 'B+' },\n        { id: 14, name: 'New C', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n      expect(collection.entities[2].name).toBe('B+');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n  });\n\n  describe('SAVE_UPSERT_MANY (Pessimistic)', () => {\n    it('should only set the loading flag', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createAction('Hero', EntityOp.SAVE_UPSERT_MANY, heroes);\n      expectOnlySetLoadingFlag(action, initialCache);\n    });\n  });\n\n  describe('SAVE_UPSERT_MANY_SUCCESS (Optimistic)', () => {\n    function createTestAction(heroes: Hero[]) {\n      return createAction('Hero', EntityOp.SAVE_UPSERT_MANY_SUCCESS, heroes, {\n        isOptimistic: true,\n      });\n    }\n\n    // server returned additional heroes\n    it('should add new heroes to collection, even if they were not among the saved upserted entities', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 2, name: 'Updated name' },\n        { id: 14, name: 'New C', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n    });\n\n    // You cannot change the key with SAVE_UPSERT_MANY_SUCCESS\n    // You'd have to do it with SAVE_UPDATE_ONE...\n    it('should NOT change the id of an existing entity hero (will add instead)', () => {\n      const hero = initialHeroes[0];\n      hero.id = 13;\n\n      const action = createTestAction([hero]);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13]);\n      expect(collection.entities[13].name).toEqual(collection.entities[2].name);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const heroes: Hero[] = [\n        { id: undefined as any, name: 'New A', power: 'Strong' }, // missing its id\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /does not have a valid entity key/\n      );\n    });\n\n    // because the hero was already added to the collection by SAVE_UPSERT_MANY\n    // should update values (but not id) if the server changed them\n    // as it might with a concurrency property.\n    it('should update an existing entity with that ID in collection', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 2, name: 'Updated name', power: 'Updated power' },\n        { id: 14, name: 'New C', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n      expect(collection.entities[2].name).toBe('Updated name');\n      expect(collection.entities[2].power).toBe('Updated power');\n    });\n  });\n\n  describe('SAVE_UPSERT_MANY_SUCCESS (Pessimistic)', () => {\n    function createTestAction(heroes: Hero[]) {\n      return createAction('Hero', EntityOp.SAVE_UPSERT_MANY_SUCCESS, heroes, {\n        isOptimistic: false,\n      });\n    }\n\n    it('should add new heroes to collection', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const collection = entityReducer(initialCache, action)['Hero'];\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const heroes: Hero[] = [\n        { id: undefined as any, name: 'New A', power: 'Strong' }, // missing id\n        { id: 14, name: 'New B', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /does not have a valid entity key/\n      );\n    });\n\n    it('should update an existing entity in collection', () => {\n      const heroes: Hero[] = [\n        { id: 13, name: 'New A', power: 'Strong' },\n        { id: 2, name: 'Updated name', power: 'Updated power' },\n        { id: 14, name: 'New C', power: 'Swift' },\n      ];\n      const action = createTestAction(heroes);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13, 14]);\n      expect(collection.entities[2].name).toBe('Updated name');\n      expect(collection.entities[2].power).toBe('Updated power');\n    });\n  });\n\n  describe('SAVE_UPSERT_MANY_ERROR', () => {\n    it('should only clear the loading flag', () => {\n      const { entityCache, addedEntity, updatedEntity } =\n        createTestTrackedEntities();\n      const originalAction = createAction('Hero', EntityOp.SAVE_UPSERT_MANY, [\n        addedEntity,\n        updatedEntity,\n      ]);\n      const error: EntityActionDataServiceError = {\n        error: new DataServiceError(new Error('Test Error'), {\n          method: 'POST',\n          url: 'foo',\n        }),\n        originalAction,\n      };\n      const action = createAction(\n        'Hero',\n        EntityOp.SAVE_UPSERT_MANY_ERROR,\n        error\n      );\n      expectOnlySetLoadingFlag(action, entityCache);\n    });\n  });\n\n  // #endregion saves\n\n  // #region cache-only\n  describe('ADD_ONE', () => {\n    function createTestAction(hero: Hero) {\n      return createAction('Hero', EntityOp.ADD_ONE, hero);\n    }\n\n    it('should add a new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const hero = { name: 'New One', power: 'Strong' };\n      // bad add, no id.\n      const action = createTestAction(<any>hero);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /missing or invalid entity key/\n      );\n    });\n\n    it('should NOT update an existing entity in collection', () => {\n      const hero: Hero = { id: 2, name: 'B+' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n  });\n\n  describe('UPDATE_MANY', () => {\n    function createTestAction(heroes: Update<Hero>[]) {\n      return createAction('Hero', EntityOp.UPDATE_MANY, heroes);\n    }\n\n    it('should not add new hero to collection', () => {\n      const heroes: Hero[] = [{ id: 3, name: 'New One' }];\n      const updates = heroes.map((h) => toHeroUpdate(h));\n      const action = createTestAction(updates);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n    });\n\n    it('should update existing entity in collection', () => {\n      const heroes: Hero[] = [{ id: 2, name: 'B+' }];\n      const updates = heroes.map((h) => toHeroUpdate(h));\n      const action = createTestAction(updates);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B+');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n\n    it('should update multiple existing entities in collection', () => {\n      const heroes: Hero[] = [\n        { id: 1, name: 'A+' },\n        { id: 2, name: 'B+' },\n        { id: 3, name: 'New One' },\n      ];\n      const updates = heroes.map((h) => toHeroUpdate(h));\n      const action = createTestAction(updates);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      // Did not add the 'New One'\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[1].name).toBe('A+');\n      expect(collection.entities[2].name).toBe('B+');\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n\n    it('can update existing entity key in collection', () => {\n      // Change the pkey (id) and the name of former hero:2\n      const heroes: Hero[] = [{ id: 42, name: 'Super' }];\n      const updates = [{ id: 2, changes: heroes[0] }];\n      const action = createTestAction(updates);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([42, 1]);\n      expect(collection.entities[42].name).toBe('Super');\n      // unmentioned property stays the same\n      expect(collection.entities[42].power).toBe('Fast');\n    });\n  });\n\n  describe('UPDATE_ONE', () => {\n    function createTestAction(hero: Update<Hero>) {\n      return createAction('Hero', EntityOp.UPDATE_ONE, hero);\n    }\n\n    it('should not add a new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(toHeroUpdate(hero));\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n    });\n\n    it('should error if new hero lacks its pkey', () => {\n      const hero = { name: 'New One', power: 'Strong' };\n      // bad update: not an Update<T>\n      const action = createTestAction(<any>hero);\n      const state = entityReducer(initialCache, action);\n      expect(state).toBe(initialCache);\n      expect(action.payload.error!.message).toMatch(\n        /missing or invalid entity key/\n      );\n    });\n\n    it('should update existing entity in collection', () => {\n      const hero: Hero = { id: 2, name: 'B+' };\n      const action = createTestAction(toHeroUpdate(hero));\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B+');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n\n    it('can update existing entity key in collection', () => {\n      // Change the pkey (id) and the name of former hero:2\n      const hero: Hero = { id: 42, name: 'Super' };\n      const update = { id: 2, changes: hero };\n      const action = createTestAction(update);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([42, 1]);\n      expect(collection.entities[42].name).toBe('Super');\n      // unmentioned property stays the same\n      expect(collection.entities[42].power).toBe('Fast');\n    });\n  });\n\n  describe('UPSERT_MANY', () => {\n    function createTestAction(heroes: Hero[]) {\n      return createAction('Hero', EntityOp.UPSERT_MANY, heroes);\n    }\n\n    it('should add new hero to collection', () => {\n      const updates: Hero[] = [{ id: 13, name: 'New One', power: 'Strong' }];\n      const action = createTestAction(updates);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13]);\n      expect(collection.entities[13].name).toBe('New One');\n      expect(collection.entities[13].power).toBe('Strong');\n    });\n\n    it('should update existing entity in collection', () => {\n      const updates: Hero[] = [{ id: 2, name: 'B+' }];\n      const action = createTestAction(updates);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B+');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n\n    it('should update multiple existing entities in collection', () => {\n      const updates: Hero[] = [\n        { id: 1, name: 'A+' },\n        { id: 2, name: 'B+' },\n        { id: 13, name: 'New One', power: 'Strong' },\n      ];\n      const action = createTestAction(updates);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      // Did not add the 'New One'\n      expect(collection.ids).toEqual([2, 1, 13]);\n      expect(collection.entities[1].name).toBe('A+');\n      expect(collection.entities[2].name).toBe('B+');\n      expect(collection.entities[2].power).toBe('Fast');\n      expect(collection.entities[13].name).toBe('New One');\n      expect(collection.entities[13].power).toBe('Strong');\n    });\n  });\n\n  describe('UPSERT_ONE', () => {\n    function createTestAction(hero: Hero) {\n      return createAction('Hero', EntityOp.UPSERT_ONE, hero);\n    }\n\n    it('should add new hero to collection', () => {\n      const hero: Hero = { id: 13, name: 'New One', power: 'Strong' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1, 13]);\n      expect(collection.entities[13].name).toBe('New One');\n      expect(collection.entities[13].power).toBe('Strong');\n    });\n\n    it('should update existing entity in collection', () => {\n      const hero: Hero = { id: 2, name: 'B+' };\n      const action = createTestAction(hero);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities[2].name).toBe('B+');\n      // unmentioned property stays the same\n      expect(collection.entities[2].power).toBe('Fast');\n    });\n  });\n\n  describe('SET FLAGS', () => {\n    it('should set filter value with SET_FILTER', () => {\n      const action = createAction(\n        'Hero',\n        EntityOp.SET_FILTER,\n        'test filter value'\n      );\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.filter).toEqual('test filter value');\n    });\n\n    it('should set loaded flag with SET_LOADED', () => {\n      const beforeLoaded = initialCache['Hero'].loaded;\n      const expectedLoaded = !beforeLoaded;\n      const action = createAction('Hero', EntityOp.SET_LOADED, expectedLoaded);\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.loaded).toEqual(expectedLoaded);\n    });\n\n    it('should set loading flag with SET_LOADING', () => {\n      const beforeLoading = initialCache['Hero'].loading;\n      const expectedLoading = !beforeLoading;\n      const action = createAction(\n        'Hero',\n        EntityOp.SET_LOADING,\n        expectedLoading\n      );\n      const state = entityReducer(initialCache, action);\n      const collection = state['Hero'];\n\n      expect(collection.loading).toEqual(expectedLoading);\n    });\n  });\n  // #endregion cache-only\n\n  /** TODO: TEST REMAINING ACTIONS **/\n\n  /***\n   * Todo: test all other reducer actions\n   * Not a high priority because these other EntityReducer methods delegate to the\n   * @ngrx/entity EntityAdapter reducer methods which are presumed to be well tested.\n   ***/\n\n  describe('reducer override', () => {\n    const queryLoadAction = createAction('Hero', EntityOp.QUERY_LOAD);\n\n    beforeEach(() => {\n      const eds = new EntityDefinitionService([metadata]);\n      const def = eds.getDefinition<Hero>('Hero');\n      const reducer = createReadOnlyHeroReducer(def.entityAdapter);\n      // override regular Hero reducer\n      entityReducerRegistry.registerReducer('Hero', reducer);\n    });\n\n    // Make sure read-only reducer doesn't change QUERY_ALL behavior\n    it('QUERY_LOAD_SUCCESS —clears loading flag and fills collection', () => {\n      let state = entityReducer({}, queryLoadAction);\n      let collection = state['Hero'];\n      expect(collection.loaded).toBe(false);\n      expect(collection.loading).toBe(true);\n\n      const heroes: Hero[] = [\n        { id: 2, name: 'B' },\n        { id: 1, name: 'A' },\n      ];\n      const action = createAction('Hero', EntityOp.QUERY_LOAD_SUCCESS, heroes);\n      state = entityReducer(state, action);\n      collection = state['Hero'];\n      expect(collection.ids).toEqual([2, 1]);\n      expect(collection.entities['1']).toBe(heroes[1]);\n      expect(collection.entities['2']).toBe(heroes[0]);\n      expect(collection.loaded).toBe(true);\n      expect(collection.loading).toBe(false);\n    });\n\n    it('QUERY_LOAD_ERROR clears loading flag and does not fill collection', () => {\n      let state = entityReducer({}, queryLoadAction);\n      const action = createAction('Hero', EntityOp.QUERY_LOAD_ERROR);\n      state = entityReducer(state, action);\n      const collection = state['Hero'];\n      expect(collection.loading).toBe(false);\n      expect(collection.ids.length).toBe(0);\n    });\n\n    it('QUERY_LOAD_SUCCESS works for \"Villain\" entity with non-id primary key', () => {\n      let state = entityReducer({}, queryLoadAction);\n      const villains: Villain[] = [\n        { key: '2', name: 'B' },\n        { key: '1', name: 'A' },\n      ];\n      const action = createAction(\n        'Villain',\n        EntityOp.QUERY_LOAD_SUCCESS,\n        villains\n      );\n      state = entityReducer(state, action);\n      const collection = state['Villain'];\n      expect(collection.loading).toBe(false);\n      expect(collection.ids).toEqual(['2', '1']);\n      expect(collection.entities['1']).toBe(villains[1]);\n      expect(collection.entities['2']).toBe(villains[0]);\n    });\n\n    it('QUERY_MANY is illegal for \"Hero\" collection', () => {\n      const initialState = entityReducer({}, queryLoadAction);\n\n      const action = createAction('Hero', EntityOp.QUERY_MANY);\n      const state = entityReducer(initialState, action);\n\n      // Expect override reducer to throw error and for\n      // EntityReducer to catch it and set the `EntityAction.payload.error`\n      expect(action.payload.error!.message).toMatch(\n        /illegal operation for the \"Hero\" collection/\n      );\n      expect(state).toBe(initialState);\n    });\n\n    it('QUERY_MANY still works for \"Villain\" collection', () => {\n      const action = createAction('Villain', EntityOp.QUERY_MANY);\n      const state = entityReducer({}, action);\n      const collection = state['Villain'];\n      expect(collection.loading).toBe(true);\n    });\n\n    /** Make Hero collection readonly except for QUERY_LOAD  */\n    function createReadOnlyHeroReducer(adapter: EntityAdapter<Hero>) {\n      return function heroReducer(\n        collection: EntityCollection<Hero>,\n        action: EntityAction\n      ): EntityCollection<Hero> {\n        switch (action.payload.entityOp) {\n          case EntityOp.QUERY_LOAD:\n            return collection.loading\n              ? collection\n              : { ...collection, loading: true };\n\n          case EntityOp.QUERY_LOAD_SUCCESS:\n            return {\n              ...adapter.setAll(action.payload.data, collection),\n              loaded: true,\n              loading: false,\n              changeState: {},\n            };\n\n          case EntityOp.QUERY_LOAD_ERROR: {\n            return collection.loading\n              ? { ...collection, loading: false }\n              : collection;\n          }\n\n          default:\n            throw new Error(\n              `${action.payload.entityOp} is an illegal operation for the \"Hero\" collection`\n            );\n        }\n      };\n    }\n  });\n\n  // #region helpers\n  function createCollection<T = any>(\n    entityName: string,\n    data: T[],\n    selectId: IdSelector<any>\n  ) {\n    return {\n      ...collectionCreator.create<T>(entityName),\n      ids: data.map((e) => selectId(e)) as string[] | number[],\n      entities: data.reduce((acc, e) => {\n        acc[selectId(e)] = e;\n        return acc;\n      }, {} as any),\n    } as EntityCollection<T>;\n  }\n\n  function createInitialCache(entityMap: { [entityName: string]: any[] }) {\n    const cache: EntityCache = {};\n    // eslint-disable-next-line guard-for-in\n    for (const entityName in entityMap) {\n      const selectId =\n        metadata[entityName].selectId || ((entity: any) => entity.id);\n      cache[entityName] = createCollection(\n        entityName,\n        entityMap[entityName],\n        selectId\n      );\n    }\n\n    return cache;\n  }\n\n  /**\n   * Prepare the state of the collection with some test data.\n   * Assumes that ADD_ALL, ADD_ONE, REMOVE_ONE, and UPDATE_ONE are working\n   */\n  function createTestTrackedEntities() {\n    const startingHeroes = [\n      { id: 2, name: 'B', power: 'Fast' },\n      { id: 1, name: 'A', power: 'Invisible' },\n      { id: 3, name: 'C', power: 'Strong' },\n    ];\n\n    const [removedEntity, preUpdatedEntity] = startingHeroes;\n    let action = createAction('Hero', EntityOp.ADD_ALL, startingHeroes);\n    let entityCache = entityReducer({}, action);\n\n    const addedEntity = { id: 42, name: 'E', power: 'Smart' };\n    action = createAction('Hero', EntityOp.ADD_ONE, addedEntity);\n    entityCache = entityReducer(entityCache, action);\n\n    action = createAction('Hero', EntityOp.REMOVE_ONE, removedEntity.id);\n    entityCache = entityReducer(entityCache, action);\n\n    const updatedEntity = { ...preUpdatedEntity, name: 'A Updated' };\n    action = createAction('Hero', EntityOp.UPDATE_ONE, {\n      id: updatedEntity.id,\n      changes: updatedEntity,\n    });\n    entityCache = entityReducer(entityCache, action);\n\n    return {\n      entityCache,\n      addedEntity,\n      removedEntity,\n      preUpdatedEntity,\n      startingHeroes,\n      updatedEntity,\n    };\n  }\n\n  /** Test for ChangeState with expected ChangeType */\n  function expectChangeType(\n    change: ChangeState<any>,\n    expectedChangeType: ChangeType,\n    msg?: string\n  ) {\n    expect(ChangeType[change.changeType]).toEqual(\n      ChangeType[expectedChangeType]\n    );\n  }\n\n  /** Test that loading flag changed in expected way and the rest of the collection stayed the same. */\n  function expectOnlySetLoadingFlag(\n    action: EntityAction,\n    entityCache: EntityCache\n  ) {\n    // Flag should be true when op starts, false after error or success\n    const expectedLoadingFlag = !/error|success/i.test(action.payload.entityOp);\n    const initialCollection = entityCache['Hero'];\n    const newCollection = entityReducer(entityCache, action)['Hero'];\n    expect(newCollection.loading).toBe(expectedLoadingFlag);\n    expect({\n      ...newCollection,\n      loading: initialCollection.loading, // revert flag for test\n    }).toEqual(initialCollection);\n  }\n  // #endregion helpers\n});\n"
  },
  {
    "path": "modules/data/spec/selectors/entity-selectors$.spec.ts",
    "content": "import { Action, MemoizedSelector, StateObservable, Store } from '@ngrx/store';\n\nimport { BehaviorSubject, Observable, Subject } from 'rxjs';\nimport {\n  EntityMetadata,\n  EntityCache,\n  EntitySelectors$Factory,\n  EntitySelectorsFactory,\n  createEntityCacheSelector,\n  ENTITY_CACHE_NAME,\n  EntityCollection,\n  EntityActionFactory,\n  EntityOp,\n  createEmptyEntityCollection,\n  PropsFilterFnFactory,\n  EntitySelectors$,\n  EntitySelectors,\n} from '../../';\nimport { vi } from 'vitest';\n\ndescribe('EntitySelectors$', () => {\n  /** HeroMetadata identifies extra collection state properties */\n  const heroMetadata: EntityMetadata<Hero> = {\n    entityName: 'Hero',\n    filterFn: nameFilter,\n    additionalCollectionState: {\n      foo: 'Foo',\n      bar: 3.14,\n    },\n  };\n\n  /** As entityAdapter.initialState would create it */\n  const emptyHeroCollection = createHeroState({ foo: 'foo', bar: 3.14 });\n\n  const villainMetadata: EntityMetadata<Villain> = {\n    entityName: 'Villain',\n    selectId: (villain) => villain.key,\n  };\n\n  // Hero has a super-set of EntitySelectors$\n  describe('EntitySelectors$Factory.create (Hero)', () => {\n    // Some immutable cache states\n    const emptyCache: EntityCache = {};\n\n    const initializedHeroCache: EntityCache = <any>{\n      // The state of the HeroCollection in this test suite\n      // as the EntityReducer might initialize it.\n      Hero: emptyHeroCollection,\n    };\n\n    let collectionCreator: any;\n\n    let bar: number;\n    let collection: HeroCollection;\n    let foo: string;\n    let heroes: Hero[];\n    let loaded: boolean;\n    let loading: boolean;\n\n    // The store during tests will be the entity cache\n    let store: Store<{ entityCache: EntityCache }>;\n\n    // Observable of state changes, which these tests simulate\n    let state$: BehaviorSubject<{ entityCache: EntityCache }>;\n\n    let actions$: Subject<Action>;\n\n    const nextCacheState = (cache: EntityCache) =>\n      state$.next({ entityCache: cache });\n\n    let heroCollectionSelectors: HeroSelectors;\n\n    let factory: EntitySelectors$Factory;\n\n    beforeEach(() => {\n      actions$ = new Subject<Action>();\n      state$ = new BehaviorSubject({ entityCache: emptyCache });\n      store = new Store<{ entityCache: EntityCache }>(\n        state$ as unknown as StateObservable,\n        null as any,\n        null as any\n      );\n\n      // EntitySelectors\n      collectionCreator = {\n        create: vi.fn().mockName('create'),\n      };\n      collectionCreator.create.mockReturnValue(emptyHeroCollection);\n      const entitySelectorsFactory = new EntitySelectorsFactory(\n        collectionCreator\n      );\n      heroCollectionSelectors = entitySelectorsFactory.create<\n        Hero,\n        HeroSelectors\n      >(heroMetadata);\n\n      // EntitySelectorFactory\n      factory = new EntitySelectors$Factory(\n        store,\n        actions$ as any,\n        createEntityCacheSelector(ENTITY_CACHE_NAME)\n      );\n\n      // listen for changes to the hero collection\n      store\n        .select<HeroCollection>(ENTITY_CACHE_NAME as any, 'Hero')\n        .subscribe((c: HeroCollection) => (collection = c));\n    });\n\n    function subscribeToSelectors(selectors$: HeroSelectors$) {\n      selectors$.entities$.subscribe((h) => (heroes = h));\n      selectors$.loaded$.subscribe((l) => (loaded = l));\n      selectors$.loading$.subscribe((l) => (loading = l));\n      selectors$.foo$.subscribe((f) => (foo = f));\n      selectors$.bar$.subscribe((b) => (bar = b));\n    }\n\n    it('can select$ the default empty collection when store collection is undefined ', () => {\n      const selectors$ = factory.create<Hero, HeroSelectors$>(\n        'Hero',\n        heroCollectionSelectors\n      );\n      let selectorCollection: EntityCollection<HeroCollection>;\n      selectors$.collection$.subscribe((c) => (selectorCollection = c));\n      expect(selectorCollection!).toBeDefined();\n      expect(selectorCollection!.entities).toEqual({});\n\n      // Important: the selector is returning these values;\n      // They are not actually in the store's entity cache collection!\n      expect(collection).toBeUndefined();\n    });\n\n    it('selectors$ emit default empty values when collection is undefined', () => {\n      const selectors$ = factory.create<Hero, HeroSelectors$>(\n        'Hero',\n        heroCollectionSelectors\n      );\n\n      subscribeToSelectors(selectors$);\n\n      expect(heroes).toEqual([]);\n      expect(loaded).toBe(false);\n      expect(loading).toBe(false);\n      expect(foo).toBe('foo');\n      expect(bar).toBe(3.14);\n    });\n\n    it('selectors$ emit updated hero values', () => {\n      const selectors$ = factory.create<Hero, HeroSelectors$>(\n        'Hero',\n        heroCollectionSelectors\n      );\n\n      subscribeToSelectors(selectors$);\n\n      // prime the store for Hero first use as the EntityReducer would\n      nextCacheState(initializedHeroCache);\n\n      // set foo and add an entity as the reducer would\n      collection = {\n        ...collection,\n        ...{\n          foo: 'FooDoo',\n          ids: [42],\n          loaded: true,\n          entities: { 42: { id: 42, name: 'Bob' } },\n        },\n      };\n\n      // update the store as a reducer would\n      nextCacheState({ ...emptyCache, Hero: collection });\n\n      // Selectors$ should have emitted the updated values.\n      expect(heroes).toEqual([{ id: 42, name: 'Bob' }]);\n      expect(loaded).toBe(true); // as if had QueryAll\n      expect(loading).toBe(false); // didn't change\n      expect(foo).toEqual('FooDoo');\n      expect(bar).toEqual(3.14); // didn't change\n    });\n\n    it('selectors$ emit supplied defaultCollectionState when collection is undefined', () => {\n      // N.B. This is an absurd default state, suitable for test purposes only.\n      // The default state feature exists to prevent selectors$ subscriptions\n      // from bombing before the collection is initialized or\n      // during time-travel debugging.\n      const defaultHeroState = createHeroState({\n        ids: [1],\n        entities: { 1: { id: 1, name: 'A' } },\n        loaded: true,\n        foo: 'foo foo',\n        bar: 42,\n      });\n\n      collectionCreator.create.mockReturnValue(defaultHeroState);\n      const selectors$ = factory.create<Hero, HeroSelectors$>(\n        'Hero',\n        heroCollectionSelectors\n      ); // <- override default state\n\n      subscribeToSelectors(selectors$);\n\n      expect(heroes).toEqual([{ id: 1, name: 'A' }]);\n      expect(foo).toEqual('foo foo');\n      expect(bar).toEqual(42);\n\n      // Important: the selector is returning these values;\n      // They are not actually in the store's entity cache collection!\n      expect(collection).toBeUndefined();\n    });\n\n    it('`entityCache$` should observe the entire entity cache', () => {\n      const entityCacheValues: any = [];\n      factory.entityCache$.subscribe((ec) => entityCacheValues.push(ec));\n\n      // prime the store for Hero first use as the EntityReducer would\n      nextCacheState(initializedHeroCache);\n\n      expect(entityCacheValues.length).toEqual(2);\n      expect(entityCacheValues[0]).toEqual({});\n      expect(entityCacheValues[1].Hero).toBeDefined();\n    });\n\n    it('`actions$` emits hero collection EntityActions and no other actions', () => {\n      const actionsReceived: Action[] = [];\n      const selectors$ = factory.create<Hero, HeroSelectors$>(\n        'Hero',\n        heroCollectionSelectors\n      );\n      const entityActions$ = selectors$.entityActions$;\n      entityActions$.subscribe((action) => actionsReceived.push(action));\n\n      const eaFactory = new EntityActionFactory();\n      actions$.next({ type: 'Generic action' });\n      // EntityAction but not for heroes\n      actions$.next(eaFactory.create('Villain', EntityOp.QUERY_ALL));\n      // Hero EntityAction\n      const heroAction = eaFactory.create('Hero', EntityOp.QUERY_ALL);\n      actions$.next(heroAction);\n\n      expect(actionsReceived.length).toBe(1);\n      expect(actionsReceived[0]).toBe(heroAction);\n    });\n\n    it('`errors$` emits hero collection EntityAction errors and no other actions', () => {\n      const actionsReceived: Action[] = [];\n      const selectors$ = factory.create<Hero, HeroSelectors$>(\n        'Hero',\n        heroCollectionSelectors\n      );\n      const errors$ = selectors$.errors$;\n      errors$.subscribe((action) => actionsReceived.push(action));\n\n      const eaFactory = new EntityActionFactory();\n      actions$.next({ type: 'Generic action' });\n      // EntityAction error but not for heroes\n      actions$.next(eaFactory.create('Villain', EntityOp.QUERY_ALL_ERROR));\n      // Hero EntityAction (but not an error)\n      actions$.next(eaFactory.create('Hero', EntityOp.QUERY_ALL));\n      // Hero EntityAction Error\n      const heroErrorAction = eaFactory.create(\n        'Hero',\n        EntityOp.QUERY_ALL_ERROR\n      );\n      actions$.next(heroErrorAction);\n      expect(actionsReceived.length).toBe(1);\n      expect(actionsReceived[0]).toBe(heroErrorAction);\n    });\n  });\n});\n\n/////// Test values and helpers /////////\n\nfunction createHeroState(state: Partial<HeroCollection>): HeroCollection {\n  return {\n    ...createEmptyEntityCollection<Hero>('Hero'),\n    ...state,\n  } as HeroCollection;\n}\n\nfunction nameFilter<T>(entities: T[], pattern: string) {\n  return PropsFilterFnFactory<any>(['name'])(entities, pattern);\n}\n\n/// Hero\ninterface Hero {\n  id: number;\n  name: string;\n}\n\n/** HeroCollection is EntityCollection<Hero> with extra collection properties */\ninterface HeroCollection extends EntityCollection<Hero> {\n  foo: string;\n  bar: number;\n}\n\n/** HeroSelectors identifies the extra selectors for the extra collection properties */\ninterface HeroSelectors extends EntitySelectors<Hero> {\n  selectFoo: MemoizedSelector<Object, string>;\n  selectBar: MemoizedSelector<Object, number>;\n}\n\n/** HeroSelectors identifies the extra selectors for the extra collection properties */\ninterface HeroSelectors$ extends EntitySelectors$<Hero> {\n  foo$: Observable<string> | Store<string>;\n  bar$: Observable<number> | Store<number>;\n}\n\n/// Villain\ninterface Villain {\n  key: string;\n  name: string;\n}\n"
  },
  {
    "path": "modules/data/spec/selectors/entity-selectors.spec.ts",
    "content": "import { MemoizedSelector } from '@ngrx/store';\nimport {\n  EntityMetadata,\n  EntitySelectorsFactory,\n  EntityCollection,\n  createEmptyEntityCollection,\n  PropsFilterFnFactory,\n  EntitySelectors,\n} from '../../';\nimport { vi } from 'vitest';\n\ndescribe('EntitySelectors', () => {\n  /** HeroMetadata identifies the extra collection state properties */\n  const heroMetadata: EntityMetadata<Hero> = {\n    entityName: 'Hero',\n    filterFn: nameFilter,\n    additionalCollectionState: {\n      foo: 'Foo',\n      bar: 3.14,\n    },\n  };\n\n  const villainMetadata: EntityMetadata<Villain> = {\n    entityName: 'Villain',\n    selectId: (villain) => villain.key,\n  };\n\n  let collectionCreator: any;\n  let entitySelectorsFactory: EntitySelectorsFactory;\n\n  beforeEach(() => {\n    collectionCreator = {\n      create: vi.fn().mockName('create'),\n    };\n    entitySelectorsFactory = new EntitySelectorsFactory(collectionCreator);\n  });\n\n  describe('#createCollectionSelector', () => {\n    const initialState = createHeroState({\n      ids: [1],\n      entities: { 1: { id: 1, name: 'A' } },\n      foo: 'foo foo',\n      bar: 42,\n    });\n\n    it('creates collection selector that defaults to initial state', () => {\n      collectionCreator.create.mockReturnValue(initialState);\n      const selectors = entitySelectorsFactory.createCollectionSelector<\n        Hero,\n        HeroCollection\n      >('Hero');\n      const state = { entityCache: {} }; // ngrx store with empty cache\n      const collection = selectors(state);\n      expect(collection.entities).toEqual(initialState.entities);\n      expect(collection.foo).toEqual('foo foo');\n      expect(collectionCreator.create).toHaveBeenCalled();\n    });\n\n    it('collection selector should return cached collection when it exists', () => {\n      // must specify type-args when initialState isn't available for type inference\n      const selectors = entitySelectorsFactory.createCollectionSelector<\n        Hero,\n        HeroCollection\n      >('Hero');\n\n      // ngrx store with populated Hero collection\n      const state = {\n        entityCache: {\n          Hero: {\n            ids: [42],\n            entities: { 42: { id: 42, name: 'The Answer' } },\n            filter: '',\n            loading: true,\n            foo: 'towel',\n            bar: 0,\n          },\n        },\n      };\n\n      const collection = selectors(state);\n      expect(collection.entities[42]).toEqual({ id: 42, name: 'The Answer' });\n      expect(collection.foo).toBe('towel');\n      expect(collectionCreator.create).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('#createEntitySelectors', () => {\n    let heroCollection: HeroCollection;\n    let heroEntities: Hero[];\n\n    beforeEach(() => {\n      heroEntities = [\n        { id: 42, name: 'A' },\n        { id: 48, name: 'B' },\n      ];\n\n      heroCollection = <HeroCollection>(<any>{\n        ids: [42, 48],\n        entities: {\n          42: heroEntities[0],\n          48: heroEntities[1],\n        },\n        filter: 'B',\n        foo: 'Foo',\n      });\n    });\n\n    it('should have expected Hero selectors (a super-set of EntitySelectors)', () => {\n      const store = { entityCache: { Hero: heroCollection } };\n\n      const selectors = entitySelectorsFactory.create<Hero, HeroSelectors>(\n        heroMetadata\n      );\n\n      expect(selectors.selectEntities).toBeDefined();\n      expect(selectors.selectEntities(store)).toEqual(heroEntities);\n\n      expect(selectors.selectFilteredEntities(store)).toEqual(\n        heroEntities.filter((h) => h.name === 'B')\n      );\n\n      expect(selectors.selectFoo).toBeDefined();\n      expect(selectors.selectFoo(store)).toBe('Foo');\n    });\n\n    it('should have all Hero when create EntitySelectorFactory directly', () => {\n      const store = { entityCache: { Hero: heroCollection } };\n\n      // Create EntitySelectorFactory directly rather than injecting it!\n      // Works ONLY if have not changed the name of the EntityCache.\n      // In this case, where also not supplying the EntityCollectionCreator\n      // selector for additional collection properties might fail,\n      // but doesn't in this test because the additional Foo property is in the store.\n\n      const eaFactory = new EntitySelectorsFactory();\n      const selectors = eaFactory.create<Hero, HeroSelectors>(heroMetadata);\n\n      expect(selectors.selectEntities).toBeDefined();\n      expect(selectors.selectEntities(store)).toEqual(heroEntities);\n\n      expect(selectors.selectFilteredEntities(store)).toEqual(\n        heroEntities.filter((h) => h.name === 'B')\n      );\n\n      expect(selectors.selectFoo).toBeDefined();\n      expect(selectors.selectFoo(store)).toBe('Foo');\n    });\n\n    it('should create default selectors (no filter, no extras) when create with \"Hero\" instead of hero metadata', () => {\n      const store = { entityCache: { Hero: heroCollection } };\n\n      // const selectors = entitySelectorsFactory.create<Hero, HeroSelectors>('Hero');\n      // There won't be extra selectors so type selectors for Hero collection only\n      const selectors = entitySelectorsFactory.create<Hero>('Hero');\n      expect(selectors.selectEntities).toBeDefined();\n      expect(selectors.selectFoo).not.toBeDefined();\n      expect(selectors.selectFilteredEntities(store)).toEqual(heroEntities);\n    });\n\n    it('should have expected Villain selectors', () => {\n      const collection = <EntityCollection<Villain>>(<any>{\n        ids: [24],\n        entities: { 24: { key: 'evil', name: 'A' } },\n        filter: 'B', // doesn't matter because no filter function\n      });\n      const store = { entityCache: { Villain: collection } };\n\n      const selectors = entitySelectorsFactory.create<Villain>(villainMetadata);\n      const expectedEntities: Villain[] = [{ key: 'evil', name: 'A' }];\n\n      expect(selectors.selectEntities).toBeDefined();\n      expect(selectors.selectEntities(store)).toEqual(expectedEntities);\n\n      expect(selectors.selectFilteredEntities(store)).toEqual(expectedEntities);\n    });\n  });\n});\n\n/////// Test values and helpers /////////\n\nfunction createHeroState(state: Partial<HeroCollection>): HeroCollection {\n  return {\n    ...createEmptyEntityCollection<Hero>('Hero'),\n    ...state,\n  } as HeroCollection;\n}\n\nfunction nameFilter<T>(entities: T[], pattern: string) {\n  return PropsFilterFnFactory<any>(['name'])(entities, pattern);\n}\n\n/// Hero\ninterface Hero {\n  id: number;\n  name: string;\n}\n\n/** HeroCollection is EntityCollection<Hero> with extra collection properties */\ninterface HeroCollection extends EntityCollection<Hero> {\n  foo: string;\n  bar: number;\n}\n\n/** HeroSelectors identifies the extra selectors for the extra collection properties */\ninterface HeroSelectors extends EntitySelectors<Hero> {\n  selectFoo: MemoizedSelector<Object, string>;\n  selectBar: MemoizedSelector<Object, number>;\n}\n\n/// Villain\ninterface Villain {\n  key: string;\n  name: string;\n}\n"
  },
  {
    "path": "modules/data/spec/selectors/related-entity-selectors.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { createSelector, StoreModule, Store } from '@ngrx/store';\nimport { Actions } from '@ngrx/effects';\nimport { Update } from '@ngrx/entity';\n\nimport { Observable } from 'rxjs';\nimport { skip } from 'rxjs/operators';\n\nimport {\n  EntityMetadataMap,\n  EntityActionFactory,\n  EntitySelectorsFactory,\n  EntityCache,\n  EntityDataModuleWithoutEffects,\n  ENTITY_METADATA_TOKEN,\n  EntityOp,\n  EntityAction,\n} from '../../';\n\nconst entityMetadataMap: EntityMetadataMap = {\n  Battle: {},\n  Hero: {},\n  HeroPowerMap: {},\n  Power: {\n    sortComparer: sortByName,\n  },\n  Sidekick: {},\n};\n\ndescribe('Related-entity Selectors', () => {\n  // #region setup\n  let eaFactory: EntityActionFactory;\n  let entitySelectorsFactory: EntitySelectorsFactory;\n  let store: Store<EntityCache>;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [StoreModule.forRoot({}), EntityDataModuleWithoutEffects],\n      providers: [\n        // required by EntityData but not used in these tests\n        { provide: Actions, useValue: null },\n        {\n          provide: ENTITY_METADATA_TOKEN,\n          multi: true,\n          useValue: entityMetadataMap,\n        },\n      ],\n    });\n\n    store = TestBed.inject(Store);\n    eaFactory = TestBed.inject(EntityActionFactory);\n    entitySelectorsFactory = TestBed.inject(EntitySelectorsFactory);\n    initializeCache(eaFactory, store);\n  });\n\n  // #endregion setup\n\n  describe('hero -> sidekick (1-1)', () => {\n    function setCollectionSelectors() {\n      const heroSelectors = entitySelectorsFactory.create<Hero>('Hero');\n      const selectHeroMap = heroSelectors.selectEntityMap;\n\n      const sidekickSelectors =\n        entitySelectorsFactory.create<Sidekick>('Sidekick');\n      const selectSidekickMap = sidekickSelectors.selectEntityMap;\n\n      return {\n        selectHeroMap,\n        selectSidekickMap,\n      };\n    }\n\n    function createHeroSidekickSelector$(heroId: number): Observable<Sidekick> {\n      const { selectHeroMap, selectSidekickMap } = setCollectionSelectors();\n      const selectHero = createSelector(\n        selectHeroMap,\n        (heroes) => heroes[heroId]\n      );\n      const selectSideKick = createSelector(\n        selectHero,\n        selectSidekickMap,\n        (hero, sidekicks) => {\n          const sidekickId = hero && hero.sidekickFk!;\n          return sidekickId && sidekicks[sidekickId];\n        }\n      );\n      return store.select(selectSideKick) as Observable<Sidekick>;\n    }\n\n    // Note: async done() callback ensures test passes only if subscribe(successCallback()) called.\n\n    it('should get Alpha Hero sidekick', () =>\n      new Promise<void>((done) => {\n        createHeroSidekickSelector$(1).subscribe((sk) => {\n          expect(sk.name).toBe('Bob');\n          done();\n        });\n      }));\n\n    it('should get Alpha Hero updated sidekick', () =>\n      new Promise<void>((done) => {\n        // Skip the initial sidekick and check the one after update\n        createHeroSidekickSelector$(1)\n          .pipe(skip(1))\n          .subscribe((sk) => {\n            expect(sk.name).toBe('Robert');\n            done();\n          });\n\n        // update the related sidekick\n        const action = eaFactory.create<Update<Sidekick>>(\n          'Sidekick',\n          EntityOp.UPDATE_ONE,\n          { id: 1, changes: { id: 1, name: 'Robert' } }\n        );\n        store.dispatch(action);\n      }));\n\n    it('should get Alpha Hero changed sidekick', () =>\n      new Promise<void>((done) => {\n        // Skip the initial sidekick and check the one after update\n        createHeroSidekickSelector$(1)\n          .pipe(skip(1))\n          .subscribe((sk) => {\n            expect(sk.name).toBe('Sally');\n            done();\n          });\n\n        // update the hero's sidekick from fk=1 to fk=2\n        const action = eaFactory.create<Update<Hero>>(\n          'Hero',\n          EntityOp.UPDATE_ONE,\n          { id: 1, changes: { id: 1, sidekickFk: 2 } } // Sally\n        );\n        store.dispatch(action);\n      }));\n\n    it('changing a different hero should NOT trigger first hero selector', () =>\n      new Promise<void>((done) => {\n        let alphaCount = 0;\n\n        createHeroSidekickSelector$(1).subscribe((sk) => {\n          alphaCount += 1;\n        });\n\n        // update a different hero's sidekick from fk=2 (Sally) to fk=1 (Bob)\n        createHeroSidekickSelector$(2)\n          .pipe(skip(1))\n          .subscribe((sk) => {\n            expect(sk.name).toBe('Bob');\n            expect(alphaCount).toEqual(1);\n            done();\n          });\n\n        const action = eaFactory.create<Update<Hero>>(\n          'Hero',\n          EntityOp.UPDATE_ONE,\n          { id: 2, changes: { id: 2, sidekickFk: 1 } } // Bob\n        );\n        store.dispatch(action);\n      }));\n\n    it('should get undefined sidekick if hero not found', () =>\n      new Promise<void>((done) => {\n        createHeroSidekickSelector$(1234).subscribe((sk) => {\n          expect(sk).toBeUndefined();\n          done();\n        });\n      }));\n\n    it('should get undefined sidekick from Gamma because it has no sidekickFk', () =>\n      new Promise<void>((done) => {\n        createHeroSidekickSelector$(3).subscribe((sk) => {\n          expect(sk).toBeUndefined();\n          done();\n        });\n      }));\n\n    it('should get Gamma sidekick after creating and assigning one', () =>\n      new Promise<void>((done) => {\n        // Skip(1), the initial state in which Gamma has no sidekick\n        // Note that BOTH dispatches complete synchronously, before the selector updates\n        // so we only have to skip one.\n        createHeroSidekickSelector$(3)\n          .pipe(skip(1))\n          .subscribe((sk) => {\n            expect(sk.name).toBe('Robin');\n            done();\n          });\n\n        // create a new sidekick\n        let action: EntityAction = eaFactory.create<Sidekick>(\n          'Sidekick',\n          EntityOp.ADD_ONE,\n          {\n            id: 42,\n            name: 'Robin',\n          }\n        );\n        store.dispatch(action);\n\n        // assign new sidekick to Gamma\n        action = eaFactory.create<Update<Hero>>('Hero', EntityOp.UPDATE_ONE, {\n          id: 3,\n          changes: { id: 3, sidekickFk: 42 },\n        });\n        store.dispatch(action);\n      }));\n  });\n\n  describe('hero -> battles (1-m)', () => {\n    function setCollectionSelectors() {\n      const heroSelectors = entitySelectorsFactory.create<Hero>('Hero');\n      const selectHeroMap = heroSelectors.selectEntityMap;\n\n      const battleSelectors = entitySelectorsFactory.create<Battle>('Battle');\n      const selectBattleEntities = battleSelectors.selectEntities;\n\n      const selectHeroBattleMap = createSelector(\n        selectBattleEntities,\n        (battles) =>\n          battles.reduce(\n            (acc, battle) => {\n              const hid = battle.heroFk;\n              if (hid) {\n                const hbs = acc[hid];\n                if (hbs) {\n                  hbs.push(battle);\n                } else {\n                  acc[hid] = [battle];\n                }\n              }\n              return acc;\n            },\n            {} as { [heroId: number]: Battle[] }\n          )\n      );\n\n      return {\n        selectHeroMap,\n        selectHeroBattleMap,\n      };\n    }\n\n    function createHeroBattlesSelector$(heroId: number): Observable<Battle[]> {\n      const { selectHeroMap, selectHeroBattleMap } = setCollectionSelectors();\n\n      const selectHero = createSelector(\n        selectHeroMap,\n        (heroes) => heroes[heroId]\n      );\n\n      const selectHeroBattles = createSelector(\n        selectHero,\n        selectHeroBattleMap,\n        (hero, heroBattleMap) => {\n          const hid = hero && hero.id;\n          return (hid && heroBattleMap[hid]) || [];\n        }\n      );\n      return store.select(selectHeroBattles);\n    }\n\n    // TODO: more tests\n    // Note: async done() callback ensures test passes only if subscribe(successCallback()) called.\n\n    it('should get Alpha Hero battles', () =>\n      new Promise<void>((done) => {\n        createHeroBattlesSelector$(1).subscribe((battles) => {\n          expect(battles.length).toBe(3);\n          done();\n        });\n      }));\n\n    it('should get Alpha Hero battles again after updating one of its battles', () =>\n      new Promise<void>((done) => {\n        // Skip the initial sidekick and check the one after update\n        createHeroBattlesSelector$(1)\n          .pipe(skip(1))\n          .subscribe((battles) => {\n            expect(battles[0].name).toBe('Scalliwag');\n            done();\n          });\n\n        // update the first of the related battles\n        const action = eaFactory.create<Update<Battle>>(\n          'Battle',\n          EntityOp.UPDATE_ONE,\n          { id: 100, changes: { id: 100, name: 'Scalliwag' } }\n        );\n        store.dispatch(action);\n      }));\n\n    it('Gamma Hero should have no battles', () =>\n      new Promise<void>((done) => {\n        createHeroBattlesSelector$(3).subscribe((battles) => {\n          expect(battles.length).toBe(0);\n          done();\n        });\n      }));\n  });\n\n  describe('hero -> heropower <- power (m-m)', () => {\n    function setCollectionSelectors() {\n      const heroSelectors = entitySelectorsFactory.create<Hero>('Hero');\n      const selectHeroMap = heroSelectors.selectEntityMap;\n\n      const powerSelectors = entitySelectorsFactory.create<Power>('Power');\n      const selectPowerMap = powerSelectors.selectEntityMap;\n\n      const heroPowerMapSelectors =\n        entitySelectorsFactory.create<HeroPowerMap>('HeroPowerMap');\n      const selectHeroPowerMapEntities = heroPowerMapSelectors.selectEntities;\n\n      const selectHeroPowerIds = createSelector(\n        selectHeroPowerMapEntities,\n        (hpMaps) =>\n          hpMaps.reduce(\n            (acc, hpMap) => {\n              const hid = hpMap.heroFk;\n              if (hid) {\n                const hpIds = acc[hid];\n                if (hpIds) {\n                  hpIds.push(hpMap.powerFk);\n                } else {\n                  acc[hid] = [hpMap.powerFk];\n                }\n              }\n              return acc;\n            },\n            {} as { [heroId: number]: number[] }\n          )\n      );\n\n      return {\n        selectHeroMap,\n        selectHeroPowerIds,\n        selectPowerMap,\n      };\n    }\n\n    function createHeroPowersSelector$(heroId: number): Observable<Power[]> {\n      const { selectHeroMap, selectHeroPowerIds, selectPowerMap } =\n        setCollectionSelectors();\n\n      const selectHero = createSelector(\n        selectHeroMap,\n        (heroes) => heroes[heroId]\n      );\n\n      const selectHeroPowers = createSelector(\n        selectHero,\n        selectHeroPowerIds,\n        selectPowerMap,\n        (hero, heroPowerIds, powerMap) => {\n          const hid = hero && hero.id;\n          const pids = (hid && heroPowerIds[hid]) || [];\n          const powers = pids\n            .map((id) => powerMap[id])\n            .filter((power) => power);\n          return powers;\n        }\n      );\n      return store.select(selectHeroPowers) as Observable<Power[]>;\n    }\n\n    // TODO: more tests\n    // Note: async done() callback ensures test passes only if subscribe(successCallback()) called.\n\n    it('should get Alpha Hero powers', () =>\n      new Promise<void>((done) => {\n        createHeroPowersSelector$(1).subscribe((powers) => {\n          expect(powers.length).toBe(3);\n          done();\n        });\n      }));\n\n    it('should get Beta Hero power', () =>\n      new Promise<void>((done) => {\n        createHeroPowersSelector$(2).subscribe((powers) => {\n          expect(powers.length).toBe(1);\n          expect(powers[0].name).toBe('Invisibility');\n          done();\n        });\n      }));\n\n    it('Beta Hero should have no powers after delete', () =>\n      new Promise<void>((done) => {\n        createHeroPowersSelector$(2)\n          .pipe(skip(1))\n          .subscribe((powers) => {\n            expect(powers.length).toBe(0);\n            done();\n          });\n\n        // delete Beta's one power via the HeroPowerMap\n        const action: EntityAction = eaFactory.create<number>(\n          'HeroPowerMap',\n          EntityOp.REMOVE_ONE,\n          96\n        );\n        store.dispatch(action);\n      }));\n\n    it('Gamma Hero should have no powers', () =>\n      new Promise<void>((done) => {\n        createHeroPowersSelector$(3).subscribe((powers) => {\n          expect(powers.length).toBe(0);\n          done();\n        });\n      }));\n  });\n});\n\n// #region Test support\n\ninterface Hero {\n  id: number;\n  name: string;\n  saying?: string;\n  sidekickFk?: number;\n}\n\ninterface Battle {\n  id: number;\n  name: string;\n  heroFk: number;\n  won: boolean;\n}\n\ninterface HeroPowerMap {\n  id: number;\n  heroFk: number;\n  powerFk: number;\n}\n\ninterface Power {\n  id: number;\n  name: string;\n}\n\ninterface Sidekick {\n  id: number;\n  name: string;\n}\n\n/** Sort Comparer to sort the entity collection by its name property */\nexport function sortByName(a: { name: string }, b: { name: string }): number {\n  return a.name.localeCompare(b.name);\n}\n\nfunction initializeCache(\n  eaFactory: EntityActionFactory,\n  store: Store<EntityCache>\n) {\n  let action: EntityAction;\n\n  action = eaFactory.create<Sidekick[]>('Sidekick', EntityOp.ADD_ALL, [\n    { id: 1, name: 'Bob' },\n    { id: 2, name: 'Sally' },\n  ]);\n  store.dispatch(action);\n\n  action = eaFactory.create<Hero[]>('Hero', EntityOp.ADD_ALL, [\n    { id: 1, name: 'Alpha', sidekickFk: 1 },\n    { id: 2, name: 'Beta', sidekickFk: 2 },\n    { id: 3, name: 'Gamma' }, // no sidekick\n  ]);\n  store.dispatch(action);\n\n  action = eaFactory.create<Battle[]>('Battle', EntityOp.ADD_ALL, [\n    { id: 100, heroFk: 1, name: 'Plains of Yon', won: true },\n    { id: 200, heroFk: 1, name: 'Yippee-kai-eh', won: false },\n    { id: 300, heroFk: 1, name: 'Yada Yada', won: true },\n    { id: 400, heroFk: 2, name: 'Tally-hoo', won: true },\n  ]);\n  store.dispatch(action);\n\n  action = eaFactory.create<Power[]>('Power', EntityOp.ADD_ALL, [\n    { id: 10, name: 'Speed' },\n    { id: 20, name: 'Strength' },\n    { id: 30, name: 'Invisibility' },\n  ]);\n  store.dispatch(action);\n\n  action = eaFactory.create<HeroPowerMap[]>('HeroPowerMap', EntityOp.ADD_ALL, [\n    { id: 99, heroFk: 1, powerFk: 10 },\n    { id: 98, heroFk: 1, powerFk: 20 },\n    { id: 97, heroFk: 1, powerFk: 30 },\n    { id: 96, heroFk: 2, powerFk: 30 },\n    // Gamma has no powers\n  ]);\n  store.dispatch(action);\n}\n// #endregion Test support\n"
  },
  {
    "path": "modules/data/spec/utils/default-pluralizer.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { DefaultPluralizer, Pluralizer, PLURAL_NAMES_TOKEN } from '../../';\n\ndescribe('DefaultPluralizer', () => {\n  describe('without plural names', () => {\n    let pluralizer: Pluralizer;\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        providers: [{ provide: Pluralizer, useClass: DefaultPluralizer }],\n      });\n\n      pluralizer = TestBed.inject(Pluralizer);\n    });\n    it('should turn \"Hero\" to \"Heros\" because no plural names map', () => {\n      // No map so 'Hero' gets default pluralization\n      expect(pluralizer.pluralize('Hero')).toBe('Heros');\n    });\n\n    it('should pluralize \"Villain\" which is not in plural names', () => {\n      // default pluralization with 's'\n      expect(pluralizer.pluralize('Villain')).toBe('Villains');\n    });\n\n    it('should pluralize \"consonant + y\" with \"-ies\"', () => {\n      expect(pluralizer.pluralize('Company')).toBe('Companies');\n    });\n\n    it('should pluralize \"vowel + y\" with \"-es\"', () => {\n      expect(pluralizer.pluralize('Cowboy')).toBe('Cowboys');\n    });\n\n    it('should pluralize \"Information\" as \"Information ', () => {\n      // known \"uncoumtables\"\n      expect(pluralizer.pluralize('Information')).toBe('Information');\n    });\n\n    it('should pluralize \"SkyBox\" which is not in plural names', () => {\n      // default pluralization of word ending in 'x'\n      expect(pluralizer.pluralize('SkyBox')).toBe('SkyBoxes');\n    });\n  });\n\n  describe('with injected plural names', () => {\n    let pluralizer: Pluralizer;\n\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        providers: [\n          // Demonstrate multi-provider\n          // Default would turn \"Hero\" into \"Heros\". Fix it.\n          {\n            provide: PLURAL_NAMES_TOKEN,\n            multi: true,\n            useValue: { Hero: 'Heroes' },\n          },\n          // \"Foots\" is deliberately wrong. Count on override in next provider\n          {\n            provide: PLURAL_NAMES_TOKEN,\n            multi: true,\n            useValue: { Foot: 'Foots' },\n          },\n          // Demonstrate overwrite of 'Foot' while setting multiple names\n          {\n            provide: PLURAL_NAMES_TOKEN,\n            multi: true,\n            useValue: { Foot: 'Feet', Person: 'People' },\n          },\n          { provide: Pluralizer, useClass: DefaultPluralizer },\n        ],\n      });\n\n      pluralizer = TestBed.inject(Pluralizer);\n    });\n\n    it('should pluralize \"Villain\" which is not in plural names', () => {\n      // default pluralization with 's'\n      expect(pluralizer.pluralize('Villain')).toBe('Villains');\n    });\n\n    it('should pluralize \"Hero\" using plural names', () => {\n      expect(pluralizer.pluralize('Hero')).toBe('Heroes');\n    });\n\n    it('should be case sensitive when using map', () => {\n      // uses default pluralization rule, not the names map\n      expect(pluralizer.pluralize('hero')).toBe('heros');\n    });\n\n    it('should pluralize \"Person\" using a plural name added to the map later', () => {\n      expect(pluralizer.pluralize('Person')).toBe('People');\n    });\n\n    it('later plural name map replaces earlier one', () => {\n      expect(pluralizer.pluralize('Foot')).toBe('Feet');\n    });\n  });\n});\n"
  },
  {
    "path": "modules/data/spec/utils/utils.spec.ts",
    "content": "import { CorrelationIdGenerator } from '../../';\n\ndescribe('Utilities (utils)', () => {\n  describe('CorrelationIdGenerator', () => {\n    const prefix = 'CRID';\n\n    it('generates a non-zero integer id', () => {\n      const generator = new CorrelationIdGenerator();\n      const id = generator.next();\n      expect(id).toBe(prefix + 1);\n    });\n\n    it('generates successive integer ids', () => {\n      const generator = new CorrelationIdGenerator();\n      const id1 = generator.next();\n      const id2 = generator.next();\n      expect(id1).toBe(prefix + 1);\n      expect(id2).toBe(prefix + 2);\n    });\n\n    it('new instance of the service has its own ids', () => {\n      const generator1 = new CorrelationIdGenerator();\n      const generator2 = new CorrelationIdGenerator();\n      const id1 = generator1.next();\n      const id2 = generator1.next();\n      const id3 = generator2.next();\n      expect(id1).toBe(prefix + 1);\n      expect(id2).toBe(prefix + 2);\n      expect(id3).toBe(prefix + 1);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/data/src/actions/entity-action-factory.ts",
    "content": "import { Injectable } from '@angular/core';\n\nimport { EntityOp } from './entity-op';\nimport {\n  EntityAction,\n  EntityActionOptions,\n  EntityActionPayload,\n} from './entity-action';\n@Injectable()\nexport class EntityActionFactory {\n  /**\n   * Create an EntityAction to perform an operation (op) for a particular entity type\n   * (entityName) with optional data and other optional flags\n   * @param entityName Name of the entity type\n   * @param entityOp Operation to perform (EntityOp)\n   * @param [data] data for the operation\n   * @param [options] additional options\n   */\n  create<P = any>(\n    entityName: string,\n    entityOp: EntityOp,\n    data?: P,\n    options?: EntityActionOptions\n  ): EntityAction<P>;\n\n  /**\n   * Create an EntityAction to perform an operation (op) for a particular entity type\n   * (entityName) with optional data and other optional flags\n   * @param payload Defines the EntityAction and its options\n   */\n  create<P = any>(payload: EntityActionPayload<P>): EntityAction<P>;\n\n  // polymorphic create for the two signatures\n  create<P = any>(\n    nameOrPayload: EntityActionPayload<P> | string,\n    entityOp?: EntityOp,\n    data?: P,\n    options?: EntityActionOptions\n  ): EntityAction<P> {\n    const payload: EntityActionPayload<P> =\n      typeof nameOrPayload === 'string'\n        ? ({\n            ...(options || {}),\n            entityName: nameOrPayload,\n            entityOp,\n            data,\n          } as EntityActionPayload<P>)\n        : nameOrPayload;\n    return this.createCore(payload);\n  }\n\n  /**\n   * Create an EntityAction to perform an operation (op) for a particular entity type\n   * (entityName) with optional data and other optional flags\n   * @param payload Defines the EntityAction and its options\n   */\n  protected createCore<P = any>(payload: EntityActionPayload<P>) {\n    const { entityName, entityOp, tag } = payload;\n    if (!entityName) {\n      throw new Error('Missing entity name for new action');\n    }\n    if (entityOp == null) {\n      throw new Error('Missing EntityOp for new action');\n    }\n    const type = this.formatActionType(entityOp, tag || entityName);\n    return { type, payload };\n  }\n\n  /**\n   * Create an EntityAction from another EntityAction, replacing properties with those from newPayload;\n   * @param from Source action that is the base for the new action\n   * @param newProperties New EntityAction properties that replace the source action properties\n   */\n  createFromAction<P = any>(\n    from: EntityAction,\n    newProperties: Partial<EntityActionPayload<P>>\n  ): EntityAction<P> {\n    return this.create({ ...from.payload, ...newProperties });\n  }\n\n  formatActionType(op: string, tag: string) {\n    return `[${tag}] ${op}`;\n    // return `${op} [${tag}]`.toUpperCase(); // example of an alternative\n  }\n}\n"
  },
  {
    "path": "modules/data/src/actions/entity-action-guard.ts",
    "content": "import { IdSelector, Update } from '@ngrx/entity';\n\nimport { EntityAction } from './entity-action';\nimport { UpdateResponseData } from '../actions/update-response-data';\n\n/**\n * Guard methods that ensure EntityAction payload is as expected.\n * Each method returns that payload if it passes the guard or\n * throws an error.\n */\nexport class EntityActionGuard<T> {\n  constructor(\n    private entityName: string,\n    private selectId: IdSelector<T>\n  ) {}\n\n  /** Throw if the action payload is not an entity with a valid key */\n  mustBeEntity(action: EntityAction<T>): T {\n    const data = this.extractData(action);\n    if (!data) {\n      return this.throwError(action, `should have a single entity.`);\n    }\n    const id = this.selectId(data);\n    if (this.isNotKeyType(id)) {\n      this.throwError(action, `has a missing or invalid entity key (id)`);\n    }\n    return data as T;\n  }\n\n  /** Throw if the action payload is not an array of entities with valid keys */\n  mustBeEntities(action: EntityAction<T[]>): T[] {\n    const data = this.extractData(action);\n    if (!Array.isArray(data)) {\n      return this.throwError(action, `should be an array of entities`);\n    }\n    data.forEach((entity, i) => {\n      const id = this.selectId(entity);\n      if (this.isNotKeyType(id)) {\n        const msg = `, item ${i + 1}, does not have a valid entity key (id)`;\n        this.throwError(action, msg);\n      }\n    });\n    return data;\n  }\n\n  /** Throw if the action payload is not a single, valid key */\n  mustBeKey(action: EntityAction<string | number>): string | number | never {\n    const data = this.extractData(action);\n    if (data === undefined) {\n      throw new Error(`should be a single entity key`);\n    }\n    if (this.isNotKeyType(data)) {\n      throw new Error(`is not a valid key (id)`);\n    }\n    return data;\n  }\n\n  /** Throw if the action payload is not an array of valid keys */\n  mustBeKeys(action: EntityAction<(string | number)[]>): (string | number)[] {\n    const data = this.extractData(action);\n    if (!Array.isArray(data)) {\n      return this.throwError(action, `should be an array of entity keys (id)`);\n    }\n    data.forEach((id, i) => {\n      if (this.isNotKeyType(id)) {\n        const msg = `${this.entityName} ', item ${\n          i + 1\n        }, is not a valid entity key (id)`;\n        this.throwError(action, msg);\n      }\n    });\n    return data;\n  }\n\n  /** Throw if the action payload is not an update with a valid key (id) */\n  mustBeUpdate(action: EntityAction<Update<T>>): Update<T> {\n    const data = this.extractData(action);\n    if (!data) {\n      return this.throwError(action, `should be a single entity update`);\n    }\n    const { id, changes } = data;\n    const id2 = this.selectId(changes as T);\n    if (this.isNotKeyType(id) || this.isNotKeyType(id2)) {\n      this.throwError(action, `has a missing or invalid entity key (id)`);\n    }\n    return data;\n  }\n\n  /** Throw if the action payload is not an array of updates with valid keys (ids) */\n  mustBeUpdates(action: EntityAction<Update<T>[]>): Update<T>[] {\n    const data = this.extractData(action);\n    if (!Array.isArray(data)) {\n      return this.throwError(action, `should be an array of entity updates`);\n    }\n    data.forEach((item, i) => {\n      const { id, changes } = item;\n      const id2 = this.selectId(changes as T);\n      if (this.isNotKeyType(id) || this.isNotKeyType(id2)) {\n        this.throwError(\n          action,\n          `, item ${i + 1}, has a missing or invalid entity key (id)`\n        );\n      }\n    });\n    return data;\n  }\n\n  /** Throw if the action payload is not an update response with a valid key (id) */\n  mustBeUpdateResponse(\n    action: EntityAction<UpdateResponseData<T>>\n  ): UpdateResponseData<T> {\n    const data = this.extractData(action);\n    if (!data) {\n      return this.throwError(action, `should be a single entity update`);\n    }\n    const { id, changes } = data;\n    const id2 = this.selectId(changes as T);\n    if (this.isNotKeyType(id) || this.isNotKeyType(id2)) {\n      this.throwError(action, `has a missing or invalid entity key (id)`);\n    }\n    return data;\n  }\n\n  /** Throw if the action payload is not an array of update responses with valid keys (ids) */\n  mustBeUpdateResponses(\n    action: EntityAction<UpdateResponseData<T>[]>\n  ): UpdateResponseData<T>[] {\n    const data = this.extractData(action);\n    if (!Array.isArray(data)) {\n      return this.throwError(action, `should be an array of entity updates`);\n    }\n    data.forEach((item, i) => {\n      const { id, changes } = item;\n      const id2 = this.selectId(changes as T);\n      if (this.isNotKeyType(id) || this.isNotKeyType(id2)) {\n        this.throwError(\n          action,\n          `, item ${i + 1}, has a missing or invalid entity key (id)`\n        );\n      }\n    });\n    return data;\n  }\n\n  private extractData<T>(action: EntityAction<T>) {\n    return action.payload && action.payload.data;\n  }\n\n  /** Return true if this key (id) is invalid */\n  private isNotKeyType(id: any) {\n    return typeof id !== 'string' && typeof id !== 'number';\n  }\n\n  private throwError(action: EntityAction, msg: string): never {\n    throw new Error(\n      `${this.entityName} EntityAction guard for \"${action.type}\": payload ${msg}`\n    );\n  }\n}\n"
  },
  {
    "path": "modules/data/src/actions/entity-action-operators.ts",
    "content": "import { OperatorFunction } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\nimport { EntityAction } from './entity-action';\nimport { EntityOp } from './entity-op';\nimport { flattenArgs } from '../utils/utilities';\n\n/**\n * Select actions concerning one of the allowed Entity operations\n * @param allowedEntityOps Entity operations (e.g, EntityOp.QUERY_ALL) whose actions should be selected\n * Example:\n * ```\n *  this.actions.pipe(ofEntityOp(EntityOp.QUERY_ALL, EntityOp.QUERY_MANY), ...)\n *  this.actions.pipe(ofEntityOp(...queryOps), ...)\n *  this.actions.pipe(ofEntityOp(queryOps), ...)\n *  this.actions.pipe(ofEntityOp(), ...) // any action with a defined `entityOp` property\n * ```\n */\nexport function ofEntityOp<T extends EntityAction>(\n  allowedOps: string[] | EntityOp[]\n): OperatorFunction<EntityAction, T>;\nexport function ofEntityOp<T extends EntityAction>(\n  ...allowedOps: (string | EntityOp)[]\n): OperatorFunction<EntityAction, T>;\nexport function ofEntityOp<T extends EntityAction>(\n  ...allowedEntityOps: any[]\n): OperatorFunction<EntityAction, T> {\n  const ops: string[] = flattenArgs(allowedEntityOps);\n  switch (ops.length) {\n    case 0:\n      return filter(\n        (action: EntityAction): action is T =>\n          action.payload && action.payload.entityOp != null\n      );\n    case 1:\n      const op = ops[0];\n      return filter(\n        (action: EntityAction): action is T =>\n          action.payload && op === action.payload.entityOp\n      );\n    default:\n      return filter((action: EntityAction): action is T => {\n        const entityOp = action.payload && action.payload.entityOp;\n        return entityOp && ops.some((o) => o === entityOp);\n      });\n  }\n}\n\n/**\n * Select actions concerning one of the allowed Entity types\n * @param allowedEntityNames Entity-type names (e.g, 'Hero') whose actions should be selected\n * Example:\n * ```\n *  this.actions.pipe(ofEntityType(), ...) // ayn EntityAction with a defined entity type property\n *  this.actions.pipe(ofEntityType('Hero'), ...) // EntityActions for the Hero entity\n *  this.actions.pipe(ofEntityType('Hero', 'Villain', 'Sidekick'), ...)\n *  this.actions.pipe(ofEntityType(...theChosen), ...)\n *  this.actions.pipe(ofEntityType(theChosen), ...)\n * ```\n */\nexport function ofEntityType<T extends EntityAction>(\n  allowedEntityNames?: string[]\n): OperatorFunction<EntityAction, T>;\nexport function ofEntityType<T extends EntityAction>(\n  ...allowedEntityNames: string[]\n): OperatorFunction<EntityAction, T>;\nexport function ofEntityType<T extends EntityAction>(\n  ...allowedEntityNames: any[]\n): OperatorFunction<EntityAction, T> {\n  const names: string[] = flattenArgs(allowedEntityNames);\n  switch (names.length) {\n    case 0:\n      return filter(\n        (action: EntityAction): action is T =>\n          action.payload && action.payload.entityName != null\n      );\n    case 1:\n      const name = names[0];\n      return filter(\n        (action: EntityAction): action is T =>\n          action.payload && name === action.payload.entityName\n      );\n    default:\n      return filter((action: EntityAction): action is T => {\n        const entityName = action.payload && action.payload.entityName;\n        return !!entityName && names.some((n) => n === entityName);\n      });\n  }\n}\n"
  },
  {
    "path": "modules/data/src/actions/entity-action.ts",
    "content": "import { Action } from '@ngrx/store';\n\nimport { EntityOp } from './entity-op';\nimport { MergeStrategy } from './merge-strategy';\nimport { HttpOptions } from '../dataservices/interfaces';\n\n/** Action concerning an entity collection. */\nexport interface EntityAction<P = any> extends Action {\n  readonly type: string;\n  readonly payload: EntityActionPayload<P>;\n}\n\n/** Options of an EntityAction */\nexport interface EntityActionOptions {\n  /** Correlate related EntityActions, particularly related saves. Must be serializable. */\n  readonly correlationId?: any;\n  /** True if should perform action optimistically (before server responds) */\n  readonly isOptimistic?: boolean;\n  readonly mergeStrategy?: MergeStrategy;\n  /** The tag to use in the action's type. The entityName if no tag specified. */\n  readonly tag?: string;\n  /** Options that will be passed to the dataService http request. Allows setting of Query Parameters and Headers */\n  readonly httpOptions?: HttpOptions;\n\n  // Mutable actions are BAD.\n  // Unfortunately, these mutations are the only way to stop @ngrx/effects\n  // from processing these actions.\n\n  /**\n   * The action was determined (usually by a reducer) to be in error.\n   * Downstream effects should not process but rather treat it as an error.\n   */\n  error?: Error;\n\n  /**\n   * Downstream effects should skip processing this action but should return\n   * an innocuous Observable<Action> of success.\n   */\n  skip?: boolean;\n}\n\n/** Payload of an EntityAction */\nexport interface EntityActionPayload<P = any> extends EntityActionOptions {\n  readonly entityName: string;\n  readonly entityOp: EntityOp;\n  readonly data?: P;\n}\n"
  },
  {
    "path": "modules/data/src/actions/entity-cache-action.ts",
    "content": "/*\n * Actions dedicated to the EntityCache as a whole\n */\nimport { Action } from '@ngrx/store';\n\nimport { ChangeSet, ChangeSetOperation } from './entity-cache-change-set';\nexport { ChangeSet, ChangeSetOperation } from './entity-cache-change-set';\n\nimport { DataServiceError } from '../dataservices/data-service-error';\nimport { EntityActionOptions } from '../actions/entity-action';\nimport { EntityCache } from '../reducers/entity-cache';\nimport { MergeStrategy } from '../actions/merge-strategy';\n\nexport enum EntityCacheAction {\n  CLEAR_COLLECTIONS = '@ngrx/data/entity-cache/clear-collections',\n  LOAD_COLLECTIONS = '@ngrx/data/entity-cache/load-collections',\n  MERGE_QUERY_SET = '@ngrx/data/entity-cache/merge-query-set',\n  SET_ENTITY_CACHE = '@ngrx/data/entity-cache/set-cache',\n\n  SAVE_ENTITIES = '@ngrx/data/entity-cache/save-entities',\n  SAVE_ENTITIES_CANCEL = '@ngrx/data/entity-cache/save-entities-cancel',\n  SAVE_ENTITIES_CANCELED = '@ngrx/data/entity-cache/save-entities-canceled',\n  SAVE_ENTITIES_ERROR = '@ngrx/data/entity-cache/save-entities-error',\n  SAVE_ENTITIES_SUCCESS = '@ngrx/data/entity-cache/save-entities-success',\n}\n\n/**\n * Hash of entities keyed by EntityCollection name,\n * typically the result of a query that returned results from a multi-collection query\n * that will be merged into an EntityCache via the `MergeQuerySet` action.\n */\nexport interface EntityCacheQuerySet {\n  [entityName: string]: any[];\n}\n\n/**\n * Clear the collections identified in the collectionSet.\n * @param [collections] Array of names of the collections to clear.\n * If empty array, does nothing. If no array, clear all collections.\n * @param [tag] Optional tag to identify the operation from the app perspective.\n */\nexport class ClearCollections implements Action {\n  readonly payload: { collections?: string[]; tag?: string };\n  readonly type = EntityCacheAction.CLEAR_COLLECTIONS;\n\n  constructor(collections?: string[], tag?: string) {\n    this.payload = { collections, tag };\n  }\n}\n\n/**\n * Create entity cache action that loads multiple entity collections at the same time.\n * before any selectors$ observables emit.\n * @param querySet The collections to load, typically the result of a query.\n * @param [tag] Optional tag to identify the operation from the app perspective.\n * in the form of a map of entity collections.\n */\nexport class LoadCollections implements Action {\n  readonly payload: { collections: EntityCacheQuerySet; tag?: string };\n  readonly type = EntityCacheAction.LOAD_COLLECTIONS;\n\n  constructor(collections: EntityCacheQuerySet, tag?: string) {\n    this.payload = { collections, tag };\n  }\n}\n\n/**\n * Create entity cache action that merges entities from a query result\n * that returned entities from multiple collections.\n * Corresponding entity cache reducer should add and update all collections\n * at the same time, before any selectors$ observables emit.\n * @param querySet The result of the query in the form of a map of entity collections.\n * These are the entity data to merge into the respective collections.\n * @param mergeStrategy How to merge a queried entity when it is already in the collection.\n * The default is MergeStrategy.PreserveChanges\n * @param [tag] Optional tag to identify the operation from the app perspective.\n */\nexport class MergeQuerySet implements Action {\n  readonly payload: {\n    querySet: EntityCacheQuerySet;\n    mergeStrategy?: MergeStrategy;\n    tag?: string;\n  };\n\n  readonly type = EntityCacheAction.MERGE_QUERY_SET;\n\n  constructor(\n    querySet: EntityCacheQuerySet,\n    mergeStrategy?: MergeStrategy,\n    tag?: string\n  ) {\n    this.payload = {\n      querySet,\n      mergeStrategy:\n        mergeStrategy === null ? MergeStrategy.PreserveChanges : mergeStrategy,\n      tag,\n    };\n  }\n}\n\n/**\n * Create entity cache action for replacing the entire entity cache.\n * Dangerous because brute force but useful as when re-hydrating an EntityCache\n * from local browser storage when the application launches.\n * @param cache New state of the entity cache\n * @param [tag] Optional tag to identify the operation from the app perspective.\n */\nexport class SetEntityCache implements Action {\n  readonly payload: { cache: EntityCache; tag?: string };\n  readonly type = EntityCacheAction.SET_ENTITY_CACHE;\n\n  constructor(\n    public readonly cache: EntityCache,\n    tag?: string\n  ) {\n    this.payload = { cache, tag };\n  }\n}\n\n// #region SaveEntities\nexport class SaveEntities implements Action {\n  readonly payload: {\n    readonly changeSet: ChangeSet;\n    readonly url: string;\n    readonly correlationId?: any;\n    readonly isOptimistic?: boolean;\n    readonly mergeStrategy?: MergeStrategy;\n    readonly tag?: string;\n    error?: Error;\n    skip?: boolean; // not used\n  };\n  readonly type = EntityCacheAction.SAVE_ENTITIES;\n\n  constructor(\n    changeSet: ChangeSet,\n    url: string,\n    options?: EntityActionOptions\n  ) {\n    options = options || {};\n    if (changeSet) {\n      changeSet.tag = changeSet.tag || options.tag;\n    }\n    this.payload = { changeSet, url, ...options, tag: changeSet.tag };\n  }\n}\n\nexport class SaveEntitiesCancel implements Action {\n  readonly payload: {\n    readonly correlationId: any;\n    readonly reason?: string;\n    readonly entityNames?: string[];\n    readonly tag?: string;\n  };\n  readonly type = EntityCacheAction.SAVE_ENTITIES_CANCEL;\n\n  constructor(\n    correlationId: any,\n    reason?: string,\n    entityNames?: string[],\n    tag?: string\n  ) {\n    this.payload = { correlationId, reason, entityNames, tag };\n  }\n}\n\nexport class SaveEntitiesCanceled implements Action {\n  readonly payload: {\n    readonly correlationId: any;\n    readonly reason?: string;\n    readonly tag?: string;\n  };\n  readonly type = EntityCacheAction.SAVE_ENTITIES_CANCELED;\n\n  constructor(correlationId: any, reason?: string, tag?: string) {\n    this.payload = { correlationId, reason, tag };\n  }\n}\n\nexport class SaveEntitiesError {\n  readonly payload: {\n    readonly error: DataServiceError;\n    readonly originalAction: SaveEntities;\n    readonly correlationId: any;\n  };\n  readonly type = EntityCacheAction.SAVE_ENTITIES_ERROR;\n  constructor(error: DataServiceError, originalAction: SaveEntities) {\n    const correlationId = originalAction.payload.correlationId;\n    this.payload = { error, originalAction, correlationId };\n  }\n}\n\nexport class SaveEntitiesSuccess implements Action {\n  readonly payload: {\n    readonly changeSet: ChangeSet;\n    readonly url: string;\n    readonly correlationId?: any;\n    readonly isOptimistic?: boolean;\n    readonly mergeStrategy?: MergeStrategy;\n    readonly tag?: string;\n    error?: Error;\n    skip?: boolean; // not used\n  };\n  readonly type = EntityCacheAction.SAVE_ENTITIES_SUCCESS;\n\n  constructor(\n    changeSet: ChangeSet,\n    url: string,\n    options?: EntityActionOptions\n  ) {\n    options = options || {};\n    if (changeSet) {\n      changeSet.tag = changeSet.tag || options.tag;\n    }\n    this.payload = { changeSet, url, ...options, tag: changeSet.tag };\n  }\n}\n// #endregion SaveEntities\n"
  },
  {
    "path": "modules/data/src/actions/entity-cache-change-set.ts",
    "content": "import { Update } from '@ngrx/entity';\n\nexport enum ChangeSetOperation {\n  Add = 'Add',\n  Delete = 'Delete',\n  Update = 'Update',\n  Upsert = 'Upsert',\n}\nexport interface ChangeSetAdd<T = any> {\n  op: ChangeSetOperation.Add;\n  entityName: string;\n  entities: T[];\n}\n\nexport interface ChangeSetDelete {\n  op: ChangeSetOperation.Delete;\n  entityName: string;\n  entities: string[] | number[];\n}\n\nexport interface ChangeSetUpdate<T = any> {\n  op: ChangeSetOperation.Update;\n  entityName: string;\n  entities: Update<T>[];\n}\n\nexport interface ChangeSetUpsert<T = any> {\n  op: ChangeSetOperation.Upsert;\n  entityName: string;\n  entities: T[];\n}\n\n/**\n * A entities of a single entity type, which are changed in the same way by a ChangeSetOperation\n */\nexport type ChangeSetItem =\n  | ChangeSetAdd\n  | ChangeSetDelete\n  | ChangeSetUpdate\n  | ChangeSetUpsert;\n\n/*\n * A set of entity Changes, typically to be saved.\n */\nexport interface ChangeSet<T = any> {\n  /** An array of ChangeSetItems to be processed in the array order */\n  changes: ChangeSetItem[];\n\n  /**\n   * An arbitrary, serializable object that should travel with the ChangeSet.\n   * Meaningful to the ChangeSet producer and consumer. Ignored by @ngrx/data.\n   */\n  extras?: T;\n\n  /** An arbitrary string, identifying the ChangeSet and perhaps its purpose */\n  tag?: string;\n}\n\n/**\n * Factory to create a ChangeSetItem for a ChangeSetOperation\n */\nexport class ChangeSetItemFactory {\n  /** Create the ChangeSetAdd for new entities of the given entity type */\n  add<T>(entityName: string, entities: T | T[]): ChangeSetAdd<T> {\n    entities = Array.isArray(entities) ? entities : entities ? [entities] : [];\n    return { entityName, op: ChangeSetOperation.Add, entities };\n  }\n\n  /** Create the ChangeSetDelete for primary keys of the given entity type */\n  delete(\n    entityName: string,\n    keys: number | number[] | string | string[]\n  ): ChangeSetDelete {\n    const ids = Array.isArray(keys)\n      ? keys\n      : keys\n        ? ([keys] as string[] | number[])\n        : [];\n    return { entityName, op: ChangeSetOperation.Delete, entities: ids };\n  }\n\n  /** Create the ChangeSetUpdate for Updates of entities of the given entity type */\n  update<T extends { id: string | number }>(\n    entityName: string,\n    updates: Update<T> | Update<T>[]\n  ): ChangeSetUpdate<T> {\n    updates = Array.isArray(updates) ? updates : updates ? [updates] : [];\n    return { entityName, op: ChangeSetOperation.Update, entities: updates };\n  }\n\n  /** Create the ChangeSetUpsert for new or existing entities of the given entity type */\n  upsert<T>(entityName: string, entities: T | T[]): ChangeSetUpsert<T> {\n    entities = Array.isArray(entities) ? entities : entities ? [entities] : [];\n    return { entityName, op: ChangeSetOperation.Upsert, entities };\n  }\n}\n\n/**\n * Instance of a factory to create a ChangeSetItem for a ChangeSetOperation\n */\nexport const changeSetItemFactory = new ChangeSetItemFactory();\n\n/**\n * Return ChangeSet after filtering out null and empty ChangeSetItems.\n * @param changeSet ChangeSet with changes to filter\n */\nexport function excludeEmptyChangeSetItems(changeSet: ChangeSet): ChangeSet {\n  changeSet = changeSet && changeSet.changes ? changeSet : { changes: [] };\n  const changes = changeSet.changes.filter(\n    (c) => c != null && c.entities && c.entities.length > 0\n  );\n  return { ...changeSet, changes };\n}\n"
  },
  {
    "path": "modules/data/src/actions/entity-op.ts",
    "content": "// Ensure that these suffix values and the EntityOp suffixes match\n// Cannot do that programmatically.\n\n/** General purpose entity action operations, good for any entity type */\nexport enum EntityOp {\n  // Persistance operations\n  CANCEL_PERSIST = '@ngrx/data/cancel-persist',\n  CANCELED_PERSIST = '@ngrx/data/canceled-persist',\n\n  QUERY_ALL = '@ngrx/data/query-all',\n  QUERY_ALL_SUCCESS = '@ngrx/data/query-all/success',\n  QUERY_ALL_ERROR = '@ngrx/data/query-all/error',\n\n  QUERY_LOAD = '@ngrx/data/query-load',\n  QUERY_LOAD_SUCCESS = '@ngrx/data/query-load/success',\n  QUERY_LOAD_ERROR = '@ngrx/data/query-load/error',\n\n  QUERY_MANY = '@ngrx/data/query-many',\n  QUERY_MANY_SUCCESS = '@ngrx/data/query-many/success',\n  QUERY_MANY_ERROR = '@ngrx/data/query-many/error',\n\n  QUERY_BY_KEY = '@ngrx/data/query-by-key',\n  QUERY_BY_KEY_SUCCESS = '@ngrx/data/query-by-key/success',\n  QUERY_BY_KEY_ERROR = '@ngrx/data/query-by-key/error',\n\n  SAVE_ADD_MANY = '@ngrx/data/save/add-many',\n  SAVE_ADD_MANY_ERROR = '@ngrx/data/save/add-many/error',\n  SAVE_ADD_MANY_SUCCESS = '@ngrx/data/save/add-many/success',\n\n  SAVE_ADD_ONE = '@ngrx/data/save/add-one',\n  SAVE_ADD_ONE_ERROR = '@ngrx/data/save/add-one/error',\n  SAVE_ADD_ONE_SUCCESS = '@ngrx/data/save/add-one/success',\n\n  SAVE_DELETE_MANY = '@ngrx/data/save/delete-many',\n  SAVE_DELETE_MANY_SUCCESS = '@ngrx/data/save/delete-many/success',\n  SAVE_DELETE_MANY_ERROR = '@ngrx/data/save/delete-many/error',\n\n  SAVE_DELETE_ONE = '@ngrx/data/save/delete-one',\n  SAVE_DELETE_ONE_SUCCESS = '@ngrx/data/save/delete-one/success',\n  SAVE_DELETE_ONE_ERROR = '@ngrx/data/save/delete-one/error',\n\n  SAVE_UPDATE_MANY = '@ngrx/data/save/update-many',\n  SAVE_UPDATE_MANY_SUCCESS = '@ngrx/data/save/update-many/success',\n  SAVE_UPDATE_MANY_ERROR = '@ngrx/data/save/update-many/error',\n\n  SAVE_UPDATE_ONE = '@ngrx/data/save/update-one',\n  SAVE_UPDATE_ONE_SUCCESS = '@ngrx/data/save/update-one/success',\n  SAVE_UPDATE_ONE_ERROR = '@ngrx/data/save/update-one/error',\n\n  // Use only if the server supports upsert;\n  SAVE_UPSERT_MANY = '@ngrx/data/save/upsert-many',\n  SAVE_UPSERT_MANY_SUCCESS = '@ngrx/data/save/upsert-many/success',\n  SAVE_UPSERT_MANY_ERROR = '@ngrx/data/save/upsert-many/error',\n\n  // Use only if the server supports upsert;\n  SAVE_UPSERT_ONE = '@ngrx/data/save/upsert-one',\n  SAVE_UPSERT_ONE_SUCCESS = '@ngrx/data/save/upsert-one/success',\n  SAVE_UPSERT_ONE_ERROR = '@ngrx/data/save/upsert-one/error',\n\n  // Cache operations\n  ADD_ALL = '@ngrx/data/add-all',\n  ADD_MANY = '@ngrx/data/add-many',\n  ADD_ONE = '@ngrx/data/add-one',\n  REMOVE_ALL = '@ngrx/data/remove-all',\n  REMOVE_MANY = '@ngrx/data/remove-many',\n  REMOVE_ONE = '@ngrx/data/remove-one',\n  UPDATE_MANY = '@ngrx/data/update-many',\n  UPDATE_ONE = '@ngrx/data/update-one',\n  UPSERT_MANY = '@ngrx/data/upsert-many',\n  UPSERT_ONE = '@ngrx/data/upsert-one',\n\n  COMMIT_ALL = '@ngrx/data/commit-all',\n  COMMIT_MANY = '@ngrx/data/commit-many',\n  COMMIT_ONE = '@ngrx/data/commit-one',\n  UNDO_ALL = '@ngrx/data/undo-all',\n  UNDO_MANY = '@ngrx/data/undo-many',\n  UNDO_ONE = '@ngrx/data/undo-one',\n\n  SET_CHANGE_STATE = '@ngrx/data/set-change-state',\n  SET_COLLECTION = '@ngrx/data/set-collection',\n  SET_FILTER = '@ngrx/data/set-filter',\n  SET_LOADED = '@ngrx/data/set-loaded',\n  SET_LOADING = '@ngrx/data/set-loading',\n}\n\n/** \"Success\" suffix appended to EntityOps that are successful.*/\nexport const OP_SUCCESS = '/success';\n\n/** \"Error\" suffix appended to EntityOps that have failed.*/\nexport const OP_ERROR = '/error';\n\n/** Make the error EntityOp corresponding to the given EntityOp */\nexport function makeErrorOp(op: EntityOp): EntityOp {\n  return <EntityOp>(op + OP_ERROR);\n}\n\n/** Make the success EntityOp corresponding to the given EntityOp */\nexport function makeSuccessOp(op: EntityOp): EntityOp {\n  return <EntityOp>(op + OP_SUCCESS);\n}\n"
  },
  {
    "path": "modules/data/src/actions/merge-strategy.ts",
    "content": "/** How to merge an entity, after query or save, when the corresponding entity in the collection has unsaved changes. */\nexport enum MergeStrategy {\n  /**\n   * Update the collection entities and ignore all change tracking for this operation.\n   * Each entity's `changeState` is untouched.\n   */\n  IgnoreChanges,\n  /**\n   * Updates current values for unchanged entities.\n   * For each changed entity it preserves the current value and overwrites the `originalValue` with the merge entity.\n   * This is the query-success default.\n   */\n  PreserveChanges,\n  /**\n   * Replace the current collection entities.\n   * For each merged entity it discards the `changeState` and sets the `changeType` to \"unchanged\".\n   * This is the save-success default.\n   */\n  OverwriteChanges,\n}\n"
  },
  {
    "path": "modules/data/src/actions/update-response-data.ts",
    "content": "/**\n * Data returned in an EntityAction from the EntityEffects for SAVE_UPDATE_ONE_SUCCESS.\n * Effectively extends Update<T> with a 'changed' flag.\n * The is true if the server sent back changes to the entity data after update.\n * Such changes must be in the entity data in changes property.\n * Default is false (server did not return entity data; assume it changed nothing).\n * See EntityEffects.\n */\nexport interface UpdateResponseData<T> {\n  /** Original key (id) of the entity */\n  id: number | string;\n  /** Entity update data. Should include the key (original or changed) */\n  changes: Partial<T>;\n  /**\n   * Whether the server made additional changes after processing the update.\n   * Such additional changes should be in the 'changes' object.\n   * Default is false\n   */\n  changed?: boolean;\n}\n"
  },
  {
    "path": "modules/data/src/dataservices/data-service-error.ts",
    "content": "import { EntityAction } from '../actions/entity-action';\nimport { RequestData } from './interfaces';\n\n/**\n * Error from a DataService\n * The source error either comes from a failed HTTP response or was thrown within the service.\n * @param error the HttpErrorResponse or the error thrown by the service\n * @param requestData the HTTP request information such as the method and the url.\n */\nexport class DataServiceError extends Error {\n  constructor(\n    public error: any,\n    public requestData: RequestData | null\n  ) {\n    super(\n      typeof error === 'string' ? error : (extractMessage(error) ?? undefined)\n    );\n    this.name = this.constructor.name;\n  }\n}\n\n// Many ways the error can be shaped. These are the ways we recognize.\nfunction extractMessage(sourceError: any): string | null {\n  const { error, body, message } = sourceError;\n  let errMessage: string | null = null;\n  if (error) {\n    // prefer HttpErrorResponse.error to its message property\n    errMessage = typeof error === 'string' ? error : error.message;\n  } else if (message) {\n    errMessage = message;\n  } else if (body) {\n    // try the body if no error or message property\n    errMessage = typeof body === 'string' ? body : body.error;\n  }\n\n  return typeof errMessage === 'string'\n    ? errMessage\n    : errMessage\n      ? JSON.stringify(errMessage)\n      : null;\n}\n\n/** Payload for an EntityAction data service error such as QUERY_ALL_ERROR */\nexport interface EntityActionDataServiceError {\n  error: DataServiceError;\n  originalAction: EntityAction;\n}\n"
  },
  {
    "path": "modules/data/src/dataservices/default-data-service-config.ts",
    "content": "import { EntityHttpResourceUrls } from './http-url-generator';\n\n/**\n * Optional configuration settings for an entity collection data service\n * such as the `DefaultDataService<T>`.\n */\nexport abstract class DefaultDataServiceConfig {\n  /**\n   * root path of the web api.  may also include protocol, domain, and port\n   * for remote api, e.g.: `'https://api-domain.com:8000/api/v1'` (default: 'api')\n   */\n  root?: string;\n  /**\n   * Known entity HttpResourceUrls.\n   * HttpUrlGenerator will create these URLs for entity types not listed here.\n   */\n  entityHttpResourceUrls?: EntityHttpResourceUrls;\n  /** Is a DELETE 404 really OK? (default: true) */\n  delete404OK?: boolean;\n  /** Simulate GET latency in a demo (default: 0) */\n  getDelay?: number;\n  /** Simulate save method (PUT/POST/DELETE) latency in a demo (default: 0) */\n  saveDelay?: number;\n  /** request timeout in MS (default: 0)*/\n  timeout?: number; //\n  /** to keep leading & trailing slashes or not; false by default */\n  trailingSlashEndpoints?: boolean;\n}\n"
  },
  {
    "path": "modules/data/src/dataservices/default-data.service.ts",
    "content": "import { Injectable, isDevMode, Optional } from '@angular/core';\nimport {\n  HttpClient,\n  HttpErrorResponse,\n  HttpHeaders,\n  HttpParams,\n} from '@angular/common/http';\n\nimport { Observable, of, throwError } from 'rxjs';\nimport { catchError, delay, map, timeout } from 'rxjs/operators';\n\nimport { Update } from '@ngrx/entity';\n\nimport { DataServiceError } from './data-service-error';\nimport { DefaultDataServiceConfig } from './default-data-service-config';\nimport {\n  EntityCollectionDataService,\n  HttpMethods,\n  HttpOptions,\n  QueryParams,\n  RequestData,\n} from './interfaces';\nimport { HttpUrlGenerator } from './http-url-generator';\n\n/**\n * A basic, generic entity data service\n * suitable for persistence of most entities.\n * Assumes a common REST-y web API\n */\nexport class DefaultDataService<T> implements EntityCollectionDataService<T> {\n  protected _name: string;\n  protected delete404OK: boolean;\n  protected entityName: string;\n  protected entityUrl: string;\n  protected entitiesUrl: string;\n  protected getDelay = 0;\n  protected saveDelay = 0;\n  protected timeout = 0;\n  protected trailingSlashEndpoints = false;\n\n  get name() {\n    return this._name;\n  }\n\n  constructor(\n    entityName: string,\n    protected http: HttpClient,\n    protected httpUrlGenerator: HttpUrlGenerator,\n    config?: DefaultDataServiceConfig\n  ) {\n    this._name = `${entityName} DefaultDataService`;\n    this.entityName = entityName;\n    const {\n      root = 'api',\n      delete404OK = true,\n      getDelay = 0,\n      saveDelay = 0,\n      timeout: to = 0,\n      trailingSlashEndpoints = false,\n    } = config || {};\n    this.delete404OK = delete404OK;\n    this.entityUrl = httpUrlGenerator.entityResource(\n      entityName,\n      root,\n      trailingSlashEndpoints\n    );\n    this.entitiesUrl = httpUrlGenerator.collectionResource(entityName, root);\n    this.getDelay = getDelay;\n    this.saveDelay = saveDelay;\n    this.timeout = to;\n  }\n\n  add(entity: T, options?: HttpOptions): Observable<T> {\n    const entityOrError =\n      entity || new Error(`No \"${this.entityName}\" entity to add`);\n    return this.execute('POST', this.entityUrl, entityOrError, null, options);\n  }\n\n  delete(\n    key: number | string,\n    options?: HttpOptions\n  ): Observable<number | string> {\n    let err: Error | undefined;\n    if (key == null) {\n      err = new Error(`No \"${this.entityName}\" key to delete`);\n    }\n\n    return this.execute(\n      'DELETE',\n      this.entityUrl + key,\n      err,\n      null,\n      options\n    ).pipe(\n      // forward the id of deleted entity as the result of the HTTP DELETE\n      map((result) => key as number | string)\n    );\n  }\n\n  getAll(options?: HttpOptions): Observable<T[]> {\n    return this.execute('GET', this.entitiesUrl, null, null, options);\n  }\n\n  getById(key: number | string, options?: HttpOptions): Observable<T> {\n    let err: Error | undefined;\n    if (key == null) {\n      err = new Error(`No \"${this.entityName}\" key to get`);\n    }\n    return this.execute('GET', this.entityUrl + key, err, null, options);\n  }\n\n  getWithQuery(\n    queryParams: QueryParams | string | undefined,\n    options?: HttpOptions\n  ): Observable<T[]> {\n    const qParams =\n      typeof queryParams === 'string'\n        ? { fromString: queryParams }\n        : { fromObject: queryParams };\n    const params = new HttpParams(qParams);\n\n    return this.execute(\n      'GET',\n      this.entitiesUrl,\n      undefined,\n      { params },\n      options\n    );\n  }\n\n  update(update: Update<T>, options?: HttpOptions): Observable<T> {\n    const id = update && update.id;\n    const updateOrError =\n      id == null\n        ? new Error(`No \"${this.entityName}\" update data or id`)\n        : update.changes;\n    return this.execute(\n      'PUT',\n      this.entityUrl + id,\n      updateOrError,\n      null,\n      options\n    );\n  }\n\n  // Important! Only call if the backend service supports upserts as a POST to the target URL\n  upsert(entity: T, options?: HttpOptions): Observable<T> {\n    const entityOrError =\n      entity || new Error(`No \"${this.entityName}\" entity to upsert`);\n    return this.execute('POST', this.entityUrl, entityOrError, null, options);\n  }\n\n  protected execute(\n    method: HttpMethods,\n    url: string,\n    data?: any, // data, error, or undefined/null\n    options?: any, // options or undefined/null\n    httpOptions?: HttpOptions // these override any options passed via options\n  ): Observable<any> {\n    let entityActionHttpClientOptions: any = undefined;\n    if (httpOptions) {\n      entityActionHttpClientOptions = {\n        headers: httpOptions?.httpHeaders\n          ? new HttpHeaders(httpOptions?.httpHeaders)\n          : undefined,\n        params: httpOptions?.httpParams\n          ? new HttpParams(httpOptions?.httpParams)\n          : undefined,\n      };\n    }\n\n    // Now we may have:\n    // options: containing headers, params, or any other allowed http options already in angular's api\n    // entityActionHttpClientOptions: headers and params in angular's api\n\n    // We therefore need to merge these so that the action ones override the\n    // existing keys where applicable.\n\n    // If any options have been specified, pass them to http client. Note\n    // the new http options, if specified, will override any options passed\n    // from the deprecated options parameter\n    let mergedOptions: any = undefined;\n    if (options || entityActionHttpClientOptions) {\n      if (isDevMode() && options && entityActionHttpClientOptions) {\n        console.warn(\n          '@ngrx/data: options.httpParams will be merged with queryParams when both are are provided to getWithQuery(). In the event of a conflict HttpOptions.httpParams will override queryParams`. The queryParams parameter of getWithQuery() will be removed in next major release.'\n        );\n      }\n\n      mergedOptions = {\n        ...options,\n        headers: entityActionHttpClientOptions?.headers ?? options?.headers,\n        params: entityActionHttpClientOptions?.params ?? options?.params,\n      };\n    }\n\n    const req: RequestData = {\n      method,\n      url,\n      data,\n      options: mergedOptions,\n    };\n\n    if (data instanceof Error) {\n      return this.handleError(req)(data);\n    }\n\n    let result$: Observable<ArrayBuffer>;\n\n    switch (method) {\n      case 'DELETE': {\n        result$ = this.http.delete(url, mergedOptions);\n        if (this.saveDelay) {\n          result$ = result$.pipe(delay(this.saveDelay));\n        }\n        break;\n      }\n      case 'GET': {\n        result$ = this.http.get(url, mergedOptions);\n        if (this.getDelay) {\n          result$ = result$.pipe(delay(this.getDelay));\n        }\n        break;\n      }\n      case 'POST': {\n        result$ = this.http.post(url, data, mergedOptions);\n        if (this.saveDelay) {\n          result$ = result$.pipe(delay(this.saveDelay));\n        }\n        break;\n      }\n      // N.B.: It must return an Update<T>\n      case 'PUT': {\n        result$ = this.http.put(url, data, mergedOptions);\n        if (this.saveDelay) {\n          result$ = result$.pipe(delay(this.saveDelay));\n        }\n        break;\n      }\n      default: {\n        const error = new Error('Unimplemented HTTP method, ' + method);\n        result$ = throwError(error);\n      }\n    }\n    if (this.timeout) {\n      result$ = result$.pipe(timeout(this.timeout + this.saveDelay));\n    }\n    return result$.pipe(catchError(this.handleError(req)));\n  }\n\n  private handleError(reqData: RequestData) {\n    return (err: any) => {\n      const ok = this.handleDelete404(err, reqData);\n      if (ok) {\n        return ok;\n      }\n      const error = new DataServiceError(err, reqData);\n      return throwError(error);\n    };\n  }\n\n  private handleDelete404(error: HttpErrorResponse, reqData: RequestData) {\n    if (\n      error.status === 404 &&\n      reqData.method === 'DELETE' &&\n      this.delete404OK\n    ) {\n      return of({});\n    }\n    return undefined;\n  }\n}\n\n/**\n * Create a basic, generic entity data service\n * suitable for persistence of most entities.\n * Assumes a common REST-y web API\n */\n@Injectable()\nexport class DefaultDataServiceFactory {\n  constructor(\n    protected http: HttpClient,\n    protected httpUrlGenerator: HttpUrlGenerator,\n    @Optional() protected config?: DefaultDataServiceConfig\n  ) {\n    config = config || {};\n    httpUrlGenerator.registerHttpResourceUrls(config.entityHttpResourceUrls);\n  }\n\n  /**\n   * Create a default {EntityCollectionDataService} for the given entity type\n   * @param entityName {string} Name of the entity type for this data service\n   */\n  create<T>(entityName: string): EntityCollectionDataService<T> {\n    return new DefaultDataService<T>(\n      entityName,\n      this.http,\n      this.httpUrlGenerator,\n      this.config\n    );\n  }\n}\n"
  },
  {
    "path": "modules/data/src/dataservices/entity-cache-data.service.ts",
    "content": "import { Injectable, Optional } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\n\nimport { Observable, throwError } from 'rxjs';\nimport { catchError, delay, map, timeout } from 'rxjs/operators';\n\nimport { IdSelector } from '@ngrx/entity';\n\nimport {\n  ChangeSetOperation,\n  ChangeSet,\n  ChangeSetItem,\n  ChangeSetUpdate,\n  excludeEmptyChangeSetItems,\n} from '../actions/entity-cache-change-set';\nimport { DataServiceError } from './data-service-error';\nimport { DefaultDataServiceConfig } from './default-data-service-config';\nimport { EntityDefinitionService } from '../entity-metadata/entity-definition.service';\nimport { RequestData } from './interfaces';\n\nconst updateOp = ChangeSetOperation.Update;\n\n/**\n * Default data service for making remote service calls targeting the entire EntityCache.\n * See EntityDataService for services that target a single EntityCollection\n */\n@Injectable()\nexport class EntityCacheDataService {\n  protected idSelectors: { [entityName: string]: IdSelector<any> } = {};\n  protected saveDelay = 0;\n  protected timeout = 0;\n\n  constructor(\n    protected entityDefinitionService: EntityDefinitionService,\n    protected http: HttpClient,\n    @Optional() config?: DefaultDataServiceConfig\n  ) {\n    const { saveDelay = 0, timeout: to = 0 } = config || {};\n    this.saveDelay = saveDelay;\n    this.timeout = to;\n  }\n\n  /**\n   * Save changes to multiple entities across one or more entity collections.\n   * Server endpoint must understand the essential SaveEntities protocol,\n   * in particular the ChangeSet interface (except for Update<T>).\n   * This implementation extracts the entity changes from a ChangeSet Update<T>[] and sends those.\n   * It then reconstructs Update<T>[] in the returned observable result.\n   * @param changeSet  An array of SaveEntityItems.\n   * Each SaveEntityItem describe a change operation for one or more entities of a single collection,\n   * known by its 'entityName'.\n   * @param url The server endpoint that receives this request.\n   */\n  saveEntities(changeSet: ChangeSet, url: string): Observable<ChangeSet> {\n    changeSet = this.filterChangeSet(changeSet);\n    // Assume server doesn't understand @ngrx/entity Update<T> structure;\n    // Extract the entity changes from the Update<T>[] and restore on the return from server\n    changeSet = this.flattenUpdates(changeSet);\n\n    let result$: Observable<ChangeSet> = this.http\n      .post<ChangeSet>(url, changeSet)\n      .pipe(\n        map((result) => this.restoreUpdates(result)),\n        catchError(this.handleError({ method: 'POST', url, data: changeSet }))\n      );\n\n    if (this.timeout) {\n      result$ = result$.pipe(timeout(this.timeout));\n    }\n\n    if (this.saveDelay) {\n      result$ = result$.pipe(delay(this.saveDelay));\n    }\n\n    return result$;\n  }\n\n  // #region helpers\n  protected handleError(reqData: RequestData) {\n    return (err: any) => {\n      const error = new DataServiceError(err, reqData);\n      return throwError(error);\n    };\n  }\n\n  /**\n   * Filter changeSet to remove unwanted ChangeSetItems.\n   * This implementation excludes null and empty ChangeSetItems.\n   * @param changeSet ChangeSet with changes to filter\n   */\n  protected filterChangeSet(changeSet: ChangeSet): ChangeSet {\n    return excludeEmptyChangeSetItems(changeSet);\n  }\n\n  /**\n   * Convert the entities in update changes from @ngrx Update<T> structure to just T.\n   * Reverse of restoreUpdates().\n   */\n  protected flattenUpdates(changeSet: ChangeSet): ChangeSet {\n    let changes = changeSet.changes;\n    if (changes.length === 0) {\n      return changeSet;\n    }\n    let hasMutated = false;\n    changes = changes.map((item) => {\n      if (item.op === updateOp && item.entities.length > 0) {\n        hasMutated = true;\n        return {\n          ...item,\n          entities: (item as ChangeSetUpdate).entities.map((u) => u.changes),\n        };\n      } else {\n        return item;\n      }\n    }) as ChangeSetItem[];\n    return hasMutated ? { ...changeSet, changes } : changeSet;\n  }\n\n  /**\n   * Convert the flattened T entities in update changes back to @ngrx Update<T> structures.\n   * Reverse of flattenUpdates().\n   */\n  protected restoreUpdates(changeSet: ChangeSet): ChangeSet {\n    if (changeSet == null) {\n      // Nothing? Server probably responded with 204 - No Content because it made no changes to the inserted or updated entities\n      return changeSet;\n    }\n    let changes = changeSet.changes;\n    if (changes.length === 0) {\n      return changeSet;\n    }\n    let hasMutated = false;\n    changes = changes.map((item) => {\n      if (item.op === updateOp) {\n        // These are entities, not Updates; convert back to Updates\n        hasMutated = true;\n        const selectId = this.getIdSelector(item.entityName);\n        return {\n          ...item,\n          entities: item.entities.map((u: any) => ({\n            id: selectId(u),\n            changes: u,\n          })),\n        } as ChangeSetUpdate;\n      } else {\n        return item;\n      }\n    }) as ChangeSetItem[];\n    return hasMutated ? { ...changeSet, changes } : changeSet;\n  }\n\n  /**\n   * Get the id (primary key) selector function for an entity type\n   * @param entityName name of the entity type\n   */\n  protected getIdSelector(entityName: string) {\n    let idSelector = this.idSelectors[entityName];\n    if (!idSelector) {\n      idSelector =\n        this.entityDefinitionService.getDefinition(entityName).selectId;\n      this.idSelectors[entityName] = idSelector;\n    }\n    return idSelector;\n  }\n  // #endregion helpers\n}\n"
  },
  {
    "path": "modules/data/src/dataservices/entity-data.service.ts",
    "content": "import { Injectable } from '@angular/core';\n\nimport { EntityCollectionDataService } from './interfaces';\nimport { DefaultDataServiceFactory } from './default-data.service';\n\n/**\n * Registry of EntityCollection data services that make REST-like CRUD calls\n * to entity collection endpoints.\n */\n@Injectable()\nexport class EntityDataService {\n  protected services: { [name: string]: EntityCollectionDataService<any> } = {};\n\n  // TODO:  Optionally inject specialized entity data services\n  // for those that aren't derived from BaseDataService.\n  constructor(protected defaultDataServiceFactory: DefaultDataServiceFactory) {}\n\n  /**\n   * Get (or create) a data service for entity type\n   * @param entityName - the name of the type\n   *\n   * Examples:\n   *   getService('Hero'); // data service for Heroes, untyped\n   *   getService<Hero>('Hero'); // data service for Heroes, typed as Hero\n   */\n  getService<T>(entityName: string): EntityCollectionDataService<T> {\n    entityName = entityName.trim();\n    let service = this.services[entityName];\n    if (!service) {\n      service = this.defaultDataServiceFactory.create(entityName);\n      this.services[entityName] = service;\n    }\n    return service;\n  }\n\n  /**\n   * Register an EntityCollectionDataService for an entity type\n   * @param entityName - the name of the entity type\n   * @param service - data service for that entity type\n   *\n   * Examples:\n   *   registerService('Hero', myHeroDataService);\n   *   registerService('Villain', myVillainDataService);\n   */\n  registerService<T>(\n    entityName: string,\n    service: EntityCollectionDataService<T>\n  ) {\n    this.services[entityName.trim()] = service;\n  }\n\n  /**\n   * Register a batch of data services.\n   * @param services - data services to merge into existing services\n   *\n   * Examples:\n   *   registerServices({\n   *     Hero: myHeroDataService,\n   *     Villain: myVillainDataService\n   *   });\n   */\n  registerServices(services: {\n    [name: string]: EntityCollectionDataService<any>;\n  }) {\n    this.services = { ...this.services, ...services };\n  }\n}\n"
  },
  {
    "path": "modules/data/src/dataservices/http-url-generator.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Pluralizer } from '../utils/interfaces';\n\n/**\n * Known resource URLS for specific entity types.\n * Each entity's resource URLS are endpoints that\n * target single entity and multi-entity HTTP operations.\n * Used by the `DefaultHttpUrlGenerator`.\n */\nexport abstract class EntityHttpResourceUrls {\n  [entityName: string]: HttpResourceUrls;\n}\n\n/**\n * Resource URLS for HTTP operations that target single entity\n * and multi-entity endpoints.\n */\nexport interface HttpResourceUrls {\n  /**\n   * The URL path for a single entity endpoint, e.g, `some-api-root/hero/`\n   * such as you'd use to add a hero.\n   * Example: `httpClient.post<Hero>('some-api-root/hero/', addedHero)`.\n   * Note trailing slash (/).\n   */\n  entityResourceUrl: string;\n  /**\n   * The URL path for a multiple-entity endpoint, e.g, `some-api-root/heroes/`\n   * such as you'd use when getting all heroes.\n   * Example: `httpClient.get<Hero[]>('some-api-root/heroes/')`\n   * Note trailing slash (/).\n   */\n  collectionResourceUrl: string;\n}\n\n/**\n * Generate the base part of an HTTP URL for\n * single entity or entity collection resource\n */\nexport abstract class HttpUrlGenerator {\n  /**\n   * Return the base URL for a single entity resource,\n   * e.g., the base URL to get a single hero by its id\n   */\n  abstract entityResource(\n    entityName: string,\n    root: string,\n    trailingSlashEndpoints: boolean\n  ): string;\n\n  /**\n   * Return the base URL for a collection resource,\n   * e.g., the base URL to get all heroes\n   */\n  abstract collectionResource(entityName: string, root: string): string;\n\n  /**\n   * Register known single-entity and collection resource URLs for HTTP calls\n   * @param entityHttpResourceUrls {EntityHttpResourceUrls} resource urls for specific entity type names\n   */\n  abstract registerHttpResourceUrls(\n    entityHttpResourceUrls?: EntityHttpResourceUrls\n  ): void;\n}\n\n@Injectable()\nexport class DefaultHttpUrlGenerator implements HttpUrlGenerator {\n  /**\n   * Known single-entity and collection resource URLs for HTTP calls.\n   * Generator methods returns these resource URLs for a given entity type name.\n   * If the resources for an entity type name are not know, it generates\n   * and caches a resource name for future use\n   */\n  protected knownHttpResourceUrls: EntityHttpResourceUrls = {};\n\n  constructor(private pluralizer: Pluralizer) {}\n\n  /**\n   * Get or generate the entity and collection resource URLs for the given entity type name\n   * @param entityName {string} Name of the entity type, e.g, 'Hero'\n   * @param root {string} Root path to the resource, e.g., 'some-api`\n   */\n  protected getResourceUrls(\n    entityName: string,\n    root: string,\n    trailingSlashEndpoints = false\n  ): HttpResourceUrls {\n    let resourceUrls = this.knownHttpResourceUrls[entityName];\n    if (!resourceUrls) {\n      const nRoot = trailingSlashEndpoints ? root : normalizeRoot(root);\n      resourceUrls = {\n        entityResourceUrl: `${nRoot}/${entityName}/`.toLowerCase(),\n        collectionResourceUrl: `${nRoot}/${this.pluralizer.pluralize(\n          entityName\n        )}/`.toLowerCase(),\n      };\n      this.registerHttpResourceUrls({ [entityName]: resourceUrls });\n    }\n    return resourceUrls;\n  }\n\n  /**\n   * Create the path to a single entity resource\n   * @param entityName {string} Name of the entity type, e.g, 'Hero'\n   * @param root {string} Root path to the resource, e.g., 'some-api`\n   * @returns complete path to resource, e.g, 'some-api/hero'\n   */\n  entityResource(\n    entityName: string,\n    root: string,\n    trailingSlashEndpoints: boolean\n  ): string {\n    return this.getResourceUrls(entityName, root, trailingSlashEndpoints)\n      .entityResourceUrl;\n  }\n\n  /**\n   * Create the path to a multiple entity (collection) resource\n   * @param entityName {string} Name of the entity type, e.g, 'Hero'\n   * @param root {string} Root path to the resource, e.g., 'some-api`\n   * @returns complete path to resource, e.g, 'some-api/heroes'\n   */\n  collectionResource(entityName: string, root: string): string {\n    return this.getResourceUrls(entityName, root).collectionResourceUrl;\n  }\n\n  /**\n   * Register known single-entity and collection resource URLs for HTTP calls\n   * @param entityHttpResourceUrls {EntityHttpResourceUrls} resource urls for specific entity type names\n   * Well-formed resource urls end in a '/';\n   * Note: this method does not ensure that resource urls are well-formed.\n   */\n  registerHttpResourceUrls(\n    entityHttpResourceUrls: EntityHttpResourceUrls\n  ): void {\n    this.knownHttpResourceUrls = {\n      ...this.knownHttpResourceUrls,\n      ...(entityHttpResourceUrls || {}),\n    };\n  }\n}\n\n/** Remove leading & trailing spaces or slashes */\nexport function normalizeRoot(root: string) {\n  return root.replace(/^[/\\s]+|[/\\s]+$/g, '');\n}\n"
  },
  {
    "path": "modules/data/src/dataservices/interfaces.ts",
    "content": "import { Observable } from 'rxjs';\nimport { Update } from '@ngrx/entity';\n\n/** A service that performs REST-like HTTP data operations for an entity collection */\nexport interface EntityCollectionDataService<T> {\n  readonly name: string;\n  add(entity: T, httpOptions?: HttpOptions): Observable<T>;\n  delete(\n    id: number | string,\n    httpOptions?: HttpOptions\n  ): Observable<number | string>;\n  getAll(httpOptions?: HttpOptions): Observable<T[]>;\n  getById(id: any, httpOptions?: HttpOptions): Observable<T>;\n  getWithQuery(\n    params: QueryParams | string,\n    httpOptions?: HttpOptions\n  ): Observable<T[]>;\n  update(update: Update<T>, httpOptions?: HttpOptions): Observable<T>;\n  upsert(entity: T, httpOptions?: HttpOptions): Observable<T>;\n}\n\nexport type HttpMethods = 'DELETE' | 'GET' | 'POST' | 'PUT';\n\nexport interface RequestData {\n  method: HttpMethods;\n  url: string;\n  data?: any;\n  options?: any;\n}\n\n/**\n * A key/value map of parameters to be turned into an HTTP query string\n * Same as HttpClient's HttpParamsOptions which at the time of writing was\n * NOT exported at package level\n * https://github.com/angular/angular/issues/22013\n *\n * @deprecated Use HttpOptions instead. getWithQuery still accepts QueryParams as its\n * first argument, but HttpOptions.httpParams uses Angular's own HttpParamsOptions which\n * HttpClient accepts as an argument.\n */\nexport interface QueryParams {\n  [name: string]:\n    | string\n    | number\n    | boolean\n    | ReadonlyArray<string | number | boolean>;\n}\n\n/**\n * Options that adhere to the constructor arguments for HttpParams and\n * HttpHeaders.\n */\nexport interface HttpOptions {\n  httpParams?: HttpParams;\n  httpHeaders?: HttpHeaders;\n}\n\n/**\n * Type that adheres to angular's Http Headers\n */\nexport type HttpHeaders = string | { [p: string]: string | string[] };\n\n/**\n * Options that partially adheres to angular's HttpParamsOptions. The non-serializable encoder property is omitted.\n */\nexport declare interface HttpParams {\n  /**\n   * String representation of the HTTP parameters in URL-query-string format.\n   * Mutually exclusive with `fromObject`.\n   */\n  fromString?: string;\n  /** Object map of the HTTP parameters. Mutually exclusive with `fromString`. */\n  fromObject?: {\n    [param: string]:\n      | string\n      | number\n      | boolean\n      | ReadonlyArray<string | number | boolean>;\n  };\n}\n"
  },
  {
    "path": "modules/data/src/dataservices/persistence-result-handler.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Action } from '@ngrx/store';\n\nimport {\n  DataServiceError,\n  EntityActionDataServiceError,\n} from './data-service-error';\nimport { EntityAction } from '../actions/entity-action';\nimport { EntityActionFactory } from '../actions/entity-action-factory';\nimport { makeErrorOp, makeSuccessOp } from '../actions/entity-op';\nimport { Logger } from '../utils/interfaces';\n\n/**\n * Handling of responses from persistence operation\n */\nexport abstract class PersistenceResultHandler {\n  /** Handle successful result of persistence operation for an action */\n  abstract handleSuccess(originalAction: EntityAction): (data: any) => Action;\n\n  /** Handle error result of persistence operation for an action */\n  abstract handleError(\n    originalAction: EntityAction\n  ): (\n    error: DataServiceError | Error\n  ) => EntityAction<EntityActionDataServiceError>;\n}\n\n/**\n * Default handling of responses from persistence operation,\n * specifically an EntityDataService\n */\n@Injectable()\nexport class DefaultPersistenceResultHandler\n  implements PersistenceResultHandler\n{\n  constructor(\n    private logger: Logger,\n    private entityActionFactory: EntityActionFactory\n  ) {}\n\n  /** Handle successful result of persistence operation on an EntityAction */\n  handleSuccess(originalAction: EntityAction): (data: any) => Action {\n    const successOp = makeSuccessOp(originalAction.payload.entityOp);\n    return (data: any) =>\n      this.entityActionFactory.createFromAction(originalAction, {\n        entityOp: successOp,\n        data,\n      });\n  }\n\n  /** Handle error result of persistence operation on an EntityAction */\n  handleError(\n    originalAction: EntityAction\n  ): (\n    error: DataServiceError | Error\n  ) => EntityAction<EntityActionDataServiceError> {\n    const errorOp = makeErrorOp(originalAction.payload.entityOp);\n\n    return (err: DataServiceError | Error) => {\n      const error =\n        err instanceof DataServiceError ? err : new DataServiceError(err, null);\n      const errorData: EntityActionDataServiceError = { error, originalAction };\n      this.logger.error(errorData);\n      const action =\n        this.entityActionFactory.createFromAction<EntityActionDataServiceError>(\n          originalAction,\n          {\n            entityOp: errorOp,\n            data: errorData,\n          }\n        );\n      return action;\n    };\n  }\n}\n"
  },
  {
    "path": "modules/data/src/dispatchers/entity-cache-dispatcher.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { Action, ScannedActionsSubject, Store } from '@ngrx/store';\n\nimport { Observable, of, Subscription, throwError } from 'rxjs';\nimport { filter, mergeMap, shareReplay, take } from 'rxjs/operators';\n\nimport { CorrelationIdGenerator } from '../utils/correlation-id-generator';\nimport { EntityActionOptions } from '../actions/entity-action';\nimport { EntityCache } from '../reducers/entity-cache';\nimport { EntityDispatcherDefaultOptions } from './entity-dispatcher-default-options';\n\nimport { MergeStrategy } from '../actions/merge-strategy';\nimport { PersistanceCanceled } from './entity-dispatcher';\n\nimport { ChangeSet, ChangeSetItem } from '../actions/entity-cache-change-set';\nimport {\n  ClearCollections,\n  EntityCacheAction,\n  EntityCacheQuerySet,\n  LoadCollections,\n  MergeQuerySet,\n  SetEntityCache,\n  SaveEntities,\n  SaveEntitiesCancel,\n  SaveEntitiesError,\n  SaveEntitiesSuccess,\n} from '../actions/entity-cache-action';\n\n/**\n * Dispatches Entity Cache actions to the EntityCache reducer\n */\n@Injectable()\nexport class EntityCacheDispatcher {\n  /**\n   * Actions scanned by the store after it processed them with reducers.\n   * A replay observable of the most recent action reduced by the store.\n   */\n  reducedActions$: Observable<Action>;\n  private raSubscription: Subscription;\n\n  constructor(\n    /** Generates correlation ids for query and save methods */\n    private correlationIdGenerator: CorrelationIdGenerator,\n    /**\n     * Dispatcher options configure dispatcher behavior such as\n     * whether add is optimistic or pessimistic by default.\n     */\n    private defaultDispatcherOptions: EntityDispatcherDefaultOptions,\n    /** Actions scanned by the store after it processed them with reducers. */\n    @Inject(ScannedActionsSubject) scannedActions$: Observable<Action>,\n    /** The store, scoped to the EntityCache */\n    private store: Store<EntityCache>\n  ) {\n    // Replay because sometimes in tests will fake data service with synchronous observable\n    // which makes subscriber miss the dispatched actions.\n    // Of course that's a testing mistake. But easy to forget, leading to painful debugging.\n    this.reducedActions$ = scannedActions$.pipe(shareReplay(1));\n    // Start listening so late subscriber won't miss the most recent action.\n    this.raSubscription = this.reducedActions$.subscribe();\n  }\n\n  /**\n   * Dispatch an Action to the store.\n   * @param action the Action\n   * @returns the dispatched Action\n   */\n  dispatch(action: Action): Action {\n    this.store.dispatch(action);\n    return action;\n  }\n\n  /**\n   * Dispatch action to cancel the saveEntities request with matching correlation id.\n   * @param correlationId The correlation id for the corresponding action\n   * @param [reason] explains why canceled and by whom.\n   * @param [entityNames] array of entity names so can turn off loading flag for their collections.\n   * @param [tag] tag to identify the operation from the app perspective.\n   */\n  cancelSaveEntities(\n    correlationId: any,\n    reason?: string,\n    entityNames?: string[],\n    tag?: string\n  ): void {\n    if (!correlationId) {\n      throw new Error('Missing correlationId');\n    }\n    const action = new SaveEntitiesCancel(\n      correlationId,\n      reason,\n      entityNames,\n      tag\n    );\n    this.dispatch(action);\n  }\n\n  /** Clear the named entity collections in cache\n   * @param [collections] Array of names of the collections to clear.\n   * If empty array, does nothing. If null/undefined/no array, clear all collections.\n   * @param [tag] tag to identify the operation from the app perspective.\n   */\n  clearCollections(collections?: string[], tag?: string) {\n    this.dispatch(new ClearCollections(collections, tag));\n  }\n\n  /**\n   * Load multiple entity collections at the same time.\n   * before any selectors$ observables emit.\n   * @param collections The collections to load, typically the result of a query.\n   * @param [tag] tag to identify the operation from the app perspective.\n   * in the form of a map of entity collections.\n   */\n  loadCollections(collections: EntityCacheQuerySet, tag?: string) {\n    this.dispatch(new LoadCollections(collections, tag));\n  }\n\n  /**\n   * Merges entities from a query result\n   * that returned entities from multiple collections.\n   * Corresponding entity cache reducer should add and update all collections\n   * at the same time, before any selectors$ observables emit.\n   * @param querySet The result of the query in the form of a map of entity collections.\n   * These are the entity data to merge into the respective collections.\n   * @param mergeStrategy How to merge a queried entity when it is already in the collection.\n   * The default is MergeStrategy.PreserveChanges\n   * @param [tag] tag to identify the operation from the app perspective.\n   */\n  mergeQuerySet(\n    querySet: EntityCacheQuerySet,\n    mergeStrategy?: MergeStrategy,\n    tag?: string\n  ) {\n    this.dispatch(new MergeQuerySet(querySet, mergeStrategy, tag));\n  }\n\n  /**\n   * Create entity cache action for replacing the entire entity cache.\n   * Dangerous because brute force but useful as when re-hydrating an EntityCache\n   * from local browser storage when the application launches.\n   * @param cache New state of the entity cache\n   * @param [tag] tag to identify the operation from the app perspective.\n   */\n  setEntityCache(cache: EntityCache, tag?: string) {\n    this.dispatch(new SetEntityCache(cache, tag));\n  }\n\n  /**\n   * Dispatch action to save multiple entity changes to remote storage.\n   * Relies on an Ngrx Effect such as EntityEffects.saveEntities$.\n   * Important: only call if your server supports the SaveEntities protocol\n   * through your EntityDataService.saveEntities method.\n   * @param changes Either the entities to save, as an array of {ChangeSetItem}, or\n   * a ChangeSet that holds such changes.\n   * @param url The server url which receives the save request\n   * @param [options] options such as tag, correlationId, isOptimistic, and mergeStrategy.\n   * These values are defaulted if not supplied.\n   * @returns A terminating Observable<ChangeSet> with data returned from the server\n   * after server reports successful save OR the save error.\n   * TODO: should return the matching entities from cache rather than the raw server data.\n   */\n  saveEntities(\n    changes: ChangeSetItem[] | ChangeSet,\n    url: string,\n    options?: EntityActionOptions\n  ): Observable<ChangeSet> {\n    const changeSet = Array.isArray(changes) ? { changes } : changes;\n    options = options || {};\n    const correlationId =\n      options.correlationId == null\n        ? this.correlationIdGenerator.next()\n        : options.correlationId;\n    const isOptimistic =\n      options.isOptimistic == null\n        ? this.defaultDispatcherOptions.optimisticSaveEntities || false\n        : options.isOptimistic === true;\n    const tag = options.tag || 'Save Entities';\n    options = { ...options, correlationId, isOptimistic, tag };\n    const action = new SaveEntities(changeSet, url, options);\n    this.dispatch(action);\n    return this.getSaveEntitiesResponseData$(options.correlationId).pipe(\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Return Observable of data from the server-success SaveEntities action with\n   * the given Correlation Id, after that action was processed by the ngrx store.\n   * or else put the server error on the Observable error channel.\n   * @param crid The correlationId for both the save and response actions.\n   */\n  private getSaveEntitiesResponseData$(crid: any): Observable<ChangeSet> {\n    /**\n     * reducedActions$ must be replay observable of the most recent action reduced by the store.\n     * because the response action might have been dispatched to the store\n     * before caller had a chance to subscribe.\n     */\n    return this.reducedActions$.pipe(\n      filter(\n        (act: Action) =>\n          act.type === EntityCacheAction.SAVE_ENTITIES_SUCCESS ||\n          act.type === EntityCacheAction.SAVE_ENTITIES_ERROR ||\n          act.type === EntityCacheAction.SAVE_ENTITIES_CANCEL\n      ),\n      filter((act: Action) => crid === (act as any).payload.correlationId),\n      take(1),\n      mergeMap((act) => {\n        return act.type === EntityCacheAction.SAVE_ENTITIES_CANCEL\n          ? throwError(\n              new PersistanceCanceled(\n                (act as SaveEntitiesCancel).payload.reason\n              )\n            )\n          : act.type === EntityCacheAction.SAVE_ENTITIES_SUCCESS\n            ? of((act as SaveEntitiesSuccess).payload.changeSet)\n            : throwError((act as SaveEntitiesError).payload);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "modules/data/src/dispatchers/entity-commands.ts",
    "content": "import { Observable } from 'rxjs';\nimport { EntityActionOptions } from '../actions/entity-action';\nimport { QueryParams } from '../dataservices/interfaces';\n\n/** Commands that update the remote server. */\nexport interface EntityServerCommands<T> {\n  /**\n   * Dispatch action to save a new entity to remote storage.\n   * @param entity entity to add, which may omit its key if pessimistic and the server creates the key;\n   * must have a key if optimistic save.\n   * @returns A terminating Observable of the entity\n   * after server reports successful save or the save error.\n   */\n  add(\n    entity: Partial<T>,\n    options?: EntityActionOptions & { isOptimistic?: false }\n  ): Observable<T>;\n  add(\n    entity: T,\n    options: EntityActionOptions & { isOptimistic: true }\n  ): Observable<T>;\n  add(entity: T, options?: EntityActionOptions): Observable<T>;\n\n  /**\n   * Dispatch action to cancel the persistence operation (query or save) with the given correlationId.\n   * @param correlationId The correlation id for the corresponding EntityAction\n   * @param [reason] explains why canceled and by whom.\n   * @param [options] options such as the tag\n   */\n  cancel(\n    correlationId: any,\n    reason?: string,\n    options?: EntityActionOptions\n  ): void;\n\n  /**\n   * Dispatch action to delete entity from remote storage by key.\n   * @param key The entity to delete\n   * @param [options] options that influence save and merge behavior\n   * @returns A terminating Observable of the deleted key\n   * after server reports successful save or the save error.\n   */\n  delete(entity: T, options?: EntityActionOptions): Observable<number | string>;\n\n  /**\n   * Dispatch action to delete entity from remote storage by key.\n   * @param key The primary key of the entity to remove\n   * @param [options] options that influence save and merge behavior\n   * @returns Observable of the deleted key\n   * after server reports successful save or the save error.\n   */\n  delete(\n    key: number | string,\n    options?: EntityActionOptions\n  ): Observable<number | string>;\n\n  /**\n   * Dispatch action to query remote storage for all entities and\n   * merge the queried entities into the cached collection.\n   * @param [options] options that influence merge behavior\n   * @returns A terminating Observable of the collection\n   * after server reports successful query or the query error.\n   * @see load()\n   */\n  getAll(options?: EntityActionOptions): Observable<T[]>;\n\n  /**\n   * Dispatch action to query remote storage for the entity with this primary key.\n   * If the server returns an entity,\n   * merge it into the cached collection.\n   * @param key The primary key of the entity to get.\n   * @param [options] options that influence merge behavior\n   * @returns A terminating Observable of the queried entities that are in the collection\n   * after server reports success or the query error.\n   */\n  getByKey(key: any, options?: EntityActionOptions): Observable<T>;\n\n  /**\n   * Dispatch action to query remote storage for the entities that satisfy a query expressed\n   * with either a query parameter map or an HTTP URL query string,\n   * and merge the results into the cached collection.\n   * @param queryParams the query in a form understood by the server\n   * @param [options] options that influence merge behavior\n   * @returns A terminating Observable of the queried entities\n   * after server reports successful query or the query error.\n   */\n  getWithQuery(\n    queryParams: QueryParams | string,\n    options?: EntityActionOptions\n  ): Observable<T[]>;\n\n  /**\n   * Dispatch action to query remote storage for all entities and\n   * completely replace the cached collection with the queried entities.\n   * @param [options] options that influence load behavior\n   * @returns A terminating Observable of the entities in the collection\n   * after server reports successful query or the query error.\n   * @see getAll\n   */\n  load(options?: EntityActionOptions): Observable<T[]>;\n\n  /**\n   * Dispatch action to query remote storage for the entities that satisfy a query expressed\n   * with either a query parameter map or an HTTP URL query string, and\n   * completely replace the cached collection with the queried entities.\n   * @param queryParams the query in a form understood by the server\n   * @param [options] options that influence load behavior\n   * @returns A terminating Observable of the entities in the collection\n   * after server reports successful query or the query error.\n   * @see getWithQuery\n   */\n  loadWithQuery(\n    queryParams: QueryParams | string,\n    options?: EntityActionOptions\n  ): Observable<T[]>;\n\n  /**\n   * Dispatch action to save the updated entity (or partial entity) in remote storage.\n   * The update entity may be partial (but must have its key)\n   * in which case it patches the existing entity.\n   * @param entity update entity, which might be a partial of T but must at least have its key.\n   * @param [options] options that influence save and merge behavior\n   * @returns A terminating Observable of the updated entity\n   * after server reports successful save or the save error.\n   */\n  update(entity: Partial<T>, options?: EntityActionOptions): Observable<T>;\n\n  /**\n   * Dispatch action to save a new or update an existing entity to remote storage.\n   * Only dispatch this action if your server supports upsert.\n   * @param entity entity to upsert, which may omit its key if pessimistic and the server creates the key;\n   * must have a key if optimistic save.\n   * @returns A terminating Observable of the entity\n   * after server reports successful save or the save error.\n   */\n  upsert(entity: T, options?: EntityActionOptions): Observable<T>;\n}\n\n/*** A collection's cache-only commands, which do not update remote storage ***/\n\nexport interface EntityCacheCommands<T> {\n  /**\n   * Replace all entities in the cached collection.\n   * Does not save to remote storage.\n   * @param entities to add directly to cache.\n   * @param [options] options such as mergeStrategy\n   */\n  addAllToCache(entities: T[], options?: EntityActionOptions): void;\n\n  /**\n   * Add a new entity directly to the cache.\n   * Does not save to remote storage.\n   * Ignored if an entity with the same primary key is already in cache.\n   * @param entity to add directly to cache.\n   * @param [options] options such as mergeStrategy\n   */\n  addOneToCache(entity: T, options?: EntityActionOptions): void;\n\n  /**\n   * Add multiple new entities directly to the cache.\n   * Does not save to remote storage.\n   * Entities with primary keys already in cache are ignored.\n   * @param entities to add directly to cache.\n   * @param [options] options such as mergeStrategy\n   */\n  addManyToCache(entities: T[], options?: EntityActionOptions): void;\n\n  /** Clear the cached entity collection */\n  clearCache(options?: EntityActionOptions): void;\n\n  /**\n   * Remove an entity directly from the cache.\n   * Does not delete that entity from remote storage.\n   * @param entity The entity to remove\n   * @param [options] options such as mergeStrategy\n   */\n  removeOneFromCache(entity: T, options?: EntityActionOptions): void;\n\n  /**\n   * Remove an entity directly from the cache.\n   * Does not delete that entity from remote storage.\n   * @param key The primary key of the entity to remove\n   * @param [options] options such as mergeStrategy\n   */\n  removeOneFromCache(key: number | string, options?: EntityActionOptions): void;\n\n  /**\n   * Remove multiple entities directly from the cache.\n   * Does not delete these entities from remote storage.\n   * @param entity The entities to remove\n   * @param [options] options such as mergeStrategy\n   */\n  removeManyFromCache(entities: T[], options?: EntityActionOptions): void;\n\n  /**\n   * Remove multiple entities directly from the cache.\n   * Does not delete these entities from remote storage.\n   * @param keys The primary keys of the entities to remove\n   * @param [options] options such as mergeStrategy\n   */\n  removeManyFromCache(\n    keys: (number | string)[],\n    options?: EntityActionOptions\n  ): void;\n\n  /**\n   * Update a cached entity directly.\n   * Does not update that entity in remote storage.\n   * Ignored if an entity with matching primary key is not in cache.\n   * The update entity may be partial (but must have its key)\n   * in which case it patches the existing entity.\n   * @param entity to update directly in cache.\n   * @param [options] options such as mergeStrategy\n   */\n  updateOneInCache(entity: Partial<T>, options?: EntityActionOptions): void;\n\n  /**\n   * Update multiple cached entities directly.\n   * Does not update these entities in remote storage.\n   * Entities whose primary keys are not in cache are ignored.\n   * Update entities may be partial but must at least have their keys.\n   * such partial entities patch their cached counterparts.\n   * @param entities to update directly in cache.\n   * @param [options] options such as mergeStrategy\n   */\n  updateManyInCache(\n    entities: Partial<T>[],\n    options?: EntityActionOptions\n  ): void;\n\n  /**\n   * Insert or update a cached entity directly.\n   * Does not save to remote storage.\n   * Upsert entity might be a partial of T but must at least have its key.\n   * Pass the Update<T> structure as the payload.\n   * @param entity to upsert directly in cache.\n   * @param [options] options such as mergeStrategy\n   */\n  upsertOneInCache(entity: Partial<T>, options?: EntityActionOptions): void;\n\n  /**\n   * Insert or update multiple cached entities directly.\n   * Does not save to remote storage.\n   * Upsert entities might be partial but must at least have their keys.\n   * Pass an array of the Update<T> structure as the payload.\n   * @param entities to upsert directly in cache.\n   * @param [options] options such as mergeStrategy\n   */\n  upsertManyInCache(\n    entities: Partial<T>[],\n    options?: EntityActionOptions\n  ): void;\n\n  /**\n   * Set the pattern that the collection's filter applies\n   * when using the `filteredEntities` selector.\n   */\n  setFilter(pattern: any, options?: EntityActionOptions): void;\n\n  /** Set the loaded flag */\n  setLoaded(isLoaded: boolean, options?: EntityActionOptions): void;\n\n  /** Set the loading flag */\n  setLoading(isLoading: boolean, options?: EntityActionOptions): void;\n}\n\n/** Commands that dispatch entity actions for a collection */\nexport interface EntityCommands<T>\n  extends EntityServerCommands<T>,\n    EntityCacheCommands<T> {}\n"
  },
  {
    "path": "modules/data/src/dispatchers/entity-dispatcher-base.ts",
    "content": "import { Action, createSelector, Store } from '@ngrx/store';\nimport { IdSelector, Update } from '@ngrx/entity';\n\nimport { Observable, of, throwError } from 'rxjs';\nimport {\n  filter,\n  map,\n  mergeMap,\n  shareReplay,\n  withLatestFrom,\n  take,\n} from 'rxjs/operators';\n\nimport { CorrelationIdGenerator } from '../utils/correlation-id-generator';\nimport { defaultSelectId, toUpdateFactory } from '../utils/utilities';\nimport { EntityAction, EntityActionOptions } from '../actions/entity-action';\nimport { EntityActionFactory } from '../actions/entity-action-factory';\nimport { EntityActionGuard } from '../actions/entity-action-guard';\nimport { EntityCache } from '../reducers/entity-cache';\nimport { EntityCacheSelector } from '../selectors/entity-cache-selector';\nimport { EntityCollection } from '../reducers/entity-collection';\nimport { EntityCommands } from './entity-commands';\nimport { EntityDispatcher, PersistanceCanceled } from './entity-dispatcher';\nimport { EntityDispatcherDefaultOptions } from './entity-dispatcher-default-options';\nimport { EntityOp, OP_ERROR, OP_SUCCESS } from '../actions/entity-op';\nimport { MergeStrategy } from '../actions/merge-strategy';\nimport { QueryParams } from '../dataservices/interfaces';\nimport { UpdateResponseData } from '../actions/update-response-data';\n\n/**\n * Dispatches EntityCollection actions to their reducers and effects (default implementation).\n * All save commands rely on an Ngrx Effect such as `EntityEffects.persist$`.\n */\nexport class EntityDispatcherBase<T> implements EntityDispatcher<T> {\n  /** Utility class with methods to validate EntityAction payloads.*/\n  guard: EntityActionGuard<T>;\n\n  private entityCollection$: Observable<EntityCollection<T>>;\n\n  /**\n   * Convert an entity (or partial entity) into the `Update<T>` object\n   * `update...` and `upsert...` methods take `Update<T>` args\n   */\n  toUpdate: (entity: Partial<T>) => Update<T>;\n\n  constructor(\n    /** Name of the entity type for which entities are dispatched */\n    public entityName: string,\n    /** Creates an {EntityAction} */\n    public entityActionFactory: EntityActionFactory,\n    /** The store, scoped to the EntityCache */\n    public store: Store<EntityCache>,\n    /** Returns the primary key (id) of this entity */\n    public selectId: IdSelector<T> = defaultSelectId,\n    /**\n     * Dispatcher options configure dispatcher behavior such as\n     * whether add is optimistic or pessimistic by default.\n     */\n    private defaultDispatcherOptions: EntityDispatcherDefaultOptions,\n    /** Actions scanned by the store after it processed them with reducers. */\n    private reducedActions$: Observable<Action>,\n    /** Store selector for the EntityCache */\n    entityCacheSelector: EntityCacheSelector,\n    /** Generates correlation ids for query and save methods */\n    private correlationIdGenerator: CorrelationIdGenerator\n  ) {\n    this.guard = new EntityActionGuard(entityName, selectId);\n    this.toUpdate = toUpdateFactory<T>(selectId);\n\n    const collectionSelector = createSelector(\n      entityCacheSelector,\n      (cache) => cache[entityName] as EntityCollection<T>\n    );\n    this.entityCollection$ = store.select(collectionSelector);\n  }\n\n  /**\n   * Create an {EntityAction} for this entity type.\n   * @param entityOp {EntityOp} the entity operation\n   * @param [data] the action data\n   * @param [options] additional options\n   * @returns the EntityAction\n   */\n  createEntityAction<P = any>(\n    entityOp: EntityOp,\n    data?: P,\n    options?: EntityActionOptions\n  ): EntityAction<P> {\n    return this.entityActionFactory.create({\n      entityName: this.entityName,\n      entityOp,\n      data,\n      ...options,\n    });\n  }\n\n  /**\n   * Create an {EntityAction} for this entity type and\n   * dispatch it immediately to the store.\n   * @param op {EntityOp} the entity operation\n   * @param [data] the action data\n   * @param [options] additional options\n   * @returns the dispatched EntityAction\n   */\n  createAndDispatch<P = any>(\n    op: EntityOp,\n    data?: P,\n    options?: EntityActionOptions\n  ): EntityAction<P> {\n    const action = this.createEntityAction(op, data, options);\n    this.dispatch(action);\n    return action;\n  }\n\n  /**\n   * Dispatch an Action to the store.\n   * @param action the Action\n   * @returns the dispatched Action\n   */\n  dispatch(action: Action): Action {\n    this.store.dispatch(action);\n    return action;\n  }\n\n  // #region Query and save operations\n\n  /**\n   * Dispatch action to save a new entity to remote storage.\n   * @param entity entity to add, which may omit its key if pessimistic and the server creates the key;\n   * must have a key if optimistic save.\n   * @returns A terminating Observable of the entity\n   * after server reports successful save or the save error.\n   */\n  add(entity: T, options?: EntityActionOptions): Observable<T> {\n    options = this.setSaveEntityActionOptions(\n      options,\n      this.defaultDispatcherOptions.optimisticAdd\n    );\n    const action = this.createEntityAction(\n      EntityOp.SAVE_ADD_ONE,\n      entity,\n      options\n    );\n    if (options.isOptimistic) {\n      this.guard.mustBeEntity(action);\n    }\n    this.dispatch(action);\n    return this.getResponseData$<T>(options.correlationId).pipe(\n      // Use the returned entity data's id to get the entity from the collection\n      // as it might be different from the entity returned from the server.\n      withLatestFrom(this.entityCollection$),\n      map(([e, collection]) => collection.entities[this.selectId(e)]!),\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Dispatch action to cancel the persistence operation (query or save).\n   * Will cause save observable to error with a PersistenceCancel error.\n   * Caller is responsible for undoing changes in cache from pending optimistic save\n   * @param correlationId The correlation id for the corresponding EntityAction\n   * @param [reason] explains why canceled and by whom.\n   */\n  cancel(\n    correlationId: any,\n    reason?: string,\n    options?: EntityActionOptions\n  ): void {\n    if (!correlationId) {\n      throw new Error('Missing correlationId');\n    }\n    this.createAndDispatch(EntityOp.CANCEL_PERSIST, reason, { correlationId });\n  }\n\n  /**\n   * Dispatch action to delete entity from remote storage by key.\n   * @param key The primary key of the entity to remove\n   * @returns A terminating Observable of the deleted key\n   * after server reports successful save or the save error.\n   */\n  delete(entity: T, options?: EntityActionOptions): Observable<number | string>;\n\n  /**\n   * Dispatch action to delete entity from remote storage by key.\n   * @param key The entity to delete\n   * @returns A terminating Observable of the deleted key\n   * after server reports successful save or the save error.\n   */\n  delete(\n    key: number | string,\n    options?: EntityActionOptions\n  ): Observable<number | string>;\n  delete(\n    arg: number | string | T,\n    options?: EntityActionOptions\n  ): Observable<number | string> {\n    options = this.setSaveEntityActionOptions(\n      options,\n      this.defaultDispatcherOptions.optimisticDelete\n    );\n    const key = this.getKey(arg);\n    const action = this.createEntityAction(\n      EntityOp.SAVE_DELETE_ONE,\n      key,\n      options\n    );\n    this.guard.mustBeKey(action);\n    this.dispatch(action);\n    return this.getResponseData$<number | string>(options.correlationId).pipe(\n      map(() => key),\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Dispatch action to query remote storage for all entities and\n   * merge the queried entities into the cached collection.\n   * @returns A terminating Observable of the queried entities that are in the collection\n   * after server reports success query or the query error.\n   * @see load()\n   */\n  getAll(options?: EntityActionOptions): Observable<T[]> {\n    options = this.setQueryEntityActionOptions(options);\n    const action = this.createEntityAction(EntityOp.QUERY_ALL, null, options);\n    this.dispatch(action);\n    return this.getResponseData$<T[]>(options.correlationId).pipe(\n      // Use the returned entity ids to get the entities from the collection\n      // as they might be different from the entities returned from the server\n      // because of unsaved changes (deletes or updates).\n      withLatestFrom(this.entityCollection$),\n      map(([entities, collection]) =>\n        entities.reduce((acc, e) => {\n          const entity = collection.entities[this.selectId(e)];\n          if (entity) {\n            acc.push(entity); // only return an entity found in the collection\n          }\n          return acc;\n        }, [] as T[])\n      ),\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Dispatch action to query remote storage for the entity with this primary key.\n   * If the server returns an entity,\n   * merge it into the cached collection.\n   * @returns A terminating Observable of the collection\n   * after server reports successful query or the query error.\n   */\n  getByKey(key: any, options?: EntityActionOptions): Observable<T> {\n    options = this.setQueryEntityActionOptions(options);\n    const action = this.createEntityAction(EntityOp.QUERY_BY_KEY, key, options);\n    this.dispatch(action);\n    return this.getResponseData$<T>(options.correlationId).pipe(\n      // Use the returned entity data's id to get the entity from the collection\n      // as it might be different from the entity returned from the server.\n      withLatestFrom(this.entityCollection$),\n      map(\n        ([entity, collection]) => collection.entities[this.selectId(entity)]!\n      ),\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Dispatch action to query remote storage for the entities that satisfy a query expressed\n   * with either a query parameter map or an HTTP URL query string,\n   * and merge the results into the cached collection.\n   * @param queryParams the query in a form understood by the server\n   * @returns A terminating Observable of the queried entities\n   * after server reports successful query or the query error.\n   */\n  getWithQuery(\n    queryParams: QueryParams | string,\n    options?: EntityActionOptions\n  ): Observable<T[]> {\n    options = this.setQueryEntityActionOptions(options);\n    const action = this.createEntityAction(\n      EntityOp.QUERY_MANY,\n      queryParams,\n      options\n    );\n    this.dispatch(action);\n    return this.getResponseData$<T[]>(options.correlationId).pipe(\n      // Use the returned entity ids to get the entities from the collection\n      // as they might be different from the entities returned from the server\n      // because of unsaved changes (deletes or updates).\n      withLatestFrom(this.entityCollection$),\n      map(([entities, collection]) =>\n        entities.reduce((acc, e) => {\n          const entity = collection.entities[this.selectId(e)];\n          if (entity) {\n            acc.push(entity); // only return an entity found in the collection\n          }\n          return acc;\n        }, [] as T[])\n      ),\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Dispatch action to query remote storage for all entities and\n   * completely replace the cached collection with the queried entities.\n   * @returns A terminating Observable of the entities in the collection\n   * after server reports successful query or the query error.\n   * @see getAll\n   */\n  load(options?: EntityActionOptions): Observable<T[]> {\n    options = this.setQueryEntityActionOptions(options);\n    const action = this.createEntityAction(EntityOp.QUERY_LOAD, null, options);\n    this.dispatch(action);\n    return this.getResponseData$<T[]>(options.correlationId).pipe(\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Dispatch action to query remote storage for the entities that satisfy a query expressed\n   * with either a query parameter map or an HTTP URL query string,\n   * and completely replace the cached collection with the queried entities.\n   * @param queryParams the query in a form understood by the server\n   * @param [options] options that influence load behavior\n   * @returns A terminating Observable of the queried entities\n   * after server reports successful query or the query error.\n   */\n  loadWithQuery(\n    queryParams: QueryParams | string,\n    options?: EntityActionOptions\n  ): Observable<T[]> {\n    options = this.setQueryEntityActionOptions(options);\n    const action = this.createEntityAction(\n      EntityOp.QUERY_MANY,\n      queryParams,\n      options\n    );\n    this.dispatch(action);\n    return this.getResponseData$<T[]>(options.correlationId).pipe(\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Dispatch action to save the updated entity (or partial entity) in remote storage.\n   * The update entity may be partial (but must have its key)\n   * in which case it patches the existing entity.\n   * @param entity update entity, which might be a partial of T but must at least have its key.\n   * @returns A terminating Observable of the updated entity\n   * after server reports successful save or the save error.\n   */\n  update(entity: Partial<T>, options?: EntityActionOptions): Observable<T> {\n    // update entity might be a partial of T but must at least have its key.\n    // pass the Update<T> structure as the payload\n    const update = this.toUpdate(entity);\n    options = this.setSaveEntityActionOptions(\n      options,\n      this.defaultDispatcherOptions.optimisticUpdate\n    );\n    const action = this.createEntityAction(\n      EntityOp.SAVE_UPDATE_ONE,\n      update,\n      options\n    );\n    if (options.isOptimistic) {\n      this.guard.mustBeUpdate(action);\n    }\n    this.dispatch(action);\n    return this.getResponseData$<UpdateResponseData<T>>(\n      options.correlationId\n    ).pipe(\n      // Use the update entity data id to get the entity from the collection\n      // as might be different from the entity returned from the server\n      // because the id changed or there are unsaved changes.\n      map((updateData) => updateData.changes),\n      withLatestFrom(this.entityCollection$),\n      map(([e, collection]) => collection.entities[this.selectId(e as T)]!),\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Dispatch action to save a new or existing entity to remote storage.\n   * Only dispatch this action if your server supports upsert.\n   * @param entity entity to add, which may omit its key if pessimistic and the server creates the key;\n   * must have a key if optimistic save.\n   * @returns A terminating Observable of the entity\n   * after server reports successful save or the save error.\n   */\n  upsert(entity: T, options?: EntityActionOptions): Observable<T> {\n    options = this.setSaveEntityActionOptions(\n      options,\n      this.defaultDispatcherOptions.optimisticUpsert\n    );\n    const action = this.createEntityAction(\n      EntityOp.SAVE_UPSERT_ONE,\n      entity,\n      options\n    );\n    if (options.isOptimistic) {\n      this.guard.mustBeEntity(action);\n    }\n    this.dispatch(action);\n    return this.getResponseData$<T>(options.correlationId).pipe(\n      // Use the returned entity data's id to get the entity from the collection\n      // as it might be different from the entity returned from the server.\n      withLatestFrom(this.entityCollection$),\n      map(([e, collection]) => collection.entities[this.selectId(e)]!),\n      shareReplay(1)\n    );\n  }\n  // #endregion Query and save operations\n\n  // #region Cache-only operations that do not update remote storage\n\n  // Unguarded for performance.\n  // EntityCollectionReducer<T> runs a guard (which throws)\n  // Developer should understand cache-only methods well enough\n  // to call them with the proper entities.\n  // May reconsider and add guards in future.\n\n  /**\n   * Replace all entities in the cached collection.\n   * Does not save to remote storage.\n   */\n  addAllToCache(entities: T[], options?: EntityActionOptions): void {\n    this.createAndDispatch(EntityOp.ADD_ALL, entities, options);\n  }\n\n  /**\n   * Add a new entity directly to the cache.\n   * Does not save to remote storage.\n   * Ignored if an entity with the same primary key is already in cache.\n   */\n  addOneToCache(entity: T, options?: EntityActionOptions): void {\n    this.createAndDispatch(EntityOp.ADD_ONE, entity, options);\n  }\n\n  /**\n   * Add multiple new entities directly to the cache.\n   * Does not save to remote storage.\n   * Entities with primary keys already in cache are ignored.\n   */\n  addManyToCache(entities: T[], options?: EntityActionOptions): void {\n    this.createAndDispatch(EntityOp.ADD_MANY, entities, options);\n  }\n\n  /** Clear the cached entity collection */\n  clearCache(options?: EntityActionOptions): void {\n    this.createAndDispatch(EntityOp.REMOVE_ALL, undefined, options);\n  }\n\n  /**\n   * Remove an entity directly from the cache.\n   * Does not delete that entity from remote storage.\n   * @param entity The entity to remove\n   */\n  removeOneFromCache(entity: T, options?: EntityActionOptions): void;\n\n  /**\n   * Remove an entity directly from the cache.\n   * Does not delete that entity from remote storage.\n   * @param key The primary key of the entity to remove\n   */\n  removeOneFromCache(key: number | string, options?: EntityActionOptions): void;\n  removeOneFromCache(\n    arg: (number | string) | T,\n    options?: EntityActionOptions\n  ): void {\n    this.createAndDispatch(EntityOp.REMOVE_ONE, this.getKey(arg), options);\n  }\n\n  /**\n   * Remove multiple entities directly from the cache.\n   * Does not delete these entities from remote storage.\n   * @param entity The entities to remove\n   */\n  removeManyFromCache(entities: T[], options?: EntityActionOptions): void;\n\n  /**\n   * Remove multiple entities directly from the cache.\n   * Does not delete these entities from remote storage.\n   * @param keys The primary keys of the entities to remove\n   */\n  removeManyFromCache(\n    keys: (number | string)[],\n    options?: EntityActionOptions\n  ): void;\n  removeManyFromCache(\n    args: (number | string)[] | T[],\n    options?: EntityActionOptions\n  ): void {\n    if (!args || args.length === 0) {\n      return;\n    }\n    const keys =\n      typeof args[0] === 'object'\n        ? // if array[0] is a key, assume they're all keys\n          (<T[]>args).map((arg) => this.getKey(arg))\n        : args;\n    this.createAndDispatch(EntityOp.REMOVE_MANY, keys, options);\n  }\n\n  /**\n   * Update a cached entity directly.\n   * Does not update that entity in remote storage.\n   * Ignored if an entity with matching primary key is not in cache.\n   * The update entity may be partial (but must have its key)\n   * in which case it patches the existing entity.\n   */\n  updateOneInCache(entity: Partial<T>, options?: EntityActionOptions): void {\n    // update entity might be a partial of T but must at least have its key.\n    // pass the Update<T> structure as the payload\n    const update: Update<T> = this.toUpdate(entity);\n    this.createAndDispatch(EntityOp.UPDATE_ONE, update, options);\n  }\n\n  /**\n   * Update multiple cached entities directly.\n   * Does not update these entities in remote storage.\n   * Entities whose primary keys are not in cache are ignored.\n   * Update entities may be partial but must at least have their keys.\n   * such partial entities patch their cached counterparts.\n   */\n  updateManyInCache(\n    entities: Partial<T>[],\n    options?: EntityActionOptions\n  ): void {\n    if (!entities || entities.length === 0) {\n      return;\n    }\n    const updates: Update<T>[] = entities.map((entity) =>\n      this.toUpdate(entity)\n    );\n    this.createAndDispatch(EntityOp.UPDATE_MANY, updates, options);\n  }\n\n  /**\n   * Add or update a new entity directly to the cache.\n   * Does not save to remote storage.\n   * Upsert entity might be a partial of T but must at least have its key.\n   * Pass the Update<T> structure as the payload\n   */\n  upsertOneInCache(entity: Partial<T>, options?: EntityActionOptions): void {\n    this.createAndDispatch(EntityOp.UPSERT_ONE, entity, options);\n  }\n\n  /**\n   * Add or update multiple cached entities directly.\n   * Does not save to remote storage.\n   */\n  upsertManyInCache(\n    entities: Partial<T>[],\n    options?: EntityActionOptions\n  ): void {\n    if (!entities || entities.length === 0) {\n      return;\n    }\n    this.createAndDispatch(EntityOp.UPSERT_MANY, entities, options);\n  }\n\n  /**\n   * Set the pattern that the collection's filter applies\n   * when using the `filteredEntities` selector.\n   */\n  setFilter(pattern: any): void {\n    this.createAndDispatch(EntityOp.SET_FILTER, pattern);\n  }\n\n  /** Set the loaded flag */\n  setLoaded(isLoaded: boolean): void {\n    this.createAndDispatch(EntityOp.SET_LOADED, !!isLoaded);\n  }\n\n  /** Set the loading flag */\n  setLoading(isLoading: boolean): void {\n    this.createAndDispatch(EntityOp.SET_LOADING, !!isLoading);\n  }\n  // #endregion Cache-only operations that do not update remote storage\n\n  // #region private helpers\n\n  /** Get key from entity (unless arg is already a key) */\n  private getKey(arg: number | string | T) {\n    return typeof arg === 'object'\n      ? this.selectId(arg)\n      : (arg as number | string);\n  }\n\n  /**\n   * Return Observable of data from the server-success EntityAction with\n   * the given Correlation Id, after that action was processed by the ngrx store.\n   * or else put the server error on the Observable error channel.\n   * @param crid The correlationId for both the save and response actions.\n   */\n  private getResponseData$<D = any>(crid: any): Observable<D> {\n    /**\n     * reducedActions$ must be replay observable of the most recent action reduced by the store.\n     * because the response action might have been dispatched to the store\n     * before caller had a chance to subscribe.\n     */\n    return this.reducedActions$.pipe(\n      filter((act: any) => !!act.payload),\n      filter((act: EntityAction) => {\n        const { correlationId, entityName, entityOp } = act.payload;\n        return (\n          entityName === this.entityName &&\n          correlationId === crid &&\n          (entityOp.endsWith(OP_SUCCESS) ||\n            entityOp.endsWith(OP_ERROR) ||\n            entityOp === EntityOp.CANCEL_PERSIST)\n        );\n      }),\n      take(1),\n      mergeMap((act) => {\n        const { entityOp } = act.payload;\n        return entityOp === EntityOp.CANCEL_PERSIST\n          ? throwError(new PersistanceCanceled(act.payload.data))\n          : entityOp.endsWith(OP_SUCCESS)\n            ? of(act.payload.data as D)\n            : throwError(act.payload.data.error);\n      })\n    );\n  }\n\n  private setQueryEntityActionOptions(\n    options?: EntityActionOptions\n  ): EntityActionOptions {\n    options = options || {};\n    const correlationId =\n      options.correlationId == null\n        ? this.correlationIdGenerator.next()\n        : options.correlationId;\n    return { ...options, correlationId };\n  }\n\n  private setSaveEntityActionOptions(\n    options?: EntityActionOptions,\n    defaultOptimism?: boolean\n  ): EntityActionOptions {\n    options = options || {};\n    const correlationId =\n      options.correlationId == null\n        ? this.correlationIdGenerator.next()\n        : options.correlationId;\n    const isOptimistic =\n      options.isOptimistic == null\n        ? defaultOptimism || false\n        : options.isOptimistic === true;\n    return { ...options, correlationId, isOptimistic };\n  }\n  // #endregion private helpers\n}\n"
  },
  {
    "path": "modules/data/src/dispatchers/entity-dispatcher-default-options.ts",
    "content": "import { Injectable } from '@angular/core';\n/**\n * Default options for EntityDispatcher behavior\n * such as whether `add()` is optimistic or pessimistic by default.\n * An optimistic save modifies the collection immediately and before saving to the server.\n * A pessimistic save modifies the collection after the server confirms the save was successful.\n * This class initializes the defaults to the safest values.\n * Provide an alternative to change the defaults for all entity collections.\n */\n@Injectable()\nexport class EntityDispatcherDefaultOptions {\n  /** True if added entities are saved optimistically; false if saved pessimistically. */\n  optimisticAdd = false;\n  /** True if deleted entities are saved optimistically; false if saved pessimistically. */\n  optimisticDelete = true;\n  /** True if updated entities are saved optimistically; false if saved pessimistically. */\n  optimisticUpdate = false;\n  /** True if upsert entities are saved optimistically; false if saved pessimistically. */\n  optimisticUpsert = false;\n  /** True if entities in a cache saveEntities request are saved optimistically; false if saved pessimistically. */\n  optimisticSaveEntities = false;\n}\n"
  },
  {
    "path": "modules/data/src/dispatchers/entity-dispatcher-factory.ts",
    "content": "import { Inject, Injectable, OnDestroy } from '@angular/core';\nimport { Action, Store, ScannedActionsSubject } from '@ngrx/store';\nimport { IdSelector } from '@ngrx/entity';\nimport { Observable, Subscription } from 'rxjs';\nimport { shareReplay } from 'rxjs/operators';\n\nimport { CorrelationIdGenerator } from '../utils/correlation-id-generator';\nimport { EntityDispatcherDefaultOptions } from './entity-dispatcher-default-options';\nimport { defaultSelectId } from '../utils/utilities';\nimport { EntityActionFactory } from '../actions/entity-action-factory';\nimport { EntityCache } from '../reducers/entity-cache';\nimport {\n  EntityCacheSelector,\n  ENTITY_CACHE_SELECTOR_TOKEN,\n} from '../selectors/entity-cache-selector';\nimport { EntityDispatcher } from './entity-dispatcher';\nimport { EntityDispatcherBase } from './entity-dispatcher-base';\n\n/** Creates EntityDispatchers for entity collections */\n@Injectable()\nexport class EntityDispatcherFactory implements OnDestroy {\n  /**\n   * Actions scanned by the store after it processed them with reducers.\n   * A replay observable of the most recent action reduced by the store.\n   */\n  reducedActions$: Observable<Action>;\n  private raSubscription: Subscription;\n\n  constructor(\n    private entityActionFactory: EntityActionFactory,\n    private store: Store<EntityCache>,\n    private entityDispatcherDefaultOptions: EntityDispatcherDefaultOptions,\n    @Inject(ScannedActionsSubject) scannedActions$: Observable<Action>,\n    @Inject(ENTITY_CACHE_SELECTOR_TOKEN)\n    private entityCacheSelector: EntityCacheSelector,\n    private correlationIdGenerator: CorrelationIdGenerator\n  ) {\n    // Replay because sometimes in tests will fake data service with synchronous observable\n    // which makes subscriber miss the dispatched actions.\n    // Of course that's a testing mistake. But easy to forget, leading to painful debugging.\n    this.reducedActions$ = scannedActions$.pipe(shareReplay(1));\n    // Start listening so late subscriber won't miss the most recent action.\n    this.raSubscription = this.reducedActions$.subscribe();\n  }\n\n  /**\n   * Create an `EntityDispatcher` for an entity type `T` and store.\n   */\n  create<T>(\n    /** Name of the entity type */\n    entityName: string,\n    /**\n     * Function that returns the primary key for an entity `T`.\n     * Usually acquired from `EntityDefinition` metadata.\n     * @param {IdSelector<T>} selectId\n     */\n    selectId: IdSelector<T> = defaultSelectId,\n    /** Defaults for options that influence dispatcher behavior such as whether\n     * `add()` is optimistic or pessimistic;\n     * @param {Partial<EntityDispatcherDefaultOptions>} defaultOptions\n     */\n    defaultOptions: Partial<EntityDispatcherDefaultOptions> = {}\n  ): EntityDispatcher<T> {\n    // merge w/ defaultOptions with injected defaults\n    const options: EntityDispatcherDefaultOptions = {\n      ...this.entityDispatcherDefaultOptions,\n      ...defaultOptions,\n    };\n    return new EntityDispatcherBase<T>(\n      entityName,\n      this.entityActionFactory,\n      this.store,\n      selectId,\n      options,\n      this.reducedActions$,\n      this.entityCacheSelector,\n      this.correlationIdGenerator\n    );\n  }\n\n  ngOnDestroy() {\n    this.raSubscription.unsubscribe();\n  }\n}\n"
  },
  {
    "path": "modules/data/src/dispatchers/entity-dispatcher.ts",
    "content": "import { Action, Store } from '@ngrx/store';\nimport { IdSelector, Update } from '@ngrx/entity';\n\nimport { EntityAction, EntityActionOptions } from '../actions/entity-action';\nimport { EntityActionGuard } from '../actions/entity-action-guard';\nimport { EntityCommands } from './entity-commands';\nimport { EntityCache } from '../reducers/entity-cache';\nimport { EntityOp } from '../actions/entity-op';\n\n/**\n * Dispatches EntityCollection actions to their reducers and effects.\n * The substance of the interface is in EntityCommands.\n */\nexport interface EntityDispatcher<T> extends EntityCommands<T> {\n  /** Name of the entity type */\n  readonly entityName: string;\n\n  /**\n   * Utility class with methods to validate EntityAction payloads.\n   */\n  readonly guard: EntityActionGuard<T>;\n\n  /** Returns the primary key (id) of this entity */\n  readonly selectId: IdSelector<T>;\n\n  /** Returns the store, scoped to the EntityCache */\n  readonly store: Store<EntityCache>;\n\n  /**\n   * Create an {EntityAction} for this entity type.\n   * @param op {EntityOp} the entity operation\n   * @param [data] the action data\n   * @param [options] additional options\n   * @returns the EntityAction\n   */\n  createEntityAction<P = any>(\n    op: EntityOp,\n    data?: P,\n    options?: EntityActionOptions\n  ): EntityAction<P>;\n\n  /**\n   * Create an {EntityAction} for this entity type and\n   * dispatch it immediately to the store.\n   * @param op {EntityOp} the entity operation\n   * @param [data] the action data\n   * @param [options] additional options\n   * @returns the dispatched EntityAction\n   */\n  createAndDispatch<P = any>(\n    op: EntityOp,\n    data?: P,\n    options?: EntityActionOptions\n  ): EntityAction<P>;\n\n  /**\n   * Dispatch an Action to the store.\n   * @param action the Action\n   * @returns the dispatched Action\n   */\n  dispatch(action: Action): Action;\n\n  /**\n   * Convert an entity (or partial entity) into the `Update<T>` object\n   * `update...` and `upsert...` methods take `Update<T>` args\n   */\n  toUpdate(entity: Partial<T>): Update<T>;\n}\n\n/**\n * Persistence operation canceled\n */\nexport class PersistanceCanceled {\n  constructor(public readonly message?: string) {\n    this.message = message || 'Canceled by user';\n  }\n}\n"
  },
  {
    "path": "modules/data/src/effects/entity-cache-effects.ts",
    "content": "import { Inject, Injectable, Optional } from '@angular/core';\nimport { Action } from '@ngrx/store';\nimport { Actions, ofType, createEffect } from '@ngrx/effects';\n\nimport {\n  asyncScheduler,\n  Observable,\n  of,\n  merge,\n  race,\n  SchedulerLike,\n} from 'rxjs';\nimport {\n  concatMap,\n  catchError,\n  delay,\n  filter,\n  map,\n  mergeMap,\n} from 'rxjs/operators';\n\nimport { DataServiceError } from '../dataservices/data-service-error';\nimport {\n  ChangeSet,\n  excludeEmptyChangeSetItems,\n} from '../actions/entity-cache-change-set';\nimport { EntityActionFactory } from '../actions/entity-action-factory';\nimport { EntityOp } from '../actions/entity-op';\n\nimport {\n  EntityCacheAction,\n  SaveEntities,\n  SaveEntitiesCancel,\n  SaveEntitiesCanceled,\n  SaveEntitiesError,\n  SaveEntitiesSuccess,\n} from '../actions/entity-cache-action';\nimport { EntityCacheDataService } from '../dataservices/entity-cache-data.service';\nimport { ENTITY_EFFECTS_SCHEDULER } from './entity-effects-scheduler';\nimport { Logger } from '../utils/interfaces';\n\n@Injectable()\nexport class EntityCacheEffects {\n  // See https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md\n  /** Delay for error and skip observables. Must be multiple of 10 for marble testing. */\n  private responseDelay = 10;\n\n  constructor(\n    private actions: Actions,\n    private dataService: EntityCacheDataService,\n    private entityActionFactory: EntityActionFactory,\n    private logger: Logger,\n    /**\n     * Injecting an optional Scheduler that will be undefined\n     * in normal application usage, but its injected here so that you can mock out\n     * during testing using the RxJS TestScheduler for simulating passages of time.\n     */\n    @Optional()\n    @Inject(ENTITY_EFFECTS_SCHEDULER)\n    private scheduler: SchedulerLike\n  ) {}\n\n  /**\n   * Observable of SAVE_ENTITIES_CANCEL actions with non-null correlation ids\n   */\n  saveEntitiesCancel$: Observable<SaveEntitiesCancel> = createEffect(\n    () =>\n      this.actions.pipe(\n        ofType(EntityCacheAction.SAVE_ENTITIES_CANCEL),\n        filter((a: SaveEntitiesCancel) => a.payload.correlationId != null)\n      ),\n    { dispatch: false }\n  );\n\n  // Concurrent persistence requests considered unsafe.\n  // `mergeMap` allows for concurrent requests which may return in any order\n  saveEntities$: Observable<Action> = createEffect(() =>\n    this.actions.pipe(\n      ofType(EntityCacheAction.SAVE_ENTITIES),\n      mergeMap((action: SaveEntities) => this.saveEntities(action))\n    )\n  );\n\n  /**\n   * Perform the requested SaveEntities actions and return a scalar Observable<Action>\n   * that the effect should dispatch to the store after the server responds.\n   * @param action The SaveEntities action\n   */\n  saveEntities(action: SaveEntities): Observable<Action> {\n    const error = action.payload.error;\n    if (error) {\n      return this.handleSaveEntitiesError$(action)(error);\n    }\n    try {\n      const changeSet = excludeEmptyChangeSetItems(action.payload.changeSet);\n      const { correlationId, mergeStrategy, tag, url } = action.payload;\n      const options = { correlationId, mergeStrategy, tag };\n\n      if (changeSet.changes.length === 0) {\n        // nothing to save\n        return of(new SaveEntitiesSuccess(changeSet, url, options));\n      }\n\n      // Cancellation: returns Observable<SaveEntitiesCanceled> for a saveEntities action\n      // whose correlationId matches the cancellation correlationId\n      const c = this.saveEntitiesCancel$.pipe(\n        filter((a) => correlationId === a.payload.correlationId),\n        map(\n          (a) =>\n            new SaveEntitiesCanceled(\n              correlationId,\n              a.payload.reason,\n              a.payload.tag\n            )\n        )\n      );\n\n      // Data: SaveEntities result as a SaveEntitiesSuccess action\n      const d = this.dataService.saveEntities(changeSet, url).pipe(\n        concatMap((result) =>\n          this.handleSaveEntitiesSuccess$(\n            action,\n            this.entityActionFactory\n          )(result)\n        ),\n        catchError(this.handleSaveEntitiesError$(action))\n      );\n\n      // Emit which ever gets there first; the other observable is terminated.\n      return race(c, d);\n    } catch (err: any) {\n      return this.handleSaveEntitiesError$(action)(err);\n    }\n  }\n\n  /** return handler of error result of saveEntities, returning a scalar observable of error action */\n  private handleSaveEntitiesError$(\n    action: SaveEntities\n  ): (err: DataServiceError | Error) => Observable<Action> {\n    // Although error may return immediately,\n    // ensure observable takes some time,\n    // as app likely assumes asynchronous response.\n    return (err: DataServiceError | Error) => {\n      const error =\n        err instanceof DataServiceError ? err : new DataServiceError(err, null);\n      return of(new SaveEntitiesError(error, action)).pipe(\n        delay(this.responseDelay, this.scheduler || asyncScheduler)\n      );\n    };\n  }\n\n  /** return handler of the ChangeSet result of successful saveEntities() */\n  private handleSaveEntitiesSuccess$(\n    action: SaveEntities,\n    entityActionFactory: EntityActionFactory\n  ): (changeSet: ChangeSet) => Observable<Action> {\n    const { url, correlationId, mergeStrategy, tag } = action.payload;\n    const options = { correlationId, mergeStrategy, tag };\n\n    return (changeSet) => {\n      // DataService returned a ChangeSet with possible updates to the saved entities\n      if (changeSet) {\n        return of(new SaveEntitiesSuccess(changeSet, url, options));\n      }\n\n      // No ChangeSet = Server probably responded '204 - No Content' because\n      // it made no changes to the inserted/updated entities.\n      // Respond with success action best on the ChangeSet in the request.\n      changeSet = action.payload.changeSet;\n\n      // If pessimistic save, return success action with the original ChangeSet\n      if (!action.payload.isOptimistic) {\n        return of(new SaveEntitiesSuccess(changeSet, url, options));\n      }\n\n      // If optimistic save, avoid cache grinding by just turning off the loading flags\n      // for all collections in the original ChangeSet\n      const entityNames = changeSet.changes.reduce(\n        (acc, item) =>\n          acc.indexOf(item.entityName) === -1\n            ? acc.concat(item.entityName)\n            : acc,\n        [] as string[]\n      );\n      return merge(\n        entityNames.map((name) =>\n          entityActionFactory.create(name, EntityOp.SET_LOADING, false)\n        )\n      );\n    };\n  }\n}\n"
  },
  {
    "path": "modules/data/src/effects/entity-effects-scheduler.ts",
    "content": "import { InjectionToken } from '@angular/core';\nimport { SchedulerLike } from 'rxjs';\n\n// See https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md\n/** Token to inject a special RxJS Scheduler during marble tests. */\nexport const ENTITY_EFFECTS_SCHEDULER = new InjectionToken<SchedulerLike>(\n  '@ngrx/data Entity Effects Scheduler'\n);\n"
  },
  {
    "path": "modules/data/src/effects/entity-effects.ts",
    "content": "import { Inject, Injectable, Optional } from '@angular/core';\nimport { Action } from '@ngrx/store';\nimport { Actions, createEffect } from '@ngrx/effects';\nimport { Update } from '@ngrx/entity';\n\nimport { asyncScheduler, Observable, of, race, SchedulerLike } from 'rxjs';\nimport { catchError, delay, filter, map, mergeMap } from 'rxjs/operators';\n\nimport { EntityAction } from '../actions/entity-action';\nimport { EntityActionFactory } from '../actions/entity-action-factory';\nimport { ENTITY_EFFECTS_SCHEDULER } from './entity-effects-scheduler';\nimport { EntityOp, makeSuccessOp } from '../actions/entity-op';\nimport { ofEntityOp } from '../actions/entity-action-operators';\nimport { UpdateResponseData } from '../actions/update-response-data';\n\nimport { EntityDataService } from '../dataservices/entity-data.service';\nimport { PersistenceResultHandler } from '../dataservices/persistence-result-handler.service';\n\nexport const persistOps: EntityOp[] = [\n  EntityOp.QUERY_ALL,\n  EntityOp.QUERY_LOAD,\n  EntityOp.QUERY_BY_KEY,\n  EntityOp.QUERY_MANY,\n  EntityOp.SAVE_ADD_ONE,\n  EntityOp.SAVE_DELETE_ONE,\n  EntityOp.SAVE_UPDATE_ONE,\n  EntityOp.SAVE_UPSERT_ONE,\n];\n\n@Injectable()\nexport class EntityEffects {\n  // See https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md\n  /** Delay for error and skip observables. Must be multiple of 10 for marble testing. */\n  private responseDelay = 10;\n\n  /**\n   * Observable of non-null cancellation correlation ids from CANCEL_PERSIST actions\n   */\n  cancel$: Observable<any> = createEffect(\n    () =>\n      this.actions.pipe(\n        ofEntityOp(EntityOp.CANCEL_PERSIST),\n        map((action: EntityAction) => action.payload.correlationId),\n        filter((id) => id != null)\n      ),\n    { dispatch: false }\n  );\n\n  // `mergeMap` allows for concurrent requests which may return in any order\n  persist$: Observable<Action> = createEffect(() =>\n    this.actions.pipe(\n      ofEntityOp(persistOps),\n      mergeMap((action) => this.persist(action))\n    )\n  );\n\n  constructor(\n    private actions: Actions<EntityAction>,\n    private dataService: EntityDataService,\n    private entityActionFactory: EntityActionFactory,\n    private resultHandler: PersistenceResultHandler,\n    /**\n     * Injecting an optional Scheduler that will be undefined\n     * in normal application usage, but its injected here so that you can mock out\n     * during testing using the RxJS TestScheduler for simulating passages of time.\n     */\n    @Optional()\n    @Inject(ENTITY_EFFECTS_SCHEDULER)\n    private scheduler: SchedulerLike\n  ) {}\n\n  /**\n   * Perform the requested persistence operation and return a scalar Observable<Action>\n   * that the effect should dispatch to the store after the server responds.\n   * @param action A persistence operation EntityAction\n   */\n  persist(action: EntityAction): Observable<Action> {\n    if (action.payload.skip) {\n      // Should not persist. Pretend it succeeded.\n      return this.handleSkipSuccess$(action);\n    }\n    if (action.payload.error) {\n      return this.handleError$(action)(action.payload.error);\n    }\n    try {\n      // Cancellation: returns Observable of CANCELED_PERSIST for a persistence EntityAction\n      // whose correlationId matches cancellation correlationId\n      const c = this.cancel$.pipe(\n        filter((id) => action.payload.correlationId === id),\n        map((id) =>\n          this.entityActionFactory.createFromAction(action, {\n            entityOp: EntityOp.CANCELED_PERSIST,\n          })\n        )\n      );\n\n      // Data: entity collection DataService result as a successful persistence EntityAction\n      const d = this.callDataService(action).pipe(\n        map(this.resultHandler.handleSuccess(action)),\n        catchError(this.handleError$(action))\n      );\n\n      // Emit which ever gets there first; the other observable is terminated.\n      return race(c, d);\n    } catch (err: any) {\n      return this.handleError$(action)(err);\n    }\n  }\n\n  private callDataService(action: EntityAction) {\n    const { entityName, entityOp, data, httpOptions } = action.payload;\n    const service = this.dataService.getService(entityName);\n    switch (entityOp) {\n      case EntityOp.QUERY_ALL:\n      case EntityOp.QUERY_LOAD:\n        return service.getAll(httpOptions);\n\n      case EntityOp.QUERY_BY_KEY:\n        return service.getById(data, httpOptions);\n\n      case EntityOp.QUERY_MANY:\n        return service.getWithQuery(data, httpOptions);\n\n      case EntityOp.SAVE_ADD_ONE:\n        return service.add(data, httpOptions);\n\n      case EntityOp.SAVE_DELETE_ONE:\n        return service.delete(data, httpOptions);\n\n      case EntityOp.SAVE_UPDATE_ONE:\n        const { id, changes } = data as Update<any>; // data must be Update<T>\n        return service.update(data, httpOptions).pipe(\n          map((updatedEntity: any) => {\n            // Return an Update<T> with updated entity data.\n            // If server returned entity data, merge with the changes that were sent\n            // and set the 'changed' flag to true.\n            // If server did not return entity data,\n            // assume it made no additional changes of its own, return the original changes,\n            // and set the `changed` flag to `false`.\n            const hasData =\n              updatedEntity && Object.keys(updatedEntity).length > 0;\n            const responseData: UpdateResponseData<any> = hasData\n              ? { id, changes: { ...changes, ...updatedEntity }, changed: true }\n              : { id, changes, changed: false };\n            return responseData;\n          })\n        );\n\n      case EntityOp.SAVE_UPSERT_ONE:\n        return service.upsert(data, httpOptions).pipe(\n          map((upsertedEntity: any) => {\n            const hasData =\n              upsertedEntity && Object.keys(upsertedEntity).length > 0;\n            return hasData ? upsertedEntity : data; // ensure a returned entity value.\n          })\n        );\n      default:\n        throw new Error(`Persistence action \"${entityOp}\" is not implemented.`);\n    }\n  }\n\n  /**\n   * Handle error result of persistence operation on an EntityAction,\n   * returning a scalar observable of error action\n   */\n  private handleError$(\n    action: EntityAction\n  ): (error: Error) => Observable<EntityAction> {\n    // Although error may return immediately,\n    // ensure observable takes some time,\n    // as app likely assumes asynchronous response.\n    return (error: Error) =>\n      of(this.resultHandler.handleError(action)(error)).pipe(\n        delay(this.responseDelay, this.scheduler || asyncScheduler)\n      );\n  }\n\n  /**\n   * Because EntityAction.payload.skip is true, skip the persistence step and\n   * return a scalar success action that looks like the operation succeeded.\n   */\n  private handleSkipSuccess$(\n    originalAction: EntityAction\n  ): Observable<EntityAction> {\n    const successOp = makeSuccessOp(originalAction.payload.entityOp);\n    const successAction = this.entityActionFactory.createFromAction(\n      originalAction,\n      {\n        entityOp: successOp,\n      }\n    );\n    // Although returns immediately,\n    // ensure observable takes one tick (by using a promise),\n    // as app likely assumes asynchronous response.\n    return of(successAction).pipe(\n      delay(this.responseDelay, this.scheduler || asyncScheduler)\n    );\n  }\n}\n"
  },
  {
    "path": "modules/data/src/entity-data-config.ts",
    "content": "import { InjectionToken } from '@angular/core';\nimport { MetaReducer } from '@ngrx/store';\nimport { EntityCache } from './reducers/entity-cache';\nimport { EntityAction } from './actions/entity-action';\nimport { EntityMetadataMap } from './entity-metadata/entity-metadata';\nimport { EntityCollection } from './reducers/entity-collection';\n\nexport interface EntityDataModuleConfig {\n  entityMetadata?: EntityMetadataMap;\n  entityCacheMetaReducers?: (\n    | MetaReducer<EntityCache>\n    | InjectionToken<MetaReducer<EntityCache>>\n  )[];\n  entityCollectionMetaReducers?: MetaReducer<EntityCollection, EntityAction>[];\n  // Initial EntityCache state or a function that returns that state\n  initialEntityCacheState?: EntityCache | (() => EntityCache);\n  pluralNames?: { [name: string]: string };\n}\n"
  },
  {
    "path": "modules/data/src/entity-data-without-effects.module.ts",
    "content": "import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { EntityDataModuleConfig } from './entity-data-config';\nimport {\n  BASE_ENTITY_DATA_PROVIDERS,\n  provideEntityDataConfig,\n} from './provide-entity-data';\n\n/**\n * Module without effects or dataservices which means no HTTP calls\n * This module helpful for internal testing.\n * Also helpful for apps that handle server access on their own and\n * therefore opt-out of @ngrx/effects for entities\n */\n@NgModule({\n  providers: [BASE_ENTITY_DATA_PROVIDERS],\n})\nexport class EntityDataModuleWithoutEffects {\n  static forRoot(\n    config: EntityDataModuleConfig\n  ): ModuleWithProviders<EntityDataModuleWithoutEffects> {\n    return {\n      ngModule: EntityDataModuleWithoutEffects,\n      providers: [provideEntityDataConfig(config)],\n    };\n  }\n}\n"
  },
  {
    "path": "modules/data/src/entity-data.module.ts",
    "content": "import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { EntityDataModuleConfig } from './entity-data-config';\nimport { EntityDataModuleWithoutEffects } from './entity-data-without-effects.module';\nimport {\n  ENTITY_DATA_EFFECTS_PROVIDERS,\n  provideEntityDataConfig,\n} from './provide-entity-data';\n\n/**\n * entity-data main module includes effects and HTTP data services\n * Configure with `forRoot`.\n * No `forFeature` yet.\n */\n@NgModule({\n  imports: [EntityDataModuleWithoutEffects],\n  providers: [ENTITY_DATA_EFFECTS_PROVIDERS],\n})\nexport class EntityDataModule {\n  static forRoot(\n    config: EntityDataModuleConfig\n  ): ModuleWithProviders<EntityDataModule> {\n    return {\n      ngModule: EntityDataModule,\n      providers: [provideEntityDataConfig(config)],\n    };\n  }\n}\n"
  },
  {
    "path": "modules/data/src/entity-metadata/entity-definition.service.ts",
    "content": "import { Inject, Injectable, Optional } from '@angular/core';\n\nimport { createEntityDefinition, EntityDefinition } from './entity-definition';\nimport {\n  EntityMetadata,\n  EntityMetadataMap,\n  ENTITY_METADATA_TOKEN,\n} from './entity-metadata';\n\nexport interface EntityDefinitions {\n  [entityName: string]: EntityDefinition<any>;\n}\n\n/** Registry of EntityDefinitions for all cached entity types */\n@Injectable()\nexport class EntityDefinitionService {\n  /** {EntityDefinition} for all cached entity types */\n  private readonly definitions: EntityDefinitions = {};\n\n  constructor(\n    @Optional()\n    @Inject(ENTITY_METADATA_TOKEN)\n    entityMetadataMaps: EntityMetadataMap[]\n  ) {\n    if (entityMetadataMaps) {\n      entityMetadataMaps.forEach((map) => this.registerMetadataMap(map));\n    }\n  }\n\n  /**\n   * Get (or create) a data service for entity type\n   * @param entityName - the name of the type\n   *\n   * Examples:\n   *   getDefinition('Hero'); // definition for Heroes, untyped\n   *   getDefinition<Hero>(`Hero`); // definition for Heroes, typed with Hero interface\n   */\n  getDefinition<T>(\n    entityName: string,\n    shouldThrow = true\n  ): EntityDefinition<T> {\n    entityName = entityName.trim();\n    const definition = this.definitions[entityName];\n    if (!definition && shouldThrow) {\n      throw new Error(`No EntityDefinition for entity type \"${entityName}\".`);\n    }\n    return definition;\n  }\n\n  //////// Registration methods //////////\n\n  /**\n   * Create and register the {EntityDefinition} for the {EntityMetadata} of an entity type\n   * @param name - the name of the entity type\n   * @param definition - {EntityMetadata} for a collection for that entity type\n   *\n   * Examples:\n   *   registerMetadata(myHeroEntityDefinition);\n   */\n  registerMetadata(metadata: EntityMetadata) {\n    if (metadata) {\n      const definition = createEntityDefinition(metadata);\n      this.registerDefinition(definition);\n    }\n  }\n\n  /**\n   * Register an EntityMetadataMap.\n   * @param metadataMap - a map of entityType names to entity metadata\n   *\n   * Examples:\n   *   registerMetadataMap({\n   *     'Hero': myHeroMetadata,\n   *     Villain: myVillainMetadata\n   *   });\n   */\n  registerMetadataMap(metadataMap: EntityMetadataMap = {}) {\n    // The entity type name should be the same as the map key\n    Object.keys(metadataMap || {}).forEach((entityName) =>\n      this.registerMetadata({ entityName, ...metadataMap[entityName] })\n    );\n  }\n\n  /**\n   * Register an {EntityDefinition} for an entity type\n   * @param definition - EntityDefinition of a collection for that entity type\n   *\n   * Examples:\n   *   registerDefinition('Hero', myHeroEntityDefinition);\n   */\n  registerDefinition<T>(definition: EntityDefinition<T>) {\n    this.definitions[definition.entityName] = definition;\n  }\n\n  /**\n   * Register a batch of EntityDefinitions.\n   * @param definitions - map of entityType name and associated EntityDefinitions to merge.\n   *\n   * Examples:\n   *   registerDefinitions({\n   *     'Hero': myHeroEntityDefinition,\n   *     Villain: myVillainEntityDefinition\n   *   });\n   */\n  registerDefinitions(definitions: EntityDefinitions) {\n    Object.assign(this.definitions, definitions);\n  }\n}\n"
  },
  {
    "path": "modules/data/src/entity-metadata/entity-definition.ts",
    "content": "import { EntityAdapter, createEntityAdapter } from '@ngrx/entity';\nimport { Comparer, IdSelector } from '@ngrx/entity';\n\nimport { EntityDispatcherDefaultOptions } from '../dispatchers/entity-dispatcher-default-options';\nimport { defaultSelectId } from '../utils/utilities';\nimport { EntityCollection } from '../reducers/entity-collection';\nimport { EntityMetadata } from './entity-metadata';\n\nexport interface EntityDefinition<T = any> {\n  entityName: string;\n  entityAdapter: EntityAdapter<T>;\n  entityDispatcherOptions?: Partial<EntityDispatcherDefaultOptions>;\n  initialState: EntityCollection<T>;\n  metadata: EntityMetadata<T>;\n  noChangeTracking: boolean;\n  selectId: IdSelector<T>;\n  sortComparer: false | Comparer<T>;\n}\n\nexport function createEntityDefinition<T, S extends object>(\n  metadata: EntityMetadata<T, S>\n): EntityDefinition<T> {\n  let entityName = metadata.entityName;\n  if (!entityName) {\n    throw new Error('Missing required entityName');\n  }\n  metadata.entityName = entityName = entityName.trim();\n  const selectId = metadata.selectId || defaultSelectId;\n  const sortComparer = (metadata.sortComparer = metadata.sortComparer || false);\n\n  const entityAdapter = createEntityAdapter<T>({ selectId, sortComparer });\n\n  const entityDispatcherOptions: Partial<EntityDispatcherDefaultOptions> =\n    metadata.entityDispatcherOptions || {};\n\n  const initialState: EntityCollection<T> = entityAdapter.getInitialState({\n    entityName,\n    filter: '',\n    loaded: false,\n    loading: false,\n    changeState: {},\n    ...(metadata.additionalCollectionState || {}),\n  });\n\n  const noChangeTracking = metadata.noChangeTracking === true; // false by default\n\n  return {\n    entityName,\n    entityAdapter,\n    entityDispatcherOptions,\n    initialState,\n    metadata,\n    noChangeTracking,\n    selectId,\n    sortComparer,\n  };\n}\n"
  },
  {
    "path": "modules/data/src/entity-metadata/entity-filters.ts",
    "content": "/**\n * Filters the `entities` array argument and returns the original `entities`,\n * or a new filtered array of entities.\n * NEVER mutate the original `entities` array itself.\n **/\nexport type EntityFilterFn<T> = (entities: T[], pattern?: any) => T[];\n\n/**\n * Creates an {EntityFilterFn} that matches RegExp or RegExp string pattern\n * anywhere in any of the given props of an entity.\n * If pattern is a string, spaces are significant and ignores case.\n */\nexport function PropsFilterFnFactory<T = any>(\n  props: (keyof T)[] = []\n): EntityFilterFn<T> {\n  if (props.length === 0) {\n    // No properties -> nothing could match -> return unfiltered\n    return (entities: T[], pattern: string) => entities;\n  }\n\n  return (entities: T[], pattern: string | RegExp) => {\n    if (!entities) {\n      return [];\n    }\n\n    const regExp =\n      typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern;\n    if (regExp) {\n      const predicate = (e: any) => props.some((prop) => regExp.test(e[prop]));\n      return entities.filter(predicate);\n    }\n    return entities;\n  };\n}\n"
  },
  {
    "path": "modules/data/src/entity-metadata/entity-metadata.ts",
    "content": "import { InjectionToken } from '@angular/core';\n\nimport { IdSelector, Comparer } from '@ngrx/entity';\n\nimport { EntityDispatcherDefaultOptions } from '../dispatchers/entity-dispatcher-default-options';\nimport { EntityFilterFn } from './entity-filters';\n\nexport const ENTITY_METADATA_TOKEN = new InjectionToken<EntityMetadataMap>(\n  '@ngrx/data Entity Metadata'\n);\n\n/** Metadata that describe an entity type and its collection to @ngrx/data */\nexport interface EntityMetadata<T = any, S extends object = {}> {\n  entityName: string;\n  entityDispatcherOptions?: Partial<EntityDispatcherDefaultOptions>;\n  filterFn?: EntityFilterFn<T>;\n  noChangeTracking?: boolean;\n  selectId?: IdSelector<T>;\n  sortComparer?: false | Comparer<T>;\n  additionalCollectionState?: S;\n}\n\n/** Map entity-type name to its EntityMetadata */\nexport interface EntityMetadataMap {\n  [entityName: string]: Partial<EntityMetadata<any>>;\n}\n"
  },
  {
    "path": "modules/data/src/entity-services/entity-collection-service-base.ts",
    "content": "import { Action, Store } from '@ngrx/store';\nimport { Dictionary, IdSelector, Update } from '@ngrx/entity';\n\nimport { Observable } from 'rxjs';\n\nimport { EntityAction, EntityActionOptions } from '../actions/entity-action';\nimport { EntityActionGuard } from '../actions/entity-action-guard';\nimport {\n  EntityCollection,\n  ChangeStateMap,\n} from '../reducers/entity-collection';\nimport { EntityDispatcher } from '../dispatchers/entity-dispatcher';\nimport { EntityCollectionService } from './entity-collection-service';\nimport { EntityCollectionServiceElementsFactory } from './entity-collection-service-elements-factory';\nimport { EntityOp } from '../actions/entity-op';\nimport { EntitySelectors } from '../selectors/entity-selectors';\nimport { EntitySelectors$ } from '../selectors/entity-selectors$';\nimport { QueryParams } from '../dataservices/interfaces';\n\n/**\n * Base class for a concrete EntityCollectionService<T>.\n * Can be instantiated. Cannot be injected. Use EntityCollectionServiceFactory to create.\n * @param EntityCollectionServiceElements The ingredients for this service\n * as a source of supporting services for creating an EntityCollectionService<T> instance.\n */\nexport class EntityCollectionServiceBase<\n  T,\n  S$ extends EntitySelectors$<T> = EntitySelectors$<T>,\n> implements EntityCollectionService<T>\n{\n  /** Dispatcher of EntityCommands (EntityActions) */\n  readonly dispatcher: EntityDispatcher<T>;\n\n  /** All selectors of entity collection properties */\n  readonly selectors: EntitySelectors<T>;\n\n  /** All selectors$ (observables of entity collection properties) */\n  readonly selectors$: S$;\n\n  constructor(\n    /** Name of the entity type of this collection service */\n    public readonly entityName: string,\n    /** Creates the core elements of the EntityCollectionService for this entity type */\n    serviceElementsFactory: EntityCollectionServiceElementsFactory\n  ) {\n    entityName = entityName.trim();\n    const { dispatcher, selectors, selectors$ } = serviceElementsFactory.create<\n      T,\n      S$\n    >(entityName);\n\n    this.entityName = entityName;\n    this.dispatcher = dispatcher;\n    this.guard = dispatcher.guard;\n    this.selectId = dispatcher.selectId;\n    this.toUpdate = dispatcher.toUpdate;\n\n    this.selectors = selectors;\n    this.selectors$ = selectors$;\n    this.collection$ = selectors$.collection$;\n    this.count$ = selectors$.count$;\n    this.entities$ = selectors$.entities$;\n    this.entityActions$ = selectors$.entityActions$;\n    this.entityMap$ = selectors$.entityMap$;\n    this.errors$ = selectors$.errors$;\n    this.filter$ = selectors$.filter$;\n    this.filteredEntities$ = selectors$.filteredEntities$;\n    this.keys$ = selectors$.keys$;\n    this.loaded$ = selectors$.loaded$;\n    this.loading$ = selectors$.loading$;\n    this.changeState$ = selectors$.changeState$;\n  }\n\n  /**\n   * Create an {EntityAction} for this entity type.\n   * @param op {EntityOp} the entity operation\n   * @param [data] the action data\n   * @param [options] additional options\n   * @returns the EntityAction\n   */\n  createEntityAction<P = any>(\n    op: EntityOp,\n    data?: P,\n    options?: EntityActionOptions\n  ): EntityAction<P> {\n    return this.dispatcher.createEntityAction(op, data, options);\n  }\n\n  /**\n   * Create an {EntityAction} for this entity type and\n   * dispatch it immediately to the store.\n   * @param op {EntityOp} the entity operation\n   * @param [data] the action data\n   * @param [options] additional options\n   * @returns the dispatched EntityAction\n   */\n  createAndDispatch<P = any>(\n    op: EntityOp,\n    data?: P,\n    options?: EntityActionOptions\n  ): EntityAction<P> {\n    return this.dispatcher.createAndDispatch(op, data, options);\n  }\n\n  /**\n   * Dispatch an action of any type to the ngrx store.\n   * @param action the Action\n   * @returns the dispatched Action\n   */\n  dispatch(action: Action): Action {\n    return this.dispatcher.dispatch(action);\n  }\n\n  /** The NgRx Store for the {EntityCache} */\n  get store() {\n    return this.dispatcher.store;\n  }\n\n  /**\n   * Utility class with methods to validate EntityAction payloads.\n   */\n  guard: EntityActionGuard<T>;\n\n  /** Returns the primary key (id) of this entity */\n  selectId: IdSelector<T>;\n\n  /**\n   * Convert an entity (or partial entity) into the `Update<T>` object\n   * `update...` and `upsert...` methods take `Update<T>` args\n   */\n  toUpdate: (entity: Partial<T>) => Update<T>;\n\n  // region Dispatch commands\n\n  /**\n   * Dispatch action to save a new entity to remote storage.\n   * @param entity entity to add, which may omit its key if pessimistic and the server creates the key;\n   * must have a key if optimistic save.\n   * @param [options] options that influence save and merge behavior\n   * @returns Observable of the entity\n   * after server reports successful save or the save error.\n   */\n  add(\n    entity: Partial<T>,\n    options: EntityActionOptions & { isOptimistic: false }\n  ): Observable<T>;\n  add(entity: T, options?: EntityActionOptions): Observable<T>;\n  add(entity: T, options?: EntityActionOptions): Observable<T> {\n    return this.dispatcher.add(entity, options);\n  }\n\n  /**\n   * Dispatch action to cancel the persistence operation (query or save) with the given correlationId.\n   * @param correlationId The correlation id for the corresponding EntityAction\n   * @param [reason] explains why canceled and by whom.\n   * @param [options] options such as the tag and mergeStrategy\n   */\n  cancel(\n    correlationId: any,\n    reason?: string,\n    options?: EntityActionOptions\n  ): void {\n    this.dispatcher.cancel(correlationId, reason, options);\n  }\n\n  /**\n   * Dispatch action to delete entity from remote storage by key.\n   * @param key The entity to delete\n   * @param [options] options that influence save and merge behavior\n   * @returns Observable of the deleted key\n   * after server reports successful save or the save error.\n   */\n  delete(entity: T, options?: EntityActionOptions): Observable<number | string>;\n\n  /**\n   * Dispatch action to delete entity from remote storage by key.\n   * @param key The primary key of the entity to remove\n   * @param [options] options that influence save and merge behavior\n   * @returns Observable of the deleted key\n   * after server reports successful save or the save error.\n   */\n  delete(\n    key: number | string,\n    options?: EntityActionOptions\n  ): Observable<number | string>;\n  delete(\n    arg: number | string | T,\n    options?: EntityActionOptions\n  ): Observable<number | string> {\n    return this.dispatcher.delete(arg as any, options);\n  }\n\n  /**\n   * Dispatch action to query remote storage for all entities and\n   * merge the queried entities into the cached collection.\n   * @param [options] options that influence merge behavior\n   * @returns Observable of the collection\n   * after server reports successful query or the query error.\n   * @see load()\n   */\n  getAll(options?: EntityActionOptions): Observable<T[]> {\n    return this.dispatcher.getAll(options);\n  }\n\n  /**\n   * Dispatch action to query remote storage for the entity with this primary key.\n   * If the server returns an entity,\n   * merge it into the cached collection.\n   * @param key The primary key of the entity to get.\n   * @param [options] options that influence merge behavior\n   * @returns Observable of the queried entity that is in the collection\n   * after server reports success or the query error.\n   */\n  getByKey(key: any, options?: EntityActionOptions): Observable<T> {\n    return this.dispatcher.getByKey(key, options);\n  }\n\n  /**\n   * Dispatch action to query remote storage for the entities that satisfy a query expressed\n   * with either a query parameter map or an HTTP URL query string,\n   * and merge the results into the cached collection.\n   * @param queryParams the query in a form understood by the server\n   * @param [options] options that influence merge behavior\n   * @returns Observable of the queried entities\n   * after server reports successful query or the query error.\n   */\n  getWithQuery(\n    queryParams: QueryParams | string,\n    options?: EntityActionOptions\n  ): Observable<T[]> {\n    return this.dispatcher.getWithQuery(queryParams, options);\n  }\n\n  /**\n   * Dispatch action to query remote storage for all entities and\n   * completely replace the cached collection with the queried entities.\n   * @param [options] options that influence load behavior\n   * @returns Observable of the collection\n   * after server reports successful query or the query error.\n   * @see getAll\n   */\n  load(options?: EntityActionOptions): Observable<T[]> {\n    return this.dispatcher.load(options);\n  }\n\n  /**\n   * Dispatch action to query remote storage for the entities that satisfy a query expressed\n   * with either a query parameter map or an HTTP URL query string,\n   * and completely replace the cached collection with the queried entities.\n   * @param queryParams the query in a form understood by the server\n   * @param [options] options that influence load behavior\n   * @returns Observable of the queried entities\n   * after server reports successful query or the query error.\n   */\n  loadWithQuery(\n    queryParams: QueryParams | string,\n    options?: EntityActionOptions\n  ): Observable<T[]> {\n    return this.dispatcher.loadWithQuery(queryParams, options);\n  }\n\n  /**\n   * Dispatch action to save the updated entity (or partial entity) in remote storage.\n   * The update entity may be partial (but must have its key)\n   * in which case it patches the existing entity.\n   * @param entity update entity, which might be a partial of T but must at least have its key.\n   * @param [options] options that influence save and merge behavior\n   * @returns Observable of the updated entity\n   * after server reports successful save or the save error.\n   */\n  update(entity: Partial<T>, options?: EntityActionOptions): Observable<T> {\n    return this.dispatcher.update(entity, options);\n  }\n\n  /**\n   * Dispatch action to save a new or existing entity to remote storage.\n   * Call only if the server supports upsert.\n   * @param entity entity to add or upsert.\n   * It may omit its key if an add, and is pessimistic, and the server creates the key;\n   * must have a key if optimistic save.\n   * @param [options] options that influence save and merge behavior\n   * @returns Observable of the entity\n   * after server reports successful save or the save error.\n   */\n  upsert(entity: T, options?: EntityActionOptions): Observable<T> {\n    return this.dispatcher.upsert(entity, options);\n  }\n\n  /*** Cache-only operations that do not update remote storage ***/\n\n  /**\n   * Replace all entities in the cached collection.\n   * Does not save to remote storage.\n   * @param entities to add directly to cache.\n   * @param [options] options such as mergeStrategy\n   */\n  addAllToCache(entities: T[], options?: EntityActionOptions): void {\n    this.dispatcher.addAllToCache(entities, options);\n  }\n\n  /**\n   * Add a new entity directly to the cache.\n   * Does not save to remote storage.\n   * Ignored if an entity with the same primary key is already in cache.\n   * @param entity to add directly to cache.\n   * @param [options] options such as mergeStrategy\n   */\n  addOneToCache(entity: T, options?: EntityActionOptions): void {\n    this.dispatcher.addOneToCache(entity, options);\n  }\n\n  /**\n   * Add multiple new entities directly to the cache.\n   * Does not save to remote storage.\n   * Entities with primary keys already in cache are ignored.\n   * @param entities to add directly to cache.\n   * @param [options] options such as mergeStrategy\n   */\n  addManyToCache(entities: T[], options?: EntityActionOptions): void {\n    this.dispatcher.addManyToCache(entities, options);\n  }\n\n  /** Clear the cached entity collection */\n  clearCache(): void {\n    this.dispatcher.clearCache();\n  }\n\n  /**\n   * Remove an entity directly from the cache.\n   * Does not delete that entity from remote storage.\n   * @param entity The entity to remove\n   * @param [options] options such as mergeStrategy\n   */\n  removeOneFromCache(entity: T, options?: EntityActionOptions): void;\n\n  /**\n   * Remove an entity directly from the cache.\n   * Does not delete that entity from remote storage.\n   * @param key The primary key of the entity to remove\n   * @param [options] options such as mergeStrategy\n   */\n  removeOneFromCache(key: number | string, options?: EntityActionOptions): void;\n  removeOneFromCache(\n    arg: (number | string) | T,\n    options?: EntityActionOptions\n  ): void {\n    this.dispatcher.removeOneFromCache(arg as any, options);\n  }\n\n  /**\n   * Remove multiple entities directly from the cache.\n   * Does not delete these entities from remote storage.\n   * @param entity The entities to remove\n   * @param [options] options such as mergeStrategy\n   */\n  removeManyFromCache(entities: T[], options?: EntityActionOptions): void;\n\n  /**\n   * Remove multiple entities directly from the cache.\n   * Does not delete these entities from remote storage.\n   * @param keys The primary keys of the entities to remove\n   * @param [options] options such as mergeStrategy\n   */\n  removeManyFromCache(\n    keys: (number | string)[],\n    options?: EntityActionOptions\n  ): void;\n  removeManyFromCache(\n    args: (number | string)[] | T[],\n    options?: EntityActionOptions\n  ): void {\n    this.dispatcher.removeManyFromCache(args as any[], options);\n  }\n\n  /**\n   * Update a cached entity directly.\n   * Does not update that entity in remote storage.\n   * Ignored if an entity with matching primary key is not in cache.\n   * The update entity may be partial (but must have its key)\n   * in which case it patches the existing entity.\n   * @param entity to update directly in cache.\n   * @param [options] options such as mergeStrategy\n   */\n  updateOneInCache(entity: Partial<T>, options?: EntityActionOptions): void {\n    // update entity might be a partial of T but must at least have its key.\n    // pass the Update<T> structure as the payload\n    this.dispatcher.updateOneInCache(entity, options);\n  }\n\n  /**\n   * Update multiple cached entities directly.\n   * Does not update these entities in remote storage.\n   * Entities whose primary keys are not in cache are ignored.\n   * Update entities may be partial but must at least have their keys.\n   * such partial entities patch their cached counterparts.\n   * @param entities to update directly in cache.\n   * @param [options] options such as mergeStrategy\n   */\n  updateManyInCache(\n    entities: Partial<T>[],\n    options?: EntityActionOptions\n  ): void {\n    this.dispatcher.updateManyInCache(entities, options);\n  }\n\n  /**\n   * Insert or update a cached entity directly.\n   * Does not save to remote storage.\n   * Upsert entity might be a partial of T but must at least have its key.\n   * Pass the Update<T> structure as the payload.\n   * @param entity to upsert directly in cache.\n   * @param [options] options such as mergeStrategy\n   */\n  upsertOneInCache(entity: Partial<T>, options?: EntityActionOptions): void {\n    this.dispatcher.upsertOneInCache(entity, options);\n  }\n\n  /**\n   * Insert or update multiple cached entities directly.\n   * Does not save to remote storage.\n   * Upsert entities might be partial but must at least have their keys.\n   * Pass an array of the Update<T> structure as the payload.\n   * @param entities to upsert directly in cache.\n   * @param [options] options such as mergeStrategy\n   */\n  upsertManyInCache(\n    entities: Partial<T>[],\n    options?: EntityActionOptions\n  ): void {\n    this.dispatcher.upsertManyInCache(entities, options);\n  }\n\n  /**\n   * Set the pattern that the collection's filter applies\n   * when using the `filteredEntities` selector.\n   */\n  setFilter(pattern: any): void {\n    this.dispatcher.setFilter(pattern);\n  }\n\n  /** Set the loaded flag */\n  setLoaded(isLoaded: boolean): void {\n    this.dispatcher.setLoaded(!!isLoaded);\n  }\n\n  /** Set the loading flag */\n  setLoading(isLoading: boolean): void {\n    this.dispatcher.setLoading(!!isLoading);\n  }\n\n  // endregion Dispatch commands\n\n  // region Selectors$\n  /** Observable of the collection as a whole */\n  collection$: Observable<EntityCollection<T>> | Store<EntityCollection<T>>;\n\n  /** Observable of count of entities in the cached collection. */\n  count$: Observable<number> | Store<number>;\n\n  /** Observable of all entities in the cached collection. */\n  entities$: Observable<T[]> | Store<T[]>;\n\n  /** Observable of actions related to this entity type. */\n  entityActions$: Observable<EntityAction>;\n\n  /** Observable of the map of entity keys to entities */\n  entityMap$: Observable<Dictionary<T>> | Store<Dictionary<T>>;\n\n  /** Observable of error actions related to this entity type. */\n  errors$: Observable<EntityAction>;\n\n  /** Observable of the filter pattern applied by the entity collection's filter function */\n  filter$: Observable<any> | Store<any>;\n\n  /** Observable of entities in the cached collection that pass the filter function */\n  filteredEntities$: Observable<T[]> | Store<T[]>;\n\n  /** Observable of the keys of the cached collection, in the collection's native sort order */\n  keys$: Observable<string[] | number[]> | Store<string[] | number[]>;\n\n  /** Observable true when the collection has been loaded */\n  loaded$: Observable<boolean> | Store<boolean>;\n\n  /** Observable true when a multi-entity query command is in progress. */\n  loading$: Observable<boolean> | Store<boolean>;\n\n  /** Original entity values for entities with unsaved changes */\n  changeState$: Observable<ChangeStateMap<T>> | Store<ChangeStateMap<T>>;\n\n  // endregion Selectors$\n}\n"
  },
  {
    "path": "modules/data/src/entity-services/entity-collection-service-elements-factory.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { EntityDispatcher } from '../dispatchers/entity-dispatcher';\nimport { EntityDispatcherFactory } from '../dispatchers/entity-dispatcher-factory';\nimport { EntityDefinitionService } from '../entity-metadata/entity-definition.service';\nimport {\n  EntitySelectors,\n  EntitySelectorsFactory,\n} from '../selectors/entity-selectors';\nimport {\n  EntitySelectors$,\n  EntitySelectors$Factory,\n} from '../selectors/entity-selectors$';\n\n/** Core ingredients of an EntityCollectionService */\nexport interface EntityCollectionServiceElements<\n  T,\n  S$ extends EntitySelectors$<T> = EntitySelectors$<T>,\n> {\n  readonly dispatcher: EntityDispatcher<T>;\n  readonly entityName: string;\n  readonly selectors: EntitySelectors<T>;\n  readonly selectors$: S$;\n}\n\n/** Creates the core elements of the EntityCollectionService for an entity type. */\n@Injectable()\nexport class EntityCollectionServiceElementsFactory {\n  constructor(\n    private entityDispatcherFactory: EntityDispatcherFactory,\n    private entityDefinitionService: EntityDefinitionService,\n    private entitySelectorsFactory: EntitySelectorsFactory,\n    private entitySelectors$Factory: EntitySelectors$Factory\n  ) {}\n\n  /**\n   * Get the ingredients for making an EntityCollectionService for this entity type\n   * @param entityName - name of the entity type\n   */\n  create<T, S$ extends EntitySelectors$<T> = EntitySelectors$<T>>(\n    entityName: string\n  ): EntityCollectionServiceElements<T, S$> {\n    entityName = entityName.trim();\n    const definition =\n      this.entityDefinitionService.getDefinition<T>(entityName);\n    const dispatcher = this.entityDispatcherFactory.create<T>(\n      entityName,\n      definition.selectId,\n      definition.entityDispatcherOptions\n    );\n    const selectors = this.entitySelectorsFactory.create<T>(\n      definition.metadata\n    );\n    const selectors$ = this.entitySelectors$Factory.create<T, S$>(\n      entityName,\n      selectors\n    );\n    return {\n      dispatcher,\n      entityName,\n      selectors,\n      selectors$,\n    };\n  }\n}\n"
  },
  {
    "path": "modules/data/src/entity-services/entity-collection-service-factory.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { EntityCollectionService } from './entity-collection-service';\nimport { EntityCollectionServiceBase } from './entity-collection-service-base';\nimport { EntityCollectionServiceElementsFactory } from './entity-collection-service-elements-factory';\nimport { EntitySelectors$ } from '../selectors/entity-selectors$';\n\n/**\n * Creates EntityCollectionService instances for\n * a cached collection of T entities in the ngrx store.\n */\n@Injectable()\nexport class EntityCollectionServiceFactory {\n  constructor(\n    /** Creates the core elements of the EntityCollectionService for an entity type. */\n    public entityCollectionServiceElementsFactory: EntityCollectionServiceElementsFactory\n  ) {}\n\n  /**\n   * Create an EntityCollectionService for an entity type\n   * @param entityName - name of the entity type\n   */\n  create<T, S$ extends EntitySelectors$<T> = EntitySelectors$<T>>(\n    entityName: string\n  ): EntityCollectionService<T> {\n    return new EntityCollectionServiceBase<T, S$>(\n      entityName,\n      this.entityCollectionServiceElementsFactory\n    );\n  }\n}\n"
  },
  {
    "path": "modules/data/src/entity-services/entity-collection-service.ts",
    "content": "import { EntityAction, EntityActionOptions } from '../actions/entity-action';\nimport { EntityCommands } from '../dispatchers/entity-commands';\nimport { EntityDispatcher } from '../dispatchers/entity-dispatcher';\nimport { EntityOp } from '../actions/entity-op';\nimport { EntitySelectors$ } from '../selectors/entity-selectors$';\nimport { EntitySelectors } from '../selectors/entity-selectors';\n\n/**\n * A facade for managing\n * a cached collection of T entities in the ngrx store.\n */\nexport interface EntityCollectionService<T>\n  extends EntityCommands<T>,\n    EntitySelectors$<T> {\n  /**\n   * Create an {EntityAction} for this entity type.\n   * @param op {EntityOp} the entity operation\n   * @param [data] the action data\n   * @param [options] additional options\n   * @returns the EntityAction\n   */\n  createEntityAction(\n    op: EntityOp,\n    payload?: any,\n    options?: EntityActionOptions\n  ): EntityAction<T>;\n\n  /**\n   * Create an {EntityAction} for this entity type and\n   * dispatch it immediately to the store.\n   * @param op {EntityOp} the entity operation\n   * @param [data] the action data\n   * @param [options] additional options\n   * @returns the dispatched EntityAction\n   */\n  createAndDispatch<P = any>(\n    op: EntityOp,\n    data?: P,\n    options?: EntityActionOptions\n  ): EntityAction<P>;\n\n  /** Dispatcher of EntityCommands (EntityActions) */\n  readonly dispatcher: EntityDispatcher<T>;\n\n  /** Name of the entity type for this collection service */\n  readonly entityName: string;\n\n  /** All selector functions of the entity collection */\n  readonly selectors: EntitySelectors<T>;\n\n  /** All selectors$ (observables of the selectors of entity collection properties) */\n  readonly selectors$: EntitySelectors$<T>;\n}\n"
  },
  {
    "path": "modules/data/src/entity-services/entity-services-base.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Action, Store } from '@ngrx/store';\n\nimport { Observable } from 'rxjs';\n\nimport { EntityAction } from '../actions/entity-action';\nimport { EntityCache } from '../reducers/entity-cache';\nimport { EntityCollectionService } from './entity-collection-service';\nimport { EntityCollectionServiceFactory } from './entity-collection-service-factory';\nimport { EntityCollectionServiceMap, EntityServices } from './entity-services';\nimport { EntitySelectors$ } from '../selectors/entity-selectors$';\nimport { EntityServicesElements } from './entity-services-elements';\n\n/**\n * Base/default class of a central registry of EntityCollectionServices for all entity types.\n * Create your own subclass to add app-specific members for an improved developer experience.\n *\n * @usageNotes\n * ```ts\n * export class EntityServices extends EntityServicesBase {\n *   constructor(entityServicesElements: EntityServicesElements) {\n *     super(entityServicesElements);\n *   }\n *   // Extend with well-known, app entity collection services\n *   // Convenience property to return a typed custom entity collection service\n *   get companyService() {\n *     return this.getEntityCollectionService<Model.Company>('Company') as CompanyService;\n *   }\n *   // Convenience dispatch methods\n *   clearCompany(companyId: string) {\n *     this.dispatch(new ClearCompanyAction(companyId));\n *   }\n * }\n * ```\n */\n@Injectable()\nexport class EntityServicesBase implements EntityServices {\n  // Dear @ngrx/data developer: think hard before changing the constructor.\n  // Doing so will break apps that derive from this base class,\n  // and many apps will derive from this class.\n  //\n  // Do not give this constructor an implementation.\n  // Doing so makes it hard to mock classes that derive from this class.\n  // Use getter properties instead. For example, see entityCache$\n  constructor(private entityServicesElements: EntityServicesElements) {}\n\n  // #region EntityServicesElement-based properties\n\n  /** Observable of error EntityActions (e.g. QUERY_ALL_ERROR) for all entity types */\n  get entityActionErrors$(): Observable<EntityAction> {\n    return this.entityServicesElements.entityActionErrors$;\n  }\n\n  /** Observable of the entire entity cache */\n  get entityCache$(): Observable<EntityCache> | Store<EntityCache> {\n    return this.entityServicesElements.entityCache$;\n  }\n\n  /** Factory to create a default instance of an EntityCollectionService */\n  get entityCollectionServiceFactory(): EntityCollectionServiceFactory {\n    return this.entityServicesElements.entityCollectionServiceFactory;\n  }\n\n  /**\n   * Actions scanned by the store after it processed them with reducers.\n   * A replay observable of the most recent action reduced by the store.\n   */\n  get reducedActions$(): Observable<Action> {\n    return this.entityServicesElements.reducedActions$;\n  }\n\n  /** The ngrx store, scoped to the EntityCache */\n  protected get store(): Store<EntityCache> {\n    return this.entityServicesElements.store;\n  }\n\n  // #endregion EntityServicesElement-based properties\n\n  /** Dispatch any action to the store */\n  dispatch(action: Action) {\n    this.store.dispatch(action);\n  }\n\n  /** Registry of EntityCollectionService instances */\n  private readonly EntityCollectionServices: EntityCollectionServiceMap = {};\n\n  /**\n   * Create a new default instance of an EntityCollectionService.\n   * Prefer getEntityCollectionService() unless you really want a new default instance.\n   * This one will NOT be registered with EntityServices!\n   * @param entityName {string} Name of the entity type of the service\n   */\n  protected createEntityCollectionService<\n    T,\n    S$ extends EntitySelectors$<T> = EntitySelectors$<T>,\n  >(entityName: string): EntityCollectionService<T> {\n    return this.entityCollectionServiceFactory.create<T, S$>(entityName);\n  }\n\n  /** Get (or create) the singleton instance of an EntityCollectionService\n   * @param entityName {string} Name of the entity type of the service\n   */\n  getEntityCollectionService<\n    T,\n    S$ extends EntitySelectors$<T> = EntitySelectors$<T>,\n  >(entityName: string): EntityCollectionService<T> {\n    let service = this.EntityCollectionServices[entityName];\n    if (!service) {\n      service = this.createEntityCollectionService<T, S$>(entityName);\n      this.EntityCollectionServices[entityName] = service;\n    }\n    return service;\n  }\n\n  /** Register an EntityCollectionService under its entity type name.\n   * Will replace a pre-existing service for that type.\n   * @param service {EntityCollectionService} The entity service\n   * @param serviceName {string} optional service name to use instead of the service's entityName\n   */\n  registerEntityCollectionService<T>(\n    service: EntityCollectionService<T>,\n    serviceName?: string\n  ) {\n    this.EntityCollectionServices[serviceName || service.entityName] = service;\n  }\n\n  /**\n   * Register entity services for several entity types at once.\n   * Will replace a pre-existing service for that type.\n   * @param entityCollectionServices {EntityCollectionServiceMap | EntityCollectionService<any>[]}\n   * EntityCollectionServices to register, either as a map or an array\n   */\n  registerEntityCollectionServices(\n    entityCollectionServices:\n      | EntityCollectionServiceMap\n      | EntityCollectionService<any>[]\n  ): void {\n    if (Array.isArray(entityCollectionServices)) {\n      entityCollectionServices.forEach((service) =>\n        this.registerEntityCollectionService(service)\n      );\n    } else {\n      Object.keys(entityCollectionServices || {}).forEach((serviceName) => {\n        this.registerEntityCollectionService(\n          entityCollectionServices[serviceName],\n          serviceName\n        );\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "modules/data/src/entity-services/entity-services-elements.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Action, Store } from '@ngrx/store';\nimport { Observable } from 'rxjs';\n\nimport { EntityAction } from '../actions/entity-action';\nimport { EntityCache } from '../reducers/entity-cache';\nimport { EntityDispatcherFactory } from '../dispatchers/entity-dispatcher-factory';\nimport { EntitySelectors$Factory } from '../selectors/entity-selectors$';\nimport { EntityCollectionServiceFactory } from './entity-collection-service-factory';\n\n/** Core ingredients of an EntityServices class */\n@Injectable()\nexport class EntityServicesElements {\n  constructor(\n    /**\n     * Creates EntityCollectionService instances for\n     * a cached collection of T entities in the ngrx store.\n     */\n    public readonly entityCollectionServiceFactory: EntityCollectionServiceFactory,\n    /** Creates EntityDispatchers for entity collections */\n    entityDispatcherFactory: EntityDispatcherFactory,\n    /** Creates observable EntitySelectors$ for entity collections. */\n    entitySelectors$Factory: EntitySelectors$Factory,\n    /** The ngrx store, scoped to the EntityCache */\n    public readonly store: Store<EntityCache>\n  ) {\n    this.entityActionErrors$ = entitySelectors$Factory.entityActionErrors$;\n    this.entityCache$ = entitySelectors$Factory.entityCache$;\n    this.reducedActions$ = entityDispatcherFactory.reducedActions$;\n  }\n\n  /** Observable of error EntityActions (e.g. QUERY_ALL_ERROR) for all entity types */\n  readonly entityActionErrors$: Observable<EntityAction>;\n\n  /** Observable of the entire entity cache */\n  readonly entityCache$: Observable<EntityCache> | Store<EntityCache>;\n\n  /**\n   * Actions scanned by the store after it processed them with reducers.\n   * A replay observable of the most recent action reduced by the store.\n   */\n  readonly reducedActions$: Observable<Action>;\n}\n"
  },
  {
    "path": "modules/data/src/entity-services/entity-services.ts",
    "content": "import { Action, Store } from '@ngrx/store';\nimport { Observable } from 'rxjs';\n\nimport { EntityAction } from '../actions/entity-action';\nimport { EntityCache } from '../reducers/entity-cache';\nimport { EntityCollectionService } from './entity-collection-service';\n\n/**\n * Class-Interface for EntityCache and EntityCollection services.\n * Serves as an Angular provider token for this service class.\n * Includes a registry of EntityCollectionServices for all entity types.\n * Creates a new default EntityCollectionService for any entity type not in the registry.\n * Optionally register specialized EntityCollectionServices for individual types\n */\nexport abstract class EntityServices {\n  /** Dispatch any action to the store */\n  abstract dispatch(action: Action): void;\n\n  /** Observable of error EntityActions (e.g. QUERY_ALL_ERROR) for all entity types */\n  abstract readonly entityActionErrors$: Observable<EntityAction>;\n\n  /** Observable of the entire entity cache */\n  abstract readonly entityCache$: Observable<EntityCache> | Store<EntityCache>;\n\n  /** Get (or create) the singleton instance of an EntityCollectionService\n   * @param entityName {string} Name of the entity type of the service\n   */\n  abstract getEntityCollectionService<T = any>(\n    entityName: string\n  ): EntityCollectionService<T>;\n\n  /**\n   * Actions scanned by the store after it processed them with reducers.\n   * A replay observable of the most recent Action (not just EntityAction) reduced by the store.\n   */\n  abstract readonly reducedActions$: Observable<Action>;\n\n  // #region EntityCollectionService creation and registration API\n\n  /** Register an EntityCollectionService under its entity type name.\n   * Will replace a pre-existing service for that type.\n   * @param service {EntityCollectionService} The entity service\n   */\n  abstract registerEntityCollectionService<T>(\n    service: EntityCollectionService<T>\n  ): void;\n\n  /** Register entity services for several entity types at once.\n   * Will replace a pre-existing service for that type.\n   * @param entityCollectionServices Array of EntityCollectionServices to register\n   */\n  abstract registerEntityCollectionServices(\n    entityCollectionServices: EntityCollectionService<any>[]\n  ): void;\n\n  /** Register entity services for several entity types at once.\n   * Will replace a pre-existing service for that type.\n   * @param entityCollectionServiceMap Map of service-name to entity-collection-service\n   */\n  abstract registerEntityCollectionServices(\n    // eslint-disable-next-line @typescript-eslint/unified-signatures\n    entityCollectionServiceMap: EntityCollectionServiceMap\n  ): void;\n  // #endregion EntityCollectionService creation and registration API\n}\n\n/**\n * A map of service or entity names to their corresponding EntityCollectionServices.\n */\nexport interface EntityCollectionServiceMap {\n  [entityName: string]: EntityCollectionService<any>;\n}\n"
  },
  {
    "path": "modules/data/src/index.ts",
    "content": "// actions\nexport { EntityActionFactory } from './actions/entity-action-factory';\nexport { EntityActionGuard } from './actions/entity-action-guard';\nexport { ofEntityOp, ofEntityType } from './actions/entity-action-operators';\nexport {\n  EntityAction,\n  EntityActionOptions,\n  EntityActionPayload,\n} from './actions/entity-action';\nexport {\n  EntityCacheAction,\n  EntityCacheQuerySet,\n  ClearCollections,\n  LoadCollections,\n  MergeQuerySet,\n  SetEntityCache,\n  SaveEntities,\n  SaveEntitiesCancel,\n  SaveEntitiesCanceled,\n  SaveEntitiesError,\n  SaveEntitiesSuccess,\n} from './actions/entity-cache-action';\nexport {\n  ChangeSetOperation,\n  ChangeSetAdd,\n  ChangeSetDelete,\n  ChangeSetUpdate,\n  ChangeSetUpsert,\n  ChangeSetItem,\n  ChangeSet,\n  ChangeSetItemFactory,\n  changeSetItemFactory,\n  excludeEmptyChangeSetItems,\n} from './actions/entity-cache-change-set';\n\nexport {\n  EntityOp,\n  OP_SUCCESS,\n  OP_ERROR,\n  makeErrorOp,\n  makeSuccessOp,\n} from './actions/entity-op';\nexport { MergeStrategy } from './actions/merge-strategy';\nexport { UpdateResponseData } from './actions/update-response-data';\n\n// // dataservices\nexport { DataServiceError } from './dataservices/data-service-error';\nexport { EntityActionDataServiceError } from './dataservices/data-service-error';\nexport { DefaultDataServiceConfig } from './dataservices/default-data-service-config';\nexport { DefaultDataService } from './dataservices/default-data.service';\nexport { DefaultDataServiceFactory } from './dataservices/default-data.service';\nexport { EntityCacheDataService } from './dataservices/entity-cache-data.service';\nexport { EntityDataService } from './dataservices/entity-data.service';\nexport { EntityHttpResourceUrls } from './dataservices/http-url-generator';\nexport { HttpResourceUrls } from './dataservices/http-url-generator';\nexport { HttpUrlGenerator } from './dataservices/http-url-generator';\nexport { DefaultHttpUrlGenerator } from './dataservices/http-url-generator';\nexport { normalizeRoot } from './dataservices/http-url-generator';\nexport {\n  EntityCollectionDataService,\n  HttpMethods,\n  HttpOptions,\n  RequestData,\n  QueryParams,\n} from './dataservices/interfaces';\nexport {\n  PersistenceResultHandler,\n  DefaultPersistenceResultHandler,\n} from './dataservices/persistence-result-handler.service';\n\n// // dispatchers\nexport { EntityCacheDispatcher } from './dispatchers/entity-cache-dispatcher';\nexport {\n  EntityServerCommands,\n  EntityCacheCommands,\n  EntityCommands,\n} from './dispatchers/entity-commands';\nexport { EntityDispatcherBase } from './dispatchers/entity-dispatcher-base';\nexport { EntityDispatcherDefaultOptions } from './dispatchers/entity-dispatcher-default-options';\nexport { EntityDispatcherFactory } from './dispatchers/entity-dispatcher-factory';\nexport {\n  EntityDispatcher,\n  PersistanceCanceled,\n} from './dispatchers/entity-dispatcher';\n\n// // effects\nexport { EntityCacheEffects } from './effects/entity-cache-effects';\nexport { persistOps, EntityEffects } from './effects/entity-effects';\n\n// // entity-metadata\nexport {\n  EntityDefinitions,\n  EntityDefinitionService,\n} from './entity-metadata/entity-definition.service';\nexport {\n  EntityDefinition,\n  createEntityDefinition,\n} from './entity-metadata/entity-definition';\nexport {\n  EntityFilterFn,\n  PropsFilterFnFactory,\n} from './entity-metadata/entity-filters';\nexport {\n  ENTITY_METADATA_TOKEN,\n  EntityMetadata,\n  EntityMetadataMap,\n} from './entity-metadata/entity-metadata';\n\n// // entity-services\nexport { EntityCollectionServiceBase } from './entity-services/entity-collection-service-base';\nexport {\n  EntityCollectionServiceElements,\n  EntityCollectionServiceElementsFactory,\n} from './entity-services/entity-collection-service-elements-factory';\nexport { EntityCollectionServiceFactory } from './entity-services/entity-collection-service-factory';\nexport { EntityCollectionService } from './entity-services/entity-collection-service';\nexport { EntityServicesBase } from './entity-services/entity-services-base';\nexport { EntityServicesElements } from './entity-services/entity-services-elements';\nexport {\n  EntityServices,\n  EntityCollectionServiceMap,\n} from './entity-services/entity-services';\n\n// // reducers\nexport {\n  ENTITY_CACHE_NAME,\n  ENTITY_CACHE_NAME_TOKEN,\n  ENTITY_CACHE_META_REDUCERS,\n  ENTITY_COLLECTION_META_REDUCERS,\n  INITIAL_ENTITY_CACHE_STATE,\n} from './reducers/constants';\nexport { EntityCacheReducerFactory } from './reducers/entity-cache-reducer';\nexport { EntityCache } from './reducers/entity-cache';\nexport { EntityChangeTrackerBase } from './reducers/entity-change-tracker-base';\nexport { EntityChangeTracker } from './reducers/entity-change-tracker';\nexport {\n  EntityCollectionCreator,\n  createEmptyEntityCollection,\n} from './reducers/entity-collection-creator';\nexport {\n  EntityCollectionReducerMethodMap,\n  EntityCollectionReducerMethods,\n  EntityCollectionReducerMethodsFactory,\n} from './reducers/entity-collection-reducer-methods';\nexport {\n  EntityCollectionReducers,\n  EntityCollectionReducerRegistry,\n} from './reducers/entity-collection-reducer-registry';\nexport {\n  EntityCollectionReducer,\n  EntityCollectionReducerFactory,\n} from './reducers/entity-collection-reducer';\nexport {\n  ChangeType,\n  ChangeState,\n  ChangeStateMap,\n  EntityCollection,\n} from './reducers/entity-collection';\n\n// // selectors\nexport {\n  ENTITY_CACHE_SELECTOR_TOKEN,\n  entityCacheSelectorProvider,\n  EntityCacheSelector,\n  createEntityCacheSelector,\n} from './selectors/entity-cache-selector';\nexport {\n  CollectionSelectors,\n  EntitySelectors,\n  EntitySelectorsFactory,\n} from './selectors/entity-selectors';\nexport {\n  EntitySelectors$,\n  EntitySelectors$Factory,\n} from './selectors/entity-selectors$';\n\n// // Utils\nexport { CorrelationIdGenerator } from './utils/correlation-id-generator';\nexport { DefaultLogger } from './utils/default-logger';\nexport { DefaultPluralizer } from './utils/default-pluralizer';\nexport { getGuid, getGuidComb, guidComparer } from './utils/guid-fns';\nexport {\n  Logger,\n  EntityPluralNames,\n  PLURAL_NAMES_TOKEN,\n  Pluralizer,\n} from './utils/interfaces';\nexport {\n  defaultSelectId,\n  flattenArgs,\n  toUpdateFactory,\n} from './utils/utilities';\n\n// // EntityDataConfig\nexport { EntityDataModuleConfig } from './entity-data-config';\n\n// // EntityDataModule\nexport { EntityDataModuleWithoutEffects } from './entity-data-without-effects.module';\nexport { EntityDataModule } from './entity-data.module';\n\n// // Standalone APIs\nexport { provideEntityData, withEffects } from './provide-entity-data';\n"
  },
  {
    "path": "modules/data/src/provide-entity-data.ts",
    "content": "import {\n  EnvironmentProviders,\n  inject,\n  InjectionToken,\n  makeEnvironmentProviders,\n  provideEnvironmentInitializer,\n  Provider,\n} from '@angular/core';\nimport {\n  ActionReducerFactory,\n  combineReducers,\n  MetaReducer,\n  ReducerManager,\n} from '@ngrx/store';\nimport { EffectSources } from '@ngrx/effects';\nimport { EntityDispatcherDefaultOptions } from './dispatchers/entity-dispatcher-default-options';\nimport { EntityActionFactory } from './actions/entity-action-factory';\nimport { EntityCacheDispatcher } from './dispatchers/entity-cache-dispatcher';\nimport { entityCacheSelectorProvider } from './selectors/entity-cache-selector';\nimport { EntityCollectionServiceElementsFactory } from './entity-services/entity-collection-service-elements-factory';\nimport { EntityCollectionServiceFactory } from './entity-services/entity-collection-service-factory';\nimport { EntityServices } from './entity-services/entity-services';\nimport { EntityCollectionCreator } from './reducers/entity-collection-creator';\nimport { EntityCollectionReducerFactory } from './reducers/entity-collection-reducer';\nimport { EntityCollectionReducerMethodsFactory } from './reducers/entity-collection-reducer-methods';\nimport { EntityCollectionReducerRegistry } from './reducers/entity-collection-reducer-registry';\nimport { EntityDispatcherFactory } from './dispatchers/entity-dispatcher-factory';\nimport { EntityDefinitionService } from './entity-metadata/entity-definition.service';\nimport { EntityCacheReducerFactory } from './reducers/entity-cache-reducer';\nimport {\n  ENTITY_CACHE_META_REDUCERS,\n  ENTITY_CACHE_NAME,\n  ENTITY_CACHE_NAME_TOKEN,\n  ENTITY_COLLECTION_META_REDUCERS,\n  INITIAL_ENTITY_CACHE_STATE,\n} from './reducers/constants';\nimport { EntityCache } from './reducers/entity-cache';\nimport { EntitySelectorsFactory } from './selectors/entity-selectors';\nimport { EntitySelectors$Factory } from './selectors/entity-selectors$';\nimport { EntityServicesBase } from './entity-services/entity-services-base';\nimport { EntityServicesElements } from './entity-services/entity-services-elements';\nimport { DefaultLogger } from './utils/default-logger';\nimport { Logger, PLURAL_NAMES_TOKEN, Pluralizer } from './utils/interfaces';\nimport { CorrelationIdGenerator } from './utils/correlation-id-generator';\nimport { ENTITY_METADATA_TOKEN } from './entity-metadata/entity-metadata';\nimport { DefaultDataServiceFactory } from './dataservices/default-data.service';\nimport {\n  DefaultPersistenceResultHandler,\n  PersistenceResultHandler,\n} from './dataservices/persistence-result-handler.service';\nimport {\n  DefaultHttpUrlGenerator,\n  HttpUrlGenerator,\n} from './dataservices/http-url-generator';\nimport { EntityCacheDataService } from './dataservices/entity-cache-data.service';\nimport { EntityDataService } from './dataservices/entity-data.service';\nimport { EntityCacheEffects } from './effects/entity-cache-effects';\nimport { EntityEffects } from './effects/entity-effects';\nimport { DefaultPluralizer } from './utils/default-pluralizer';\nimport { EntityDataModuleConfig } from './entity-data-config';\n\nexport const BASE_ENTITY_DATA_PROVIDERS: Array<\n  Provider | EnvironmentProviders\n> = [\n  CorrelationIdGenerator,\n  EntityDispatcherDefaultOptions,\n  EntityActionFactory,\n  EntityCacheDispatcher,\n  EntityCacheReducerFactory,\n  entityCacheSelectorProvider,\n  EntityCollectionCreator,\n  EntityCollectionReducerFactory,\n  EntityCollectionReducerMethodsFactory,\n  EntityCollectionReducerRegistry,\n  EntityCollectionServiceElementsFactory,\n  EntityCollectionServiceFactory,\n  EntityDefinitionService,\n  EntityDispatcherFactory,\n  EntitySelectorsFactory,\n  EntitySelectors$Factory,\n  EntityServicesElements,\n  { provide: ENTITY_CACHE_NAME_TOKEN, useValue: ENTITY_CACHE_NAME },\n  { provide: EntityServices, useClass: EntityServicesBase },\n  { provide: Logger, useClass: DefaultLogger },\n  provideEnvironmentInitializer(() => initializeBaseEntityData()),\n];\n\nfunction initializeBaseEntityData(): void {\n  const reducerManager = inject(ReducerManager);\n  const entityCacheReducerFactory = inject(EntityCacheReducerFactory);\n  const entityCacheName = inject(ENTITY_CACHE_NAME_TOKEN, {\n    optional: true,\n  });\n  const initialStateOrFn = inject(INITIAL_ENTITY_CACHE_STATE, {\n    optional: true,\n  });\n  const metaReducersOrTokens = inject<\n    Array<MetaReducer<EntityCache> | InjectionToken<MetaReducer<EntityCache>>>\n  >(ENTITY_CACHE_META_REDUCERS, {\n    optional: true,\n  });\n\n  // Add the @ngrx/data feature to the Store's features\n  const key = entityCacheName || ENTITY_CACHE_NAME;\n  const metaReducers = (metaReducersOrTokens || []).map((mr) => {\n    return mr instanceof InjectionToken ? inject(mr) : mr;\n  });\n  const initialState =\n    typeof initialStateOrFn === 'function'\n      ? initialStateOrFn()\n      : initialStateOrFn;\n\n  const entityCacheFeature = {\n    key,\n    reducers: entityCacheReducerFactory.create(),\n    reducerFactory: combineReducers as ActionReducerFactory<EntityCache>,\n    initialState: initialState || {},\n    metaReducers: metaReducers,\n  };\n  reducerManager.addFeature(entityCacheFeature);\n}\n\nexport const ENTITY_DATA_EFFECTS_PROVIDERS: Array<\n  Provider | EnvironmentProviders\n> = [\n  DefaultDataServiceFactory,\n  EntityCacheDataService,\n  EntityDataService,\n  EntityCacheEffects,\n  EntityEffects,\n  { provide: HttpUrlGenerator, useClass: DefaultHttpUrlGenerator },\n  {\n    provide: PersistenceResultHandler,\n    useClass: DefaultPersistenceResultHandler,\n  },\n  { provide: Pluralizer, useClass: DefaultPluralizer },\n  provideEnvironmentInitializer(() => initializeEntityDataEffects()),\n];\n\nfunction initializeEntityDataEffects(): void {\n  const effectsSources = inject(EffectSources);\n  const entityCacheEffects = inject(EntityCacheEffects);\n  const entityEffects = inject(EntityEffects);\n\n  effectsSources.addEffects(entityCacheEffects);\n  effectsSources.addEffects(entityEffects);\n}\n\nexport function provideEntityDataConfig(\n  config: EntityDataModuleConfig\n): Provider[] {\n  return [\n    {\n      provide: ENTITY_CACHE_META_REDUCERS,\n      useValue: config.entityCacheMetaReducers\n        ? config.entityCacheMetaReducers\n        : [],\n    },\n    {\n      provide: ENTITY_COLLECTION_META_REDUCERS,\n      useValue: config.entityCollectionMetaReducers\n        ? config.entityCollectionMetaReducers\n        : [],\n    },\n    {\n      provide: PLURAL_NAMES_TOKEN,\n      multi: true,\n      useValue: config.pluralNames ? config.pluralNames : {},\n    },\n    {\n      provide: ENTITY_METADATA_TOKEN,\n      multi: true,\n      useValue: config.entityMetadata ? config.entityMetadata : [],\n    },\n  ];\n}\n\n/**\n * Sets up base entity data providers with entity config.\n * This function should to be used at the root level.\n *\n * @usageNotes\n *\n * ### Providing entity data with effects\n *\n * When used with `withEffects` feature, the `provideEntityData` function is\n * an alternative to `EntityDataModule.forRoot`\n *\n * ```ts\n * import { provideStore } from '@ngrx/store';\n * import { provideEffects } from '@ngrx/effects';\n * import {\n *   EntityMetadataMap,\n *   provideEntityData,\n *   withEffects,\n * } from '@ngrx/data';\n *\n * const entityMetadata: EntityMetadataMap = {\n *   Hero: {},\n *   Villain: {},\n * };\n * const pluralNames = { Hero: 'Heroes' };\n *\n * bootstrapApplication(AppComponent, {\n *   providers: [\n *     provideStore(),\n *     provideEffects(),\n *     provideEntityData({ entityMetadata, pluralNames }, withEffects()),\n *   ],\n * });\n * ```\n *\n * ### Providing entity data without effects\n *\n * When used without `withEffects` feature, the `provideEntityData` function is\n * an alternative to `EntityDataModuleWithoutEffects.forRoot`.\n *\n * ```ts\n * import { provideStore } from '@ngrx/store';\n * import { EntityMetadataMap, provideEntityData } from '@ngrx/data';\n *\n * const entityMetadata: EntityMetadataMap = {\n *   Musician: {},\n *   Song: {},\n * };\n *\n * bootstrapApplication(AppComponent, {\n *   providers: [\n *     provideStore(),\n *     provideEntityData({ entityMetadata }),\n *   ],\n * });\n * ```\n *\n */\nexport function provideEntityData(\n  config: EntityDataModuleConfig,\n  ...features: EntityDataFeature[]\n): EnvironmentProviders {\n  return makeEnvironmentProviders([\n    BASE_ENTITY_DATA_PROVIDERS,\n    provideEntityDataConfig(config),\n    ...features.map((feature) => feature.ɵproviders),\n  ]);\n}\n\nenum EntityDataFeatureKind {\n  WithEffects,\n}\n\ninterface EntityDataFeature {\n  ɵkind: EntityDataFeatureKind;\n  ɵproviders: Provider[];\n}\n\n/**\n * Registers entity data effects and provides HTTP data services.\n *\n * @see `provideEntityData`\n */\nexport function withEffects(): EntityDataFeature {\n  return {\n    ɵkind: EntityDataFeatureKind.WithEffects,\n    ɵproviders: [ENTITY_DATA_EFFECTS_PROVIDERS],\n  };\n}\n"
  },
  {
    "path": "modules/data/src/reducers/constants.ts",
    "content": "import { InjectionToken } from '@angular/core';\nimport { MetaReducer } from '@ngrx/store';\nimport { EntityCache } from './entity-cache';\n\nexport const ENTITY_CACHE_NAME = 'entityCache';\nexport const ENTITY_CACHE_NAME_TOKEN = new InjectionToken<string>(\n  '@ngrx/data Entity Cache Name'\n);\n\nexport const ENTITY_CACHE_META_REDUCERS = new InjectionToken<\n  MetaReducer<any, any>[]\n>('@ngrx/data Entity Cache Meta Reducers');\nexport const ENTITY_COLLECTION_META_REDUCERS = new InjectionToken<\n  MetaReducer<any, any>[]\n>('@ngrx/data Entity Collection Meta Reducers');\n\nexport const INITIAL_ENTITY_CACHE_STATE = new InjectionToken<\n  EntityCache | (() => EntityCache)\n>('@ngrx/data Initial Entity Cache State');\n"
  },
  {
    "path": "modules/data/src/reducers/entity-cache-reducer.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Action, ActionReducer } from '@ngrx/store';\n\nimport { EntityAction } from '../actions/entity-action';\nimport { EntityCache } from './entity-cache';\n\nimport {\n  EntityCacheAction,\n  ClearCollections,\n  LoadCollections,\n  MergeQuerySet,\n  SaveEntities,\n  SaveEntitiesCancel,\n  SaveEntitiesError,\n  SaveEntitiesSuccess,\n} from '../actions/entity-cache-action';\n\nimport {\n  ChangeSetOperation,\n  ChangeSetItem,\n} from '../actions/entity-cache-change-set';\n\nimport { EntityCollection } from './entity-collection';\nimport { EntityCollectionCreator } from './entity-collection-creator';\nimport { EntityCollectionReducerRegistry } from './entity-collection-reducer-registry';\nimport { EntityOp } from '../actions/entity-op';\nimport { Logger } from '../utils/interfaces';\nimport { MergeStrategy } from '../actions/merge-strategy';\n\n/**\n * Creates the EntityCacheReducer via its create() method\n */\n@Injectable()\nexport class EntityCacheReducerFactory {\n  constructor(\n    private entityCollectionCreator: EntityCollectionCreator,\n    private entityCollectionReducerRegistry: EntityCollectionReducerRegistry,\n    private logger: Logger\n  ) {}\n\n  /**\n   * Create the @ngrx/data entity cache reducer which either responds to entity cache level actions\n   * or (more commonly) delegates to an EntityCollectionReducer based on the action.payload.entityName.\n   */\n  create(): ActionReducer<EntityCache, Action> {\n    // This technique ensures a named function appears in the debugger\n    return entityCacheReducer.bind(this);\n\n    function entityCacheReducer(\n      this: EntityCacheReducerFactory,\n      entityCache: EntityCache = {},\n      action: { type: string; payload?: any }\n    ): EntityCache {\n      // EntityCache actions\n      switch (action.type) {\n        case EntityCacheAction.CLEAR_COLLECTIONS: {\n          return this.clearCollectionsReducer(\n            entityCache,\n            action as ClearCollections\n          );\n        }\n\n        case EntityCacheAction.LOAD_COLLECTIONS: {\n          return this.loadCollectionsReducer(\n            entityCache,\n            action as LoadCollections\n          );\n        }\n\n        case EntityCacheAction.MERGE_QUERY_SET: {\n          return this.mergeQuerySetReducer(\n            entityCache,\n            action as MergeQuerySet\n          );\n        }\n\n        case EntityCacheAction.SAVE_ENTITIES: {\n          return this.saveEntitiesReducer(entityCache, action as SaveEntities);\n        }\n\n        case EntityCacheAction.SAVE_ENTITIES_CANCEL: {\n          return this.saveEntitiesCancelReducer(\n            entityCache,\n            action as SaveEntitiesCancel\n          );\n        }\n\n        case EntityCacheAction.SAVE_ENTITIES_ERROR: {\n          return this.saveEntitiesErrorReducer(\n            entityCache,\n            action as SaveEntitiesError\n          );\n        }\n\n        case EntityCacheAction.SAVE_ENTITIES_SUCCESS: {\n          return this.saveEntitiesSuccessReducer(\n            entityCache,\n            action as SaveEntitiesSuccess\n          );\n        }\n\n        case EntityCacheAction.SET_ENTITY_CACHE: {\n          // Completely replace the EntityCache. Be careful!\n          return action.payload.cache;\n        }\n      }\n\n      // Apply entity collection reducer if this is a valid EntityAction for a collection\n      const payload = action.payload;\n      if (payload && payload.entityName && payload.entityOp && !payload.error) {\n        return this.applyCollectionReducer(entityCache, action as EntityAction);\n      }\n\n      // Not a valid EntityAction\n      return entityCache;\n    }\n  }\n\n  /**\n   * Reducer to clear multiple collections at the same time.\n   * @param entityCache the entity cache\n   * @param action a ClearCollections action whose payload is an array of collection names.\n   * If empty array, does nothing. If no array, clears all the collections.\n   */\n  protected clearCollectionsReducer(\n    entityCache: EntityCache,\n    action: ClearCollections\n  ) {\n    // eslint-disable-next-line prefer-const\n    let { collections, tag } = action.payload;\n    const entityOp = EntityOp.REMOVE_ALL;\n\n    if (!collections) {\n      // Collections is not defined. Clear all collections.\n      collections = Object.keys(entityCache);\n    }\n\n    entityCache = collections.reduce((newCache, entityName) => {\n      const payload = { entityName, entityOp };\n      const act: EntityAction = {\n        type: `[${entityName}] ${action.type}`,\n        payload,\n      };\n      newCache = this.applyCollectionReducer(newCache, act);\n      return newCache;\n    }, entityCache);\n    return entityCache;\n  }\n\n  /**\n   * Reducer to load collection in the form of a hash of entity data for multiple collections.\n   * @param entityCache the entity cache\n   * @param action a LoadCollections action whose payload is the QuerySet of entity collections to load\n   */\n  protected loadCollectionsReducer(\n    entityCache: EntityCache,\n    action: LoadCollections\n  ) {\n    const { collections, tag } = action.payload;\n    const entityOp = EntityOp.ADD_ALL;\n    const entityNames = Object.keys(collections);\n    entityCache = entityNames.reduce((newCache, entityName) => {\n      const payload = {\n        entityName,\n        entityOp,\n        data: collections[entityName],\n      };\n      const act: EntityAction = {\n        type: `[${entityName}] ${action.type}`,\n        payload,\n      };\n      newCache = this.applyCollectionReducer(newCache, act);\n      return newCache;\n    }, entityCache);\n    return entityCache;\n  }\n\n  /**\n   * Reducer to merge query sets in the form of a hash of entity data for multiple collections.\n   * @param entityCache the entity cache\n   * @param action a MergeQuerySet action with the query set and a MergeStrategy\n   */\n  protected mergeQuerySetReducer(\n    entityCache: EntityCache,\n    action: MergeQuerySet\n  ) {\n    // eslint-disable-next-line prefer-const\n    let { mergeStrategy, querySet, tag } = action.payload;\n    mergeStrategy =\n      mergeStrategy === null ? MergeStrategy.PreserveChanges : mergeStrategy;\n    const entityOp = EntityOp.QUERY_MANY_SUCCESS;\n\n    const entityNames = Object.keys(querySet);\n    entityCache = entityNames.reduce((newCache, entityName) => {\n      const payload = {\n        entityName,\n        entityOp,\n        data: querySet[entityName],\n        mergeStrategy,\n      };\n      const act: EntityAction = {\n        type: `[${entityName}] ${action.type}`,\n        payload,\n      };\n      newCache = this.applyCollectionReducer(newCache, act);\n      return newCache;\n    }, entityCache);\n    return entityCache;\n  }\n\n  // #region saveEntities reducers\n  protected saveEntitiesReducer(\n    entityCache: EntityCache,\n    action: SaveEntities\n  ) {\n    const { changeSet, correlationId, isOptimistic, mergeStrategy, tag } =\n      action.payload;\n\n    try {\n      changeSet.changes.forEach((item) => {\n        const entityName = item.entityName;\n        const payload = {\n          entityName,\n          entityOp: getEntityOp(item),\n          data: item.entities,\n          correlationId,\n          isOptimistic,\n          mergeStrategy,\n          tag,\n        };\n\n        const act: EntityAction = {\n          type: `[${entityName}] ${action.type}`,\n          payload,\n        };\n        entityCache = this.applyCollectionReducer(entityCache, act);\n        if (act.payload.error) {\n          throw act.payload.error;\n        }\n      });\n    } catch (error: any) {\n      action.payload.error = error;\n    }\n\n    return entityCache;\n    function getEntityOp(item: ChangeSetItem) {\n      switch (item.op) {\n        case ChangeSetOperation.Add:\n          return EntityOp.SAVE_ADD_MANY;\n        case ChangeSetOperation.Delete:\n          return EntityOp.SAVE_DELETE_MANY;\n        case ChangeSetOperation.Update:\n          return EntityOp.SAVE_UPDATE_MANY;\n        case ChangeSetOperation.Upsert:\n          return EntityOp.SAVE_UPSERT_MANY;\n      }\n    }\n  }\n\n  protected saveEntitiesCancelReducer(\n    entityCache: EntityCache,\n    action: SaveEntitiesCancel\n  ) {\n    // This implementation can only clear the loading flag for the collections involved\n    // If the save was optimistic, you'll have to compensate to fix the cache as you think necessary\n    return this.clearLoadingFlags(\n      entityCache,\n      action.payload.entityNames || []\n    );\n  }\n\n  protected saveEntitiesErrorReducer(\n    entityCache: EntityCache,\n    action: SaveEntitiesError\n  ) {\n    const originalAction = action.payload.originalAction;\n    const originalChangeSet = originalAction.payload.changeSet;\n\n    // This implementation can only clear the loading flag for the collections involved\n    // If the save was optimistic, you'll have to compensate to fix the cache as you think necessary\n    const entityNames = originalChangeSet.changes.map(\n      (item) => item.entityName\n    );\n    return this.clearLoadingFlags(entityCache, entityNames);\n  }\n\n  protected saveEntitiesSuccessReducer(\n    entityCache: EntityCache,\n    action: SaveEntitiesSuccess\n  ) {\n    const { changeSet, correlationId, isOptimistic, mergeStrategy, tag } =\n      action.payload;\n\n    changeSet.changes.forEach((item) => {\n      const entityName = item.entityName;\n      const payload = {\n        entityName,\n        entityOp: getEntityOp(item),\n        data: item.entities,\n        correlationId,\n        isOptimistic,\n        mergeStrategy,\n        tag,\n      };\n\n      const act: EntityAction = {\n        type: `[${entityName}] ${action.type}`,\n        payload,\n      };\n      entityCache = this.applyCollectionReducer(entityCache, act);\n    });\n\n    return entityCache;\n    function getEntityOp(item: ChangeSetItem) {\n      switch (item.op) {\n        case ChangeSetOperation.Add:\n          return EntityOp.SAVE_ADD_MANY_SUCCESS;\n        case ChangeSetOperation.Delete:\n          return EntityOp.SAVE_DELETE_MANY_SUCCESS;\n        case ChangeSetOperation.Update:\n          return EntityOp.SAVE_UPDATE_MANY_SUCCESS;\n        case ChangeSetOperation.Upsert:\n          return EntityOp.SAVE_UPSERT_MANY_SUCCESS;\n      }\n    }\n  }\n  // #endregion saveEntities reducers\n\n  // #region helpers\n  /** Apply reducer for the action's EntityCollection (if the action targets a collection) */\n  private applyCollectionReducer(\n    cache: EntityCache = {},\n    action: EntityAction\n  ) {\n    const entityName = action.payload.entityName;\n    const collection = cache[entityName];\n    const reducer =\n      this.entityCollectionReducerRegistry.getOrCreateReducer(entityName);\n\n    let newCollection: EntityCollection;\n    try {\n      newCollection = collection\n        ? reducer(collection, action)\n        : reducer(this.entityCollectionCreator.create(entityName), action);\n    } catch (error: any) {\n      this.logger.error(error);\n      action.payload.error = error;\n    }\n\n    return action.payload.error || collection === newCollection!\n      ? cache\n      : { ...cache, [entityName]: newCollection! };\n  }\n\n  /** Ensure loading is false for every collection in entityNames */\n  private clearLoadingFlags(entityCache: EntityCache, entityNames: string[]) {\n    let isMutated = false;\n    entityNames.forEach((entityName) => {\n      const collection = entityCache[entityName];\n      if (collection.loading) {\n        if (!isMutated) {\n          entityCache = { ...entityCache };\n          isMutated = true;\n        }\n        entityCache[entityName] = { ...collection, loading: false };\n      }\n    });\n    return entityCache;\n  }\n  // #endregion helpers\n}\n"
  },
  {
    "path": "modules/data/src/reducers/entity-cache.ts",
    "content": "import { EntityCollection } from './entity-collection';\n\nexport interface EntityCache {\n  // Must be `any` since we don't know what type of collections we will have\n  [name: string]: EntityCollection<any>;\n}\n"
  },
  {
    "path": "modules/data/src/reducers/entity-change-tracker-base.ts",
    "content": "import { EntityAdapter, IdSelector, Update } from '@ngrx/entity';\n\nimport { ChangeType, EntityCollection } from './entity-collection';\nimport { defaultSelectId } from '../utils/utilities';\nimport { EntityChangeTracker } from './entity-change-tracker';\nimport { MergeStrategy } from '../actions/merge-strategy';\nimport { UpdateResponseData } from '../actions/update-response-data';\n\n/**\n * The default implementation of EntityChangeTracker with\n * methods for tracking, committing, and reverting/undoing unsaved entity changes.\n * Used by EntityCollectionReducerMethods which should call tracker methods BEFORE modifying the collection.\n * See EntityChangeTracker docs.\n */\nexport class EntityChangeTrackerBase<T> implements EntityChangeTracker<T> {\n  constructor(\n    private adapter: EntityAdapter<T>,\n    private selectId: IdSelector<T>\n  ) {\n    /** Extract the primary key (id); default to `id` */\n    this.selectId = selectId || defaultSelectId;\n  }\n\n  // #region commit methods\n  /**\n   * Commit all changes as when the collection has been completely reloaded from the server.\n   * Harmless when there are no entity changes to commit.\n   * @param collection The entity collection\n   */\n  commitAll(collection: EntityCollection<T>): EntityCollection<T> {\n    return Object.keys(collection.changeState).length === 0\n      ? collection\n      : { ...collection, changeState: {} };\n  }\n\n  /**\n   * Commit changes for the given entities as when they have been refreshed from the server.\n   * Harmless when there are no entity changes to commit.\n   * @param entityOrIdList The entities to clear tracking or their ids.\n   * @param collection The entity collection\n   */\n  commitMany(\n    entityOrIdList: (number | string | T)[],\n    collection: EntityCollection<T>\n  ): EntityCollection<T> {\n    if (entityOrIdList == null || entityOrIdList.length === 0) {\n      return collection; // nothing to commit\n    }\n    let didMutate = false;\n    const changeState = entityOrIdList.reduce((chgState, entityOrId) => {\n      const id =\n        typeof entityOrId === 'object'\n          ? this.selectId(entityOrId)\n          : (entityOrId as string | number);\n      if (chgState[id]) {\n        if (!didMutate) {\n          chgState = { ...chgState };\n          didMutate = true;\n        }\n        delete chgState[id];\n      }\n      return chgState;\n    }, collection.changeState);\n\n    return didMutate ? { ...collection, changeState } : collection;\n  }\n\n  /**\n   * Commit changes for the given entity as when it have been refreshed from the server.\n   * Harmless when no entity changes to commit.\n   * @param entityOrId The entity to clear tracking or its id.\n   * @param collection The entity collection\n   */\n  commitOne(\n    entityOrId: number | string | T,\n    collection: EntityCollection<T>\n  ): EntityCollection<T> {\n    return entityOrId == null\n      ? collection\n      : this.commitMany([entityOrId], collection);\n  }\n\n  // #endregion commit methods\n\n  // #region merge query\n  /**\n   * Merge query results into the collection, adjusting the ChangeState per the mergeStrategy.\n   * @param entities Entities returned from querying the server.\n   * @param collection The entity collection\n   * @param [mergeStrategy] How to merge a queried entity when the corresponding entity in the collection has an unsaved change.\n   * Defaults to MergeStrategy.PreserveChanges.\n   * @returns The merged EntityCollection.\n   */\n  mergeQueryResults(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    return this.mergeServerUpserts(\n      entities,\n      collection,\n      MergeStrategy.PreserveChanges,\n      mergeStrategy\n    );\n  }\n  // #endregion merge query results\n\n  // #region merge save results\n  /**\n   * Merge result of saving new entities into the collection, adjusting the ChangeState per the mergeStrategy.\n   * The default is MergeStrategy.OverwriteChanges.\n   * @param entities Entities returned from saving new entities to the server.\n   * @param collection The entity collection\n   * @param [mergeStrategy] How to merge a saved entity when the corresponding entity in the collection has an unsaved change.\n   * Defaults to MergeStrategy.OverwriteChanges.\n   * @returns The merged EntityCollection.\n   */\n  mergeSaveAdds(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    return this.mergeServerUpserts(\n      entities,\n      collection,\n      MergeStrategy.OverwriteChanges,\n      mergeStrategy\n    );\n  }\n\n  /**\n   * Merge successful result of deleting entities on the server that have the given primary keys\n   * Clears the entity changeState for those keys unless the MergeStrategy is ignoreChanges.\n   * @param entities keys primary keys of the entities to remove/delete.\n   * @param collection The entity collection\n   * @param [mergeStrategy] How to adjust change tracking when the corresponding entity in the collection has an unsaved change.\n   * Defaults to MergeStrategy.OverwriteChanges.\n   * @returns The merged EntityCollection.\n   */\n  mergeSaveDeletes(\n    keys: (number | string)[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    mergeStrategy =\n      mergeStrategy == null ? MergeStrategy.OverwriteChanges : mergeStrategy;\n    // same logic for all non-ignore merge strategies: always clear (commit) the changes\n    const deleteIds = keys as string[]; // make TypeScript happy\n    collection =\n      mergeStrategy === MergeStrategy.IgnoreChanges\n        ? collection\n        : this.commitMany(deleteIds, collection);\n    return this.adapter.removeMany(deleteIds, collection);\n  }\n\n  /**\n   * Merge result of saving updated entities into the collection, adjusting the ChangeState per the mergeStrategy.\n   * The default is MergeStrategy.OverwriteChanges.\n   * @param updateResponseData Entity response data returned from saving updated entities to the server.\n   * @param collection The entity collection\n   * @param [mergeStrategy] How to merge a saved entity when the corresponding entity in the collection has an unsaved change.\n   * Defaults to MergeStrategy.OverwriteChanges.\n   * @param [skipUnchanged] True means skip update if server didn't change it. False by default.\n   * If the update was optimistic and the server didn't make more changes of its own\n   * then the updates are already in the collection and shouldn't make them again.\n   * @returns The merged EntityCollection.\n   */\n  mergeSaveUpdates(\n    updateResponseData: UpdateResponseData<T>[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy,\n    skipUnchanged = false\n  ): EntityCollection<T> {\n    if (updateResponseData == null || updateResponseData.length === 0) {\n      return collection; // nothing to merge.\n    }\n\n    let didMutate = false;\n    let changeState = collection.changeState;\n    mergeStrategy =\n      mergeStrategy == null ? MergeStrategy.OverwriteChanges : mergeStrategy;\n    let updates: Update<T>[];\n\n    switch (mergeStrategy) {\n      case MergeStrategy.IgnoreChanges:\n        updates = filterChanged(updateResponseData);\n        return this.adapter.updateMany(updates, collection);\n\n      case MergeStrategy.OverwriteChanges:\n        changeState = updateResponseData.reduce((chgState, update) => {\n          const oldId = update.id;\n          const change = chgState[oldId];\n          if (change) {\n            if (!didMutate) {\n              chgState = { ...chgState };\n              didMutate = true;\n            }\n            delete chgState[oldId];\n          }\n          return chgState;\n        }, collection.changeState);\n\n        collection = didMutate ? { ...collection, changeState } : collection;\n\n        updates = filterChanged(updateResponseData);\n        return this.adapter.updateMany(updates, collection);\n\n      case MergeStrategy.PreserveChanges: {\n        const updateableEntities = [] as UpdateResponseData<T>[];\n        changeState = updateResponseData.reduce((chgState, update) => {\n          const oldId = update.id;\n          const change = chgState[oldId];\n          if (change) {\n            // Tracking a change so update original value but not the current value\n            if (!didMutate) {\n              chgState = { ...chgState };\n              didMutate = true;\n            }\n            const newId = this.selectId(update.changes as T);\n            const oldChangeState = change;\n            // If the server changed the id, register the new \"originalValue\" under the new id\n            // and remove the change tracked under the old id.\n            if (newId !== oldId) {\n              delete chgState[oldId];\n            }\n            const newOrigValue = {\n              ...(oldChangeState!.originalValue as any),\n              ...(update.changes as any),\n            };\n            (chgState as any)[newId] = {\n              ...oldChangeState,\n              originalValue: newOrigValue,\n            };\n          } else {\n            updateableEntities.push(update);\n          }\n          return chgState;\n        }, collection.changeState);\n        collection = didMutate ? { ...collection, changeState } : collection;\n\n        updates = filterChanged(updateableEntities);\n        return this.adapter.updateMany(updates, collection);\n      }\n    }\n\n    /**\n     * Conditionally keep only those updates that have additional server changes.\n     * (e.g., for optimistic saves because they updates are already in the current collection)\n     * Strip off the `changed` property.\n     * @responseData Entity response data from server.\n     * May be an UpdateResponseData<T>, a subclass of Update<T> with a 'changed' flag.\n     * @returns Update<T> (without the changed flag)\n     */\n    function filterChanged(responseData: UpdateResponseData<T>[]): Update<T>[] {\n      if (skipUnchanged === true) {\n        // keep only those updates that the server changed (knowable if is UpdateResponseData<T>)\n        responseData = responseData.filter((r) => r.changed === true);\n      }\n      // Strip unchanged property from responseData, leaving just the pure Update<T>\n      // TODO: Remove? probably not necessary as the Update isn't stored and adapter will ignore `changed`.\n      return responseData.map((r) => ({ id: r.id as any, changes: r.changes }));\n    }\n  }\n\n  /**\n   * Merge result of saving upserted entities into the collection, adjusting the ChangeState per the mergeStrategy.\n   * The default is MergeStrategy.OverwriteChanges.\n   * @param entities Entities returned from saving upserts to the server.\n   * @param collection The entity collection\n   * @param [mergeStrategy] How to merge a saved entity when the corresponding entity in the collection has an unsaved change.\n   * Defaults to MergeStrategy.OverwriteChanges.\n   * @returns The merged EntityCollection.\n   */\n  mergeSaveUpserts(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    return this.mergeServerUpserts(\n      entities,\n      collection,\n      MergeStrategy.OverwriteChanges,\n      mergeStrategy\n    );\n  }\n  // #endregion merge save results\n\n  // #region query & save helpers\n  /**\n   *\n   * @param entities Entities to merge\n   * @param collection Collection into which entities are merged\n   * @param defaultMergeStrategy How to merge when action's MergeStrategy is unspecified\n   * @param [mergeStrategy] The action's MergeStrategy\n   */\n  private mergeServerUpserts(\n    entities: T[],\n    collection: EntityCollection<T>,\n    defaultMergeStrategy: MergeStrategy,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    if (entities == null || entities.length === 0) {\n      return collection; // nothing to merge.\n    }\n\n    let didMutate = false;\n    let changeState = collection.changeState;\n    mergeStrategy =\n      mergeStrategy == null ? defaultMergeStrategy : mergeStrategy;\n\n    switch (mergeStrategy) {\n      case MergeStrategy.IgnoreChanges:\n        return this.adapter.upsertMany(entities, collection);\n\n      case MergeStrategy.OverwriteChanges:\n        collection = this.adapter.upsertMany(entities, collection);\n\n        changeState = entities.reduce((chgState, entity) => {\n          const id = this.selectId(entity);\n          const change = chgState[id];\n          if (change) {\n            if (!didMutate) {\n              chgState = { ...chgState };\n              didMutate = true;\n            }\n            delete chgState[id];\n          }\n          return chgState;\n        }, collection.changeState);\n\n        return didMutate ? { ...collection, changeState } : collection;\n\n      case MergeStrategy.PreserveChanges: {\n        const upsertEntities = [] as T[];\n        changeState = entities.reduce((chgState, entity) => {\n          const id = this.selectId(entity);\n          const change = chgState[id];\n          if (change) {\n            if (!didMutate) {\n              chgState = {\n                ...chgState,\n                [id]: {\n                  ...chgState[id]!,\n                  originalValue: entity,\n                },\n              };\n              didMutate = true;\n            }\n          } else {\n            upsertEntities.push(entity);\n          }\n          return chgState;\n        }, collection.changeState);\n\n        collection = this.adapter.upsertMany(upsertEntities, collection);\n        return didMutate ? { ...collection, changeState } : collection;\n      }\n    }\n  }\n  // #endregion query & save helpers\n\n  // #region track methods\n  /**\n   * Track multiple entities before adding them to the collection.\n   * Does NOT add to the collection (the reducer's job).\n   * @param entities The entities to add. They must all have their ids.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackAddMany(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    if (\n      mergeStrategy === MergeStrategy.IgnoreChanges ||\n      entities == null ||\n      entities.length === 0\n    ) {\n      return collection; // nothing to track\n    }\n    let didMutate = false;\n    const changeState = entities.reduce((chgState, entity) => {\n      const id = this.selectId(entity);\n      if (id == null || id === '') {\n        throw new Error(\n          `${collection.entityName} entity add requires a key to be tracked`\n        );\n      }\n      const trackedChange = chgState[id];\n\n      if (!trackedChange) {\n        if (!didMutate) {\n          didMutate = true;\n          chgState = { ...chgState };\n        }\n        chgState[id] = { changeType: ChangeType.Added };\n      }\n      return chgState;\n    }, collection.changeState);\n    return didMutate ? { ...collection, changeState } : collection;\n  }\n\n  /**\n   * Track an entity before adding it to the collection.\n   * Does NOT add to the collection (the reducer's job).\n   * @param entity The entity to add. It must have an id.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   * If not specified, implementation supplies a default strategy.\n   */\n  trackAddOne(\n    entity: T,\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    return entity == null\n      ? collection\n      : this.trackAddMany([entity], collection, mergeStrategy);\n  }\n\n  /**\n   * Track multiple entities before removing them with the intention of deleting them on the server.\n   * Does NOT remove from the collection (the reducer's job).\n   * @param keys The primary keys of the entities to delete.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackDeleteMany(\n    keys: (number | string)[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    if (\n      mergeStrategy === MergeStrategy.IgnoreChanges ||\n      keys == null ||\n      keys.length === 0\n    ) {\n      return collection; // nothing to track\n    }\n    let didMutate = false;\n    const entityMap = collection.entities;\n    const changeState = keys.reduce((chgState, id) => {\n      const originalValue = entityMap[id];\n      if (originalValue) {\n        const trackedChange = chgState[id];\n        if (trackedChange) {\n          if (trackedChange.changeType === ChangeType.Added) {\n            // Special case: stop tracking an added entity that you delete\n            // The caller must also detect this, remove it immediately from the collection\n            // and skip attempt to delete on the server.\n            cloneChgStateOnce();\n            delete chgState[id];\n          } else if (trackedChange.changeType === ChangeType.Updated) {\n            // Special case: switch change type from Updated to Deleted.\n            cloneChgStateOnce();\n            chgState[id] = { ...chgState[id], changeType: ChangeType.Deleted };\n          }\n        } else {\n          // Start tracking this entity\n          cloneChgStateOnce();\n          chgState[id] = { changeType: ChangeType.Deleted, originalValue };\n        }\n      }\n      return chgState;\n\n      function cloneChgStateOnce() {\n        if (!didMutate) {\n          didMutate = true;\n          chgState = { ...chgState };\n        }\n      }\n    }, collection.changeState);\n\n    return didMutate ? { ...collection, changeState } : collection;\n  }\n\n  /**\n   * Track an entity before it is removed with the intention of deleting it on the server.\n   * Does NOT remove from the collection (the reducer's job).\n   * @param key The primary key of the entity to delete.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackDeleteOne(\n    key: number | string,\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    return key == null\n      ? collection\n      : this.trackDeleteMany([key], collection, mergeStrategy);\n  }\n\n  /**\n   * Track multiple entities before updating them in the collection.\n   * Does NOT update the collection (the reducer's job).\n   * @param updates The entities to update.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackUpdateMany(\n    updates: Update<T>[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    if (\n      mergeStrategy === MergeStrategy.IgnoreChanges ||\n      updates == null ||\n      updates.length === 0\n    ) {\n      return collection; // nothing to track\n    }\n    let didMutate = false;\n    const entityMap = collection.entities;\n    const changeState = updates.reduce((chgState, update) => {\n      const { id, changes: entity } = update;\n      if (id == null || id === '') {\n        throw new Error(\n          `${collection.entityName} entity update requires a key to be tracked`\n        );\n      }\n      const originalValue = entityMap[id];\n      // Only track if it is in the collection. Silently ignore if it is not.\n      // @ngrx/entity adapter would also silently ignore.\n      // Todo: should missing update entity really be reported as an error?\n      if (originalValue) {\n        const trackedChange = chgState[id];\n        if (!trackedChange) {\n          if (!didMutate) {\n            didMutate = true;\n            chgState = { ...chgState };\n          }\n          chgState[id] = { changeType: ChangeType.Updated, originalValue };\n        }\n      }\n      return chgState;\n    }, collection.changeState);\n    return didMutate ? { ...collection, changeState } : collection;\n  }\n\n  /**\n   * Track an entity before updating it in the collection.\n   * Does NOT update the collection (the reducer's job).\n   * @param update The entity to update.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackUpdateOne(\n    update: Update<T>,\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    return update == null\n      ? collection\n      : this.trackUpdateMany([update], collection, mergeStrategy);\n  }\n\n  /**\n   * Track multiple entities before upserting (adding and updating) them to the collection.\n   * Does NOT update the collection (the reducer's job).\n   * @param entities The entities to add or update. They must be complete entities with ids.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackUpsertMany(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    if (\n      mergeStrategy === MergeStrategy.IgnoreChanges ||\n      entities == null ||\n      entities.length === 0\n    ) {\n      return collection; // nothing to track\n    }\n    let didMutate = false;\n    const entityMap = collection.entities;\n    const changeState = entities.reduce((chgState, entity) => {\n      const id = this.selectId(entity);\n      if (id == null || id === '') {\n        throw new Error(\n          `${collection.entityName} entity upsert requires a key to be tracked`\n        );\n      }\n      const trackedChange = chgState[id];\n\n      if (!trackedChange) {\n        if (!didMutate) {\n          didMutate = true;\n          chgState = { ...chgState };\n        }\n\n        const originalValue = entityMap[id];\n        chgState[id] =\n          originalValue == null\n            ? { changeType: ChangeType.Added }\n            : { changeType: ChangeType.Updated, originalValue };\n      }\n      return chgState;\n    }, collection.changeState);\n    return didMutate ? { ...collection, changeState } : collection;\n  }\n\n  /**\n   * Track an entity before upsert (adding and updating) it to the collection.\n   * Does NOT update the collection (the reducer's job).\n   * @param entities The entity to add or update. It must be a complete entity with its id.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackUpsertOne(\n    entity: T,\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T> {\n    return entity == null\n      ? collection\n      : this.trackUpsertMany([entity], collection, mergeStrategy);\n  }\n  // #endregion track methods\n\n  // #region undo methods\n  /**\n   * Revert the unsaved changes for all collection.\n   * Harmless when there are no entity changes to undo.\n   * @param collection The entity collection\n   */\n  undoAll(collection: EntityCollection<T>): EntityCollection<T> {\n    const ids = Object.keys(collection.changeState);\n\n    const { remove, upsert } = ids.reduce(\n      (acc, id) => {\n        const changeState = acc.chgState[id]!;\n        switch (changeState.changeType) {\n          case ChangeType.Added:\n            acc.remove.push(id);\n            break;\n          case ChangeType.Deleted:\n            const removed = changeState!.originalValue;\n            if (removed) {\n              acc.upsert.push(removed);\n            }\n            break;\n          case ChangeType.Updated:\n            acc.upsert.push(changeState!.originalValue!);\n            break;\n        }\n        return acc;\n      },\n      // entitiesToUndo\n      {\n        remove: [] as (number | string)[],\n        upsert: [] as T[],\n        chgState: collection.changeState,\n      }\n    );\n\n    collection = this.adapter.removeMany(remove as string[], collection);\n    collection = this.adapter.upsertMany(upsert, collection);\n\n    return { ...collection, changeState: {} };\n  }\n\n  /**\n   * Revert the unsaved changes for the given entities.\n   * Harmless when there are no entity changes to undo.\n   * @param entityOrIdList The entities to revert or their ids.\n   * @param collection The entity collection\n   */\n  undoMany(\n    entityOrIdList: (number | string | T)[],\n    collection: EntityCollection<T>\n  ): EntityCollection<T> {\n    if (entityOrIdList == null || entityOrIdList.length === 0) {\n      return collection; // nothing to undo\n    }\n    let didMutate = false;\n\n    const { changeState, remove, upsert } = entityOrIdList.reduce(\n      (acc, entityOrId) => {\n        let chgState = acc.changeState;\n        const id =\n          typeof entityOrId === 'object'\n            ? this.selectId(entityOrId)\n            : (entityOrId as string | number);\n        const change = chgState[id]!;\n        if (change) {\n          if (!didMutate) {\n            chgState = { ...chgState };\n            didMutate = true;\n          }\n          delete chgState[id]; // clear tracking of this entity\n          acc.changeState = chgState;\n          switch (change.changeType) {\n            case ChangeType.Added:\n              acc.remove.push(id);\n              break;\n            case ChangeType.Deleted:\n              const removed = change!.originalValue;\n              if (removed) {\n                acc.upsert.push(removed);\n              }\n              break;\n            case ChangeType.Updated:\n              acc.upsert.push(change!.originalValue!);\n              break;\n          }\n        }\n        return acc;\n      },\n      // entitiesToUndo\n      {\n        remove: [] as (number | string)[],\n        upsert: [] as T[],\n        changeState: collection.changeState,\n      }\n    );\n\n    collection = this.adapter.removeMany(remove as string[], collection);\n    collection = this.adapter.upsertMany(upsert, collection);\n    return didMutate ? { ...collection, changeState } : collection;\n  }\n\n  /**\n   * Revert the unsaved changes for the given entity.\n   * Harmless when there are no entity changes to undo.\n   * @param entityOrId The entity to revert or its id.\n   * @param collection The entity collection\n   */\n  undoOne(\n    entityOrId: number | string | T,\n    collection: EntityCollection<T>\n  ): EntityCollection<T> {\n    return entityOrId == null\n      ? collection\n      : this.undoMany([entityOrId], collection);\n  }\n  // #endregion undo methods\n}\n"
  },
  {
    "path": "modules/data/src/reducers/entity-change-tracker.ts",
    "content": "import { Update } from '@ngrx/entity';\nimport { EntityCollection } from './entity-collection';\nimport { MergeStrategy } from '../actions/merge-strategy';\nimport { UpdateResponseData } from '../actions/update-response-data';\n\n/**\n * Methods for tracking, committing, and reverting/undoing unsaved entity changes.\n * Used by EntityCollectionReducerMethods which should call tracker methods BEFORE modifying the collection.\n * See EntityChangeTracker docs.\n */\nexport interface EntityChangeTracker<T> {\n  // #region commit\n  /**\n   * Commit all changes as when the collection has been completely reloaded from the server.\n   * Harmless when there are no entity changes to commit.\n   * @param collection The entity collection\n   */\n  commitAll(collection: EntityCollection<T>): EntityCollection<T>;\n\n  /**\n   * Commit changes for the given entities as when they have been refreshed from the server.\n   * Harmless when there are no entity changes to commit.\n   * @param entityOrIdList The entities to clear tracking or their ids.\n   * @param collection The entity collection\n   */\n  commitMany(\n    entityOrIdList: (number | string | T)[],\n    collection: EntityCollection<T>\n  ): EntityCollection<T>;\n\n  /**\n   * Commit changes for the given entity as when it have been refreshed from the server.\n   * Harmless when no entity changes to commit.\n   * @param entityOrId The entity to clear tracking or its id.\n   * @param collection The entity collection\n   */\n  commitOne(\n    entityOrId: number | string | T,\n    collection: EntityCollection<T>\n  ): EntityCollection<T>;\n  // #endregion commit\n\n  // #region mergeQuery\n  /**\n   * Merge query results into the collection, adjusting the ChangeState per the mergeStrategy.\n   * @param entities Entities returned from querying the server.\n   * @param collection The entity collection\n   * @param [mergeStrategy] How to merge a queried entity when the corresponding entity in the collection has an unsaved change.\n   * If not specified, implementation supplies a default strategy.\n   * @returns The merged EntityCollection.\n   */\n  mergeQueryResults(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n  // #endregion mergeQuery\n\n  // #region mergeSave\n  /**\n   * Merge result of saving new entities into the collection, adjusting the ChangeState per the mergeStrategy.\n   * The default is MergeStrategy.OverwriteChanges.\n   * @param entities Entities returned from saving new entities to the server.\n   * @param collection The entity collection\n   * @param [mergeStrategy] How to merge a saved entity when the corresponding entity in the collection has an unsaved change.\n   * If not specified, implementation supplies a default strategy.\n   * @returns The merged EntityCollection.\n   */\n  mergeSaveAdds(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n  /**\n   * Merge successful result of deleting entities on the server that have the given primary keys\n   * Clears the entity changeState for those keys unless the MergeStrategy is ignoreChanges.\n   * @param entities keys primary keys of the entities to remove/delete.\n   * @param collection The entity collection\n   * @param [mergeStrategy] How to adjust change tracking when the corresponding entity in the collection has an unsaved change.\n   * If not specified, implementation supplies a default strategy.\n   * @returns The merged EntityCollection.\n   */\n  mergeSaveDeletes(\n    keys: (number | string)[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n\n  /**\n   * Merge result of saving upserted entities into the collection, adjusting the ChangeState per the mergeStrategy.\n   * The default is MergeStrategy.OverwriteChanges.\n   * @param entities Entities returned from saving upsert entities to the server.\n   * @param collection The entity collection\n   * @param [mergeStrategy] How to merge a saved entity when the corresponding entity in the collection has an unsaved change.\n   * If not specified, implementation supplies a default strategy.\n   * @returns The merged EntityCollection.\n   */\n  mergeSaveUpserts(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n\n  /**\n   * Merge result of saving updated entities into the collection, adjusting the ChangeState per the mergeStrategy.\n   * The default is MergeStrategy.OverwriteChanges.\n   * @param updates Entity response data returned from saving updated entities to the server.\n   * @param [mergeStrategy] How to merge a saved entity when the corresponding entity in the collection has an unsaved change.\n   * If not specified, implementation supplies a default strategy.\n   * @param [skipUnchanged] True means skip update if server didn't change it. False by default.\n   * If the update was optimistic and the server didn't make more changes of its own\n   * then the updates are already in the collection and shouldn't make them again.\n   * @param collection The entity collection\n   * @returns The merged EntityCollection.\n   */\n  mergeSaveUpdates(\n    updates: UpdateResponseData<T>[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy,\n    skipUnchanged?: boolean\n  ): EntityCollection<T>;\n  // #endregion mergeSave\n\n  // #region track\n  /**\n   * Track multiple entities before adding them to the collection.\n   * Does NOT add to the collection (the reducer's job).\n   * @param entities The entities to add. They must all have their ids.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   * If not specified, implementation supplies a default strategy.\n   */\n  trackAddMany(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n\n  /**\n   * Track an entity before adding it to the collection.\n   * Does NOT add to the collection (the reducer's job).\n   * @param entity The entity to add. It must have an id.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   * If not specified, implementation supplies a default strategy.\n   */\n  trackAddOne(\n    entity: T,\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n\n  /**\n   * Track multiple entities before removing them with the intention of deleting them on the server.\n   * Does NOT remove from the collection (the reducer's job).\n   * @param keys The primary keys of the entities to delete.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackDeleteMany(\n    keys: (number | string)[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n\n  /**\n   * Track an entity before it is removed with the intention of deleting it on the server.\n   * Does NOT remove from the collection (the reducer's job).\n   * @param key The primary key of the entity to delete.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackDeleteOne(\n    key: number | string,\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n\n  /**\n   * Track multiple entities before updating them in the collection.\n   * Does NOT update the collection (the reducer's job).\n   * @param updates The entities to update.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackUpdateMany(\n    updates: Update<T>[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n\n  /**\n   * Track an entity before updating it in the collection.\n   * Does NOT update the collection (the reducer's job).\n   * @param update The entity to update.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackUpdateOne(\n    update: Update<T>,\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n\n  /**\n   * Track multiple entities before upserting (adding and updating) them to the collection.\n   * Does NOT update the collection (the reducer's job).\n   * @param entities The entities to add or update. They must be complete entities with ids.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackUpsertMany(\n    entities: T[],\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n\n  /**\n   * Track an entity before upsert (adding and updating) it to the collection.\n   * Does NOT update the collection (the reducer's job).\n   * @param entities The entity to add or update. It must be a complete entity with its id.\n   * @param collection The entity collection\n   * @param [mergeStrategy] Track by default. Don't track if is MergeStrategy.IgnoreChanges.\n   */\n  trackUpsertOne(\n    entity: T,\n    collection: EntityCollection<T>,\n    mergeStrategy?: MergeStrategy\n  ): EntityCollection<T>;\n  // #endregion track\n\n  // #region undo\n  /**\n   * Revert the unsaved changes for all collection.\n   * Harmless when there are no entity changes to undo.\n   * @param collection The entity collection\n   */\n  undoAll(collection: EntityCollection<T>): EntityCollection<T>;\n\n  /**\n   * Revert the unsaved changes for the given entities.\n   * Harmless when there are no entity changes to undo.\n   * @param entityOrIdList The entities to revert or their ids.\n   * @param collection The entity collection\n   */\n  undoMany(\n    entityOrIdList: (number | string | T)[],\n    collection: EntityCollection<T>\n  ): EntityCollection<T>;\n\n  /**\n   * Revert the unsaved changes for the given entity.\n   * Harmless when there are no entity changes to undo.\n   * @param entityOrId The entity to revert or its id.\n   * @param collection The entity collection\n   */\n  undoOne(\n    entityOrId: number | string | T,\n    collection: EntityCollection<T>\n  ): EntityCollection<T>;\n  // #endregion undo\n}\n"
  },
  {
    "path": "modules/data/src/reducers/entity-collection-creator.ts",
    "content": "import { Injectable, Optional } from '@angular/core';\n\nimport { EntityCollection } from './entity-collection';\nimport { EntityDefinitionService } from '../entity-metadata/entity-definition.service';\n\n@Injectable()\nexport class EntityCollectionCreator {\n  constructor(\n    @Optional() private entityDefinitionService?: EntityDefinitionService\n  ) {}\n\n  /**\n   * Create the default collection for an entity type.\n   * @param entityName {string} entity type name\n   */\n  create<T = any, S extends EntityCollection<T> = EntityCollection<T>>(\n    entityName: string\n  ): S {\n    const def =\n      this.entityDefinitionService &&\n      this.entityDefinitionService.getDefinition<T>(\n        entityName,\n        false /*shouldThrow*/\n      );\n\n    const initialState = def && def.initialState;\n\n    return <S>(initialState || createEmptyEntityCollection<T>(entityName));\n  }\n}\n\nexport function createEmptyEntityCollection<T>(\n  entityName?: string\n): EntityCollection<T> {\n  return {\n    entityName,\n    ids: [],\n    entities: {},\n    filter: undefined,\n    loaded: false,\n    loading: false,\n    changeState: {},\n  } as EntityCollection<T>;\n}\n"
  },
  {
    "path": "modules/data/src/reducers/entity-collection-reducer-methods.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { EntityAdapter, IdSelector, Update } from '@ngrx/entity';\nimport {\n  ChangeStateMap,\n  ChangeType,\n  EntityCollection,\n} from './entity-collection';\nimport { EntityChangeTrackerBase } from './entity-change-tracker-base';\nimport { toUpdateFactory } from '../utils/utilities';\nimport { EntityAction } from '../actions/entity-action';\nimport { EntityActionDataServiceError } from '../dataservices/data-service-error';\nimport { EntityActionGuard } from '../actions/entity-action-guard';\nimport { EntityChangeTracker } from './entity-change-tracker';\nimport { EntityDefinition } from '../entity-metadata/entity-definition';\nimport { EntityDefinitionService } from '../entity-metadata/entity-definition.service';\nimport { EntityOp } from '../actions/entity-op';\nimport { MergeStrategy } from '../actions/merge-strategy';\nimport { UpdateResponseData } from '../actions/update-response-data';\n\n/**\n * Map of {EntityOp} to reducer method for the operation.\n * If an operation is missing, caller should return the collection for that reducer.\n */\nexport interface EntityCollectionReducerMethodMap<T> {\n  [method: string]: (\n    collection: EntityCollection<T>,\n    action: EntityAction\n  ) => EntityCollection<T>;\n}\n\n/**\n * Base implementation of reducer methods for an entity collection.\n */\nexport class EntityCollectionReducerMethods<T> {\n  protected adapter: EntityAdapter<T>;\n  protected guard: EntityActionGuard<T>;\n  /** True if this collection tracks unsaved changes */\n  protected isChangeTracking: boolean;\n\n  /** Extract the primary key (id); default to `id` */\n  selectId: IdSelector<T>;\n\n  /**\n   * Track changes to entities since the last query or save\n   * Can revert some or all of those changes\n   */\n  entityChangeTracker: EntityChangeTracker<T>;\n\n  /**\n   * Convert an entity (or partial entity) into the `Update<T>` object\n   * `id`: the primary key and\n   * `changes`: the entity (or partial entity of changes).\n   */\n  protected toUpdate: (entity: Partial<T>) => Update<T>;\n\n  /**\n   * Dictionary of the {EntityCollectionReducerMethods} for this entity type,\n   * keyed by the {EntityOp}\n   */\n  readonly methods: EntityCollectionReducerMethodMap<T> = {\n    [EntityOp.CANCEL_PERSIST]: this.cancelPersist.bind(this),\n\n    [EntityOp.QUERY_ALL]: this.queryAll.bind(this),\n    [EntityOp.QUERY_ALL_ERROR]: this.queryAllError.bind(this),\n    [EntityOp.QUERY_ALL_SUCCESS]: this.queryAllSuccess.bind(this),\n\n    [EntityOp.QUERY_BY_KEY]: this.queryByKey.bind(this),\n    [EntityOp.QUERY_BY_KEY_ERROR]: this.queryByKeyError.bind(this),\n    [EntityOp.QUERY_BY_KEY_SUCCESS]: this.queryByKeySuccess.bind(this),\n\n    [EntityOp.QUERY_LOAD]: this.queryLoad.bind(this),\n    [EntityOp.QUERY_LOAD_ERROR]: this.queryLoadError.bind(this),\n    [EntityOp.QUERY_LOAD_SUCCESS]: this.queryLoadSuccess.bind(this),\n\n    [EntityOp.QUERY_MANY]: this.queryMany.bind(this),\n    [EntityOp.QUERY_MANY_ERROR]: this.queryManyError.bind(this),\n    [EntityOp.QUERY_MANY_SUCCESS]: this.queryManySuccess.bind(this),\n\n    [EntityOp.SAVE_ADD_MANY]: this.saveAddMany.bind(this),\n    [EntityOp.SAVE_ADD_MANY_ERROR]: this.saveAddManyError.bind(this),\n    [EntityOp.SAVE_ADD_MANY_SUCCESS]: this.saveAddManySuccess.bind(this),\n\n    [EntityOp.SAVE_ADD_ONE]: this.saveAddOne.bind(this),\n    [EntityOp.SAVE_ADD_ONE_ERROR]: this.saveAddOneError.bind(this),\n    [EntityOp.SAVE_ADD_ONE_SUCCESS]: this.saveAddOneSuccess.bind(this),\n\n    [EntityOp.SAVE_DELETE_MANY]: this.saveDeleteMany.bind(this),\n    [EntityOp.SAVE_DELETE_MANY_ERROR]: this.saveDeleteManyError.bind(this),\n    [EntityOp.SAVE_DELETE_MANY_SUCCESS]: this.saveDeleteManySuccess.bind(this),\n\n    [EntityOp.SAVE_DELETE_ONE]: this.saveDeleteOne.bind(this),\n    [EntityOp.SAVE_DELETE_ONE_ERROR]: this.saveDeleteOneError.bind(this),\n    [EntityOp.SAVE_DELETE_ONE_SUCCESS]: this.saveDeleteOneSuccess.bind(this),\n\n    [EntityOp.SAVE_UPDATE_MANY]: this.saveUpdateMany.bind(this),\n    [EntityOp.SAVE_UPDATE_MANY_ERROR]: this.saveUpdateManyError.bind(this),\n    [EntityOp.SAVE_UPDATE_MANY_SUCCESS]: this.saveUpdateManySuccess.bind(this),\n\n    [EntityOp.SAVE_UPDATE_ONE]: this.saveUpdateOne.bind(this),\n    [EntityOp.SAVE_UPDATE_ONE_ERROR]: this.saveUpdateOneError.bind(this),\n    [EntityOp.SAVE_UPDATE_ONE_SUCCESS]: this.saveUpdateOneSuccess.bind(this),\n\n    [EntityOp.SAVE_UPSERT_MANY]: this.saveUpsertMany.bind(this),\n    [EntityOp.SAVE_UPSERT_MANY_ERROR]: this.saveUpsertManyError.bind(this),\n    [EntityOp.SAVE_UPSERT_MANY_SUCCESS]: this.saveUpsertManySuccess.bind(this),\n\n    [EntityOp.SAVE_UPSERT_ONE]: this.saveUpsertOne.bind(this),\n    [EntityOp.SAVE_UPSERT_ONE_ERROR]: this.saveUpsertOneError.bind(this),\n    [EntityOp.SAVE_UPSERT_ONE_SUCCESS]: this.saveUpsertOneSuccess.bind(this),\n\n    // Do nothing on save errors except turn the loading flag off.\n    // See the ChangeTrackerMetaReducers\n    // Or the app could listen for those errors and do something\n\n    /// cache only operations ///\n\n    [EntityOp.ADD_ALL]: this.addAll.bind(this),\n    [EntityOp.ADD_MANY]: this.addMany.bind(this),\n    [EntityOp.ADD_ONE]: this.addOne.bind(this),\n\n    [EntityOp.REMOVE_ALL]: this.removeAll.bind(this),\n    [EntityOp.REMOVE_MANY]: this.removeMany.bind(this),\n    [EntityOp.REMOVE_ONE]: this.removeOne.bind(this),\n\n    [EntityOp.UPDATE_MANY]: this.updateMany.bind(this),\n    [EntityOp.UPDATE_ONE]: this.updateOne.bind(this),\n\n    [EntityOp.UPSERT_MANY]: this.upsertMany.bind(this),\n    [EntityOp.UPSERT_ONE]: this.upsertOne.bind(this),\n\n    [EntityOp.COMMIT_ALL]: this.commitAll.bind(this),\n    [EntityOp.COMMIT_MANY]: this.commitMany.bind(this),\n    [EntityOp.COMMIT_ONE]: this.commitOne.bind(this),\n    [EntityOp.UNDO_ALL]: this.undoAll.bind(this),\n    [EntityOp.UNDO_MANY]: this.undoMany.bind(this),\n    [EntityOp.UNDO_ONE]: this.undoOne.bind(this),\n\n    [EntityOp.SET_CHANGE_STATE]: this.setChangeState.bind(this),\n    [EntityOp.SET_COLLECTION]: this.setCollection.bind(this),\n    [EntityOp.SET_FILTER]: this.setFilter.bind(this),\n    [EntityOp.SET_LOADED]: this.setLoaded.bind(this),\n    [EntityOp.SET_LOADING]: this.setLoading.bind(this),\n  };\n\n  constructor(\n    public entityName: string,\n    public definition: EntityDefinition<T>,\n    /*\n     * Track changes to entities since the last query or save\n     * Can revert some or all of those changes\n     */\n    entityChangeTracker?: EntityChangeTracker<T>\n  ) {\n    this.adapter = definition.entityAdapter;\n    this.isChangeTracking = definition.noChangeTracking !== true;\n    this.selectId = definition.selectId;\n\n    this.guard = new EntityActionGuard(entityName, this.selectId);\n    this.toUpdate = toUpdateFactory(this.selectId);\n\n    this.entityChangeTracker =\n      entityChangeTracker ||\n      new EntityChangeTrackerBase<T>(this.adapter, this.selectId);\n  }\n\n  /** Cancel a persistence operation */\n  protected cancelPersist(\n    collection: EntityCollection<T>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  // #region query operations\n\n  protected queryAll(collection: EntityCollection<T>): EntityCollection<T> {\n    return this.setLoadingTrue(collection);\n  }\n\n  protected queryAllError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  /**\n   * Merges query results per the MergeStrategy\n   * Sets loading flag to false and loaded flag to true.\n   */\n  protected queryAllSuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ): EntityCollection<T> {\n    const data = this.extractData(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    return {\n      ...this.entityChangeTracker.mergeQueryResults(\n        data,\n        collection,\n        mergeStrategy\n      ),\n      loaded: true,\n      loading: false,\n    };\n  }\n\n  protected queryByKey(\n    collection: EntityCollection<T>,\n    action: EntityAction<number | string>\n  ): EntityCollection<T> {\n    return this.setLoadingTrue(collection);\n  }\n\n  protected queryByKeyError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  protected queryByKeySuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<T>\n  ): EntityCollection<T> {\n    const data = this.extractData(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection =\n      data == null\n        ? collection\n        : this.entityChangeTracker.mergeQueryResults(\n            [data],\n            collection,\n            mergeStrategy\n          );\n    return this.setLoadingFalse(collection);\n  }\n\n  protected queryLoad(collection: EntityCollection<T>): EntityCollection<T> {\n    return this.setLoadingTrue(collection);\n  }\n\n  protected queryLoadError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  /**\n   * Replaces all entities in the collection\n   * Sets loaded flag to true, loading flag to false,\n   * and clears changeState for the entire collection.\n   */\n  protected queryLoadSuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ): EntityCollection<T> {\n    const data = this.extractData(action);\n    return {\n      ...this.adapter.setAll(data, collection),\n      loading: false,\n      loaded: true,\n      changeState: {},\n    };\n  }\n\n  protected queryMany(\n    collection: EntityCollection<T>,\n    action: EntityAction\n  ): EntityCollection<T> {\n    return this.setLoadingTrue(collection);\n  }\n\n  protected queryManyError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  protected queryManySuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ): EntityCollection<T> {\n    const data = this.extractData(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    return {\n      ...this.entityChangeTracker.mergeQueryResults(\n        data,\n        collection,\n        mergeStrategy\n      ),\n      loaded: true,\n      loading: false,\n    };\n  }\n  // #endregion query operations\n\n  // #region save operations\n\n  // #region saveAddMany\n  /**\n   * Save multiple new entities.\n   * If saving pessimistically, delay adding to collection until server acknowledges success.\n   * If saving optimistically; add immediately.\n   * @param collection The collection to which the entities should be added.\n   * @param action The action payload holds options, including whether the save is optimistic,\n   * and the data, which must be an array of entities.\n   * If saving optimistically, the entities must have their keys.\n   */\n  protected saveAddMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ): EntityCollection<T> {\n    if (this.isOptimistic(action)) {\n      const entities = this.guard.mustBeEntities(action); // ensure the entity has a PK\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.trackAddMany(\n        entities,\n        collection,\n        mergeStrategy\n      );\n      collection = this.adapter.addMany(entities, collection);\n    }\n    return this.setLoadingTrue(collection);\n  }\n\n  /**\n   * Attempt to save new entities failed or timed-out.\n   * Action holds the error.\n   * If saved pessimistically, new entities are not in the collection and\n   * you may not have to compensate for the error.\n   * If saved optimistically, the unsaved entities are in the collection and\n   * you may need to compensate for the error.\n   */\n  protected saveAddManyError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n  // #endregion saveAddMany\n\n  // #region saveAddOne\n  /**\n   * Successfully saved new entities to the server.\n   * If saved pessimistically, add the entities from the server to the collection.\n   * If saved optimistically, the added entities are already in the collection.\n   * However, the server might have set or modified other fields (e.g, concurrency field),\n   * and may even return additional new entities.\n   * Therefore, upsert the entities in the collection with the returned values (if any)\n   * Caution: in a race, this update could overwrite unsaved user changes.\n   * Use pessimistic add to avoid this risk.\n   * Note: saveAddManySuccess differs from saveAddOneSuccess when optimistic.\n   * saveAddOneSuccess updates (not upserts) with the lone entity from the server.\n   * There is no effect if the entity is not already in cache.\n   * saveAddManySuccess will add an entity if it is not found in cache.\n   */\n  protected saveAddManySuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ) {\n    // For pessimistic save, ensure the server generated the primary key if the client didn't send one.\n    const entities = this.guard.mustBeEntities(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    if (this.isOptimistic(action)) {\n      collection = this.entityChangeTracker.mergeSaveUpserts(\n        entities,\n        collection,\n        mergeStrategy\n      );\n    } else {\n      collection = this.entityChangeTracker.mergeSaveAdds(\n        entities,\n        collection,\n        mergeStrategy\n      );\n    }\n    return this.setLoadingFalse(collection);\n  }\n  // #endregion saveAddMany\n\n  // #region saveAddOne\n  /**\n   * Save a new entity.\n   * If saving pessimistically, delay adding to collection until server acknowledges success.\n   * If saving optimistically; add entity immediately.\n   * @param collection The collection to which the entity should be added.\n   * @param action The action payload holds options, including whether the save is optimistic,\n   * and the data, which must be an entity.\n   * If saving optimistically, the entity must have a key.\n   */\n  protected saveAddOne(\n    collection: EntityCollection<T>,\n    action: EntityAction<T>\n  ): EntityCollection<T> {\n    if (this.isOptimistic(action)) {\n      const entity = this.guard.mustBeEntity(action); // ensure the entity has a PK\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.trackAddOne(\n        entity,\n        collection,\n        mergeStrategy\n      );\n      collection = this.adapter.addOne(entity, collection);\n    }\n    return this.setLoadingTrue(collection);\n  }\n\n  /**\n   * Attempt to save a new entity failed or timed-out.\n   * Action holds the error.\n   * If saved pessimistically, the entity is not in the collection and\n   * you may not have to compensate for the error.\n   * If saved optimistically, the unsaved entity is in the collection and\n   * you may need to compensate for the error.\n   */\n  protected saveAddOneError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  /**\n   * Successfully saved a new entity to the server.\n   * If saved pessimistically, add the entity from the server to the collection.\n   * If saved optimistically, the added entity is already in the collection.\n   * However, the server might have set or modified other fields (e.g, concurrency field)\n   * Therefore, update the entity in the collection with the returned value (if any)\n   * Caution: in a race, this update could overwrite unsaved user changes.\n   * Use pessimistic add to avoid this risk.\n   */\n  protected saveAddOneSuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<T>\n  ) {\n    // For pessimistic save, ensure the server generated the primary key if the client didn't send one.\n    const entity = this.guard.mustBeEntity(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    if (this.isOptimistic(action)) {\n      const update: UpdateResponseData<T> = this.toUpdate(entity);\n      // Always update the cache with added entity returned from server\n      collection = this.entityChangeTracker.mergeSaveUpdates(\n        [update],\n        collection,\n        mergeStrategy,\n        false /*never skip*/\n      );\n    } else {\n      collection = this.entityChangeTracker.mergeSaveAdds(\n        [entity],\n        collection,\n        mergeStrategy\n      );\n    }\n    return this.setLoadingFalse(collection);\n  }\n  // #endregion saveAddOne\n\n  // #region saveAddMany\n  // TODO MANY\n  // #endregion saveAddMany\n\n  // #region saveDeleteOne\n  /**\n   * Delete an entity from the server by key and remove it from the collection (if present).\n   * If the entity is an unsaved new entity, remove it from the collection immediately\n   * and skip the server delete request.\n   * An optimistic save removes an existing entity from the collection immediately;\n   * a pessimistic save removes it after the server confirms successful delete.\n   * @param collection Will remove the entity with this key from the collection.\n   * @param action The action payload holds options, including whether the save is optimistic,\n   * and the data, which must be a primary key or an entity with a key;\n   * this reducer extracts the key from the entity.\n   */\n  protected saveDeleteOne(\n    collection: EntityCollection<T>,\n    action: EntityAction<number | string | T>\n  ): EntityCollection<T> {\n    const toDelete = this.extractData(action);\n    const deleteId =\n      typeof toDelete === 'object'\n        ? this.selectId(toDelete)\n        : (toDelete as string | number);\n    const change = collection.changeState[deleteId];\n    // If entity is already tracked ...\n    if (change) {\n      if (change.changeType === ChangeType.Added) {\n        // Remove the added entity immediately and forget about its changes (via commit).\n        collection = this.adapter.removeOne(deleteId as string, collection);\n        collection = this.entityChangeTracker.commitOne(deleteId, collection);\n        // Should not waste effort trying to delete on the server because it can't be there.\n        action.payload.skip = true;\n      } else {\n        // Re-track it as a delete, even if tracking is turned off for this call.\n        collection = this.entityChangeTracker.trackDeleteOne(\n          deleteId,\n          collection\n        );\n      }\n    }\n\n    // If optimistic delete, track current state and remove immediately.\n    if (this.isOptimistic(action)) {\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.trackDeleteOne(\n        deleteId,\n        collection,\n        mergeStrategy\n      );\n      collection = this.adapter.removeOne(deleteId as string, collection);\n    }\n\n    return this.setLoadingTrue(collection);\n  }\n\n  /**\n   * Attempt to delete the entity on the server failed or timed-out.\n   * Action holds the error.\n   * If saved pessimistically, the entity could still be in the collection and\n   * you may not have to compensate for the error.\n   * If saved optimistically, the entity is not in the collection and\n   * you may need to compensate for the error.\n   */\n  protected saveDeleteOneError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  /**\n   * Successfully deleted entity on the server. The key of the deleted entity is in the action payload data.\n   * If saved pessimistically, if the entity is still in the collection it will be removed.\n   * If saved optimistically, the entity has already been removed from the collection.\n   */\n  protected saveDeleteOneSuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<number | string>\n  ): EntityCollection<T> {\n    const deleteId = this.extractData(action);\n    if (this.isOptimistic(action)) {\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.mergeSaveDeletes(\n        [deleteId],\n        collection,\n        mergeStrategy\n      );\n    } else {\n      // Pessimistic: ignore mergeStrategy. Remove entity from the collection and from change tracking.\n      collection = this.adapter.removeOne(deleteId as string, collection);\n      collection = this.entityChangeTracker.commitOne(deleteId, collection);\n    }\n    return this.setLoadingFalse(collection);\n  }\n  // #endregion saveDeleteOne\n\n  // #region saveDeleteMany\n  /**\n   * Delete multiple entities from the server by key and remove them from the collection (if present).\n   * Removes unsaved new entities from the collection immediately\n   * but the id is still sent to the server for deletion even though the server will not find that entity.\n   * Therefore, the server must be willing to ignore a delete request for an entity it cannot find.\n   * An optimistic save removes existing entities from the collection immediately;\n   * a pessimistic save removes them after the server confirms successful delete.\n   * @param collection Removes entities from this collection.\n   * @param action The action payload holds options, including whether the save is optimistic,\n   * and the data, which must be an array of primary keys or entities with a key;\n   * this reducer extracts the key from the entity.\n   */\n  protected saveDeleteMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<(number | string | T)[]>\n  ): EntityCollection<T> {\n    const deleteIds = this.extractData(action).map((d) =>\n      typeof d === 'object' ? this.selectId(d) : (d as string | number)\n    );\n    deleteIds.forEach((deleteId) => {\n      const change = collection.changeState[deleteId];\n      // If entity is already tracked ...\n      if (change) {\n        if (change.changeType === ChangeType.Added) {\n          // Remove the added entity immediately and forget about its changes (via commit).\n          collection = this.adapter.removeOne(deleteId as string, collection);\n          collection = this.entityChangeTracker.commitOne(deleteId, collection);\n          // Should not waste effort trying to delete on the server because it can't be there.\n          action.payload.skip = true;\n        } else {\n          // Re-track it as a delete, even if tracking is turned off for this call.\n          collection = this.entityChangeTracker.trackDeleteOne(\n            deleteId,\n            collection\n          );\n        }\n      }\n    });\n    // If optimistic delete, track current state and remove immediately.\n    if (this.isOptimistic(action)) {\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.trackDeleteMany(\n        deleteIds,\n        collection,\n        mergeStrategy\n      );\n      collection = this.adapter.removeMany(deleteIds as string[], collection);\n    }\n    return this.setLoadingTrue(collection);\n  }\n\n  /**\n   * Attempt to delete the entities on the server failed or timed-out.\n   * Action holds the error.\n   * If saved pessimistically, the entities could still be in the collection and\n   * you may not have to compensate for the error.\n   * If saved optimistically, the entities are not in the collection and\n   * you may need to compensate for the error.\n   */\n  protected saveDeleteManyError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  /**\n   * Successfully deleted entities on the server. The keys of the deleted entities are in the action payload data.\n   * If saved pessimistically, entities that are still in the collection will be removed.\n   * If saved optimistically, the entities have already been removed from the collection.\n   */\n  protected saveDeleteManySuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<(number | string)[]>\n  ): EntityCollection<T> {\n    const deleteIds = this.extractData(action);\n    if (this.isOptimistic(action)) {\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.mergeSaveDeletes(\n        deleteIds,\n        collection,\n        mergeStrategy\n      );\n    } else {\n      // Pessimistic: ignore mergeStrategy. Remove entity from the collection and from change tracking.\n      collection = this.adapter.removeMany(deleteIds as string[], collection);\n      collection = this.entityChangeTracker.commitMany(deleteIds, collection);\n    }\n    return this.setLoadingFalse(collection);\n  }\n  // #endregion saveDeleteMany\n\n  // #region saveUpdateOne\n  /**\n   * Save an update to an existing entity.\n   * If saving pessimistically, update the entity in the collection after the server confirms success.\n   * If saving optimistically, update the entity immediately, before the save request.\n   * @param collection The collection to update\n   * @param action The action payload holds options, including if the save is optimistic,\n   * and the data which, must be an {Update<T>}\n   */\n  protected saveUpdateOne(\n    collection: EntityCollection<T>,\n    action: EntityAction<Update<T>>\n  ): EntityCollection<T> {\n    const update = this.guard.mustBeUpdate(action);\n    if (this.isOptimistic(action)) {\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.trackUpdateOne(\n        update,\n        collection,\n        mergeStrategy\n      );\n      collection = this.adapter.updateOne(update, collection);\n    }\n    return this.setLoadingTrue(collection);\n  }\n\n  /**\n   * Attempt to update the entity on the server failed or timed-out.\n   * Action holds the error.\n   * If saved pessimistically, the entity in the collection is in the pre-save state\n   * you may not have to compensate for the error.\n   * If saved optimistically, the entity in the collection was updated\n   * and you may need to compensate for the error.\n   */\n  protected saveUpdateOneError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  /**\n   * Successfully saved the updated entity to the server.\n   * If saved pessimistically, update the entity in the collection with data from the server.\n   * If saved optimistically, the entity was already updated in the collection.\n   * However, the server might have set or modified other fields (e.g, concurrency field)\n   * Therefore, update the entity in the collection with the returned value (if any)\n   * Caution: in a race, this update could overwrite unsaved user changes.\n   * Use pessimistic update to avoid this risk.\n   * @param collection The collection to update\n   * @param action The action payload holds options, including if the save is optimistic, and\n   * the update data which, must be an UpdateResponse<T> that corresponds to the Update sent to the server.\n   * You must include an UpdateResponse even if the save was optimistic,\n   * to ensure that the change tracking is properly reset.\n   */\n  protected saveUpdateOneSuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<UpdateResponseData<T>>\n  ): EntityCollection<T> {\n    const update = this.guard.mustBeUpdateResponse(action);\n    const isOptimistic = this.isOptimistic(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.mergeSaveUpdates(\n      [update],\n      collection,\n      mergeStrategy,\n      isOptimistic /*skip unchanged if optimistic */\n    );\n    return this.setLoadingFalse(collection);\n  }\n  // #endregion saveUpdateOne\n\n  // #region saveUpdateMany\n  /**\n   * Save updated entities.\n   * If saving pessimistically, update the entities in the collection after the server confirms success.\n   * If saving optimistically, update the entities immediately, before the save request.\n   * @param collection The collection to update\n   * @param action The action payload holds options, including if the save is optimistic,\n   * and the data which, must be an array of {Update<T>}.\n   */\n  protected saveUpdateMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<Update<T>[]>\n  ): EntityCollection<T> {\n    const updates = this.guard.mustBeUpdates(action);\n    if (this.isOptimistic(action)) {\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.trackUpdateMany(\n        updates,\n        collection,\n        mergeStrategy\n      );\n      collection = this.adapter.updateMany(updates, collection);\n    }\n    return this.setLoadingTrue(collection);\n  }\n\n  /**\n   * Attempt to update entities on the server failed or timed-out.\n   * Action holds the error.\n   * If saved pessimistically, the entities in the collection are in the pre-save state\n   * you may not have to compensate for the error.\n   * If saved optimistically, the entities in the collection were updated\n   * and you may need to compensate for the error.\n   */\n  protected saveUpdateManyError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  /**\n   * Successfully saved the updated entities to the server.\n   * If saved pessimistically, the entities in the collection will be updated with data from the server.\n   * If saved optimistically, the entities in the collection were already updated.\n   * However, the server might have set or modified other fields (e.g, concurrency field)\n   * Therefore, update the entity in the collection with the returned values (if any)\n   * Caution: in a race, this update could overwrite unsaved user changes.\n   * Use pessimistic update to avoid this risk.\n   * @param collection The collection to update\n   * @param action The action payload holds options, including if the save is optimistic,\n   * and the data which, must be an array of UpdateResponse<T>.\n   * You must include an UpdateResponse for every Update sent to the server,\n   * even if the save was optimistic, to ensure that the change tracking is properly reset.\n   */\n  protected saveUpdateManySuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<UpdateResponseData<T>[]>\n  ): EntityCollection<T> {\n    const updates = this.guard.mustBeUpdateResponses(action);\n    const isOptimistic = this.isOptimistic(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.mergeSaveUpdates(\n      updates,\n      collection,\n      mergeStrategy,\n      false /* never skip */\n    );\n    return this.setLoadingFalse(collection);\n  }\n  // #endregion saveUpdateMany\n\n  // #region saveUpsertOne\n  /**\n   * Save a new or existing entity.\n   * If saving pessimistically, delay adding to collection until server acknowledges success.\n   * If saving optimistically; add immediately.\n   * @param collection The collection to which the entity should be upserted.\n   * @param action The action payload holds options, including whether the save is optimistic,\n   * and the data, which must be a whole entity.\n   * If saving optimistically, the entity must have its key.\n   */\n  protected saveUpsertOne(\n    collection: EntityCollection<T>,\n    action: EntityAction<T>\n  ): EntityCollection<T> {\n    if (this.isOptimistic(action)) {\n      const entity = this.guard.mustBeEntity(action); // ensure the entity has a PK\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.trackUpsertOne(\n        entity,\n        collection,\n        mergeStrategy\n      );\n      collection = this.adapter.upsertOne(entity, collection);\n    }\n    return this.setLoadingTrue(collection);\n  }\n\n  /**\n   * Attempt to save new or existing entity failed or timed-out.\n   * Action holds the error.\n   * If saved pessimistically, new or updated entity is not in the collection and\n   * you may not have to compensate for the error.\n   * If saved optimistically, the unsaved entities are in the collection and\n   * you may need to compensate for the error.\n   */\n  protected saveUpsertOneError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  /**\n   * Successfully saved new or existing entities to the server.\n   * If saved pessimistically, add the entities from the server to the collection.\n   * If saved optimistically, the added entities are already in the collection.\n   * However, the server might have set or modified other fields (e.g, concurrency field)\n   * Therefore, update the entities in the collection with the returned values (if any)\n   * Caution: in a race, this update could overwrite unsaved user changes.\n   * Use pessimistic add to avoid this risk.\n   */\n  protected saveUpsertOneSuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<T>\n  ) {\n    // For pessimistic save, ensure the server generated the primary key if the client didn't send one.\n    const entity = this.guard.mustBeEntity(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    // Always update the cache with upserted entities returned from server\n    collection = this.entityChangeTracker.mergeSaveUpserts(\n      [entity],\n      collection,\n      mergeStrategy\n    );\n    return this.setLoadingFalse(collection);\n  }\n  // #endregion saveUpsertOne\n\n  // #region saveUpsertMany\n  /**\n   * Save multiple new or existing entities.\n   * If saving pessimistically, delay adding to collection until server acknowledges success.\n   * If saving optimistically; add immediately.\n   * @param collection The collection to which the entities should be upserted.\n   * @param action The action payload holds options, including whether the save is optimistic,\n   * and the data, which must be an array of whole entities.\n   * If saving optimistically, the entities must have their keys.\n   */\n  protected saveUpsertMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ): EntityCollection<T> {\n    if (this.isOptimistic(action)) {\n      const entities = this.guard.mustBeEntities(action); // ensure the entity has a PK\n      const mergeStrategy = this.extractMergeStrategy(action);\n      collection = this.entityChangeTracker.trackUpsertMany(\n        entities,\n        collection,\n        mergeStrategy\n      );\n      collection = this.adapter.upsertMany(entities, collection);\n    }\n    return this.setLoadingTrue(collection);\n  }\n\n  /**\n   * Attempt to save new or existing entities failed or timed-out.\n   * Action holds the error.\n   * If saved pessimistically, new entities are not in the collection and\n   * you may not have to compensate for the error.\n   * If saved optimistically, the unsaved entities are in the collection and\n   * you may need to compensate for the error.\n   */\n  protected saveUpsertManyError(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityActionDataServiceError>\n  ): EntityCollection<T> {\n    return this.setLoadingFalse(collection);\n  }\n\n  /**\n   * Successfully saved new or existing entities to the server.\n   * If saved pessimistically, add the entities from the server to the collection.\n   * If saved optimistically, the added entities are already in the collection.\n   * However, the server might have set or modified other fields (e.g, concurrency field)\n   * Therefore, update the entities in the collection with the returned values (if any)\n   * Caution: in a race, this update could overwrite unsaved user changes.\n   * Use pessimistic add to avoid this risk.\n   */\n  protected saveUpsertManySuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ) {\n    // For pessimistic save, ensure the server generated the primary key if the client didn't send one.\n    const entities = this.guard.mustBeEntities(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    // Always update the cache with upserted entities returned from server\n    collection = this.entityChangeTracker.mergeSaveUpserts(\n      entities,\n      collection,\n      mergeStrategy\n    );\n    return this.setLoadingFalse(collection);\n  }\n  // #endregion saveUpsertMany\n\n  // #endregion save operations\n\n  // #region cache-only operations\n\n  /**\n   * Replaces all entities in the collection\n   * Sets loaded flag to true.\n   * Merges query results, preserving unsaved changes\n   */\n  protected addAll(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ): EntityCollection<T> {\n    const entities = this.guard.mustBeEntities(action);\n    return {\n      ...this.adapter.setAll(entities, collection),\n      loading: false,\n      loaded: true,\n      changeState: {},\n    };\n  }\n\n  protected addMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ): EntityCollection<T> {\n    const entities = this.guard.mustBeEntities(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.trackAddMany(\n      entities,\n      collection,\n      mergeStrategy\n    );\n    return this.adapter.addMany(entities, collection);\n  }\n\n  protected addOne(\n    collection: EntityCollection<T>,\n    action: EntityAction<T>\n  ): EntityCollection<T> {\n    const entity = this.guard.mustBeEntity(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.trackAddOne(\n      entity,\n      collection,\n      mergeStrategy\n    );\n    return this.adapter.addOne(entity, collection);\n  }\n\n  protected removeMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<number[] | string[]>\n  ): EntityCollection<T> {\n    // payload must be entity keys\n    const keys = this.guard.mustBeKeys(action) as string[];\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.trackDeleteMany(\n      keys,\n      collection,\n      mergeStrategy\n    );\n    return this.adapter.removeMany(keys, collection);\n  }\n\n  protected removeOne(\n    collection: EntityCollection<T>,\n    action: EntityAction<number | string>\n  ): EntityCollection<T> {\n    // payload must be entity key\n    const key = this.guard.mustBeKey(action) as string;\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.trackDeleteOne(\n      key,\n      collection,\n      mergeStrategy\n    );\n    return this.adapter.removeOne(key, collection);\n  }\n\n  protected removeAll(\n    collection: EntityCollection<T>,\n    action: EntityAction<T>\n  ): EntityCollection<T> {\n    return {\n      ...this.adapter.removeAll(collection),\n      loaded: false, // Only REMOVE_ALL sets loaded to false\n      loading: false,\n      changeState: {}, // Assume clearing the collection and not trying to delete all entities\n    };\n  }\n\n  protected updateMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<Update<T>[]>\n  ): EntityCollection<T> {\n    // payload must be an array of `Updates<T>`, not entities\n    const updates = this.guard.mustBeUpdates(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.trackUpdateMany(\n      updates,\n      collection,\n      mergeStrategy\n    );\n    return this.adapter.updateMany(updates, collection);\n  }\n\n  protected updateOne(\n    collection: EntityCollection<T>,\n    action: EntityAction<Update<T>>\n  ): EntityCollection<T> {\n    // payload must be an `Update<T>`, not an entity\n    const update = this.guard.mustBeUpdate(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.trackUpdateOne(\n      update,\n      collection,\n      mergeStrategy\n    );\n    return this.adapter.updateOne(update, collection);\n  }\n\n  protected upsertMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ): EntityCollection<T> {\n    // <v6: payload must be an array of `Updates<T>`, not entities\n    // v6+: payload must be an array of T\n    const entities = this.guard.mustBeEntities(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.trackUpsertMany(\n      entities,\n      collection,\n      mergeStrategy\n    );\n    return this.adapter.upsertMany(entities, collection);\n  }\n\n  protected upsertOne(\n    collection: EntityCollection<T>,\n    action: EntityAction<T>\n  ): EntityCollection<T> {\n    // <v6: payload must be an `Update<T>`, not an entity\n    // v6+: payload must be a T\n    const entity = this.guard.mustBeEntity(action);\n    const mergeStrategy = this.extractMergeStrategy(action);\n    collection = this.entityChangeTracker.trackUpsertOne(\n      entity,\n      collection,\n      mergeStrategy\n    );\n    return this.adapter.upsertOne(entity, collection);\n  }\n\n  protected commitAll(collection: EntityCollection<T>) {\n    return this.entityChangeTracker.commitAll(collection);\n  }\n\n  protected commitMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ) {\n    return this.entityChangeTracker.commitMany(\n      this.extractData(action),\n      collection\n    );\n  }\n\n  protected commitOne(\n    collection: EntityCollection<T>,\n    action: EntityAction<T>\n  ) {\n    return this.entityChangeTracker.commitOne(\n      this.extractData(action),\n      collection\n    );\n  }\n\n  protected undoAll(collection: EntityCollection<T>) {\n    return this.entityChangeTracker.undoAll(collection);\n  }\n\n  protected undoMany(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ) {\n    return this.entityChangeTracker.undoMany(\n      this.extractData(action),\n      collection\n    );\n  }\n\n  protected undoOne(collection: EntityCollection<T>, action: EntityAction<T>) {\n    return this.entityChangeTracker.undoOne(\n      this.extractData(action),\n      collection\n    );\n  }\n\n  /** Dangerous: Completely replace the collection's ChangeState. Use rarely and wisely. */\n  protected setChangeState(\n    collection: EntityCollection<T>,\n    action: EntityAction<ChangeStateMap<T>>\n  ) {\n    const changeState = this.extractData(action);\n    return collection.changeState === changeState\n      ? collection\n      : { ...collection, changeState };\n  }\n\n  /**\n   * Dangerous: Completely replace the collection.\n   * Primarily for testing and rehydration from local storage.\n   * Use rarely and wisely.\n   */\n  protected setCollection(\n    collection: EntityCollection<T>,\n    action: EntityAction<EntityCollection<T>>\n  ) {\n    const newCollection = this.extractData(action);\n    return collection === newCollection ? collection : newCollection;\n  }\n\n  protected setFilter(\n    collection: EntityCollection<T>,\n    action: EntityAction<any>\n  ): EntityCollection<T> {\n    const filter = this.extractData(action);\n    return collection.filter === filter\n      ? collection\n      : { ...collection, filter };\n  }\n\n  protected setLoaded(\n    collection: EntityCollection<T>,\n    action: EntityAction<boolean>\n  ): EntityCollection<T> {\n    const loaded = this.extractData(action) === true || false;\n    return collection.loaded === loaded\n      ? collection\n      : { ...collection, loaded };\n  }\n\n  protected setLoading(\n    collection: EntityCollection<T>,\n    action: EntityAction<boolean>\n  ): EntityCollection<T> {\n    return this.setLoadingFlag(collection, this.extractData(action));\n  }\n\n  protected setLoadingFalse(\n    collection: EntityCollection<T>\n  ): EntityCollection<T> {\n    return this.setLoadingFlag(collection, false);\n  }\n\n  protected setLoadingTrue(\n    collection: EntityCollection<T>\n  ): EntityCollection<T> {\n    return this.setLoadingFlag(collection, true);\n  }\n\n  /** Set the collection's loading flag */\n  protected setLoadingFlag(collection: EntityCollection<T>, loading: boolean) {\n    loading = loading === true ? true : false;\n    return collection.loading === loading\n      ? collection\n      : { ...collection, loading };\n  }\n  // #endregion Cache-only operations\n\n  // #region helpers\n  /** Safely extract data from the EntityAction payload */\n  protected extractData<D = any>(action: EntityAction<D>): D {\n    return (action.payload && action.payload.data) as D;\n  }\n\n  /** Safely extract MergeStrategy from EntityAction. Set to IgnoreChanges if collection itself is not tracked. */\n  protected extractMergeStrategy(action: EntityAction) {\n    // If not tracking this collection, always ignore changes\n    return this.isChangeTracking\n      ? action.payload && action.payload.mergeStrategy\n      : MergeStrategy.IgnoreChanges;\n  }\n\n  protected isOptimistic(action: EntityAction) {\n    return action.payload && action.payload.isOptimistic === true;\n  }\n\n  // #endregion helpers\n}\n\n/**\n * Creates {EntityCollectionReducerMethods} for a given entity type.\n */\n@Injectable()\nexport class EntityCollectionReducerMethodsFactory {\n  constructor(private entityDefinitionService: EntityDefinitionService) {}\n\n  /** Create the  {EntityCollectionReducerMethods} for the named entity type */\n  create<T>(entityName: string): EntityCollectionReducerMethodMap<T> {\n    const definition =\n      this.entityDefinitionService.getDefinition<T>(entityName);\n    const methodsClass = new EntityCollectionReducerMethods(\n      entityName,\n      definition\n    );\n\n    return methodsClass.methods;\n  }\n}\n"
  },
  {
    "path": "modules/data/src/reducers/entity-collection-reducer-registry.ts",
    "content": "import { Inject, Injectable, Optional } from '@angular/core';\nimport { compose, MetaReducer } from '@ngrx/store';\n\nimport { EntityAction } from '../actions/entity-action';\nimport { EntityCollection } from './entity-collection';\nimport { ENTITY_COLLECTION_META_REDUCERS } from './constants';\nimport {\n  EntityCollectionReducer,\n  EntityCollectionReducerFactory,\n} from './entity-collection-reducer';\n\n/** A hash of EntityCollectionReducers */\nexport interface EntityCollectionReducers {\n  [entity: string]: EntityCollectionReducer<any>;\n}\n\n/**\n * Registry of entity types and their previously-constructed reducers.\n * Can create a new CollectionReducer, which it registers for subsequent use.\n */\n@Injectable()\nexport class EntityCollectionReducerRegistry {\n  protected entityCollectionReducers: EntityCollectionReducers = {};\n  private entityCollectionMetaReducer: MetaReducer<\n    EntityCollection,\n    EntityAction\n  >;\n\n  constructor(\n    private entityCollectionReducerFactory: EntityCollectionReducerFactory,\n    @Optional()\n    @Inject(ENTITY_COLLECTION_META_REDUCERS)\n    entityCollectionMetaReducers?: MetaReducer<EntityCollection, EntityAction>[]\n  ) {\n    // eslint-disable-next-line prefer-spread\n    this.entityCollectionMetaReducer = compose.apply(\n      null,\n      entityCollectionMetaReducers || []\n    ) as any;\n  }\n\n  /**\n   * Get the registered EntityCollectionReducer<T> for this entity type or create one and register it.\n   * @param entityName Name of the entity type for this reducer\n   */\n  getOrCreateReducer<T>(entityName: string): EntityCollectionReducer<T> {\n    let reducer: EntityCollectionReducer<T> =\n      this.entityCollectionReducers[entityName];\n\n    if (!reducer) {\n      reducer = this.entityCollectionReducerFactory.create<T>(entityName);\n      reducer = this.registerReducer<T>(entityName, reducer);\n      this.entityCollectionReducers[entityName] = reducer;\n    }\n    return reducer;\n  }\n\n  /**\n   * Register an EntityCollectionReducer for an entity type\n   * @param entityName - the name of the entity type\n   * @param reducer - reducer for that entity type\n   *\n   * Examples:\n   *   registerReducer('Hero', myHeroReducer);\n   *   registerReducer('Villain', myVillainReducer);\n   */\n  registerReducer<T>(\n    entityName: string,\n    reducer: EntityCollectionReducer<T>\n  ): EntityCollectionReducer<T> {\n    reducer = this.entityCollectionMetaReducer(reducer as any);\n    return (this.entityCollectionReducers[entityName.trim()] = reducer);\n  }\n\n  /**\n   * Register a batch of EntityCollectionReducers.\n   * @param reducers - reducers to merge into existing reducers\n   *\n   * Examples:\n   *   registerReducers({\n   *     Hero: myHeroReducer,\n   *     Villain: myVillainReducer\n   *   });\n   */\n  registerReducers(reducers: EntityCollectionReducers) {\n    const keys = reducers ? Object.keys(reducers) : [];\n    keys.forEach((key) => this.registerReducer(key, reducers[key]));\n  }\n}\n"
  },
  {
    "path": "modules/data/src/reducers/entity-collection-reducer.ts",
    "content": "import { Injectable } from '@angular/core';\n\nimport { EntityAction } from '../actions/entity-action';\nimport { EntityCollection } from './entity-collection';\nimport { EntityCollectionReducerMethodsFactory } from './entity-collection-reducer-methods';\n\nexport type EntityCollectionReducer<T = any> = (\n  collection: EntityCollection<T>,\n  action: EntityAction\n) => EntityCollection<T>;\n\n/** Create a default reducer for a specific entity collection */\n@Injectable()\nexport class EntityCollectionReducerFactory {\n  constructor(private methodsFactory: EntityCollectionReducerMethodsFactory) {}\n\n  /** Create a default reducer for a collection of entities of T */\n  create<T = any>(entityName: string): EntityCollectionReducer<T> {\n    const methods = this.methodsFactory.create<T>(entityName);\n\n    /** Perform Actions against a particular entity collection in the EntityCache */\n    return function entityCollectionReducer(\n      collection: EntityCollection<T>,\n      action: EntityAction\n    ): EntityCollection<T> {\n      const reducerMethod = methods[action.payload.entityOp];\n      return reducerMethod ? reducerMethod(collection, action) : collection;\n    };\n  }\n}\n"
  },
  {
    "path": "modules/data/src/reducers/entity-collection.ts",
    "content": "import { EntityState, Dictionary } from '@ngrx/entity';\n\n/** Types of change in a ChangeState instance */\nexport enum ChangeType {\n  /** The entity has not changed from its last known server state. */\n  Unchanged = 0,\n  /** The entity was added to the collection */\n  Added,\n  /** The entity is scheduled for delete and was removed from the collection */\n  Deleted,\n  /** The entity in the collection was updated */\n  Updated,\n}\n\n/**\n * Change state for an entity with unsaved changes;\n * an entry in an EntityCollection.changeState map\n */\nexport interface ChangeState<T> {\n  changeType: ChangeType;\n  originalValue?: T | undefined;\n}\n\n/**\n * Map of entity primary keys to entity ChangeStates.\n * Each entry represents an entity with unsaved changes.\n */\nexport type ChangeStateMap<T> = Dictionary<ChangeState<T>>;\n\n/**\n * Data and information about a collection of entities of a single type.\n * EntityCollections are maintained in the EntityCache within the ngrx store.\n */\nexport interface EntityCollection<T = any> extends EntityState<T> {\n  /** Name of the entity type for this collection */\n  entityName: string;\n  /** A map of ChangeStates, keyed by id, for entities with unsaved changes */\n  changeState: ChangeStateMap<T>;\n  /** The user's current collection filter pattern */\n  filter?: string;\n  /** true if collection was ever filled by QueryAll; forced false if cleared */\n  loaded: boolean;\n  /** true when a query or save operation is in progress */\n  loading: boolean;\n}\n"
  },
  {
    "path": "modules/data/src/selectors/entity-cache-selector.ts",
    "content": "import { InjectionToken, Optional, FactoryProvider } from '@angular/core';\nimport { createFeatureSelector, MemoizedSelector } from '@ngrx/store';\nimport { EntityCache } from '../reducers/entity-cache';\nimport {\n  ENTITY_CACHE_NAME,\n  ENTITY_CACHE_NAME_TOKEN,\n} from '../reducers/constants';\n\nexport const ENTITY_CACHE_SELECTOR_TOKEN = new InjectionToken<\n  MemoizedSelector<Object, EntityCache>\n>('@ngrx/data Entity Cache Selector');\n\nexport const entityCacheSelectorProvider: FactoryProvider = {\n  provide: ENTITY_CACHE_SELECTOR_TOKEN,\n  useFactory: createEntityCacheSelector,\n  deps: [[new Optional(), ENTITY_CACHE_NAME_TOKEN]],\n};\n\nexport type EntityCacheSelector = MemoizedSelector<Object, EntityCache>;\n\nexport function createEntityCacheSelector(\n  entityCacheName?: string\n): MemoizedSelector<Object, EntityCache> {\n  entityCacheName = entityCacheName || ENTITY_CACHE_NAME;\n  return createFeatureSelector<EntityCache>(entityCacheName);\n}\n"
  },
  {
    "path": "modules/data/src/selectors/entity-selectors$.ts",
    "content": "import { Inject, Injectable } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { Actions } from '@ngrx/effects';\nimport { Dictionary } from '@ngrx/entity';\n\nimport { Observable } from 'rxjs';\nimport { filter, shareReplay } from 'rxjs/operators';\n\nimport { EntityAction } from '../actions/entity-action';\nimport { OP_ERROR } from '../actions/entity-op';\nimport { ofEntityType } from '../actions/entity-action-operators';\nimport {\n  ENTITY_CACHE_SELECTOR_TOKEN,\n  EntityCacheSelector,\n} from './entity-cache-selector';\nimport { EntitySelectors } from './entity-selectors';\nimport { EntityCache } from '../reducers/entity-cache';\nimport {\n  EntityCollection,\n  ChangeStateMap,\n} from '../reducers/entity-collection';\n\n/**\n * The selector observable functions for entity collection members.\n */\nexport interface EntitySelectors$<T> {\n  /** Name of the entity collection for these selectors$ */\n  readonly entityName: string;\n\n  /** Names from custom selectors from additionalCollectionState fits here, 'any' to avoid conflict with entityName */\n  readonly [name: string]: Observable<any> | Store<any> | any;\n\n  /** Observable of the collection as a whole */\n  readonly collection$: Observable<EntityCollection> | Store<EntityCollection>;\n\n  /** Observable of count of entities in the cached collection. */\n  readonly count$: Observable<number> | Store<number>;\n\n  /** Observable of all entities in the cached collection. */\n  readonly entities$: Observable<T[]> | Store<T[]>;\n\n  /** Observable of actions related to this entity type. */\n  readonly entityActions$: Observable<EntityAction>;\n\n  /** Observable of the map of entity keys to entities */\n  readonly entityMap$: Observable<Dictionary<T>> | Store<Dictionary<T>>;\n\n  /** Observable of error actions related to this entity type. */\n  readonly errors$: Observable<EntityAction>;\n\n  /** Observable of the filter pattern applied by the entity collection's filter function */\n  readonly filter$: Observable<string> | Store<string>;\n\n  /** Observable of entities in the cached collection that pass the filter function */\n  readonly filteredEntities$: Observable<T[]> | Store<T[]>;\n\n  /** Observable of the keys of the cached collection, in the collection's native sort order */\n  readonly keys$: Observable<string[] | number[]> | Store<string[] | number[]>;\n\n  /** Observable true when the collection has been loaded */\n  readonly loaded$: Observable<boolean> | Store<boolean>;\n\n  /** Observable true when a multi-entity query command is in progress. */\n  readonly loading$: Observable<boolean> | Store<boolean>;\n\n  /** ChangeState (including original values) of entities with unsaved changes */\n  readonly changeState$:\n    | Observable<ChangeStateMap<T>>\n    | Store<ChangeStateMap<T>>;\n}\n\n/** Creates observable EntitySelectors$ for entity collections. */\n@Injectable()\nexport class EntitySelectors$Factory {\n  /** Observable of the EntityCache */\n  entityCache$: Observable<EntityCache>;\n\n  /** Observable of error EntityActions (e.g. QUERY_ALL_ERROR) for all entity types */\n  entityActionErrors$: Observable<EntityAction>;\n\n  constructor(\n    private store: Store<any>,\n    private actions: Actions<EntityAction>,\n    @Inject(ENTITY_CACHE_SELECTOR_TOKEN)\n    private selectEntityCache: EntityCacheSelector\n  ) {\n    // This service applies to the cache in ngrx/store named `cacheName`\n    this.entityCache$ = this.store.select(this.selectEntityCache);\n    this.entityActionErrors$ = actions.pipe(\n      filter(\n        (ea: EntityAction) =>\n          ea.payload &&\n          ea.payload.entityOp &&\n          ea.payload.entityOp.endsWith(OP_ERROR)\n      ),\n      shareReplay(1)\n    );\n  }\n\n  /**\n   * Creates an entity collection's selectors$ observables for this factory's store.\n   * `selectors$` are observable selectors of the cached entity collection.\n   * @param entityName - is also the name of the collection.\n   * @param selectors - selector functions for this collection.\n   **/\n  create<T, S$ extends EntitySelectors$<T> = EntitySelectors$<T>>(\n    entityName: string,\n    selectors: EntitySelectors<T>\n  ): S$ {\n    const selectors$: { [prop: string]: any } = {\n      entityName,\n    };\n\n    Object.keys(selectors).forEach((name) => {\n      if (name.startsWith('select')) {\n        // strip 'select' prefix from the selector fn name and append `$`\n        // Ex: 'selectEntities' => 'entities$'\n        const name$ = name[6].toLowerCase() + name.substring(7) + '$';\n        selectors$[name$] = this.store.select((<any>selectors)[name]);\n      }\n    });\n    selectors$['entityActions$'] = this.actions.pipe(ofEntityType(entityName));\n    selectors$['errors$'] = this.entityActionErrors$.pipe(\n      ofEntityType(entityName)\n    );\n    return selectors$ as S$;\n  }\n}\n"
  },
  {
    "path": "modules/data/src/selectors/entity-selectors.ts",
    "content": "import { Inject, Injectable, Optional } from '@angular/core';\n\n// Prod build requires `MemoizedSelector even though not used.\nimport { MemoizedSelector } from '@ngrx/store';\nimport { createSelector, Selector } from '@ngrx/store';\nimport { Dictionary } from '@ngrx/entity';\n\nimport { EntityCache } from '../reducers/entity-cache';\nimport {\n  ENTITY_CACHE_SELECTOR_TOKEN,\n  EntityCacheSelector,\n  createEntityCacheSelector,\n} from './entity-cache-selector';\nimport { ENTITY_CACHE_NAME } from '../reducers/constants';\nimport {\n  EntityCollection,\n  ChangeStateMap,\n} from '../reducers/entity-collection';\nimport { EntityCollectionCreator } from '../reducers/entity-collection-creator';\nimport { EntityMetadata } from '../entity-metadata/entity-metadata';\n\n/**\n * The selector functions for entity collection members,\n * Selects from the entity collection to the collection member\n * Contrast with {EntitySelectors}.\n */\nexport interface CollectionSelectors<T> {\n  readonly [selector: string]: any;\n\n  /** Count of entities in the cached collection. */\n  readonly selectCount: Selector<EntityCollection<T>, number>;\n\n  /** All entities in the cached collection. */\n  readonly selectEntities: Selector<EntityCollection<T>, T[]>;\n\n  /** Map of entity keys to entities */\n  readonly selectEntityMap: Selector<EntityCollection<T>, Dictionary<T>>;\n\n  /** Filter pattern applied by the entity collection's filter function */\n  readonly selectFilter: Selector<EntityCollection<T>, string>;\n\n  /** Entities in the cached collection that pass the filter function */\n  readonly selectFilteredEntities: Selector<EntityCollection<T>, T[]>;\n\n  /** Keys of the cached collection, in the collection's native sort order */\n  readonly selectKeys: Selector<EntityCollection<T>, string[] | number[]>;\n\n  /** True when the collection has been fully loaded. */\n  readonly selectLoaded: Selector<EntityCollection<T>, boolean>;\n\n  /** True when a multi-entity query command is in progress. */\n  readonly selectLoading: Selector<EntityCollection<T>, boolean>;\n\n  /** ChangeState (including original values) of entities with unsaved changes */\n  readonly selectChangeState: Selector<EntityCollection<T>, ChangeStateMap<T>>;\n}\n\n/**\n * The selector functions for entity collection members,\n * Selects from store root, through EntityCache, to the entity collection member\n * Contrast with {CollectionSelectors}.\n */\nexport interface EntitySelectors<T> {\n  /** Name of the entity collection for these selectors */\n  readonly entityName: string;\n\n  readonly [name: string]: MemoizedSelector<EntityCollection<T>, any> | string;\n\n  /** The cached EntityCollection itself */\n  readonly selectCollection: MemoizedSelector<Object, EntityCollection<T>>;\n\n  /** Count of entities in the cached collection. */\n  readonly selectCount: MemoizedSelector<Object, number>;\n\n  /** All entities in the cached collection. */\n  readonly selectEntities: MemoizedSelector<Object, T[]>;\n\n  /** The EntityCache */\n  readonly selectEntityCache: MemoizedSelector<Object, EntityCache>;\n\n  /** Map of entity keys to entities */\n  readonly selectEntityMap: MemoizedSelector<Object, Dictionary<T>>;\n\n  /** Filter pattern applied by the entity collection's filter function */\n  readonly selectFilter: MemoizedSelector<Object, string>;\n\n  /** Entities in the cached collection that pass the filter function */\n  readonly selectFilteredEntities: MemoizedSelector<Object, T[]>;\n\n  /** Keys of the cached collection, in the collection's native sort order */\n  readonly selectKeys: MemoizedSelector<Object, string[] | number[]>;\n\n  /** True when the collection has been fully loaded. */\n  readonly selectLoaded: MemoizedSelector<Object, boolean>;\n\n  /** True when a multi-entity query command is in progress. */\n  readonly selectLoading: MemoizedSelector<Object, boolean>;\n\n  /** ChangeState (including original values) of entities with unsaved changes */\n  readonly selectChangeState: MemoizedSelector<Object, ChangeStateMap<T>>;\n}\n\n/** Creates EntitySelector functions for entity collections. */\n@Injectable()\nexport class EntitySelectorsFactory {\n  private entityCollectionCreator: EntityCollectionCreator;\n  private selectEntityCache: EntityCacheSelector;\n\n  constructor(\n    @Optional() entityCollectionCreator?: EntityCollectionCreator,\n    @Optional()\n    @Inject(ENTITY_CACHE_SELECTOR_TOKEN)\n    selectEntityCache?: EntityCacheSelector\n  ) {\n    this.entityCollectionCreator =\n      entityCollectionCreator || new EntityCollectionCreator();\n    this.selectEntityCache =\n      selectEntityCache || createEntityCacheSelector(ENTITY_CACHE_NAME);\n  }\n\n  /**\n   * Create the NgRx selector from the store root to the named collection,\n   * e.g. from Object to Heroes.\n   * @param entityName the name of the collection\n   */\n  createCollectionSelector<\n    T = any,\n    C extends EntityCollection<T> = EntityCollection<T>,\n  >(entityName: string) {\n    const getCollection = (cache: EntityCache = {}) =>\n      <C>(\n        (cache[entityName] ||\n          this.entityCollectionCreator.create<T>(entityName))\n      );\n    return createSelector(this.selectEntityCache, getCollection);\n  }\n\n  /////// createCollectionSelectors //////////\n\n  // Based on @ngrx/entity/state_selectors.ts\n\n  /* eslint-disable @typescript-eslint/unified-signatures */\n  // createCollectionSelectors(metadata) overload\n  /**\n   * Creates entity collection selectors from metadata.\n   * @param metadata - EntityMetadata for the collection.\n   * May be partial but much have `entityName`.\n   */\n  createCollectionSelectors<\n    T,\n    S extends CollectionSelectors<T> = CollectionSelectors<T>,\n  >(metadata: EntityMetadata<T>): S;\n\n  /* eslint-disable @typescript-eslint/unified-signatures */\n  // createCollectionSelectors(entityName) overload\n  /**\n   * Creates default entity collection selectors for an entity type.\n   * Use the metadata overload for additional collection selectors.\n   * @param entityName - name of the entity type\n   */\n  createCollectionSelectors<\n    T,\n    S extends CollectionSelectors<T> = CollectionSelectors<T>,\n  >(entityName: string): S;\n\n  // createCollectionSelectors implementation\n  createCollectionSelectors<\n    T,\n    S extends CollectionSelectors<T> = CollectionSelectors<T>,\n  >(metadataOrName: EntityMetadata<T> | string): S {\n    const metadata =\n      typeof metadataOrName === 'string'\n        ? { entityName: metadataOrName }\n        : metadataOrName;\n    const selectKeys = (c: EntityCollection<T>) => c.ids;\n    const selectEntityMap = (c: EntityCollection<T>) => c.entities;\n\n    const selectEntities: Selector<EntityCollection<T>, T[]> = createSelector(\n      selectKeys,\n      selectEntityMap,\n      (keys: (number | string)[], entities: Dictionary<T>): T[] =>\n        keys.map((key) => entities[key] as T)\n    );\n\n    const selectCount: Selector<EntityCollection<T>, number> = createSelector(\n      selectKeys,\n      (keys) => keys.length\n    );\n\n    // EntityCollection selectors that go beyond the ngrx/entity/EntityState selectors\n    const selectFilter = (c: EntityCollection<T>) => c.filter;\n\n    const filterFn = metadata.filterFn;\n    const selectFilteredEntities: Selector<EntityCollection<T>, T[]> = filterFn\n      ? createSelector(\n          selectEntities,\n          selectFilter,\n          (entities: T[], pattern: any): T[] => filterFn(entities, pattern)\n        )\n      : selectEntities;\n\n    const selectLoaded = (c: EntityCollection<T>) => c.loaded;\n    const selectLoading = (c: EntityCollection<T>) => c.loading;\n    const selectChangeState = (c: EntityCollection<T>) => c.changeState;\n\n    // Create collection selectors for each `additionalCollectionState` property.\n    // These all extend from `selectCollection`\n    const extra = metadata.additionalCollectionState || {};\n    const extraSelectors: {\n      [name: string]: Selector<EntityCollection<T>, any>;\n    } = {};\n    Object.keys(extra).forEach((k) => {\n      extraSelectors['select' + k[0].toUpperCase() + k.slice(1)] = (\n        c: EntityCollection<T>\n      ) => (<any>c)[k];\n    });\n\n    return {\n      selectCount,\n      selectEntities,\n      selectEntityMap,\n      selectFilter,\n      selectFilteredEntities,\n      selectKeys,\n      selectLoaded,\n      selectLoading,\n      selectChangeState,\n      ...extraSelectors,\n    } as S;\n  }\n\n  /////// create //////////\n\n  // create(metadata) overload\n  /**\n   * Creates the store-rooted selectors for an entity collection.\n   * {EntitySelectors$Factory} turns them into selectors$.\n   *\n   * @param metadata - EntityMetadata for the collection.\n   * May be partial but much have `entityName`.\n   *\n   * Based on ngrx/entity/state_selectors.ts\n   * Differs in that these selectors select from the NgRx store root,\n   * through the collection, to the collection members.\n   */\n  create<T, S extends EntitySelectors<T> = EntitySelectors<T>>(\n    metadata: EntityMetadata<T>\n  ): S;\n\n  // create(entityName) overload\n  /**\n   * Creates the default store-rooted selectors for an entity collection.\n   * {EntitySelectors$Factory} turns them into selectors$.\n   * Use the metadata overload for additional collection selectors.\n   *\n   * @param entityName - name of the entity type.\n   *\n   * Based on ngrx/entity/state_selectors.ts\n   * Differs in that these selectors select from the NgRx store root,\n   * through the collection, to the collection members.\n   */\n  create<T, S extends EntitySelectors<T> = EntitySelectors<T>>(\n    // eslint-disable-next-line @typescript-eslint/unified-signatures\n    entityName: string\n  ): S;\n\n  // createCollectionSelectors implementation\n  create<T, S extends EntitySelectors<T> = EntitySelectors<T>>(\n    metadataOrName: EntityMetadata<T> | string\n  ): S {\n    const metadata =\n      typeof metadataOrName === 'string'\n        ? { entityName: metadataOrName }\n        : metadataOrName;\n    const entityName = metadata.entityName;\n    const selectCollection: Selector<\n      Object,\n      EntityCollection<T>\n    > = this.createCollectionSelector<T>(entityName);\n    const collectionSelectors = this.createCollectionSelectors<T>(metadata);\n\n    const entitySelectors: {\n      [name: string]: Selector<EntityCollection<T>, any>;\n    } = {};\n    Object.keys(collectionSelectors).forEach((k) => {\n      entitySelectors[k] = createSelector(\n        selectCollection,\n        collectionSelectors[k]\n      );\n    });\n\n    return {\n      entityName,\n      selectCollection,\n      selectEntityCache: this.selectEntityCache,\n      ...entitySelectors,\n    } as S;\n  }\n}\n"
  },
  {
    "path": "modules/data/src/utils/correlation-id-generator.ts",
    "content": "import { Injectable } from '@angular/core';\n\n/**\n * Generates a string id beginning 'CRID',\n * followed by a monotonically increasing integer for use as a correlation id.\n * As they are produced locally by a singleton service,\n * these ids are guaranteed to be unique only\n * for the duration of a single client browser instance.\n * Ngrx entity dispatcher query and save methods call this service to generate default correlation ids.\n * Do NOT use for entity keys.\n */\n@Injectable()\nexport class CorrelationIdGenerator {\n  /** Seed for the ids */\n  protected seed = 0;\n  /** Prefix of the id, 'CRID; */\n  protected prefix = 'CRID';\n  /** Return the next correlation id */\n  next() {\n    this.seed += 1;\n    return this.prefix + this.seed;\n  }\n}\n"
  },
  {
    "path": "modules/data/src/utils/default-logger.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Logger } from './interfaces';\n\n@Injectable()\nexport class DefaultLogger implements Logger {\n  error(message?: any, extra?: any) {\n    if (message) {\n      if (extra) {\n        console.error(message, extra);\n      } else {\n        console.error(message);\n      }\n    }\n  }\n\n  log(message?: any, extra?: any) {\n    if (message) {\n      if (extra) {\n        console.log(message, extra);\n      } else {\n        console.log(message);\n      }\n    }\n  }\n\n  warn(message?: any, extra?: any) {\n    if (message) {\n      if (extra) {\n        console.warn(message, extra);\n      } else {\n        console.warn(message);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "modules/data/src/utils/default-pluralizer.ts",
    "content": "import { Inject, Injectable, Optional } from '@angular/core';\nimport { EntityPluralNames, PLURAL_NAMES_TOKEN } from './interfaces';\n\nconst uncountable = [\n  // 'sheep',\n  // 'fish',\n  // 'deer',\n  // 'moose',\n  // 'rice',\n  // 'species',\n  'equipment',\n  'information',\n  'money',\n  'series',\n];\n\n@Injectable()\nexport class DefaultPluralizer {\n  pluralNames: EntityPluralNames = {};\n\n  constructor(\n    @Optional()\n    @Inject(PLURAL_NAMES_TOKEN)\n    pluralNames: EntityPluralNames[]\n  ) {\n    // merge each plural names object\n    if (pluralNames) {\n      pluralNames.forEach((pn) => this.registerPluralNames(pn));\n    }\n  }\n\n  /**\n   * Pluralize a singular name using common English language pluralization rules\n   * Examples: \"company\" -> \"companies\", \"employee\" -> \"employees\", \"tax\" -> \"taxes\"\n   */\n  pluralize(name: string) {\n    const plural = this.pluralNames[name];\n    if (plural) {\n      return plural;\n    }\n    // singular and plural are the same\n    if (uncountable.indexOf(name.toLowerCase()) >= 0) {\n      return name;\n      // vowel + y\n    } else if (/[aeiou]y$/.test(name)) {\n      return name + 's';\n      // consonant + y\n    } else if (name.endsWith('y')) {\n      return name.substring(0, name.length - 1) + 'ies';\n      // endings typically pluralized with 'es'\n    } else if (/[s|ss|sh|ch|x|z]$/.test(name)) {\n      return name + 'es';\n    } else {\n      return name + 's';\n    }\n  }\n\n  /**\n   * Register a mapping of entity type name to the entity name's plural\n   * @param pluralNames {EntityPluralNames} plural names for entity types\n   */\n  registerPluralNames(pluralNames: EntityPluralNames): void {\n    this.pluralNames = { ...this.pluralNames, ...(pluralNames || {}) };\n  }\n}\n"
  },
  {
    "path": "modules/data/src/utils/guid-fns.ts",
    "content": "/**\n  Client-side id-generators\n\n  These GUID utility functions are not used by @ngrx/data itself at this time.\n  They are included as candidates for generating persistable correlation ids if that becomes desirable.\n  They are also safe for generating unique entity ids on the client.\n\n  Note they produce 32-character hexadecimal UUID strings,\n  not the 128-bit representation found in server-side languages and databases.\n\n  These utilities are experimental and may be withdrawn or replaced in future.\n*/\n\n/**\n * Creates a Universally Unique Identifier (AKA GUID)\n */\nfunction getUuid() {\n  // The original implementation is based on this SO answer:\n  // http://stackoverflow.com/a/2117523/200253\n  return 'xxxxxxxxxx4xxyxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n    // eslint-disable-next-line no-bitwise\n    const r = (Math.random() * 16) | 0,\n      // eslint-disable-next-line no-bitwise\n      v = c === 'x' ? r : (r & 0x3) | 0x8;\n    return v.toString(16);\n  });\n}\n\n/** Alias for getUuid(). Compare with getGuidComb(). */\nexport function getGuid() {\n  return getUuid();\n}\n\n/**\n * Creates a sortable, pseudo-GUID (globally unique identifier)\n * whose trailing 6 bytes (12 hex digits) are time-based\n * Start either with the given getTime() value, seedTime,\n * or get the current time in ms.\n *\n * @param seed {number} - optional seed for reproducible time-part\n */\nexport function getGuidComb(seed?: number) {\n  // Each new Guid is greater than next if more than 1ms passes\n  // See http://thatextramile.be/blog/2009/05/using-the-guidcomb-identifier-strategy\n  // Based on breeze.core.getUuid which is based on this StackOverflow answer\n  // http://stackoverflow.com/a/2117523/200253\n  //\n  // Convert time value to hex: n.toString(16)\n  // Make sure it is 6 bytes long: ('00'+ ...).slice(-12) ... from the rear\n  // Replace LAST 6 bytes (12 hex digits) of regular Guid (that's where they sort in a Db)\n  //\n  // Play with this in jsFiddle: http://jsfiddle.net/wardbell/qS8aN/\n  const timePart = ('00' + (seed || new Date().getTime()).toString(16)).slice(\n    -12\n  );\n  return (\n    'xxxxxxxxxx4xxyxxx'.replace(/[xy]/g, function (c) {\n      /* eslint-disable no-bitwise */\n      const r = (Math.random() * 16) | 0,\n        v = c === 'x' ? r : (r & 0x3) | 0x8;\n      return v.toString(16);\n    }) + timePart\n  );\n}\n\n// Sort comparison value that's good enough\nexport function guidComparer(l: string, r: string) {\n  const lLow = l.slice(-12);\n  const rLow = r.slice(-12);\n  return lLow !== rLow\n    ? lLow < rLow\n      ? -1\n      : +(lLow !== rLow)\n    : l < r\n      ? -1\n      : +(l !== r);\n}\n"
  },
  {
    "path": "modules/data/src/utils/interfaces.ts",
    "content": "import { InjectionToken } from '@angular/core';\n\nexport abstract class Logger {\n  abstract error(message?: any, ...optionalParams: any[]): void;\n  abstract log(message?: any, ...optionalParams: any[]): void;\n  abstract warn(message?: any, ...optionalParams: any[]): void;\n}\n\n/**\n * Mapping of entity type name to its plural\n */\nexport interface EntityPluralNames {\n  [entityName: string]: string;\n}\n\nexport const PLURAL_NAMES_TOKEN = new InjectionToken<EntityPluralNames>(\n  '@ngrx/data Plural Names'\n);\n\nexport abstract class Pluralizer {\n  abstract pluralize(name: string): string;\n}\n"
  },
  {
    "path": "modules/data/src/utils/utilities.ts",
    "content": "import { IdSelector, Update } from '@ngrx/entity';\n\n/**\n * Default function that returns the entity's primary key (pkey).\n * Assumes that the entity has an `id` pkey property.\n * Returns `undefined` if no entity or `id`.\n * Every selectId fn must return `undefined` when it cannot produce a full pkey.\n */\nexport function defaultSelectId(entity: any) {\n  return entity == null ? undefined : entity.id;\n}\n\n/**\n * Flatten first arg if it is an array\n * Allows fn with ...rest signature to be called with an array instead of spread\n * Example:\n * ```\n * // See entity-action-operators.ts\n * const persistOps = [EntityOp.QUERY_ALL, EntityOp.ADD, ...];\n * actions.pipe(ofEntityOp(...persistOps)) // works\n * actions.pipe(ofEntityOp(persistOps)) // also works\n * ```\n * */\nexport function flattenArgs<T>(args?: any[]): T[] {\n  if (args == null) {\n    return [];\n  }\n  if (Array.isArray(args[0])) {\n    const [head, ...tail] = args;\n    args = [...head, ...tail];\n  }\n  return args;\n}\n\n/**\n * Return a function that converts an entity (or partial entity) into the `Update<T>`\n * whose `id` is the primary key and\n * `changes` is the entity (or partial entity of changes).\n */\nexport function toUpdateFactory<T>(selectId?: IdSelector<T>) {\n  selectId = selectId || (defaultSelectId as IdSelector<T>);\n  /**\n   * Convert an entity (or partial entity) into the `Update<T>`\n   * whose `id` is the primary key and\n   * `changes` is the entity (or partial entity of changes).\n   * @param selectId function that returns the entity's primary key (id)\n   */\n  return function toUpdate(entity: Partial<T>): Update<T> {\n    const id: any = selectId!(entity as T);\n    if (id == null) {\n      throw new Error('Primary key may not be null/undefined.');\n    }\n    return entity && { id, changes: entity };\n  };\n}\n"
  },
  {
    "path": "modules/data/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/data/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../../\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmitOnError\": false,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/data\",\n    \"paths\": {},\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"skipLibCheck\": true\n  },\n  \"files\": [\"public_api.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/data\"\n  }\n}\n"
  },
  {
    "path": "modules/data/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/data\",\n    \"paths\": {\n      \"@ngrx/data/schematics-core\": [\"./schematics-core\"],\n      \"@ngrx/data\": [\"./src\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/data/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"vitest/globals\", \"node\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/data/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [angular(), nxViteTsPaths()],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default'],\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "modules/effects/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/effects/README.md",
    "content": "# @ngrx/effects\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/effects/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: [\n      '**/dist',\n      '**/jest.config.ts',\n      '**/schematics-core/**/*.ts',\n    ],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@nx/enforce-module-boundaries': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/effects/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['testing/**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@nx/enforce-module-boundaries': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/effects/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/effects/index.ts",
    "content": "/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\nexport * from './public_api';\n"
  },
  {
    "path": "modules/effects/migrations/13_0_0/index.spec.ts",
    "content": "import { tags } from '@angular-devkit/core';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('Effects Migration 13_0_0', () => {\n  describe('@Effect to createEffect', () => {\n    const collectionPath = path.join(__dirname, '../migration.json');\n    const schematicRunner = new SchematicTestRunner(\n      'schematics',\n      collectionPath\n    );\n\n    let appTree: UnitTestTree;\n\n    beforeEach(async () => {\n      appTree = await createWorkspace(schematicRunner, appTree);\n    });\n\n    it('migrates to createEffect for dispatching effects', async () => {\n      const input = tags.stripIndent`\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n        @Effect()\n        foo$ = this.actions$.pipe(\n          ofType<LoginAction>(AuthActions.login),\n          tap(action => console.log(action))\n        );\n      }\n    `;\n\n      const output = tags.stripIndent`\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n\n        foo$ = createEffect(() => this.actions$.pipe(\n          ofType<LoginAction>(AuthActions.login),\n          tap(action => console.log(action))\n        ));\n      }\n    `;\n\n      await runTest(input, output);\n    });\n\n    it('migrates to createEffect for non-dispatching effects', async () => {\n      const input = tags.stripIndent`\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n        @Effect({ dispatch: false })\n        bar$ = this.actions$.pipe(\n          ofType(AuthActions.login, AuthActions.logout),\n          tap(action => console.log(action))\n        );\n      }\n    `;\n\n      const output = tags.stripIndent`\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n\n        bar$ = createEffect(() => this.actions$.pipe(\n          ofType(AuthActions.login, AuthActions.logout),\n          tap(action => console.log(action))\n        ), { dispatch: false });\n      }\n    `;\n\n      await runTest(input, output);\n    });\n\n    it('migrates to createEffect for effects as functions', async () => {\n      const input = tags.stripIndent`\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n        @Effect()\n        baz$ = ({ debounce = 300, scheduler = asyncScheduler } = {}) => this.actions$.pipe(\n          ofType(login),\n          tap(action => console.log(action))\n        );\n      }\n    `;\n\n      const output = tags.stripIndent`\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n\n        baz$ = createEffect(() => ({ debounce = 300, scheduler = asyncScheduler } = {}) => this.actions$.pipe(\n          ofType(login),\n          tap(action => console.log(action))\n        ));\n      }\n    `;\n\n      await runTest(input, output);\n    });\n\n    it('keeps other decorators', async () => {\n      const input = tags.stripIndent`\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n        @Effect()\n        @Log()\n        login$ = this.actions$.pipe(\n          ofType('LOGIN'),\n          map(() => ({ type: 'LOGGED_IN' }))\n        );\n        @Log()\n        @Effect()\n        logout$ = this.actions$.pipe(\n          ofType('LOGOUT'),\n          map(() => ({ type: 'LOGGED_OUT' }))\n        );\n      }\n    `;\n\n      const output = tags.stripIndent`\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n\n        @Log()\n        login$ = createEffect(() => this.actions$.pipe(\n          ofType('LOGIN'),\n          map(() => ({ type: 'LOGGED_IN' }))\n        ));\n\n        @Log()\n        logout$ = createEffect(() => this.actions$.pipe(\n          ofType('LOGOUT'),\n          map(() => ({ type: 'LOGGED_OUT' }))\n        ));\n      }\n    `;\n\n      await runTest(input, output);\n    });\n\n    it('imports createEffect from effects', async () => {\n      const input = tags.stripIndent`\n      import { Actions, ofType, Effect } from '@ngrx/effects';\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n      }\n    `;\n\n      const output = tags.stripIndent`\n      import { Actions, ofType, createEffect } from '@ngrx/effects';\n      @Injectable()\n      export class SomeEffectsClass {\n        constructor(private actions$: Actions) {}\n      }\n    `;\n\n      await runTest(input, output);\n    });\n\n    it('does not import createEffect if already imported', async () => {\n      const input = tags.stripIndent`\n      import { Actions, Effect, createEffect, ofType } from '@ngrx/effects';\n      @Injectable()\n      export class SomeEffectsClass {\n        @Effect()\n        logout$ = this.actions$.pipe(\n          ofType('LOGOUT'),\n          map(() => ({ type: 'LOGGED_OUT' }))\n        );\n        constructor(private actions$: Actions) {}\n      }\n    `;\n\n      const output = tags.stripIndent`\n      import { Actions, createEffect, ofType } from '@ngrx/effects';\n      @Injectable()\n      export class SomeEffectsClass {\n\n        logout$ = createEffect(() => this.actions$.pipe(\n          ofType('LOGOUT'),\n          map(() => ({ type: 'LOGGED_OUT' }))\n        ));\n        constructor(private actions$: Actions) {}\n      }\n    `;\n\n      await runTest(input, output);\n    });\n\n    it('does not migrate if the createEffect syntax is already used', async () => {\n      const input = tags.stripIndent`\n      import { Actions, createEffect, ofType } from '@ngrx/effects';\n      @Injectable()\n      export class SomeEffectsClass {\n        logout$ = createEffect(() => this.actions$.pipe(\n          ofType('LOGOUT'),\n          map(() => ({ type: 'LOGGED_OUT' }))\n        ));\n        constructor(private actions$: Actions) {}\n      }\n    `;\n\n      await runTest(input, input);\n    });\n\n    it('removes the @Effect decorator', async () => {\n      const input = tags.stripIndent`\n        import { Actions, createEffect, ofType } from '@ngrx/effects';\n        @Injectable()\n        export class SomeEffectsClass {\n          @Effect()\n          logout$ = createEffect(() => this.actions$.pipe(\n            ofType('LOGOUT'),\n            map(() => ({ type: 'LOGGED_OUT' }))\n          ));\n          constructor(private actions$: Actions) {}\n        }\n      `;\n      const output = tags.stripIndent`\n        import { Actions, createEffect, ofType } from '@ngrx/effects';\n        @Injectable()\n        export class SomeEffectsClass {\n          logout$ = createEffect(() => this.actions$.pipe(\n            ofType('LOGOUT'),\n            map(() => ({ type: 'LOGGED_OUT' }))\n          ));\n          constructor(private actions$: Actions) {}\n        }\n      `;\n      await runTest(input, output);\n    });\n\n    async function runTest(input: string, expected: string) {\n      const effectPath = '/some.effects.ts';\n      appTree.create(effectPath, input);\n\n      const tree = await schematicRunner.runSchematic(\n        `ngrx-effects-migration-03`,\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent(effectPath);\n\n      const removeEmptyLines = (value: string) =>\n        value.replace(/^\\s*$(?:\\r\\n?|\\n)/gm, '');\n\n      expect(removeEmptyLines(actual)).toBe(removeEmptyLines(expected));\n    }\n  });\n});\n"
  },
  {
    "path": "modules/effects/migrations/13_0_0/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Path } from '@angular-devkit/core';\nimport { Tree, Rule, chain } from '@angular-devkit/schematics';\nimport {\n  InsertChange,\n  RemoveChange,\n  replaceImport,\n  commitChanges,\n  visitTSSourceFiles,\n} from '../../schematics-core';\n\nexport function migrateToCreators(): Rule {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const effectsPerClass = sourceFile.statements\n        .filter(ts.isClassDeclaration)\n        .map((clas) =>\n          clas.members.filter(ts.isPropertyDeclaration).filter((property) => {\n            const decorators = ts.getDecorators(property);\n            return decorators && decorators.some(isEffectDecorator);\n          })\n        );\n\n      const effects = effectsPerClass.reduce(\n        (acc, effects) => acc.concat(effects),\n        []\n      );\n\n      const createEffectsChanges = replaceEffectDecorators(\n        tree,\n        sourceFile,\n        effects\n      );\n      const importChanges = replaceImport(\n        sourceFile,\n        sourceFile.fileName as Path,\n        '@ngrx/effects',\n        'Effect',\n        'createEffect'\n      );\n\n      commitChanges(tree, sourceFile.fileName, [\n        ...importChanges,\n        ...createEffectsChanges,\n      ]);\n    });\n  };\n}\n\nfunction replaceEffectDecorators(\n  host: Tree,\n  sourceFile: ts.SourceFile,\n  effects: ts.PropertyDeclaration[]\n) {\n  const inserts = effects\n    .map((effect) => {\n      if (!effect.initializer) {\n        return [];\n      }\n      const decorator = (ts.getDecorators(effect) || []).find(\n        isEffectDecorator\n      );\n      if (!decorator) {\n        return [];\n      }\n      if (effect.initializer.getText().includes('createEffect')) {\n        return [];\n      }\n      const effectArguments = getDispatchProperties(\n        host,\n        sourceFile.text,\n        decorator\n      );\n      const end = effectArguments ? `, ${effectArguments})` : ')';\n\n      return [\n        new InsertChange(\n          sourceFile.fileName,\n          effect.initializer.pos,\n          ' createEffect(() =>'\n        ),\n        new InsertChange(sourceFile.fileName, effect.initializer.end, end),\n      ];\n    })\n    .reduce((acc, inserts) => acc.concat(inserts), []);\n\n  const removes = effects\n    .map((effect) => ts.getDecorators(effect))\n    .map((decorators) => {\n      if (!decorators) {\n        return [];\n      }\n      const effectDecorators = decorators.filter(isEffectDecorator);\n      return effectDecorators.map((decorator) => {\n        return new RemoveChange(\n          sourceFile.fileName,\n          decorator.expression.pos - 1, // also get the @ sign\n          decorator.expression.end\n        );\n      });\n    })\n    .reduce((acc, removes) => acc.concat(removes), []);\n\n  return [...inserts, ...removes];\n}\n\nfunction isEffectDecorator(decorator: ts.Decorator) {\n  return (\n    ts.isCallExpression(decorator.expression) &&\n    ts.isIdentifier(decorator.expression.expression) &&\n    decorator.expression.expression.text === 'Effect'\n  );\n}\n\nfunction getDispatchProperties(\n  host: Tree,\n  fileContent: string,\n  decorator: ts.Decorator\n) {\n  if (!decorator.expression || !ts.isCallExpression(decorator.expression)) {\n    return '';\n  }\n\n  // just copy the effect properties\n  const args = fileContent\n    .substring(\n      decorator.expression.arguments.pos,\n      decorator.expression.arguments.end\n    )\n    .trim();\n  return args;\n}\n\nexport default function (): Rule {\n  return chain([migrateToCreators()]);\n}\n"
  },
  {
    "path": "modules/effects/migrations/15_0_0-beta/index.spec.ts",
    "content": "import { tags } from '@angular-devkit/core';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('Effects Migration 15_0_0-beta', () => {\n  const collectionPath = path.join(__dirname, '../migration.json');\n  const schematicRunner = new SchematicTestRunner('schematics', collectionPath);\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('replace array in provideEffects', async () => {\n    const input = tags.stripIndent`\n        import { enableProdMode, isDevMode } from '@angular/core';\n        import { bootstrapApplication } from '@angular/platform-browser';\n        import {\n          provideRouter,\n          withEnabledBlockingInitialNavigation,\n        } from '@angular/router';\n        import { provideStore } from '@ngrx/store';\n        import { provideEffects } from '@ngrx/effects';\n        import { provideRouterStore, routerReducer } from '@ngrx/router-store';\n        import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n        import { AppComponent } from './app/app.component';\n\n        import { environment } from './environments/environment';\n        import { AppEffects } from './app/app.effects';\n\n        if (environment.production) {\n          enableProdMode();\n        }\n\n        bootstrapApplication(AppComponent, {\n          providers: [\n            provideStore({ router: routerReducer }),\n            provideRouter(\n              [\n                {\n                  path: 'feature',\n                  loadChildren: () =>\n                    import('./app/lazy/feature.routes').then((m) => m.routes),\n                  providers: [\n                    provideEffects([]),\n                    provideEffects([AppEffects]),\n                    provideEffects([AppEffects1, AppEffect2]),\n                  ]\n                },\n              ],\n              withEnabledBlockingInitialNavigation()\n            ),\n            provideStoreDevtools({\n              maxAge: 25,\n              logOnly: !isDevMode(),\n              name: 'NgRx Standalone App',\n            }),\n            provideRouterStore(),\n            provideEffects([]),\n            provideEffects([AppEffects]),\n            provideEffects([AppEffects1,AppEffect2]),\n          ],\n        });\n      `;\n\n    const expected = tags.stripIndent`\n        import { enableProdMode, isDevMode } from '@angular/core';\n        import { bootstrapApplication } from '@angular/platform-browser';\n        import {\n          provideRouter,\n          withEnabledBlockingInitialNavigation,\n        } from '@angular/router';\n        import { provideStore } from '@ngrx/store';\n        import { provideEffects } from '@ngrx/effects';\n        import { provideRouterStore, routerReducer } from '@ngrx/router-store';\n        import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n        import { AppComponent } from './app/app.component';\n\n        import { environment } from './environments/environment';\n        import { AppEffects } from './app/app.effects';\n\n        if (environment.production) {\n          enableProdMode();\n        }\n\n        bootstrapApplication(AppComponent, {\n          providers: [\n            provideStore({ router: routerReducer }),\n            provideRouter(\n              [\n                {\n                  path: 'feature',\n                  loadChildren: () =>\n                    import('./app/lazy/feature.routes').then((m) => m.routes),\n                  providers: [\n                    provideEffects(),\n                    provideEffects(AppEffects),\n                    provideEffects(AppEffects1, AppEffect2),\n                  ]\n                },\n              ],\n              withEnabledBlockingInitialNavigation()\n            ),\n            provideStoreDevtools({\n              maxAge: 25,\n              logOnly: !isDevMode(),\n              name: 'NgRx Standalone App',\n            }),\n            provideRouterStore(),\n            provideEffects(),\n            provideEffects(AppEffects),\n            provideEffects(AppEffects1, AppEffect2),\n          ],\n        });\n      `;\n\n    appTree.create('main.ts', input);\n\n    const tree = await schematicRunner.runSchematic(\n      `ngrx-effects-migration-15-beta`,\n      {},\n      appTree\n    );\n\n    const actual = tree.readContent('main.ts');\n\n    expect(actual).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "modules/effects/migrations/15_0_0-beta/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Tree,\n  Rule,\n  chain,\n  SchematicContext,\n} from '@angular-devkit/schematics';\nimport {\n  commitChanges,\n  createReplaceChange,\n  ReplaceChange,\n  visitTSSourceFiles,\n} from '../../schematics-core';\n\nexport function migrateProvideEffects(): Rule {\n  return (tree: Tree, ctx: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const changes: ReplaceChange[] = [];\n\n      let isProvideEffectsImported = false;\n      visitImportSpecifiers(sourceFile, (node) => {\n        if (\n          node.name.getText() === 'provideEffects' &&\n          node.parent.parent.parent.moduleSpecifier\n            .getText()\n            .includes('@ngrx/effects')\n        ) {\n          isProvideEffectsImported = true;\n          return;\n        }\n      });\n\n      if (!isProvideEffectsImported) {\n        return;\n      }\n\n      visitProvideEffects(sourceFile, (node) => {\n        const [effectClasses] = node.arguments;\n        if (effectClasses && ts.isArrayLiteralExpression(effectClasses)) {\n          const spreaded = effectClasses.elements\n            .map((e) => e.getText())\n            .join(', ');\n          changes.push(\n            createReplaceChange(\n              sourceFile,\n              effectClasses,\n              effectClasses.getText(),\n              spreaded\n            )\n          );\n        }\n      });\n\n      commitChanges(tree, sourceFile.fileName, changes);\n\n      if (changes.length) {\n        ctx.logger.info(`[@ngrx/effects] Updated provideEffects usage`);\n      }\n    });\n  };\n}\n\nfunction visitProvideEffects(\n  node: ts.Node,\n  visitor: (node: ts.CallExpression) => void\n) {\n  if (\n    ts.isCallExpression(node) &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === 'provideEffects'\n  ) {\n    visitor(node);\n  }\n\n  ts.forEachChild(node, (childNode) => visitProvideEffects(childNode, visitor));\n}\n\nfunction visitImportSpecifiers(\n  node: ts.Node,\n  visitor: (node: ts.ImportSpecifier) => void\n) {\n  if (ts.isImportSpecifier(node)) {\n    visitor(node);\n  }\n\n  ts.forEachChild(node, (childNode) =>\n    visitImportSpecifiers(childNode, visitor)\n  );\n}\n\nexport default function (): Rule {\n  return chain([migrateProvideEffects()]);\n}\n"
  },
  {
    "path": "modules/effects/migrations/18_0_0-beta/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\nimport { tags, logging } from '@angular-devkit/core';\nimport * as path from 'path';\n\ndescribe('Effects Migration to 18.0.0-beta', () => {\n  const collectionPath = path.join(__dirname, '../migration.json');\n  const schematicRunner = new SchematicTestRunner('schematics', collectionPath);\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  const verifySchematic = async (input: string, output: string) => {\n    appTree.create('main.ts', input);\n\n    const tree = await schematicRunner.runSchematic(\n      `ngrx-effects-migration-18-beta`,\n      {},\n      appTree\n    );\n\n    const actual = tree.readContent('main.ts');\n\n    expect(actual).toBe(output);\n  };\n\n  describe('replacements', () => {\n    it('should replace the import', async () => {\n      const input = tags.stripIndent`\nimport { concatLatestFrom } from '@ngrx/effects';\n\n@Injectable()\nexport class SomeEffects {\n\n}\n      `;\n      const output = tags.stripIndent`\nimport { concatLatestFrom } from '@ngrx/operators';\n\n@Injectable()\nexport class SomeEffects {\n\n}\n      `;\n\n      await verifySchematic(input, output);\n    });\n\n    it('should also work with \" in imports', async () => {\n      const input = tags.stripIndent`\nimport { concatLatestFrom } from \"@ngrx/effects\";\n\n@Injectable()\nexport class SomeEffects {\n\n}\n      `;\n      const output = tags.stripIndent`\nimport { concatLatestFrom } from '@ngrx/operators';\n\n@Injectable()\nexport class SomeEffects {\n\n}\n      `;\n      await verifySchematic(input, output);\n    });\n\n    it('should replace if multiple imports are inside an import statement', async () => {\n      const input = tags.stripIndent`\nimport { Actions, concatLatestFrom } from '@ngrx/effects';\n\n@Injectable()\nexport class SomeEffects {\n  actions$ = inject(Actions);\n\n}\n      `;\n      const output = tags.stripIndent`\nimport { Actions } from '@ngrx/effects';\nimport { concatLatestFrom } from '@ngrx/operators';\n\n@Injectable()\nexport class SomeEffects {\n  actions$ = inject(Actions);\n\n}\n      `;\n\n      await verifySchematic(input, output);\n    });\n\n    it('should add concatLatestFrom to existing import', async () => {\n      const input = tags.stripIndent`\nimport { Actions, concatLatestFrom } from '@ngrx/effects';\nimport { tapResponse } from '@ngrx/operators';\n\n@Injectable()\nexport class SomeEffects {\n  actions$ = inject(Actions);\n\n}\n      `;\n      const output = tags.stripIndent`\nimport { Actions } from '@ngrx/effects';\nimport { tapResponse, concatLatestFrom } from '@ngrx/operators';\n\n@Injectable()\nexport class SomeEffects {\n  actions$ = inject(Actions);\n\n}\n      `;\n      await verifySchematic(input, output);\n    });\n  });\n\n  it('should work with prior import from same namespace', async () => {\n    const input = tags.stripIndent`\nimport { Actions } from '@ngrx/effects';\nimport { concatLatestFrom, createEffect, ofType } from '@ngrx/effects';\n\nclass SomeEffects {}\n      `;\n    const output = tags.stripIndent`\nimport { Actions } from '@ngrx/effects';\nimport { createEffect, ofType } from '@ngrx/effects';\nimport { concatLatestFrom } from '@ngrx/operators';\n\nclass SomeEffects {}\n      `;\n    await verifySchematic(input, output);\n  });\n\n  it('should operate on multiple files', async () => {\n    const inputMainOne = tags.stripIndent`\nimport { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';\nimport { tap } from 'rxjs/operators';\n\nclass SomeEffects {}\n`;\n\n    const outputMainOne = tags.stripIndent`\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { concatLatestFrom } from '@ngrx/operators';\nimport { tap } from 'rxjs/operators';\n\nclass SomeEffects {}\n`;\n\n    const inputMainTwo = tags.stripIndent`\nimport { provideEffects } from '@ngrx/effects';\nimport { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';\n\nclass SomeEffects {}\n`;\n\n    const outputMainTwo = tags.stripIndent`\nimport { provideEffects } from '@ngrx/effects';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { concatLatestFrom } from '@ngrx/operators';\n\nclass SomeEffects {}\n`;\n    appTree.create('mainOne.ts', inputMainOne);\n    appTree.create('mainTwo.ts', inputMainTwo);\n\n    const tree = await schematicRunner.runSchematic(\n      `ngrx-effects-migration-18-beta`,\n      {},\n      appTree\n    );\n\n    const actualMainOne = tree.readContent('mainOne.ts');\n    const actualMainTwo = tree.readContent('mainTwo.ts');\n\n    expect(actualMainOne).toBe(outputMainOne);\n    expect(actualMainTwo).toBe(outputMainTwo);\n  });\n\n  it('should report an info on multiple imports of concatLatestFrom', async () => {\n    const input = tags.stripIndent`\nimport { concatLatestFrom } from '@ngrx/effects';\nimport { concatLatestFrom, createEffect, ofType } from '@ngrx/effects';\n\nclass SomeEffects {}\n      `;\n\n    appTree.create('main.ts', input);\n    const logEntries: logging.LogEntry[] = [];\n    schematicRunner.logger.subscribe((logEntry) => logEntries.push(logEntry));\n    await schematicRunner.runSchematic(\n      `ngrx-effects-migration-18-beta`,\n      {},\n      appTree\n    );\n\n    expect(logEntries).toHaveLength(1);\n    expect(logEntries[0]).toMatchObject({\n      message:\n        '[@ngrx/effects] Skipping because of multiple `concatLatestFrom` imports',\n      level: 'info',\n    });\n  });\n\n  it('should add @ngrx/operators if they are missing', async () => {\n    const originalPackageJson = JSON.parse(\n      appTree.readContent('/package.json')\n    );\n    expect(originalPackageJson.dependencies['@ngrx/operators']).toBeUndefined();\n    expect(\n      originalPackageJson.devDependencies['@ngrx/operators']\n    ).toBeUndefined();\n\n    const tree = await schematicRunner.runSchematic(\n      `ngrx-effects-migration-18-beta`,\n      {},\n      appTree\n    );\n\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n    expect(packageJson.dependencies['@ngrx/operators']).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "modules/effects/migrations/18_0_0-beta/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Tree,\n  Rule,\n  chain,\n  SchematicContext,\n} from '@angular-devkit/schematics';\nimport {\n  addPackageToPackageJson,\n  Change,\n  commitChanges,\n  createReplaceChange,\n  InsertChange,\n  visitTSSourceFiles,\n} from '../../schematics-core';\nimport { createRemoveChange } from '../../schematics-core/utility/change';\n\nexport function migrateConcatLatestFromImport(): Rule {\n  return (tree: Tree, ctx: SchematicContext) => {\n    addPackageToPackageJson(tree, 'dependencies', '@ngrx/operators', '^18.0.0');\n\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const importDeclarations = new Array<ts.ImportDeclaration>();\n\n      getImportDeclarations(sourceFile, importDeclarations);\n\n      const effectsImportsAndDeclarations = importDeclarations\n        .map((effectsImportDeclaration) => {\n          const effectsImports = getEffectsNamedBinding(\n            effectsImportDeclaration\n          );\n          if (effectsImports) {\n            if (\n              effectsImports.elements.some(\n                (element) => element.name.getText() === 'concatLatestFrom'\n              )\n            ) {\n              return { effectsImports, effectsImportDeclaration };\n            }\n            return undefined;\n          } else {\n            return undefined;\n          }\n        })\n        .filter(Boolean);\n\n      if (effectsImportsAndDeclarations.length === 0) {\n        return;\n      } else if (effectsImportsAndDeclarations.length > 1) {\n        ctx.logger.info(\n          '[@ngrx/effects] Skipping because of multiple `concatLatestFrom` imports'\n        );\n        return;\n      }\n\n      const [effectsImportsAndDeclaration] = effectsImportsAndDeclarations;\n      if (!effectsImportsAndDeclaration) {\n        return;\n      }\n\n      const { effectsImports, effectsImportDeclaration } =\n        effectsImportsAndDeclaration;\n\n      const operatorsImportDeclaration = importDeclarations.find((node) =>\n        node.moduleSpecifier.getText().includes('@ngrx/operators')\n      );\n\n      const otherEffectsImports = effectsImports.elements\n        .filter((element) => element.name.getText() !== 'concatLatestFrom')\n        .map((element) => element.name.getText())\n        .join(', ');\n\n      const changes: Change[] = [];\n      // Remove `concatLatestFrom` from @ngrx/effects and leave the other imports\n      if (otherEffectsImports) {\n        changes.push(\n          createReplaceChange(\n            sourceFile,\n            effectsImportDeclaration,\n            effectsImportDeclaration.getText(),\n            `import { ${otherEffectsImports} } from '@ngrx/effects';`\n          )\n        );\n      }\n      // Remove complete @ngrx/effects import because it contains only `concatLatestFrom`\n      else {\n        changes.push(\n          createRemoveChange(\n            sourceFile,\n            effectsImportDeclaration,\n            effectsImportDeclaration.getStart(),\n            effectsImportDeclaration.getEnd() + 1\n          )\n        );\n      }\n\n      let importAppendedInExistingDeclaration = false;\n      if (operatorsImportDeclaration?.importClause?.namedBindings) {\n        const bindings = operatorsImportDeclaration.importClause.namedBindings;\n        if (ts.isNamedImports(bindings)) {\n          // Add import to existing @ngrx/operators\n          const updatedImports = [\n            ...bindings.elements.map((element) => element.name.getText()),\n            'concatLatestFrom',\n          ];\n          const newOperatorsImport = `import { ${updatedImports.join(\n            ', '\n          )} } from '@ngrx/operators';`;\n          changes.push(\n            createReplaceChange(\n              sourceFile,\n              operatorsImportDeclaration,\n              operatorsImportDeclaration.getText(),\n              newOperatorsImport\n            )\n          );\n          importAppendedInExistingDeclaration = true;\n        }\n      }\n\n      if (!importAppendedInExistingDeclaration) {\n        // Add new @ngrx/operators import line\n        const newOperatorsImport = `import { concatLatestFrom } from '@ngrx/operators';`;\n        changes.push(\n          new InsertChange(\n            sourceFile.fileName,\n            effectsImportDeclaration.getEnd() + 1,\n            `${newOperatorsImport}\\n` // not os-independent for snapshot tests\n          )\n        );\n      }\n\n      commitChanges(tree, sourceFile.fileName, changes);\n\n      if (changes.length) {\n        ctx.logger.info(\n          `[@ngrx/effects] Updated concatLatestFrom to import from '@ngrx/operators'`\n        );\n      }\n    });\n  };\n}\n\nfunction getImportDeclarations(\n  node: ts.Node,\n  imports: ts.ImportDeclaration[]\n): void {\n  if (ts.isImportDeclaration(node)) {\n    imports.push(node);\n  }\n\n  ts.forEachChild(node, (childNode) =>\n    getImportDeclarations(childNode, imports)\n  );\n}\n\nfunction getEffectsNamedBinding(\n  node: ts.ImportDeclaration\n): ts.NamedImports | null {\n  const namedBindings = node?.importClause?.namedBindings;\n  if (\n    node.moduleSpecifier.getText().includes('@ngrx/effects') &&\n    namedBindings &&\n    ts.isNamedImports(namedBindings)\n  ) {\n    return namedBindings;\n  }\n\n  return null;\n}\n\nexport default function (): Rule {\n  return chain([migrateConcatLatestFromImport()]);\n}\n"
  },
  {
    "path": "modules/effects/migrations/6_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport {\n  createPackageJson,\n  packagePath,\n} from '@ngrx/schematics-core/testing/create-package';\nimport {\n  upgradeVersion,\n  versionPrefixes,\n} from '@ngrx/schematics-core/testing/update';\n\nconst collectionPath = path.join(__dirname, '../migration.json');\n\ndescribe('Effects Migration 6_0_0', () => {\n  let appTree;\n  const pkgName = 'effects';\n\n  versionPrefixes.forEach((prefix) => {\n    it(`should install version ${prefix}6.0.0`, async () => {\n      appTree = new UnitTestTree(Tree.empty());\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n      const tree = createPackageJson(prefix, pkgName, appTree);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-01`,\n        {},\n        tree\n      );\n      const pkg = JSON.parse(newTree.readContent(packagePath));\n      expect(pkg.dependencies[`@ngrx/${pkgName}`]).toBe(\n        `${prefix}${upgradeVersion}`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "modules/effects/migrations/6_0_0/index.ts",
    "content": "import { Rule } from '@angular-devkit/schematics';\nimport { updatePackage } from '../../schematics-core';\n\nexport default function (): Rule {\n  return updatePackage('effects');\n}\n"
  },
  {
    "path": "modules/effects/migrations/9_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createPackageJson } from '@ngrx/schematics-core/testing/create-package';\n\ndescribe('Effects Migration 9_0_0', () => {\n  let appTree: UnitTestTree;\n  const collectionPath = path.join(__dirname, '../migration.json');\n  const pkgName = 'effects';\n\n  beforeEach(() => {\n    appTree = new UnitTestTree(Tree.empty());\n    appTree.create(\n      '/tsconfig.json',\n      `\n        {\n          \"include\": [**./*.ts\"]\n        }\n       `\n    );\n    createPackageJson('', pkgName, appTree);\n  });\n\n  describe('Replaces resubscribeOnError with useEffectsErrorHandler in effect options', () => {\n    describe('should replace resubscribeOnError configuration key with useEffectsErrorHandler', () => {\n      it('in createEffect() effect creator', () => {\n        const input = `\n  import { Injectable } from '@angular/core';\n  import { Actions, ofType, createEffect } from '@ngrx/effects';\n  import { tap } from 'rxjs/operators';\n\n  @Injectable()\n  export class LogEffects {\n    constructor(private actions$: Actions) {}\n\n    logActions$ = createEffect(() =>\n      this.actions$.pipe(\n        tap(action => console.log(action))\n      ), { resubscribeOnError: false });\n  }\n        `;\n\n        const expected = `\n  import { Injectable } from '@angular/core';\n  import { Actions, ofType, createEffect } from '@ngrx/effects';\n  import { tap } from 'rxjs/operators';\n\n  @Injectable()\n  export class LogEffects {\n    constructor(private actions$: Actions) {}\n\n    logActions$ = createEffect(() =>\n      this.actions$.pipe(\n        tap(action => console.log(action))\n      ), { useEffectsErrorHandler: false });\n  }\n        `;\n\n        test(input, expected);\n      });\n\n      it('in @Effect() decorator', () => {\n        const input = `\n  import { Injectable } from '@angular/core';\n  import { Actions, Effect, ofType } from '@ngrx/effects';\n  import { tap } from 'rxjs/operators';\n\n  @Injectable()\n  export class LogEffects {\n    constructor(private actions$: Actions) {}\n\n    @Effect({ resubscribeOnError: false })\n    logActions$ = this.actions$.pipe(\n      tap(action => console.log(action))\n    )\n  }\n        `;\n\n        const expected = `\n  import { Injectable } from '@angular/core';\n  import { Actions, Effect, ofType } from '@ngrx/effects';\n  import { tap } from 'rxjs/operators';\n\n  @Injectable()\n  export class LogEffects {\n    constructor(private actions$: Actions) {}\n\n    @Effect({ useEffectsErrorHandler: false })\n    logActions$ = this.actions$.pipe(\n      tap(action => console.log(action))\n    )\n  }\n        `;\n\n        test(input, expected);\n      });\n    });\n\n    describe('should not replace non-ngrx identifiers', () => {\n      it('in module scope', () => {\n        const input = `\nexport const resubscribeOnError = null;\n      `;\n\n        test(input, input);\n      });\n\n      it('within create effect callback', () => {\n        const input = `\nimport { Injectable } from '@angular/core';\nimport { Actions, ofType, createEffect } from '@ngrx/effects';\nimport { tap } from 'rxjs/operators';\n\n@Injectable()\nexport class LogEffects {\n  constructor(private actions$: Actions) {}\n\n  logActions$ = createEffect(() =>\n    this.actions$.pipe(\n      tap(resubscribeOnError => console.log(resubscribeOnError))\n    ));\n}\n      `;\n\n        test(input, input);\n      });\n    });\n\n    async function test(input: string, expected: string) {\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-02`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }\n  });\n});\n"
  },
  {
    "path": "modules/effects/migrations/9_0_0/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  chain,\n  Rule,\n  SchematicContext,\n  Tree,\n} from '@angular-devkit/schematics';\nimport {\n  commitChanges,\n  visitTSSourceFiles,\n  createReplaceChange,\n  ReplaceChange,\n} from '../../schematics-core';\n\nfunction renameErrorHandlerConfig(): Rule {\n  return (tree: Tree, ctx: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const changes: ReplaceChange[] = replaceEffectConfigKeys(\n        sourceFile,\n        'resubscribeOnError',\n        'useEffectsErrorHandler'\n      );\n\n      commitChanges(tree, sourceFile.fileName, changes);\n\n      if (changes.length) {\n        ctx.logger.info(\n          `[@ngrx/effects] Updated Effects configuration, see the migration guide (https://ngrx.io/guide/migration/v9#effects) for more info`\n        );\n      }\n    });\n  };\n}\n\nfunction replaceEffectConfigKeys(\n  sourceFile: ts.SourceFile,\n  oldText: string,\n  newText: string\n): ReplaceChange[] {\n  const changes: ReplaceChange[] = [];\n\n  ts.forEachChild(sourceFile, (node) => {\n    visitCreateEffectFunctionCreator(node, (createEffectNode) => {\n      const [effectDeclaration, configNode] = createEffectNode.arguments;\n      if (configNode) {\n        findAndReplaceText(configNode);\n      }\n    });\n\n    visitEffectDecorator(node, (effectDecoratorNode) => {\n      findAndReplaceText(effectDecoratorNode);\n    });\n  });\n\n  return changes;\n\n  function findAndReplaceText(node: ts.Node): void {\n    visitIdentifierWithText(node, oldText, (match) => {\n      changes.push(createReplaceChange(sourceFile, match, oldText, newText));\n    });\n  }\n}\n\nfunction visitIdentifierWithText(\n  node: ts.Node,\n  text: string,\n  visitor: (node: ts.Node) => void\n) {\n  if (ts.isIdentifier(node) && node.text === text) {\n    visitor(node);\n  }\n\n  ts.forEachChild(node, (childNode) =>\n    visitIdentifierWithText(childNode, text, visitor)\n  );\n}\n\nfunction visitEffectDecorator(node: ts.Node, visitor: (node: ts.Node) => void) {\n  if (\n    ts.isDecorator(node) &&\n    ts.isCallExpression(node.expression) &&\n    ts.isIdentifier(node.expression.expression) &&\n    node.expression.expression.text === 'Effect'\n  ) {\n    visitor(node);\n  }\n\n  ts.forEachChild(node, (childNode) =>\n    visitEffectDecorator(childNode, visitor)\n  );\n}\n\nfunction visitCreateEffectFunctionCreator(\n  node: ts.Node,\n  visitor: (node: ts.CallExpression) => void\n) {\n  if (\n    ts.isCallExpression(node) &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === 'createEffect'\n  ) {\n    visitor(node);\n  }\n\n  ts.forEachChild(node, (childNode) =>\n    visitCreateEffectFunctionCreator(childNode, visitor)\n  );\n}\n\nexport default function (): Rule {\n  return chain([renameErrorHandlerConfig()]);\n}\n"
  },
  {
    "path": "modules/effects/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"ngrx-effects-migration-01\": {\n      \"description\": \"The road to v6\",\n      \"version\": \"5.2\",\n      \"factory\": \"./6_0_0/index\"\n    },\n    \"ngrx-effects-migration-02\": {\n      \"description\": \"The road to v9\",\n      \"version\": \"9-beta\",\n      \"factory\": \"./9_0_0/index\"\n    },\n    \"ngrx-effects-migration-03\": {\n      \"description\": \"The road to v13\",\n      \"version\": \"13\",\n      \"factory\": \"./13_0_0/index\"\n    },\n    \"ngrx-effects-migration-15-beta\": {\n      \"description\": \"The road to v15 beta\",\n      \"version\": \"15-beta\",\n      \"factory\": \"./15_0_0-beta/index\"\n    },\n    \"ngrx-effects-migration-18-beta\": {\n      \"description\": \"As of NgRx v18, the `concatLatestFrom` import has been removed from `@ngrx/effects` in favor of the `@ngrx/operators` package.\",\n      \"version\": \"18-beta\",\n      \"factory\": \"./18_0_0-beta/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/effects/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/effects\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"allowedNonPeerDependencies\": [\"@ngrx/operators\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/effects/package.json",
    "content": "{\n  \"name\": \"@ngrx/effects\",\n  \"version\": \"21.0.1\",\n  \"description\": \"Side effect model for @ngrx/store\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"RxJS\",\n    \"Angular\",\n    \"Redux\",\n    \"NgRx\",\n    \"Schematics\",\n    \"Angular CLI\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"@angular/core\": \"^21.0.0\",\n    \"@ngrx/store\": \"21.0.1\",\n    \"rxjs\": \"^6.5.3 || ^7.5.0\"\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"sideEffects\": false,\n  \"dependencies\": {\n    \"tslib\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "modules/effects/project.json",
    "content": "{\n  \"name\": \"effects\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/effects/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/effects/tsconfig.build.json\",\n        \"project\": \"modules/effects/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package effects\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/effects/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/effects\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/effects\"\n          },\n          {\n            \"command\": \"ncp dist/modules/effects node_modules/@ngrx/effects\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/effects\"\n          }\n        ]\n      },\n      \"outputs\": [\n        \"{workspaceRoot}/dist/modules/effects\",\n        \"{workspaceRoot}/node_modules/@ngrx/effects\"\n      ]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/effects/*/**/*.ts\",\n          \"modules/effects/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/effects\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/effects/public_api.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/effects/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Add root side effect class\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/effects/schematics/ng-add/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Effects ng-add Schematic Effects ng-add Schematic for standalone application provides full effects setup 1`] = `\n\"import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { provideEffects } from '@ngrx/effects';\nimport { FooEffects } from './foo/foo.effects';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideBrowserGlobalErrorListeners(),\n    provideEffects(FooEffects)\n]\n};\n\"\n`;\n\nexports[`Effects ng-add Schematic Effects ng-add Schematic for standalone application provides minimal effects setup 1`] = `\n\"import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { provideEffects } from '@ngrx/effects';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideBrowserGlobalErrorListeners(),\n    provideEffects()\n]\n};\n\"\n`;\n"
  },
  {
    "path": "modules/effects/schematics/ng-add/files/__name@dasherize@if-flat__/__name@dasherize__.effects.spec.ts.template",
    "content": "import { TestBed, inject } from '@angular/core/testing';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport { Observable } from 'rxjs';\n\nimport { <%= classify(name) %>Effects } from './<%= dasherize(name) %>.effects';\n\ndescribe('<%= classify(name) %>Effects', () => {\n  let actions$: Observable<any>;\n  let effects: <%= classify(name) %>Effects;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        <%= classify(name) %>Effects,\n        provideMockActions(() => actions$)\n      ]\n    });\n\n    effects = TestBed.inject(<%= classify(name) %>Effects);\n  });\n\n  it('should be created', () => {\n    expect(effects).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "modules/effects/schematics/ng-add/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts.template",
    "content": "import { Injectable } from '@angular/core';\nimport { Actions, createEffect } from '@ngrx/effects';\n\n@Injectable()\nexport class <%= classify(name) %>Effects {\n  constructor(private actions$: Actions) {}\n}\n"
  },
  {
    "path": "modules/effects/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as RootEffectOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n  createAppModuleWithEffects,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Effects ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/effects',\n    path.join(__dirname, '../collection.json')\n  );\n\n  const defaultOptions: RootEffectOptions = {\n    name: 'foo',\n    skipPackageJson: false,\n    project: 'bar',\n    module: 'app-module',\n    flat: false,\n    group: false,\n    minimal: false,\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/effects']).toBeUndefined();\n  });\n\n  it('should create an effect', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.effects.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.effects.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should not create an effect if the minimal flag is provided', async () => {\n    const options = { ...defaultOptions, minimal: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const files = tree.files;\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n\n    expect(content).toMatch(/EffectsModule\\.forRoot\\(\\[\\]\\)/);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.effects.spec.ts`)\n    ).toBe(-1);\n    expect(files.indexOf(`${projectPath}/src/app/foo/foo.effects.ts`)).toBe(-1);\n  });\n\n  it('should not import an effect into a specified module in the minimal flag is provided', async () => {\n    const options = {\n      ...defaultOptions,\n      minimal: true,\n      module: 'app-module.ts',\n    };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).not.toMatch(\n      /import { FooEffects } from '.\\/foo\\/foo.effects'/\n    );\n  });\n\n  it('should be provided by default', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(/import { FooEffects } from '.\\/foo\\/foo.effects'/);\n  });\n\n  it('should import into a specified module', async () => {\n    const options = { ...defaultOptions, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(/import { FooEffects } from '.\\/foo\\/foo.effects'/);\n  });\n\n  it('should fail if specified module does not exist', async () => {\n    const options = {\n      ...defaultOptions,\n      module: `${projectPath}/src/app/app-moduleXXX.ts`,\n    };\n    let thrownError: Error | null = null;\n    try {\n      await schematicRunner.runSchematic('effects', options, appTree);\n    } catch (err: any) {\n      thrownError = err;\n    }\n    expect(thrownError).toBeDefined();\n  });\n\n  it('should respect the skipTests flag', async () => {\n    const options = { ...defaultOptions, skipTests: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.effects.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.effects.spec.ts`)\n    ).toEqual(-1);\n  });\n\n  it('should register the root effect in the provided module', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n\n    expect(content).toMatch(/EffectsModule\\.forRoot\\(\\[FooEffects\\]\\)/);\n  });\n\n  it('should add an effect to the empty array of registered effects', async () => {\n    const storeModule = `${projectPath}/src/app/store.module.ts`;\n    const options = {\n      ...defaultOptions,\n      module: 'store.module.ts',\n    };\n    appTree = createAppModuleWithEffects(\n      appTree,\n      storeModule,\n      'EffectsModule.forRoot([])'\n    );\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(storeModule);\n\n    expect(content).toMatch(/EffectsModule\\.forRoot\\(\\[FooEffects\\]\\)/);\n  });\n\n  it('should add an effect to the existing registered root effects', async () => {\n    const storeModule = `${projectPath}/src/app/store.module.ts`;\n    const options = {\n      ...defaultOptions,\n      module: 'store.module.ts',\n    };\n    appTree = createAppModuleWithEffects(\n      appTree,\n      storeModule,\n      'EffectsModule.forRoot([UserEffects])'\n    );\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(storeModule);\n\n    expect(content).toMatch(\n      /EffectsModule\\.forRoot\\(\\[UserEffects, FooEffects\\]\\)/\n    );\n  });\n\n  it('should not add an effect to registered effects defined with a variable', async () => {\n    const storeModule = `${projectPath}/src/app/store.module.ts`;\n    const options = { ...defaultOptions, module: 'store.module.ts' };\n    appTree = createAppModuleWithEffects(\n      appTree,\n      storeModule,\n      'EffectsModule.forRoot(effects)'\n    );\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(storeModule);\n\n    expect(content).not.toMatch(/EffectsModule\\.forRoot\\(\\[FooEffects\\]\\)/);\n  });\n\n  it('should group within an \"effects\" folder if group is set', async () => {\n    const options = {\n      ...defaultOptions,\n      flat: true,\n      skipTests: true,\n      group: true,\n    };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/effects/foo.effects.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should inject the effect service correctly', async () => {\n    const options = { ...defaultOptions, skipTests: false };\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(\n      `${projectPath}/src/app/foo/foo.effects.spec.ts`\n    );\n\n    expect(content).toMatch(/effects = TestBed\\.inject\\(FooEffects\\);/);\n  });\n\n  describe('Effects ng-add Schematic for standalone application', () => {\n    const projectPath = getTestProjectPath(undefined, {\n      name: 'bar-standalone',\n    });\n\n    const standaloneDefaultOptions = {\n      ...defaultOptions,\n      project: 'bar-standalone',\n    };\n\n    it('provides minimal effects setup', async () => {\n      const options = { ...standaloneDefaultOptions, minimal: true };\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n\n      const content = tree.readContent(`${projectPath}/src/app/app.config.ts`);\n\n      expect(content).toMatchSnapshot();\n    });\n\n    it('provides full effects setup', async () => {\n      const options = { ...standaloneDefaultOptions };\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n\n      const content = tree.readContent(`${projectPath}/src/app/app.config.ts`);\n      const files = tree.files;\n\n      expect(content).toMatchSnapshot();\n      expect(\n        files.indexOf(`${projectPath}/src/app/foo/foo.effects.spec.ts`)\n      ).toBeGreaterThanOrEqual(0);\n      expect(\n        files.indexOf(`${projectPath}/src/app/foo/foo.effects.ts`)\n      ).toBeGreaterThanOrEqual(0);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/effects/schematics/ng-add/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  Tree,\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  filter,\n  mergeWith,\n  move,\n  noop,\n  url,\n} from '@angular-devkit/schematics';\nimport {\n  InsertChange,\n  addImportToModule,\n  buildRelativePath,\n  findModuleFromOptions,\n  getProjectPath,\n  insertImport,\n  parseName,\n  stringUtils,\n  addPackageToPackageJson,\n  platformVersion,\n} from '../../schematics-core';\nimport { Schema as EffectOptions } from './schema';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\nimport { getProjectMainFile } from '../../schematics-core/utility/project';\nimport { isStandaloneApp } from '@schematics/angular/utility/ng-ast-utils';\nimport {\n  addFunctionalProvidersToStandaloneBootstrap,\n  callsProvidersFunction,\n} from '../../schematics-core/utility/standalone';\n\nfunction addImportToNgModule(options: EffectOptions): Rule {\n  return (host: Tree) => {\n    const modulePath = options.module;\n\n    if (!modulePath) {\n      return host;\n    }\n\n    if (!host.exists(modulePath)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const effectsName = `${stringUtils.classify(`${options.name}Effects`)}`;\n\n    const effectsModuleImport = insertImport(\n      source,\n      modulePath,\n      'EffectsModule',\n      '@ngrx/effects'\n    );\n\n    const effectsPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'effects/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.effects';\n    const relativePath = buildRelativePath(modulePath, effectsPath);\n    const effectsImport = insertImport(\n      source,\n      modulePath,\n      effectsName,\n      relativePath\n    );\n\n    const effectsSetup = options.minimal ? `[]` : `[${effectsName}]`;\n    const [effectsNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `EffectsModule.forRoot(${effectsSetup})`,\n      relativePath\n    );\n\n    let changes = [effectsModuleImport, effectsNgModuleImport];\n\n    if (!options.minimal) {\n      changes = changes.concat([effectsImport]);\n    }\n\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nfunction addNgRxEffectsToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/effects',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nfunction addStandaloneConfig(options: EffectOptions): Rule {\n  return (host: Tree) => {\n    const mainFile = getProjectMainFile(host, options);\n\n    if (host.exists(mainFile)) {\n      const providerFn = 'provideEffects';\n\n      if (callsProvidersFunction(host, mainFile, providerFn)) {\n        // exit because the store config is already provided\n        return host;\n      }\n\n      const effectsName = `${stringUtils.classify(`${options.name}Effects`)}`;\n\n      const providerOptions = options.minimal\n        ? []\n        : [ts.factory.createIdentifier(effectsName)];\n\n      const patchedConfigFile = addFunctionalProvidersToStandaloneBootstrap(\n        host,\n        mainFile,\n        providerFn,\n        '@ngrx/effects',\n        providerOptions\n      );\n\n      if (options.minimal) {\n        // no need to add imports if it is minimal\n        return host;\n      }\n\n      // insert effects import into the patched file\n      const configFileContent = host.read(patchedConfigFile);\n      const source = ts.createSourceFile(\n        patchedConfigFile,\n        configFileContent?.toString('utf-8') || '',\n        ts.ScriptTarget.Latest,\n        true\n      );\n\n      const effectsPath =\n        `/${options.path}/` +\n        (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n        (options.group ? 'effects/' : '') +\n        stringUtils.dasherize(options.name) +\n        '.effects';\n\n      const relativePath = buildRelativePath(\n        `/${patchedConfigFile}`,\n        effectsPath\n      );\n\n      const change = insertImport(\n        source,\n        patchedConfigFile,\n        effectsName,\n        relativePath\n      );\n\n      const recorder = host.beginUpdate(patchedConfigFile);\n\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n\n      host.commitUpdate(recorder);\n\n      return host;\n    }\n\n    throw new SchematicsException(\n      `Main file not found for a project ${options.project}`\n    );\n  };\n}\n\nexport default function (options: EffectOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    const mainFile = getProjectMainFile(host, options);\n    const isStandalone = isStandaloneApp(host, mainFile);\n\n    options.path = getProjectPath(host, options);\n\n    if (options.module && !isStandalone) {\n      options.module = findModuleFromOptions(host, options);\n    }\n\n    const parsedPath = parseName(options.path, options.name || '');\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n\n    const templateSource = apply(url('./files'), [\n      options.skipTests\n        ? filter((path) => !path.endsWith('.spec.ts.template'))\n        : noop(),\n      options.minimal ? filter((_) => false) : noop(),\n      applyTemplates({\n        ...stringUtils,\n        'if-flat': (s: string) =>\n          stringUtils.group(\n            options.flat ? '' : s,\n            options.group ? 'effects' : ''\n          ),\n        ...(options as object),\n      } as any),\n      move(parsedPath.path),\n    ]);\n\n    const configOrModuleUpdate = isStandalone\n      ? addStandaloneConfig(options)\n      : addImportToNgModule(options);\n\n    return chain([\n      branchAndMerge(chain([configOrModuleUpdate, mergeWith(templateSource)])),\n      options && options.skipPackageJson\n        ? noop()\n        : addNgRxEffectsToPackageJson(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/effects/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxRootEffect\",\n  \"title\": \"NgRx Root Effect Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the effect.\",\n      \"type\": \"string\",\n      \"default\": \"App\"\n    },\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/effects as dependency to package.json (e.g., --skipPackageJson).\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the effect.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"When true, does not create test files.\"\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"default\": \"app\",\n      \"description\": \"Allows specification of the declaring module.\",\n      \"alias\": \"m\",\n      \"subtype\": \"filepath\"\n    },\n    \"group\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Group effects file within 'effects' folder\",\n      \"aliases\": [\"g\"]\n    },\n    \"minimal\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Setup root effects module without registering initial effects.\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/effects/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  name: string;\n  skipPackageJson?: boolean;\n  path?: string;\n  flat?: boolean;\n  skipTests?: boolean;\n  project?: string;\n  module?: string;\n  group?: boolean;\n  /**\n   * Setup root effects module without registering initial effects.\n   */\n  minimal?: boolean;\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/effects/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/effects/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/effects/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/effects/spec/actions.spec.ts",
    "content": "import { Injector } from '@angular/core';\nimport {\n  Action,\n  props,\n  ScannedActionsSubject,\n  ActionsSubject,\n  createAction,\n} from '@ngrx/store';\nimport { Actions, ofType } from '../';\nimport { firstValueFrom } from 'rxjs';\nimport { map, take, toArray } from 'rxjs/operators';\n\ndescribe('Actions', function () {\n  let actions$: Actions<AddAction | SubtractAction>;\n  let dispatcher: ScannedActionsSubject;\n\n  const ADD = 'ADD';\n  const SUBTRACT = 'SUBTRACT';\n\n  interface AddAction extends Action {\n    type: 'ADD';\n  }\n\n  interface SubtractAction extends Action {\n    type: 'SUBTRACT';\n  }\n\n  const square = createAction('SQUARE');\n  const multiply = createAction('MULTYPLY', props<{ by: number }>());\n  const divide = createAction('DIVIDE', props<{ by: number }>());\n\n  const actions = [ADD, ADD, SUBTRACT, ADD, SUBTRACT];\n\n  beforeEach(function () {\n    const injector = Injector.create([\n      {\n        provide: ScannedActionsSubject,\n        useClass: ScannedActionsSubject,\n        deps: [],\n      },\n      { provide: ActionsSubject, useClass: ActionsSubject, deps: [] },\n      { provide: Actions, useClass: Actions, deps: [ScannedActionsSubject] },\n    ]);\n\n    actions$ = injector.get(Actions);\n    dispatcher = injector.get(ScannedActionsSubject);\n  });\n\n  it('should be an observable of actions', async () => {\n    const testActions = [\n      { type: ADD },\n      { type: SUBTRACT },\n      { type: SUBTRACT },\n      { type: SUBTRACT },\n    ];\n\n    const resultPromise = firstValueFrom(actions$.pipe(take(1)));\n\n    testActions.forEach((action) => dispatcher.next(action));\n    dispatcher.complete();\n\n    const value = await resultPromise;\n    expect(value.type).toEqual(ADD);\n  });\n\n  it('should filter out actions', async () => {\n    const expected = actions.filter((type) => type === ADD);\n\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType(ADD),\n        map((update) => update.type),\n        toArray()\n      )\n    );\n\n    actions.forEach((action) => dispatcher.next({ type: action }));\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual(expected);\n  });\n\n  it('should filter out actions and ofType can take an explicit type argument', async () => {\n    const expected = actions.filter((type) => type === ADD);\n\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType<AddAction>(ADD),\n        map((update) => update.type),\n        toArray()\n      )\n    );\n\n    actions.forEach((action) => dispatcher.next({ type: action }));\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual(expected);\n  });\n\n  it('should let you filter out multiple action types with explicit type argument', async () => {\n    const expected = actions.filter(\n      (type) => type === ADD || type === SUBTRACT\n    );\n\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType<AddAction | SubtractAction>(ADD, SUBTRACT),\n        map((update) => update.type),\n        toArray()\n      )\n    );\n\n    actions.forEach((action) => dispatcher.next({ type: action }));\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual(expected);\n  });\n\n  it('should filter out actions by action creator', async () => {\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType(square),\n        map((update) => update.type),\n        toArray()\n      )\n    );\n\n    [...actions, square.type].forEach((action) =>\n      dispatcher.next({ type: action })\n    );\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual(['SQUARE']);\n  });\n\n  it('should infer the type for the action when it is filter by action creator with property', async () => {\n    const MULTYPLY_BY = 5;\n\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType(multiply),\n        map((update) => update.by),\n        toArray()\n      )\n    );\n\n    actions.forEach((action) => dispatcher.next({ type: action }));\n    dispatcher.next(multiply({ by: MULTYPLY_BY }));\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual([MULTYPLY_BY]);\n  });\n\n  it('should infer the type for the action when it is filter by action creator', async () => {\n    const untypedActions$: Actions = actions$;\n    const MULTYPLY_BY = 5;\n\n    const resultPromise = firstValueFrom(\n      untypedActions$.pipe(\n        ofType(multiply),\n        map((update) => update.by),\n        toArray()\n      )\n    );\n\n    actions.forEach((action) => dispatcher.next({ type: action }));\n    dispatcher.next(multiply({ by: MULTYPLY_BY }));\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual([MULTYPLY_BY]);\n  });\n\n  it('should filter out multiple actions by action creator', async () => {\n    const DIVIDE_BY = 3;\n    const MULTYPLY_BY = 5;\n    const expected = [DIVIDE_BY, MULTYPLY_BY];\n\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType(divide, multiply),\n        map((update) => update.by),\n        toArray()\n      )\n    );\n\n    actions.forEach((action) => dispatcher.next({ type: action }));\n    dispatcher.next(divide({ by: DIVIDE_BY }));\n    dispatcher.next(divide({ by: MULTYPLY_BY }));\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual(expected);\n  });\n\n  it('should filter out actions by action creator and type string', async () => {\n    const expected = [...actions.filter((type) => type === ADD), square.type];\n\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType(ADD, square),\n        map((update) => update.type),\n        toArray()\n      )\n    );\n\n    [...actions, square.type].forEach((action) =>\n      dispatcher.next({ type: action })\n    );\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual(expected);\n  });\n\n  it('should filter out actions by action creator and type string, with explicit type argument', async () => {\n    const expected = [...actions.filter((type) => type === ADD), square.type];\n\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType<AddAction | ReturnType<typeof square>>(ADD, square),\n        map((update) => update.type),\n        toArray()\n      )\n    );\n\n    [...actions, square.type].forEach((action) =>\n      dispatcher.next({ type: action })\n    );\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual(expected);\n  });\n\n  it('should filter out up to 5 actions with type inference', async () => {\n    const expected = [divide.type, ADD, square.type, SUBTRACT, multiply.type];\n\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType(divide, ADD, square, SUBTRACT, multiply),\n        map((update) => update.type),\n        toArray()\n      )\n    );\n\n    dispatcher.next(divide({ by: 1 }));\n    dispatcher.next({ type: ADD });\n    dispatcher.next(square());\n    dispatcher.next({ type: SUBTRACT });\n    dispatcher.next(multiply({ by: 2 }));\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual(expected);\n  });\n\n  it('should support more than 5 actions', async () => {\n    const log = createAction('logarithm');\n    const expected = [\n      divide.type,\n      ADD,\n      square.type,\n      SUBTRACT,\n      multiply.type,\n      log.type,\n    ];\n\n    const resultPromise = firstValueFrom(\n      actions$.pipe(\n        ofType(divide, ADD, square, SUBTRACT, multiply, log),\n        map((update) => update.type),\n        toArray()\n      )\n    );\n\n    dispatcher.next(divide({ by: 1 }));\n    dispatcher.next({ type: ADD });\n    dispatcher.next(square());\n    dispatcher.next({ type: SUBTRACT });\n    dispatcher.next(multiply({ by: 2 }));\n    dispatcher.next(log());\n    dispatcher.complete();\n\n    const actual = await resultPromise;\n    expect(actual).toEqual(expected);\n  });\n});\n"
  },
  {
    "path": "modules/effects/spec/effect_creator.spec.ts",
    "content": "import { forkJoin, of, firstValueFrom } from 'rxjs';\nimport { createEffect, getCreateEffectMetadata } from '../src/effect_creator';\n\ndescribe('createEffect()', () => {\n  it('should flag the variable with a meta tag', () => {\n    const effect = createEffect(() => of({ type: 'a' }));\n\n    expect(effect.hasOwnProperty('__@ngrx/effects_create__')).toBe(true);\n  });\n\n  it('should dispatch by default', () => {\n    const effect = createEffect(() => of({ type: 'a' }));\n\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ dispatch: true })\n    );\n  });\n\n  it('should be possible to explicitly create a dispatching effect', () => {\n    const effect = createEffect(() => of({ type: 'a' }), {\n      dispatch: true,\n    });\n\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ dispatch: true })\n    );\n  });\n\n  it('should be possible to create a non-dispatching effect', () => {\n    const effect = createEffect(() => of({ someProp: 'a' }), {\n      dispatch: false,\n    });\n\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ dispatch: false })\n    );\n  });\n\n  it('should be possible to create a non-dispatching effect returning a non-action', () => {\n    const effect = createEffect(() => of('foo'), {\n      dispatch: false,\n    });\n\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ dispatch: false })\n    );\n  });\n\n  it('should create a non-functional effect by default', () => {\n    const obs$ = of({ type: 'a' });\n    const effect = createEffect(() => obs$);\n\n    expect(effect).toBe(obs$);\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ functional: false })\n    );\n  });\n\n  it('should be possible to explicitly create a non-functional effect', () => {\n    const obs$ = of({ type: 'a' });\n    const effect = createEffect(() => obs$, { functional: false });\n\n    expect(effect).toBe(obs$);\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ functional: false })\n    );\n  });\n\n  it('should be possible to create a functional effect', () => {\n    const source = () => of({ type: 'a' });\n    const effect = createEffect(source, { functional: true });\n\n    expect(effect).toBe(source);\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ functional: true })\n    );\n  });\n\n  it('should be possible to invoke functional effect as function', async () => {\n    const sum = createEffect((x = 10, y = 20) => of(x + y), {\n      functional: true,\n      dispatch: false,\n    });\n\n    const [defaultResult, result] = await firstValueFrom(\n      forkJoin([sum(), sum(100, 200)])\n    );\n\n    expect(defaultResult).toBe(30);\n    expect(result).toBe(300);\n  });\n\n  it('should use effects error handler by default', () => {\n    const effect = createEffect(() => of({ type: 'a' }));\n\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ useEffectsErrorHandler: true })\n    );\n  });\n\n  it('should be possible to explicitly create an effect with error handler', () => {\n    const effect = createEffect(() => of({ type: 'a' }), {\n      useEffectsErrorHandler: true,\n    });\n\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ useEffectsErrorHandler: true })\n    );\n  });\n\n  it('should be possible to create an effect without error handler', () => {\n    const effect = createEffect(() => of({ type: 'a' }), {\n      useEffectsErrorHandler: false,\n    });\n\n    expect(effect['__@ngrx/effects_create__']).toEqual(\n      expect.objectContaining({ useEffectsErrorHandler: false })\n    );\n  });\n\n  describe('getCreateEffectMetadata', () => {\n    it('should get the effects metadata for a class instance', () => {\n      class Fixture {\n        a = createEffect(() => of({ type: 'a' }));\n        b = createEffect(() => of({ type: 'b' }), { dispatch: true });\n        c = createEffect(() => of({ type: 'c' }), { dispatch: false });\n        d = createEffect(() => of({ type: 'd' }), {\n          useEffectsErrorHandler: true,\n          functional: false,\n        });\n        e = createEffect(() => of({ type: 'd' }), {\n          useEffectsErrorHandler: false,\n          functional: true,\n        });\n        f = createEffect(() => of({ type: 'e' }), {\n          dispatch: false,\n          functional: true,\n          useEffectsErrorHandler: false,\n        });\n        g = createEffect(() => of({ type: 'e' }), {\n          dispatch: true,\n          useEffectsErrorHandler: false,\n        });\n      }\n\n      const mock = new Fixture();\n\n      expect(getCreateEffectMetadata(mock)).toEqual([\n        {\n          propertyName: 'a',\n          dispatch: true,\n          functional: false,\n          useEffectsErrorHandler: true,\n        },\n        {\n          propertyName: 'b',\n          dispatch: true,\n          functional: false,\n          useEffectsErrorHandler: true,\n        },\n        {\n          propertyName: 'c',\n          dispatch: false,\n          functional: false,\n          useEffectsErrorHandler: true,\n        },\n        {\n          propertyName: 'd',\n          dispatch: true,\n          functional: false,\n          useEffectsErrorHandler: true,\n        },\n        {\n          propertyName: 'e',\n          dispatch: true,\n          functional: true,\n          useEffectsErrorHandler: false,\n        },\n        {\n          propertyName: 'f',\n          dispatch: false,\n          functional: true,\n          useEffectsErrorHandler: false,\n        },\n        {\n          propertyName: 'g',\n          dispatch: true,\n          functional: false,\n          useEffectsErrorHandler: false,\n        },\n      ]);\n    });\n\n    it('should return an empty array if the effect has not been created with createEffect()', () => {\n      const fakeCreateEffect: any = () => {};\n\n      class Fixture {\n        a = fakeCreateEffect(() => of({ type: 'A' }));\n        b = new Proxy(\n          {},\n          {\n            get(_, prop) {\n              return () => Promise.resolve('bob');\n            },\n          }\n        );\n      }\n\n      const mock = new Fixture();\n\n      expect(getCreateEffectMetadata(mock)).toEqual([]);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/effects/spec/effect_sources.spec.ts",
    "content": "import { vi } from 'vitest';\nimport { ErrorHandler } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { TestScheduler } from 'rxjs/testing';\nimport {\n  concat,\n  NEVER,\n  Observable,\n  of,\n  throwError,\n  timer,\n  Subject,\n} from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport {\n  EffectSources,\n  OnIdentifyEffects,\n  OnInitEffects,\n  createEffect,\n  EFFECTS_ERROR_HANDLER,\n  EffectsErrorHandler,\n  Actions,\n} from '../';\nimport { defaultEffectsErrorHandler } from '../src/effects_error_handler';\nimport { EffectsRunner } from '../src/effects_runner';\nimport { Store } from '@ngrx/store';\nimport { ofType } from '../src';\n\ndescribe('EffectSources', () => {\n  let mockErrorReporter: ErrorHandler;\n  let effectSources: EffectSources;\n  let effectsErrorHandler: EffectsErrorHandler;\n  let testScheduler: TestScheduler;\n\n  beforeEach(() => {\n    testScheduler = new TestScheduler((actual, expected) => {\n      expect(actual).toEqual(expected);\n    });\n\n    TestBed.configureTestingModule({\n      providers: [\n        {\n          provide: EFFECTS_ERROR_HANDLER,\n          useValue: defaultEffectsErrorHandler,\n        },\n        EffectSources,\n        EffectsRunner,\n        {\n          provide: Store,\n          useValue: {\n            dispatch: vi.fn(),\n          },\n        },\n      ],\n    });\n\n    const effectsRunner = TestBed.inject(EffectsRunner);\n    effectsRunner.start();\n\n    mockErrorReporter = TestBed.inject(ErrorHandler);\n    effectSources = TestBed.inject(EffectSources);\n    effectsErrorHandler = TestBed.inject(EFFECTS_ERROR_HANDLER);\n\n    vi.spyOn(mockErrorReporter, 'handleError').mockImplementation(() => void 0);\n  });\n\n  it('should have an \"addEffects\" method to push new source instances', () => {\n    const effectSource = {};\n    vi.spyOn(effectSources, 'next');\n\n    effectSources.addEffects(effectSource);\n\n    expect(effectSources.next).toHaveBeenCalledWith(effectSource);\n  });\n\n  describe('toActions() Operator', () => {\n    function toActions(source: any): Observable<any> {\n      source['errorHandler'] = mockErrorReporter;\n      source['effectsErrorHandler'] = effectsErrorHandler;\n      return (effectSources as any)['toActions'].call(source);\n    }\n\n    describe('with createEffect()', () => {\n      const a = { type: 'From Source A' };\n      const b = { type: 'From Source B' };\n      const c = { type: 'From Source C that completes' };\n      const d = { not: 'a valid action' };\n      const e = undefined;\n      const f = null;\n      const i = { type: 'From Source Identifier' };\n      const i2 = { type: 'From Source Identifier 2' };\n      const initAction = { type: '[SourceWithInitAction] Init' };\n\n      const circularRef = {} as any;\n      circularRef.circularRef = circularRef;\n      const g = { circularRef };\n\n      const error = new Error('An Error');\n\n      class SourceA {\n        a$ = createEffect(() => alwaysOf(a));\n      }\n\n      class SourceB {\n        b$ = createEffect(() => alwaysOf(b));\n      }\n\n      class SourceC {\n        c$ = createEffect(() => of(c));\n      }\n\n      class SourceD {\n        d$ = createEffect(() => alwaysOf(d) as any);\n      }\n\n      class SourceE {\n        e$ = createEffect(() => alwaysOf(e) as any);\n      }\n\n      class SourceF {\n        f$ = createEffect(() => alwaysOf(f) as any);\n      }\n\n      class SourceG {\n        g$ = createEffect(() => alwaysOf(g) as any);\n      }\n\n      class SourceError {\n        e$ = createEffect(() => throwError(() => error) as any);\n      }\n\n      class SourceH {\n        empty = createEffect(() => of('value') as any);\n        never = createEffect(\n          () => timer(50, testScheduler).pipe(map(() => 'update')) as any\n        );\n      }\n\n      class SourceWithIdentifier implements OnIdentifyEffects {\n        effectIdentifier: string;\n        i$ = createEffect(() => alwaysOf(i));\n\n        ngrxOnIdentifyEffects() {\n          return this.effectIdentifier;\n        }\n\n        constructor(identifier: string) {\n          this.effectIdentifier = identifier;\n        }\n      }\n\n      class SourceWithIdentifier2 implements OnIdentifyEffects {\n        effectIdentifier: string;\n        i2$ = createEffect(() => alwaysOf(i2));\n\n        ngrxOnIdentifyEffects() {\n          return this.effectIdentifier;\n        }\n\n        constructor(identifier: string) {\n          this.effectIdentifier = identifier;\n        }\n      }\n\n      class SourceWithInitAction implements OnInitEffects, OnIdentifyEffects {\n        effectIdentifier: string;\n\n        effectOne = createEffect(() => {\n          return this.actions$.pipe(\n            ofType('Action 1'),\n            map(() => ({ type: 'Action 1 Response' }))\n          );\n        });\n\n        effectTwo = createEffect(() => {\n          return this.actions$.pipe(\n            ofType('Action 2'),\n            map(() => ({ type: 'Action 2 Response' }))\n          );\n        });\n\n        ngrxOnInitEffects() {\n          return initAction;\n        }\n\n        ngrxOnIdentifyEffects() {\n          return this.effectIdentifier;\n        }\n\n        constructor(\n          private actions$: Actions,\n          identifier = ''\n        ) {\n          this.effectIdentifier = identifier;\n        }\n      }\n\n      const recordA = {\n        a: createEffect(() => alwaysOf(a), { functional: true }),\n      };\n      const recordB = {\n        b: createEffect(() => alwaysOf(b), { functional: true }),\n      };\n      // a record with functional effects that is defined as\n      // a named import in a built package doesn't have a prototype\n      // for more info see: https://github.com/ngrx/platform/issues/3972\n      const recordC = Object.freeze({\n        __proto__: null,\n        c: createEffect(() => alwaysOf(c), { functional: true }),\n      });\n      const recordD = Object.freeze({\n        __proto__: null,\n        d: createEffect(() => alwaysOf(d as any), { functional: true }),\n      });\n\n      it('should resolve effects from class instances', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--b--', {\n            a: new SourceA(),\n            b: new SourceB(),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--a--b--', { a, b });\n        });\n      });\n\n      it('should resolve effects from records', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--b--c--', {\n            a: recordA,\n            b: recordB,\n            c: recordC,\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--a--b--c--', { a, b, c });\n        });\n      });\n\n      it('should ignore duplicate class instances', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--a--a--', {\n            a: new SourceA(),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--a--------', { a });\n        });\n      });\n\n      it('should ignore different instances of the same class', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--b--', {\n            a: new SourceA(),\n            b: new SourceA(),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--a-----', { a });\n        });\n      });\n\n      it('should ignore duplicate records', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--b--', { a: recordA, b: recordA });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--a-----', { a });\n        });\n      });\n\n      it('should resolve effects with different identifiers', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--b--c--', {\n            a: new SourceWithIdentifier('a'),\n            b: new SourceWithIdentifier('b'),\n            c: new SourceWithIdentifier('c'),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--i--i--i--', { i });\n        });\n      });\n\n      it('should ignore effects with the same identifier', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--b--c--', {\n            a: new SourceWithIdentifier('a'),\n            b: new SourceWithIdentifier('a'),\n            c: new SourceWithIdentifier('a'),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--i--------', { i });\n        });\n      });\n\n      it('should resolve effects with same identifiers but different classes', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--b--c--d--', {\n            a: new SourceWithIdentifier('a'),\n            b: new SourceWithIdentifier2('a'),\n            c: new SourceWithIdentifier('b'),\n            d: new SourceWithIdentifier2('b'),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--a--b--a--b--', {\n            a: i,\n            b: i2,\n          });\n        });\n      });\n\n      it('should start with an action after being registered with OnInitEffects', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--', {\n            a: new SourceWithInitAction(new Subject()),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--a--', { a: initAction });\n        });\n      });\n\n      it('should not start twice for the same instance', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--a--', {\n            a: new SourceWithInitAction(new Subject()),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--a-----', { a: initAction });\n        });\n      });\n\n      it('should start twice for the same instance with a different key', () => {\n        testScheduler.run(({ cold, expectObservable }) => {\n          const sources$ = cold('--a--b--', {\n            a: new SourceWithInitAction(new Subject(), 'a'),\n            b: new SourceWithInitAction(new Subject(), 'b'),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('--a--a--', { a: initAction });\n        });\n      });\n\n      it('should report an error if a class-based effect dispatches an invalid action', () => {\n        const sources$ = of(new SourceD());\n\n        toActions(sources$).subscribe();\n\n        expect(mockErrorReporter.handleError).toHaveBeenCalledWith(\n          new Error(\n            'Effect \"SourceD.d$\" dispatched an invalid action: {\"not\":\"a valid action\"}'\n          )\n        );\n      });\n\n      it('should report an error if a functional effect dispatches an invalid action', () => {\n        const sources$ = of(recordD);\n\n        toActions(sources$).subscribe();\n\n        expect(mockErrorReporter.handleError).toHaveBeenCalledWith(\n          new Error(\n            'Effect \"d()\" dispatched an invalid action: {\"not\":\"a valid action\"}'\n          )\n        );\n      });\n\n      it('should report an error if an effect dispatches an `undefined`', () => {\n        const sources$ = of(new SourceE());\n\n        toActions(sources$).subscribe();\n\n        expect(mockErrorReporter.handleError).toHaveBeenCalledWith(\n          new Error(\n            'Effect \"SourceE.e$\" dispatched an invalid action: undefined'\n          )\n        );\n      });\n\n      it('should report an error if an effect dispatches a `null`', () => {\n        const sources$ = of(new SourceF());\n\n        toActions(sources$).subscribe();\n\n        expect(mockErrorReporter.handleError).toHaveBeenCalledWith(\n          new Error('Effect \"SourceF.f$\" dispatched an invalid action: null')\n        );\n      });\n\n      it('should report an error if an effect throws one', () => {\n        const sources$ = of(new SourceError());\n\n        toActions(sources$).subscribe();\n\n        expect(mockErrorReporter.handleError).toHaveBeenCalledWith(\n          new Error('An Error')\n        );\n      });\n\n      it('should resubscribe on error by default', () => {\n        testScheduler.run(({ hot, expectObservable }) => {\n          const sources$ = of(\n            new (class {\n              b$ = createEffect(() =>\n                hot('a--e--b--e--c--e--d').pipe(\n                  map((v) => {\n                    if (v == 'e') throw new Error('An Error');\n                    return v;\n                  })\n                )\n              );\n            })()\n          );\n\n          expectObservable(toActions(sources$)).toBe('a-----b-----c-----d');\n        });\n      });\n\n      it('should resubscribe on error by default when dispatch is false', () => {\n        testScheduler.run(({ hot, expectObservable }) => {\n          const sources$ = of(\n            new (class {\n              b$ = createEffect(\n                () =>\n                  hot('a--b--c--d').pipe(\n                    map((v) => {\n                      if (v == 'b') throw new Error('An Error');\n                      return v;\n                    })\n                  ),\n                { dispatch: false }\n              );\n            })()\n          );\n\n          expectObservable(toActions(sources$)).toBe('----------');\n        });\n      });\n\n      it('should not resubscribe on error when useEffectsErrorHandler is false', () => {\n        testScheduler.run(({ hot, expectObservable }) => {\n          const sources$ = of(\n            new (class {\n              b$ = createEffect(\n                () =>\n                  hot('a--b--c--d').pipe(\n                    map((v) => {\n                      if (v == 'b') throw new Error('An Error');\n                      return v;\n                    })\n                  ),\n                { dispatch: false, useEffectsErrorHandler: false }\n              );\n            })()\n          );\n\n          expectObservable(toActions(sources$)).toBe(\n            '---#',\n            undefined,\n            new Error('An Error')\n          );\n        });\n      });\n\n      it(`should not break when the action in the error message can't be stringified`, () => {\n        const sources$ = of(new SourceG());\n\n        toActions(sources$).subscribe();\n\n        expect(mockErrorReporter.handleError).toHaveBeenCalledWith(\n          new Error(\n            'Effect \"SourceG.g$\" dispatched an invalid action: [object Object]'\n          )\n        );\n      });\n\n      it('should not complete the group if just one effect completes', () => {\n        testScheduler.run(({ cold, expectObservable, scheduler }) => {\n          class SourceH {\n            empty = createEffect(() => of('value') as any);\n            never = createEffect(\n              () => timer(5, scheduler).pipe(map(() => 'update')) as any\n            );\n          }\n\n          const sources$ = cold('g', {\n            g: new SourceH(),\n          });\n\n          const output = toActions(sources$);\n\n          expectObservable(output).toBe('a----b-----', {\n            a: 'value',\n            b: 'update',\n          });\n        });\n      });\n    });\n  });\n\n  function alwaysOf<T>(value: T) {\n    return concat(of(value), NEVER);\n  }\n});\n"
  },
  {
    "path": "modules/effects/spec/effects_error_handler.spec.ts",
    "content": "import { vi, type MockInstance } from 'vitest';\nimport { ErrorHandler, Provider, Type } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { Action, Store } from '@ngrx/store';\nimport { Observable, of, throwError } from 'rxjs';\nimport { catchError } from 'rxjs/operators';\nimport { createEffect, EFFECTS_ERROR_HANDLER, EffectsModule } from '..';\nimport * as effectsSrc from '../src/effects_error_handler';\n\ndescribe('Effects Error Handler', () => {\n  let subscriptionCount: number;\n  let globalErrorHandler: MockInstance;\n  let storeNext: MockInstance;\n\n  function makeEffectTestBed(effect: Type<any>, ...providers: Provider[]) {\n    subscriptionCount = 0;\n\n    TestBed.configureTestingModule({\n      imports: [EffectsModule.forRoot([effect])],\n      providers: [\n        {\n          provide: Store,\n          useValue: {\n            next: vi.fn(),\n            dispatch: vi.fn(),\n          },\n        },\n        {\n          provide: ErrorHandler,\n          useValue: {\n            handleError: vi.fn(),\n          },\n        },\n        ...providers,\n      ],\n    });\n\n    globalErrorHandler = TestBed.inject(ErrorHandler)\n      .handleError as MockInstance;\n    const store = TestBed.inject(Store);\n    storeNext = store.next as MockInstance;\n  }\n\n  it('should retry on infinite error up to 10 times', () => {\n    makeEffectTestBed(AlwaysErrorEffect);\n\n    expect(globalErrorHandler).toHaveBeenCalledTimes(10);\n  });\n\n  it('should retry and notify error handler when effect error handler is not provided', () => {\n    makeEffectTestBed(ErrorEffect);\n\n    // two subscriptions expected:\n    // 1. Initial subscription to the effect (this will error)\n    // 2. Resubscription to the effect after error (this will not error)\n    expect(subscriptionCount).toBe(2);\n    expect(globalErrorHandler).toHaveBeenCalledWith(new Error('effectError'));\n  });\n\n  it('should use custom error behavior when EFFECTS_ERROR_HANDLER is provided', () => {\n    const effectsErrorHandlerSpy = vi\n      .fn()\n      .mockImplementation(\n        (effect$: Observable<any>, errorHandler: ErrorHandler) => {\n          return effect$.pipe(\n            catchError((err) => {\n              errorHandler.handleError(\n                new Error('inside custom handler: ' + err.message)\n              );\n              return of({ type: 'custom action' });\n            })\n          );\n        }\n      );\n\n    makeEffectTestBed(ErrorEffect, {\n      provide: EFFECTS_ERROR_HANDLER,\n      useValue: effectsErrorHandlerSpy,\n    });\n\n    expect(effectsErrorHandlerSpy).toHaveBeenCalledWith(\n      expect.any(Observable),\n      TestBed.inject(ErrorHandler)\n    );\n    expect(globalErrorHandler).toHaveBeenCalledWith(\n      new Error('inside custom handler: effectError')\n    );\n    expect(subscriptionCount).toBe(1);\n    expect(storeNext).toHaveBeenCalledWith({ type: 'custom action' });\n  });\n\n  class ErrorEffect {\n    effect$ = createEffect(errorFirstSubscriber, {\n      useEffectsErrorHandler: true,\n    });\n  }\n\n  class AlwaysErrorEffect {\n    effect$ = createEffect(() => throwError(() => 'always an error') as any);\n  }\n\n  /**\n   * This observable factory returns an observable that will never emit, but the first subscriber will get an immediate\n   * error. All subsequent subscribers will just get an observable that does not emit.\n   */\n  function errorFirstSubscriber(): Observable<Action> {\n    return new Observable((observer) => {\n      subscriptionCount++;\n\n      if (subscriptionCount === 1) {\n        observer.error(new Error('effectError'));\n      }\n    });\n  }\n});\n"
  },
  {
    "path": "modules/effects/spec/effects_feature_module.spec.ts",
    "content": "import { vi, type MockInstance } from 'vitest';\nimport { Injectable, NgModule } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  Action,\n  createFeatureSelector,\n  createSelector,\n  select,\n  Store,\n  StoreModule,\n} from '@ngrx/store';\nimport { firstValueFrom } from 'rxjs';\nimport { map, take, withLatestFrom } from 'rxjs/operators';\nimport { Actions, EffectsModule, ofType, createEffect } from '../';\nimport { EffectsFeatureModule } from '../src/effects_feature_module';\nimport { EffectsRootModule } from '../src/effects_root_module';\nimport { _FEATURE_EFFECTS_INSTANCE_GROUPS } from '../src/tokens';\n\ndescribe('Effects Feature Module', () => {\n  describe('when registered', () => {\n    const sourceA = 'sourceA';\n    const sourceB = 'sourceB';\n    const sourceC = 'sourceC';\n    const effectSourceGroups = [[sourceA], [sourceB], [sourceC]];\n\n    let mockEffectSources: { addEffects: MockInstance };\n\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        providers: [\n          {\n            provide: EffectsRootModule,\n            useValue: {\n              addEffects: vi.fn(),\n            },\n          },\n          {\n            provide: _FEATURE_EFFECTS_INSTANCE_GROUPS,\n            useValue: effectSourceGroups,\n          },\n          EffectsFeatureModule,\n        ],\n      });\n\n      mockEffectSources = TestBed.inject<unknown>(EffectsRootModule) as {\n        addEffects: MockInstance;\n      };\n    });\n\n    it('should add all effects when instantiated', () => {\n      TestBed.inject(EffectsFeatureModule);\n\n      expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceA);\n      expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceB);\n      expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceC);\n    });\n  });\n\n  describe('when registered in a different NgModule from the feature state', () => {\n    let effects: FeatureEffects;\n    let store: Store<any>;\n\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        imports: [AppModule],\n      });\n\n      effects = TestBed.inject(FeatureEffects);\n      store = TestBed.inject(Store);\n    });\n\n    it('should have the feature state defined to select from the createEffect', async () => {\n      const action = { type: 'CREATE_INCREMENT' };\n      const result = { type: 'CREATE_INCREASE' };\n\n      const effectPromise = firstValueFrom(\n        effects.createEffectWithStore.pipe(take(1))\n      );\n\n      store.dispatch(action);\n\n      const res = await effectPromise;\n      expect(res).toEqual(result);\n\n      const data = await firstValueFrom(\n        store.pipe(select(getCreateDataState), take(1))\n      );\n      expect(data).toBe(220);\n    });\n  });\n});\n\nconst FEATURE_KEY = 'feature';\n\ninterface State {\n  FEATURE_KEY: DataState;\n}\n\ninterface DataState {\n  data: number;\n  createData: number;\n}\n\nconst initialState: DataState = {\n  data: 100,\n  createData: 200,\n};\n\nfunction reducer(state: DataState = initialState, action: Action) {\n  switch (action.type) {\n    case 'INCREASE':\n      return {\n        ...state,\n        data: state.data + 10,\n      };\n    case 'CREATE_INCREASE':\n      return {\n        ...state,\n        createData: state.createData + 20,\n      };\n  }\n  return state;\n}\n\nconst getFeatureState = createFeatureSelector<DataState>(FEATURE_KEY);\n\nconst getDataState = createSelector(getFeatureState, (state) => state.data);\nconst getCreateDataState = createSelector(\n  getFeatureState,\n  (state) => state.createData\n);\n\n@Injectable()\nclass FeatureEffects {\n  constructor(\n    private actions: Actions,\n    private store: Store<State>\n  ) {}\n\n  createEffectWithStore = createEffect(() =>\n    this.actions.pipe(\n      ofType('CREATE_INCREMENT'),\n      withLatestFrom(this.store.select(getDataState)),\n      map(([action, state]) => ({ type: 'CREATE_INCREASE' }))\n    )\n  );\n}\n\n@NgModule({\n  imports: [EffectsModule.forFeature([FeatureEffects])],\n})\nclass FeatureEffectsModule {}\n\n@NgModule({\n  imports: [FeatureEffectsModule, StoreModule.forFeature(FEATURE_KEY, reducer)],\n})\nclass FeatureModule {}\n\n@NgModule({\n  imports: [StoreModule.forRoot({}), EffectsModule.forRoot([]), FeatureModule],\n})\nclass AppModule {}\n"
  },
  {
    "path": "modules/effects/spec/effects_metadata.spec.ts",
    "content": "import { getEffectsMetadata, getSourceMetadata } from '../src/effects_metadata';\nimport { of } from 'rxjs';\nimport { createEffect } from '..';\nimport { EffectMetadata } from '../src/models';\n\ndescribe('Effects metadata', () => {\n  describe('getSourceMetadata', () => {\n    it('should create metadata for createEffect', () => {\n      class Fixture {\n        effectSimple = createEffect(() => of({ type: 'a' }));\n        effectNoDispatch = createEffect(() => of({ type: 'a' }), {\n          functional: true,\n          dispatch: false,\n        });\n        noEffect: any;\n        effectWithMethod = createEffect(() => () => of({ type: 'a' }));\n        effectWithUseEffectsErrorHandler = createEffect(\n          () => () => of({ type: 'a' }),\n          {\n            dispatch: true,\n            useEffectsErrorHandler: false,\n          }\n        );\n      }\n\n      const mock = new Fixture();\n      const expected: EffectMetadata<Fixture>[] = [\n        {\n          propertyName: 'effectSimple',\n          dispatch: true,\n          functional: false,\n          useEffectsErrorHandler: true,\n        },\n        {\n          propertyName: 'effectNoDispatch',\n          dispatch: false,\n          functional: true,\n          useEffectsErrorHandler: true,\n        },\n        {\n          propertyName: 'effectWithMethod',\n          dispatch: true,\n          functional: false,\n          useEffectsErrorHandler: true,\n        },\n        {\n          propertyName: 'effectWithUseEffectsErrorHandler',\n          dispatch: true,\n          functional: false,\n          useEffectsErrorHandler: false,\n        },\n      ];\n\n      expect(getSourceMetadata(mock)).toEqual(expect.arrayContaining(expected));\n      expect(getSourceMetadata(mock).length).toEqual(expected.length);\n    });\n  });\n\n  describe('getEffectsMetadata', () => {\n    it('should get map of metadata for all effects created', () => {\n      class Fixture {\n        effectSimple = createEffect(() => of({ type: 'a' }));\n        effectNoDispatch = createEffect(() => of({ type: 'a' }), {\n          dispatch: false,\n        });\n        noEffect: any;\n        effectWithMethod = createEffect(() => () => of({ type: 'a' }));\n        effectWithUseEffectsErrorHandler = createEffect(\n          () => () => of({ type: 'a' }),\n          {\n            dispatch: true,\n            useEffectsErrorHandler: false,\n          }\n        );\n      }\n\n      const mock = new Fixture();\n\n      expect(getEffectsMetadata(mock)).toEqual({\n        effectSimple: {\n          dispatch: true,\n          useEffectsErrorHandler: true,\n        },\n        effectNoDispatch: {\n          dispatch: false,\n          useEffectsErrorHandler: true,\n        },\n        effectWithMethod: {\n          dispatch: true,\n          useEffectsErrorHandler: true,\n        },\n        effectWithUseEffectsErrorHandler: {\n          dispatch: true,\n          useEffectsErrorHandler: false,\n        },\n      });\n    });\n\n    it('should return an empty map if the class does not have effects', () => {\n      const fakeCreateEffect: any = () => {};\n      class Fixture {\n        a: any;\n        b = fakeCreateEffect(() => of({ type: 'a' }));\n      }\n\n      const mock = new Fixture();\n\n      expect(getEffectsMetadata(mock)).toEqual({});\n    });\n  });\n});\n"
  },
  {
    "path": "modules/effects/spec/effects_resolver.spec.ts",
    "content": "describe('mergeEffects', () => {\n  it('should work', () => {\n    expect(true).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "modules/effects/spec/effects_root_module.spec.ts",
    "content": "import { vi } from 'vitest';\nimport { TestBed } from '@angular/core/testing';\nimport { INIT, Store, StoreModule } from '@ngrx/store';\n\nimport { EffectsModule } from '../src/effects_module';\nimport { ROOT_EFFECTS_INIT } from '../src/effects_actions';\n\ndescribe('Effects Root Module', () => {\n  const foo = 'foo';\n  const reducer = vi.fn().mockReturnValue(foo);\n\n  beforeEach(() => {\n    reducer.mockClear();\n  });\n\n  it('dispatches the root effects init action', () => {\n    TestBed.configureTestingModule({\n      imports: [\n        StoreModule.forRoot({ reducer }, { initialState: { reducer: foo } }),\n        EffectsModule.forRoot([]),\n      ],\n    });\n\n    const store = TestBed.inject(Store);\n\n    expect(reducer).toHaveBeenCalledWith(foo, {\n      type: INIT,\n    });\n    expect(reducer).toHaveBeenCalledWith(foo, {\n      type: ROOT_EFFECTS_INIT,\n    });\n  });\n\n  it(`doesn't dispatch the root effects init action when EffectsModule isn't used`, () => {\n    TestBed.configureTestingModule({\n      imports: [\n        StoreModule.forRoot({ reducer }, { initialState: { reducer: foo } }),\n      ],\n    });\n\n    const store = TestBed.inject(Store);\n\n    expect(reducer).toHaveBeenCalledWith(foo, {\n      type: INIT,\n    });\n    expect(reducer).not.toHaveBeenCalledWith(foo, {\n      type: ROOT_EFFECTS_INIT,\n    });\n  });\n});\n"
  },
  {
    "path": "modules/effects/spec/integration.spec.ts",
    "content": "import { vi } from 'vitest';\nimport { NgModule, Injectable, InjectionToken } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { RouterTestingModule } from '@angular/router/testing';\nimport { Router } from '@angular/router';\nimport { Action, StoreModule, INIT } from '@ngrx/store';\nimport { concat, exhaustMap, map, NEVER, Observable, of, tap } from 'rxjs';\nimport {\n  EffectsModule,\n  OnInitEffects,\n  ROOT_EFFECTS_INIT,\n  OnIdentifyEffects,\n  EffectSources,\n  Actions,\n  USER_PROVIDED_EFFECTS,\n} from '..';\nimport { ofType, createEffect, OnRunEffects, EffectNotification } from '../src';\n\ndescribe('NgRx Effects Integration spec', () => {\n  it('throws if forRoot() with Effects is used more than once', async () => {\n    TestBed.configureTestingModule({\n      imports: [\n        StoreModule.forRoot({}),\n        EffectsModule.forRoot([]),\n        RouterTestingModule.withRoutes([]),\n      ],\n    });\n\n    const router: Router = TestBed.inject(Router);\n    router.resetConfig([\n      {\n        path: 'feature-path',\n        loadChildren: () => Promise.resolve(FeatModuleWithForRoot),\n      },\n    ]);\n\n    await expect(router.navigateByUrl('/feature-path')).rejects.toThrow(\n      'EffectsModule.forRoot() called twice. Feature modules should use EffectsModule.forFeature() instead.'\n    );\n  });\n\n  it('does not throw if forRoot() is used more than once with empty effects', async () => {\n    TestBed.configureTestingModule({\n      imports: [\n        StoreModule.forRoot({}),\n        EffectsModule.forRoot([]),\n        RouterTestingModule.withRoutes([]),\n      ],\n    });\n\n    const router: Router = TestBed.inject(Router);\n    router.resetConfig([\n      {\n        path: 'feature-path',\n        loadChildren: () => Promise.resolve(FeatModuleWithEmptyForRoot),\n      },\n    ]);\n\n    await expect(router.navigateByUrl('/feature-path')).resolves.toBeTruthy();\n  });\n\n  it('runs provided class and functional effects', () => {\n    const obs$ = concat(of('ngrx'), NEVER);\n    const classEffectRun = vi.fn();\n    const functionalEffectRun = vi.fn();\n    const classEffect$ = createEffect(() => obs$.pipe(tap(classEffectRun)), {\n      dispatch: false,\n    });\n    const functionalEffect = createEffect(\n      () => obs$.pipe(tap(functionalEffectRun)),\n      {\n        functional: true,\n        dispatch: false,\n      }\n    );\n\n    class ClassEffects1 {\n      classEffect$ = classEffect$;\n    }\n\n    class ClassEffects2 {\n      classEffect$ = classEffect$;\n    }\n\n    const functionalEffects1 = { functionalEffect };\n    const functionalEffects2 = { functionalEffect };\n\n    TestBed.configureTestingModule({\n      imports: [\n        StoreModule.forRoot(),\n        EffectsModule.forRoot(ClassEffects1, functionalEffects1),\n        EffectsModule.forFeature(\n          ClassEffects1,\n          functionalEffects2,\n          ClassEffects2\n        ),\n        EffectsModule.forFeature(\n          functionalEffects1,\n          functionalEffects2,\n          ClassEffects2\n        ),\n      ],\n    });\n    TestBed.inject(EffectSources);\n\n    expect(classEffectRun).toHaveBeenCalledTimes(2);\n    expect(functionalEffectRun).toHaveBeenCalledTimes(2);\n  });\n\n  it('runs user provided effects defined as injection token', () => {\n    const userProvidedEffectRun = vi.fn();\n\n    const TOKEN_EFFECTS = new InjectionToken('Token Effects', {\n      providedIn: 'root',\n      factory: () => ({\n        userProvidedEffect$: createEffect(\n          () => concat(of('ngrx'), NEVER).pipe(tap(userProvidedEffectRun)),\n          { dispatch: false }\n        ),\n      }),\n    });\n\n    TestBed.configureTestingModule({\n      imports: [StoreModule.forRoot({}), EffectsModule.forRoot([])],\n      providers: [\n        {\n          provide: USER_PROVIDED_EFFECTS,\n          useValue: [TOKEN_EFFECTS],\n          multi: true,\n        },\n      ],\n    });\n    TestBed.inject(EffectSources);\n\n    expect(userProvidedEffectRun).toHaveBeenCalledTimes(1);\n  });\n\n  describe('actions', () => {\n    const createDispatchedReducer =\n      (dispatchedActions: string[] = []) =>\n      (state = {}, action: Action) => {\n        dispatchedActions.push(action.type);\n        return state;\n      };\n\n    describe('init actions', () => {\n      it('should dispatch and react to init effect', () => {\n        const dispatchedActionsLog: string[] = [];\n        TestBed.configureTestingModule({\n          imports: [\n            StoreModule.forRoot({\n              dispatched: createDispatchedReducer(dispatchedActionsLog),\n            }),\n            EffectsModule.forRoot([EffectWithOnInitAndResponse]),\n          ],\n        });\n        TestBed.inject(EffectSources);\n\n        expect(dispatchedActionsLog).toEqual([\n          INIT,\n\n          '[EffectWithOnInitAndResponse]: INIT',\n          '[EffectWithOnInitAndResponse]: INIT Response',\n\n          ROOT_EFFECTS_INIT,\n        ]);\n      });\n\n      it('should dispatch once for an instance', () => {\n        const dispatchedActionsLog: string[] = [];\n        TestBed.configureTestingModule({\n          imports: [\n            StoreModule.forRoot({\n              dispatched: createDispatchedReducer(dispatchedActionsLog),\n            }),\n            EffectsModule.forRoot([\n              RootEffectWithInitAction,\n              RootEffectWithInitAction,\n              RootEffectWithInitAction2,\n            ]),\n            EffectsModule.forFeature([\n              RootEffectWithInitAction,\n              RootEffectWithInitAction2,\n            ]),\n          ],\n        });\n        TestBed.inject(EffectSources);\n\n        expect(dispatchedActionsLog).toEqual([\n          INIT,\n\n          '[RootEffectWithInitAction]: INIT',\n          '[RootEffectWithInitAction2]: INIT',\n\n          ROOT_EFFECTS_INIT,\n        ]);\n      });\n\n      it('should dispatch once per instance key', () => {\n        const dispatchedActionsLog: string[] = [];\n        TestBed.configureTestingModule({\n          imports: [\n            StoreModule.forRoot({\n              dispatched: createDispatchedReducer(dispatchedActionsLog),\n            }),\n            EffectsModule.forRoot([]),\n          ],\n        });\n        const effectsSources = TestBed.inject(EffectSources);\n\n        effectsSources.addEffects(\n          new FeatEffectWithIdentifierAndInitAction('One')\n        );\n        effectsSources.addEffects(\n          new FeatEffectWithIdentifierAndInitAction('Two')\n        );\n        effectsSources.addEffects(\n          new FeatEffectWithIdentifierAndInitAction('One')\n        );\n        effectsSources.addEffects(\n          new FeatEffectWithIdentifierAndInitAction('Two')\n        );\n\n        expect(dispatchedActionsLog).toEqual([\n          INIT,\n          ROOT_EFFECTS_INIT,\n\n          // for One\n          '[FeatEffectWithIdentifierAndInitAction]: INIT',\n          // for Two\n          '[FeatEffectWithIdentifierAndInitAction]: INIT',\n        ]);\n      });\n    });\n\n    it('should dispatch actions in the correct order', async () => {\n      const dispatchedActionsLog: string[] = [];\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot({\n            dispatched: createDispatchedReducer(dispatchedActionsLog),\n          }),\n          EffectsModule.forRoot([\n            EffectLoggerWithOnRunEffects,\n            RootEffectWithInitAction,\n            EffectWithOnInitAndResponse,\n            RootEffectWithoutLifecycle,\n            RootEffectWithInitActionWithPayload,\n          ]),\n          EffectsModule.forFeature([FeatEffectWithInitAction]),\n          RouterTestingModule.withRoutes([]),\n        ],\n      });\n\n      const logger = TestBed.inject(EffectLoggerWithOnRunEffects);\n\n      const effectSources = TestBed.inject(EffectSources);\n      effectSources.addEffects(\n        new FeatEffectWithIdentifierAndInitAction('one')\n      );\n      effectSources.addEffects(\n        new FeatEffectWithIdentifierAndInitAction('two')\n      );\n      effectSources.addEffects(\n        new FeatEffectWithIdentifierAndInitAction('one')\n      );\n\n      const router: Router = TestBed.inject(Router);\n\n      router.resetConfig([\n        {\n          path: 'feature-path',\n          loadChildren: () => Promise.resolve(FeatModuleWithForFeature),\n        },\n      ]);\n\n      await router.navigateByUrl('/feature-path');\n\n      const expectedLog = [\n        // first store init\n        INIT,\n\n        // second root effects\n        '[RootEffectWithInitAction]: INIT',\n        '[EffectWithOnInitAndResponse]: INIT',\n        '[EffectWithOnInitAndResponse]: INIT Response',\n        '[RootEffectWithInitActionWithPayload]: INIT',\n\n        // third effects init\n        ROOT_EFFECTS_INIT,\n\n        // next feat effects\n        '[FeatEffectWithInitAction]: INIT',\n\n        // lastly added features (3 effects but 2 unique keys)\n        '[FeatEffectWithIdentifierAndInitAction]: INIT',\n        '[FeatEffectWithIdentifierAndInitAction]: INIT',\n\n        // from lazy loaded module\n        '[FeatEffectFromLazyLoadedModuleWithInitAction]: INIT',\n      ];\n\n      // reducers should receive all actions\n      expect(dispatchedActionsLog).toEqual(expectedLog);\n\n      // ngrxOnRunEffects should receive all actions except STORE_INIT\n      expect(logger.actionsLog).toEqual(expectedLog.slice(1));\n    });\n\n    it('should dispatch user provided effects actions in order', async () => {\n      const dispatchedActionsLog: string[] = [];\n      TestBed.resetTestingModule();\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot({\n            dispatched: createDispatchedReducer(dispatchedActionsLog),\n          }),\n          EffectsModule.forRoot([\n            EffectLoggerWithOnRunEffects,\n            RootEffectWithInitAction,\n          ]),\n          RouterTestingModule.withRoutes([]),\n        ],\n        providers: [\n          UserProvidedEffect1,\n          {\n            provide: USER_PROVIDED_EFFECTS,\n            multi: true,\n            useValue: [UserProvidedEffect1],\n          },\n        ],\n      });\n\n      const logger = TestBed.inject(EffectLoggerWithOnRunEffects);\n      const router: Router = TestBed.inject(Router);\n      router.resetConfig([\n        {\n          path: 'feature-path',\n          loadChildren: () =>\n            Promise.resolve(FeatModuleWithUserProvidedEffects),\n        },\n      ]);\n\n      await router.navigateByUrl('/feature-path');\n\n      const expectedLog = [\n        // Store init\n        INIT,\n\n        // Root effects\n        '[RootEffectWithInitAction]: INIT',\n\n        // User provided effects loaded by root module\n        '[UserProvidedEffect1]: INIT',\n\n        // Effects init\n        ROOT_EFFECTS_INIT,\n\n        // User provided effects loaded by feature module\n        '[UserProvidedEffect2]: INIT',\n      ];\n      expect(dispatchedActionsLog).toEqual(expectedLog);\n    });\n  });\n\n  @Injectable()\n  class EffectLoggerWithOnRunEffects implements OnRunEffects {\n    actionsLog: string[] = [];\n\n    constructor(private actions$: Actions) {}\n\n    ngrxOnRunEffects(\n      resolvedEffects$: Observable<EffectNotification>\n    ): Observable<EffectNotification> {\n      return this.actions$.pipe(\n        tap((action) => this.actionsLog.push(action.type)),\n        exhaustMap(() => resolvedEffects$)\n      );\n    }\n  }\n\n  @Injectable()\n  class EffectWithOnInitAndResponse implements OnInitEffects {\n    response = createEffect(() => {\n      return this.actions$.pipe(\n        ofType('[EffectWithOnInitAndResponse]: INIT'),\n        map(() => ({ type: '[EffectWithOnInitAndResponse]: INIT Response' }))\n      );\n    });\n\n    noop = createEffect(() => {\n      return this.actions$.pipe(\n        ofType('noop'),\n        map(() => ({ type: 'noop response' }))\n      );\n    });\n\n    constructor(private actions$: Actions) {}\n\n    ngrxOnInitEffects(): Action {\n      return { type: '[EffectWithOnInitAndResponse]: INIT' };\n    }\n  }\n\n  class RootEffectWithInitAction implements OnInitEffects {\n    ngrxOnInitEffects(): Action {\n      return { type: '[RootEffectWithInitAction]: INIT' };\n    }\n  }\n\n  class RootEffectWithInitAction2 implements OnInitEffects {\n    ngrxOnInitEffects(): Action {\n      return { type: '[RootEffectWithInitAction2]: INIT' };\n    }\n  }\n\n  class ActionWithPayload implements Action {\n    readonly type = '[RootEffectWithInitActionWithPayload]: INIT';\n    readonly payload = 47;\n  }\n\n  class RootEffectWithInitActionWithPayload implements OnInitEffects {\n    ngrxOnInitEffects(): Action {\n      return new ActionWithPayload();\n    }\n  }\n\n  class RootEffectWithoutLifecycle {}\n\n  class UserProvidedEffect1 implements OnInitEffects {\n    public ngrxOnInitEffects(): Action {\n      return { type: '[UserProvidedEffect1]: INIT' };\n    }\n  }\n\n  class UserProvidedEffect2 implements OnInitEffects {\n    public ngrxOnInitEffects(): Action {\n      return { type: '[UserProvidedEffect2]: INIT' };\n    }\n  }\n\n  @NgModule({\n    imports: [EffectsModule.forFeature()],\n    providers: [\n      UserProvidedEffect2,\n      {\n        provide: USER_PROVIDED_EFFECTS,\n        multi: true,\n        useValue: [UserProvidedEffect2],\n      },\n    ],\n  })\n  class FeatModuleWithUserProvidedEffects {}\n\n  class FeatEffectWithInitAction implements OnInitEffects {\n    ngrxOnInitEffects(): Action {\n      return { type: '[FeatEffectWithInitAction]: INIT' };\n    }\n  }\n\n  class FeatEffectWithIdentifierAndInitAction\n    implements OnInitEffects, OnIdentifyEffects\n  {\n    ngrxOnIdentifyEffects(): string {\n      return this.effectIdentifier;\n    }\n\n    ngrxOnInitEffects(): Action {\n      return { type: '[FeatEffectWithIdentifierAndInitAction]: INIT' };\n    }\n\n    constructor(private effectIdentifier: string) {}\n  }\n\n  class FeatEffectFromLazyLoadedModuleWithInitAction implements OnInitEffects {\n    ngrxOnInitEffects(): Action {\n      return { type: '[FeatEffectFromLazyLoadedModuleWithInitAction]: INIT' };\n    }\n  }\n\n  @Injectable()\n  class SomeEffect {}\n\n  @NgModule({\n    imports: [EffectsModule.forRoot([SomeEffect])],\n  })\n  class FeatModuleWithForRoot {}\n\n  @NgModule({\n    imports: [EffectsModule.forRoot([])],\n  })\n  class FeatModuleWithEmptyForRoot {}\n\n  @NgModule({\n    imports: [\n      EffectsModule.forFeature([FeatEffectFromLazyLoadedModuleWithInitAction]),\n      // should not be loaded because it's already loaded in forRoot\n      EffectsModule.forFeature([FeatEffectWithInitAction]),\n    ],\n  })\n  class FeatModuleWithForFeature {}\n});\n"
  },
  {
    "path": "modules/effects/spec/provide_effects.spec.ts",
    "content": "import { vi } from 'vitest';\nimport {\n  inject,\n  Injectable,\n  provideEnvironmentInitializer,\n} from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  firstValueFrom,\n  forkJoin,\n  map,\n  Observable,\n  of,\n  switchMap,\n  take,\n} from 'rxjs';\nimport {\n  createAction,\n  createFeatureSelector,\n  createReducer,\n  props,\n  provideState,\n  provideStore,\n  Store,\n} from '@ngrx/store';\nimport { concatLatestFrom } from '@ngrx/operators';\nimport {\n  Actions,\n  createEffect,\n  EffectsRunner,\n  ofType,\n  provideEffects,\n  rootEffectsInit,\n} from '../src/index';\n\ndescribe('provideEffects', () => {\n  it('starts effects runner when called first time', () => {\n    TestBed.configureTestingModule({\n      providers: [\n        provideEnvironmentInitializer(() =>\n          vi.spyOn(inject(EffectsRunner), 'start')\n        ),\n        provideStore(),\n        // provide effects twice\n        provideEffects(),\n        provideEffects(),\n      ],\n    });\n\n    const effectsRunner = TestBed.inject(EffectsRunner);\n    expect(effectsRunner.start).toHaveBeenCalledTimes(1);\n  });\n\n  it('dispatches effects init action when called first time', () => {\n    TestBed.configureTestingModule({\n      providers: [\n        provideEnvironmentInitializer(() =>\n          vi.spyOn(inject(Store), 'dispatch')\n        ),\n        provideStore(),\n        // provide effects twice\n        provideEffects(),\n        provideEffects(),\n      ],\n    });\n\n    const store = TestBed.inject(Store);\n    expect(store.dispatch).toHaveBeenCalledWith(rootEffectsInit());\n    expect(store.dispatch).toHaveBeenCalledTimes(1);\n  });\n\n  it('throws an error when store is not provided', () => {\n    TestBed.configureTestingModule({\n      // provide only effects\n      providers: [provideEffects(TestEffects)],\n    });\n\n    expect(() => TestBed.inject(TestEffects)).toThrowError();\n  });\n\n  it('runs provided class effects', async () => {\n    TestBed.configureTestingModule({\n      providers: [provideStore(), provideEffects(TestEffects)],\n    });\n\n    const store = TestBed.inject(Store);\n    const effects = TestBed.inject(TestEffects);\n\n    const actionPromise = firstValueFrom(effects.simpleEffect$.pipe(take(1)));\n\n    store.dispatch(simpleEffectTest());\n\n    const action = await actionPromise;\n    expect(action).toEqual(simpleEffectDone());\n  });\n\n  it('runs provided functional effects', async () => {\n    TestBed.configureTestingModule({\n      providers: [TestService, provideStore(), provideEffects(testEffects)],\n    });\n\n    const store = TestBed.inject(Store);\n    const actions$ = TestBed.inject(Actions);\n\n    const resultPromise = firstValueFrom(\n      forkJoin([\n        actions$.pipe(ofType(simpleFnEffectDone), take(1)),\n        actions$.pipe(ofType(fnEffectWithServiceDone), take(1)),\n      ])\n    );\n\n    store.dispatch(simpleFnEffectTest());\n    store.dispatch(fnEffectWithServiceTest());\n\n    const [, { numbers }] = await resultPromise;\n    expect(numbers).toEqual([1, 2, 3]);\n  });\n\n  it('runs provided class and functional effects', async () => {\n    TestBed.configureTestingModule({\n      providers: [\n        TestService,\n        provideStore(),\n        provideEffects(TestEffects, testEffects),\n      ],\n    });\n\n    const store = TestBed.inject(Store);\n    const actions$ = TestBed.inject(Actions);\n\n    const resultPromise = firstValueFrom(\n      forkJoin([\n        actions$.pipe(ofType(simpleEffectDone), take(1)),\n        actions$.pipe(ofType(simpleFnEffectDone), take(1)),\n      ])\n    );\n\n    store.dispatch(simpleEffectTest());\n    store.dispatch(simpleFnEffectTest());\n\n    await resultPromise;\n  });\n\n  it('runs provided effects after root state registration', async () => {\n    TestBed.configureTestingModule({\n      providers: [\n        provideEffects(TestEffects),\n        // provide store after effects\n        provideStore({ [rootSliceKey]: createReducer('ngrx') }),\n      ],\n    });\n\n    const store = TestBed.inject(Store);\n    const effects = TestBed.inject(TestEffects);\n\n    const actionPromise = firstValueFrom(\n      effects.effectWithRootState$.pipe(take(1))\n    );\n\n    store.dispatch(effectWithRootStateTest());\n\n    const action = await actionPromise;\n    expect(action).toEqual(effectWithRootStateDone({ [rootSliceKey]: 'ngrx' }));\n  });\n\n  it('runs provided effects after feature state registration', async () => {\n    TestBed.configureTestingModule({\n      providers: [\n        provideStore(),\n        provideEffects(TestEffects),\n        // provide feature state after effects\n        provideState(featureSliceKey, createReducer('effects')),\n      ],\n    });\n\n    const store = TestBed.inject(Store);\n    const effects = TestBed.inject(TestEffects);\n\n    const actionPromise = firstValueFrom(\n      effects.effectWithFeatureState$.pipe(take(1))\n    );\n\n    store.dispatch(effectWithFeatureStateTest());\n\n    const action = await actionPromise;\n    expect(action).toEqual(\n      effectWithFeatureStateDone({ [featureSliceKey]: 'effects' })\n    );\n  });\n});\n\nconst rootSliceKey = 'rootSlice';\nconst featureSliceKey = 'featureSlice';\nconst selectRootSlice = createFeatureSelector<string>(rootSliceKey);\nconst selectFeatureSlice = createFeatureSelector<string>(featureSliceKey);\n\nconst simpleEffectTest = createAction('simpleEffectTest');\nconst simpleEffectDone = createAction('simpleEffectDone');\nconst simpleFnEffectTest = createAction('simpleFnEffectTest');\nconst simpleFnEffectDone = createAction('simpleFnEffectDone');\nconst fnEffectWithServiceTest = createAction('fnEffectWithServiceTest');\nconst fnEffectWithServiceDone = createAction(\n  'fnEffectWithServiceDone',\n  props<{ numbers: number[] }>()\n);\nconst effectWithRootStateTest = createAction('effectWithRootStateTest');\nconst effectWithRootStateDone = createAction(\n  'effectWithRootStateDone',\n  props<{ [rootSliceKey]: string }>()\n);\nconst effectWithFeatureStateTest = createAction('effectWithFeatureStateTest');\nconst effectWithFeatureStateDone = createAction(\n  'effectWithFeatureStateDone',\n  props<{ [featureSliceKey]: string }>()\n);\n\n@Injectable()\nclass TestEffects {\n  constructor(\n    private readonly actions$: Actions,\n    private readonly store: Store\n  ) {}\n\n  readonly simpleEffect$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(simpleEffectTest),\n      map(() => simpleEffectDone())\n    );\n  });\n\n  readonly effectWithRootState$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(effectWithRootStateTest),\n      concatLatestFrom(() => this.store.select(selectRootSlice)),\n      map(([, rootSlice]) => effectWithRootStateDone({ rootSlice }))\n    );\n  });\n\n  readonly effectWithFeatureState$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(effectWithFeatureStateTest),\n      concatLatestFrom(() => this.store.select(selectFeatureSlice)),\n      map(([, featureSlice]) => effectWithFeatureStateDone({ featureSlice }))\n    );\n  });\n}\n\n@Injectable()\nclass TestService {\n  getNumbers(): Observable<number[]> {\n    return of([1, 2, 3]);\n  }\n}\n\nconst testEffects = {\n  simpleFnEffect: createEffect(\n    () => {\n      return inject(Actions).pipe(\n        ofType(simpleFnEffectTest),\n        map(() => simpleFnEffectDone())\n      );\n    },\n    { functional: true }\n  ),\n  fnEffectWithService: createEffect(\n    (actions$ = inject(Actions), service = inject(TestService)) => {\n      return actions$.pipe(\n        ofType(fnEffectWithServiceTest),\n        switchMap(() => service.getNumbers()),\n        map((numbers) => fnEffectWithServiceDone({ numbers }))\n      );\n    },\n    { functional: true }\n  ),\n};\n"
  },
  {
    "path": "modules/effects/spec/types/effect_creator.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('createEffect()', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import { Action } from '@ngrx/store';\n      import { createEffect } from '@ngrx/effects';\n      import { createAction } from '@ngrx/store';\n      import { of } from 'rxjs';\n\n      ${code}`,\n    compilerOptions()\n  );\n\n  describe('dispatch: true', () => {\n    it('should enforce an Action return value', () => {\n      expectSnippet(`\n        const effect = createEffect(() => of({ type: 'a' }));\n      `).toSucceed();\n\n      expectSnippet(`\n        const effect = createEffect(() => of({ foo: 'a' }));\n      `).toFail(\n        /Type 'Observable<{ foo: string; }>' is not assignable to type 'EffectResult<Action<string>>'./\n      );\n    }, 10_000);\n\n    it('should help with action creator that is not called', () => {\n      // Action creator is called with parentheses.\n      expectSnippet(`\n      const action = createAction('action without props');\n      const effect = createEffect(() => of(action()));\n      `).toSucceed();\n\n      // Action creator is not called (no parentheses).\n      expectSnippet(`\n      const action = createAction('action without props');\n      const effect = createEffect(() => of(action));\n      `).toFail(\n        /ActionCreator cannot be dispatched. Did you forget to call the action creator function/\n      );\n    });\n\n    it('should enforce an Action return value when dispatch is provided', () => {\n      expectSnippet(`\n        const effect = createEffect(() => of({ type: 'a' }), { dispatch: true });\n      `).toSucceed();\n\n      expectSnippet(`\n        const effect = createEffect(() => of({ foo: 'a' }), { dispatch: true });\n      `).toFail(\n        /Type 'Observable<{ foo: string; }>' is not assignable to type 'EffectResult<Action<string>>'./\n      );\n    });\n\n    it('should create non-functional effect when functional is set to false', () => {\n      const snippet = expectSnippet(`\n        const effect1 = createEffect(\n          () => of({ type: 'a' }),\n          { functional: false }\n        );\n\n        const effect2 = createEffect(\n          () => of({ type: 'a' }),\n          // explicitly set dispatch: true\n          { functional: false, dispatch: true }\n        );\n      `);\n\n      snippet.toInfer(\n        'effect1',\n        'Observable<{ type: string; }> & CreateEffectMetadata'\n      );\n      snippet.toInfer(\n        'effect2',\n        'Observable<{ type: string; }> & CreateEffectMetadata'\n      );\n    });\n  });\n\n  describe('dispatch: false', () => {\n    it('should enforce an Observable return value', () => {\n      expectSnippet(`\n        const effect = createEffect(() => of({ foo: 'a' }), { dispatch: false });\n      `).toSucceed();\n\n      expectSnippet(`\n        const effect = createEffect(() => ({ foo: 'a' }), { dispatch: false });\n      `).toFail(\n        /Type '{ foo: string; }' is not assignable to type 'EffectResult<unknown>'./\n      );\n    });\n\n    it('should allow action creator even if it is not called', () => {\n      // Action creator is not called (no parentheses), but we have no-dispatch.\n      expectSnippet(`\n      const action = createAction('action without props');\n      const effect = createEffect(() => of(action), { dispatch: false });\n      `).toSucceed();\n    });\n\n    it('should create non-functional effect when functional is set to false', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          () => of('a'),\n          { functional: false, dispatch: false }\n        );\n      `).toInfer('effect', 'Observable<string> & CreateEffectMetadata');\n    });\n  });\n\n  describe('functional: true', () => {\n    it('should create dispatching effect without args', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          () => of({ type: 'a' }),\n          { functional: true }\n        );\n      `).toInfer(\n        'effect',\n        'FunctionalEffect<() => Observable<{ type: string; }>>'\n      );\n    });\n\n    it('should create dispatching effect with args', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          (type = 'a', x = 1, y = 2) => of({ type, x, y }),\n          { functional: true }\n        );\n      `).toInfer(\n        'effect',\n        'FunctionalEffect<(type?: string, x?: number, y?: number) => Observable<{ type: string; x: number; y: number; }>>'\n      );\n    });\n\n    it('should create dispatching effect when dispatch is set to true', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          ({ type = 'a' } = {}) => of({ type }),\n          { functional: true, dispatch: true }\n        );\n      `).toInfer(\n        'effect',\n        'FunctionalEffect<({ type }?: { type?: string; }) => Observable<{ type: string; }>>'\n      );\n    });\n\n    it('should create non-dispatching effect that returns action', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          (type = 'a') => of({ type }),\n          { functional: true, dispatch: false }\n        );\n      `).toInfer(\n        'effect',\n        'FunctionalEffect<(type?: string) => Observable<{ type: string; }>>'\n      );\n    });\n\n    it('should create non-dispatching effect that returns any observable', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          () => of('ngrx'),\n          { functional: true, dispatch: false }\n        );\n      `).toInfer('effect', 'FunctionalEffect<() => Observable<string>>');\n    });\n\n    it('should create non-dispatching effect that returns action creator', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          () => of(createAction('a')),\n          { functional: true, dispatch: false }\n        );\n      `).toInfer(\n        'effect',\n        'FunctionalEffect<() => Observable<ActionCreator<\"a\", () => Action<\"a\">>>>'\n      );\n    });\n\n    it('should be possible to invoke dispatching effect without args as function', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          () => of({ type: 'a' }),\n          { functional: true }\n        );\n        effect();\n\n        let effectArgs: Parameters<typeof effect>;\n      `).toInfer('effectArgs', '[]');\n    });\n\n    it('should be possible to invoke dispatching effect with args as function', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          (type = 'a', payload = 'b') => of({ type, payload }),\n          { functional: true }\n        );\n        effect();\n        effect('m');\n        effect('m', 's');\n\n        let effectArgs: Parameters<typeof effect>;\n      `).toInfer('effectArgs', '[type?: string, payload?: string]');\n    });\n\n    it('should be possible to invoke non-dispatching effect without args as function', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          () => of('a'),\n          { functional: true, dispatch: false }\n        );\n        effect();\n\n        let effectArgs: Parameters<typeof effect>;\n      `).toInfer('effectArgs', '[]');\n    });\n\n    it('should be possible to invoke non-dispatching effect with args as function', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          ({ a = 1, b = 2 } = {}) => of([a, b]),\n          { functional: true, dispatch: false }\n        );\n        effect();\n        effect({});\n        effect({ a: 10 });\n        effect({ b: 20 });\n        effect({ a: 100, b: 200 });\n\n        let effectArgs: Parameters<typeof effect>;\n      `).toInfer('effectArgs', '[{ a?: number; b?: number; }?]');\n    });\n\n    it('should fail when dispatching effect arguments do not have default values', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          (type: string) => of({ type }),\n          { functional: true }\n        );\n      `).toFail();\n    });\n\n    it('should fail when non-dispatching effect arguments do not have default values', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          (x: number, y: number) => of([x, y]),\n          { functional: true, dispatch: false }\n        );\n      `).toFail();\n    });\n\n    it('should fail when additional properties are added to the effect config', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          () => of({ type: 'a' }),\n          { functional: true, x: 1, y: 2 }\n        );\n      `).toFail();\n    });\n\n    it('should fail when Observable<Action> is not returned as dispatching effect result', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          () => of({ type: 123 }),\n          { functional: true }\n        );\n      `).toFail();\n\n      expectSnippet(`\n        const effect = createEffect(\n          () => of(123),\n          { functional: true, dispatch: true }\n        );\n      `).toFail();\n\n      expectSnippet(`\n        const effect = createEffect(\n          () => 123,\n          { functional: true }\n        );\n      `).toFail();\n    });\n\n    it('should fail when action creator is returned as dispatching effect result', () => {\n      expectSnippet(`\n        const effect = createEffect(\n          () => of(createAction('a')),\n          { functional: true }\n        );\n      `).toFail(\n        /ActionCreator cannot be dispatched. Did you forget to call the action creator function/\n      );\n\n      expectSnippet(`\n        const effect = createEffect(\n          () => of(createAction('a')),\n          { functional: true, dispatch: true }\n        );\n      `).toFail(\n        /ActionCreator cannot be dispatched. Did you forget to call the action creator function/\n      );\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/effects/spec/types/effects_module.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('EffectsModule()', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import {\n        CreateEffectMetadata,\n        EffectsModule,\n        FunctionalEffect,\n      } from '@ngrx/effects';\n      import { Observable } from 'rxjs';\n\n      class ClassEffects {}\n      const fnEffectsRecord: Record<string, FunctionalEffect> = {};\n      const nonFnEffectsRecord: Record<\n        string,\n        // it should fail if at least one effect in the record is not functional\n        FunctionalEffect | (Observable<unknown> & CreateEffectMetadata)\n      > = {};\n\n      ${code}`,\n    compilerOptions()\n  );\n\n  describe('forRoot', () => {\n    it('should compile without params', () => {\n      expectSnippet(`\n        EffectsModule.forRoot();\n      `).toSucceed();\n    }, 10_000);\n\n    it('should compile with a single effects class', () => {\n      expectSnippet(`\n        EffectsModule.forRoot(ClassEffects);\n      `).toSucceed();\n    });\n\n    it('should compile with an array of effects classes', () => {\n      expectSnippet(`\n        EffectsModule.forRoot([ClassEffects]);\n      `).toSucceed();\n    });\n\n    it('should compile with a sequence of effects classes', () => {\n      expectSnippet(`\n        EffectsModule.forRoot(ClassEffects, ClassEffects);\n      `).toSucceed();\n    });\n\n    it('should compile with a single functional effects record', () => {\n      expectSnippet(`\n        EffectsModule.forRoot(fnEffectsRecord);\n      `).toSucceed();\n    });\n\n    it('should compile with an array of functional effects records', () => {\n      expectSnippet(`\n        EffectsModule.forRoot([fnEffectsRecord, fnEffectsRecord]);\n      `).toSucceed();\n    });\n\n    it('should compile with a sequence of functional effects records', () => {\n      expectSnippet(`\n        EffectsModule.forRoot(fnEffectsRecord, fnEffectsRecord, fnEffectsRecord);\n      `).toSucceed();\n    });\n\n    it('should compile with an array of functional effects records and classes', () => {\n      expectSnippet(`\n        EffectsModule.forRoot([fnEffectsRecord, ClassEffects, fnEffectsRecord]);\n      `).toSucceed();\n    });\n\n    it('should compile with a sequence of functional effects records and classes', () => {\n      expectSnippet(`\n        EffectsModule.forRoot(ClassEffects, fnEffectsRecord, ClassEffects);\n      `).toSucceed();\n    });\n\n    it('should fail with a single non-functional effects record', () => {\n      expectSnippet(`\n        EffectsModule.forRoot(nonFnEffectsRecord);\n      `).toFail();\n    });\n\n    it('should fail with an array of classes, functional, and non-functional effects records', () => {\n      expectSnippet(`\n        EffectsModule.forRoot([fnEffectsRecord, ClassEffects, nonFnEffectsRecord]);\n      `).toFail();\n    });\n\n    it('should fail with a sequence of classes, functional, and non-functional effects records', () => {\n      expectSnippet(`\n        EffectsModule.forRoot(nonFnEffectsRecord, fnEffectsRecord, ClassEffects);\n      `).toFail();\n    });\n  });\n\n  describe('forFeature', () => {\n    it('should compile without params', () => {\n      expectSnippet(`\n        EffectsModule.forFeature();\n      `).toSucceed();\n    });\n\n    it('should compile with a single effects class', () => {\n      expectSnippet(`\n        EffectsModule.forFeature(ClassEffects);\n      `).toSucceed();\n    });\n\n    it('should compile with an array of effects classes', () => {\n      expectSnippet(`\n        EffectsModule.forFeature([ClassEffects]);\n      `).toSucceed();\n    });\n\n    it('should compile with a sequence of effects classes', () => {\n      expectSnippet(`\n        EffectsModule.forFeature(ClassEffects, ClassEffects);\n      `).toSucceed();\n    });\n\n    it('should compile with a single functional effects record', () => {\n      expectSnippet(`\n        EffectsModule.forFeature(fnEffectsRecord);\n      `).toSucceed();\n    });\n\n    it('should compile with an array of functional effects records', () => {\n      expectSnippet(`\n        EffectsModule.forFeature([fnEffectsRecord, fnEffectsRecord]);\n      `).toSucceed();\n    });\n\n    it('should compile with a sequence of functional effects records', () => {\n      expectSnippet(`\n        EffectsModule.forFeature(fnEffectsRecord, fnEffectsRecord, fnEffectsRecord);\n      `).toSucceed();\n    });\n\n    it('should compile with an array of functional effects records and classes', () => {\n      expectSnippet(`\n        EffectsModule.forFeature([fnEffectsRecord, ClassEffects, fnEffectsRecord]);\n      `).toSucceed();\n    });\n\n    it('should compile with a sequence of functional effects records and classes', () => {\n      expectSnippet(`\n        EffectsModule.forFeature(ClassEffects, fnEffectsRecord, ClassEffects);\n      `).toSucceed();\n    });\n\n    it('should fail with a single non-functional effects record', () => {\n      expectSnippet(`\n        EffectsModule.forFeature(nonFnEffectsRecord);\n      `).toFail();\n    });\n\n    it('should fail with an array of classes, functional, and non-functional effects records', () => {\n      expectSnippet(`\n        EffectsModule.forFeature([fnEffectsRecord, ClassEffects, nonFnEffectsRecord]);\n      `).toFail();\n    });\n\n    it('should fail with a sequence of classes, functional, and non-functional effects records', () => {\n      expectSnippet(`\n        EffectsModule.forFeature(nonFnEffectsRecord, fnEffectsRecord, ClassEffects);\n      `).toFail();\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/effects/spec/types/of_type.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('ofType()', () => {\n  describe('action creators', () => {\n    const expectSnippet = expecter(\n      (code) => `\n      import { Action, createAction, props } from '@ngrx/store';\n      import { Actions, ofType } from '@ngrx/effects';\n      import { of } from 'rxjs';\n\n      const actions$ = {} as Actions;\n\n      ${code}`,\n      compilerOptions()\n    );\n\n    it('should infer correctly', () => {\n      expectSnippet(`\n        const actionA = createAction('Action A');\n        const effect = actions$.pipe(ofType(actionA))\n      `).toInfer('effect', 'Observable<Action<\"Action A\">>');\n    }, 10_000);\n\n    it('should infer correctly with props', () => {\n      expectSnippet(`\n        const actionA = createAction('Action A', props<{ foo: string }>());\n        const effect = actions$.pipe(ofType(actionA))\n      `).toInfer('effect', 'Observable<{ foo: string; } & Action<\"Action A\">>');\n    });\n\n    it('should infer correctly with function', () => {\n      expectSnippet(`\n        const actionA = createAction('Action A', (foo: string) => ({ foo }));\n        const effect = actions$.pipe(ofType(actionA))\n      `).toInfer('effect', 'Observable<{ foo: string; } & Action<\"Action A\">>');\n    });\n\n    it('should infer correctly with multiple actions (with over 5 actions)', () => {\n      expectSnippet(`\n        const actionA = createAction('Action A');\n        const actionB = createAction('Action B');\n        const actionC = createAction('Action C');\n        const actionD = createAction('Action D');\n        const actionE = createAction('Action E');\n        const actionF = createAction('Action F');\n        const actionG = createAction('Action G');\n\n        const effect = actions$.pipe(ofType(actionA, actionB, actionC, actionD, actionE, actionF, actionG))\n      `).toInfer(\n        'effect',\n        'Observable<Action<\"Action A\"> | Action<\"Action B\"> | Action<\"Action C\"> | Action<\"Action D\"> | Action<\"Action E\"> | Action<\"Action F\"> | Action<...>>'\n      );\n    });\n  });\n\n  describe('strings with typed Actions', () => {\n    const expectSnippet = expecter(\n      (code) => `\n      import { Action } from '@ngrx/store';\n      import { Actions, ofType } from '@ngrx/effects';\n      import { of } from 'rxjs';\n\n      const ACTION_A = 'ACTION A'\n      const ACTION_B = 'ACTION B'\n      const ACTION_C = 'ACTION C'\n      const ACTION_D = 'ACTION D'\n      const ACTION_E = 'ACTION E'\n      const ACTION_F = 'ACTION F'\n\n      interface ActionA { type: typeof ACTION_A };\n      interface ActionB { type: typeof ACTION_B };\n      interface ActionC { type: typeof ACTION_C };\n      interface ActionD { type: typeof ACTION_D };\n      interface ActionE { type: typeof ACTION_E };\n      interface ActionF { type: typeof ACTION_F };\n\n      ${code}`,\n      compilerOptions()\n    );\n\n    it('should infer correctly', () => {\n      expectSnippet(`\n        const actions$ = {} as Actions<ActionA>;\n        const effect = actions$.pipe(ofType(ACTION_A))\n      `).toInfer('effect', 'Observable<ActionA>');\n    });\n\n    it('should infer correctly with multiple actions (up to 5 actions)', () => {\n      expectSnippet(`\n        const actions$ = {} as Actions<ActionA | ActionB | ActionC | ActionD | ActionE>;\n        const effect = actions$.pipe(ofType(ACTION_A, ACTION_B, ACTION_C, ACTION_D, ACTION_E))\n      `).toInfer(\n        'effect',\n        'Observable<ActionA | ActionB | ActionC | ActionD | ActionE>'\n      );\n    });\n\n    it('should infer to Action when more than 5 actions', () => {\n      expectSnippet(`\n        const actions$ = {} as Actions<ActionA | ActionB | ActionC | ActionD | ActionE | ActionF>;\n        const effect = actions$.pipe(ofType(ACTION_A, ACTION_B, ACTION_C, ACTION_D, ACTION_E, ACTION_F))\n      `).toInfer('effect', 'Observable<Action<string>>');\n    });\n\n    it('should infer to never when the action is not in Actions', () => {\n      expectSnippet(`\n        const actions$ = {} as Actions<ActionA>;\n        const effect = actions$.pipe(ofType(ACTION_B))\n      `).toInfer('effect', 'Observable<never>');\n    });\n  });\n\n  describe('strings ofType generic', () => {\n    const expectSnippet = expecter(\n      (code) => `\n      import { Action } from '@ngrx/store';\n      import { Actions, ofType } from '@ngrx/effects';\n      import { of } from 'rxjs';\n\n      const ACTION_A = 'ACTION A'\n      const ACTION_B = 'ACTION B'\n      const ACTION_C = 'ACTION C'\n      const ACTION_D = 'ACTION D'\n      const ACTION_E = 'ACTION E'\n      const ACTION_F = 'ACTION F'\n\n      interface ActionA { type: typeof ACTION_A };\n      interface ActionB { type: typeof ACTION_B };\n      interface ActionC { type: typeof ACTION_C };\n      interface ActionD { type: typeof ACTION_D };\n      interface ActionE { type: typeof ACTION_E };\n      interface ActionF { type: typeof ACTION_F };\n\n      ${code}`,\n      compilerOptions()\n    );\n\n    it('should infer correctly', () => {\n      expectSnippet(`\n        const actions$ = {} as Actions;\n        const effect = actions$.pipe(ofType<ActionA>(ACTION_A))\n      `).toInfer('effect', 'Observable<ActionA>');\n    });\n\n    it('should infer correctly with multiple actions (with over 5 actions)', () => {\n      expectSnippet(`\n        const actions$ = {} as Actions;\n        const effect = actions$.pipe(ofType<ActionA | ActionB | ActionC | ActionD | ActionE | ActionF>(ACTION_A, ACTION_B, ACTION_C, ACTION_D, ACTION_E, ACTION_F))\n      `).toInfer(\n        'effect',\n        'Observable<ActionA | ActionB | ActionC | ActionD | ActionE | ActionF>'\n      );\n    });\n\n    it('should infer to the generic even if the generic is wrong', () => {\n      expectSnippet(`\n        const actions$ = {} as Actions;\n        const effect = actions$.pipe(ofType<ActionA>(ACTION_B))\n      `).toInfer('effect', 'Observable<ActionA>');\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/effects/spec/types/provide_effects.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('provideEffects()', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import {\n        CreateEffectMetadata,\n        FunctionalEffect,\n        provideEffects,\n      } from '@ngrx/effects';\n      import { Observable } from 'rxjs';\n\n      class ClassEffects {}\n      const fnEffectsRecord: Record<string, FunctionalEffect> = {};\n      const nonFnEffectsRecord: Record<\n        string,\n        // it should fail if at least one effect in the record is not functional\n        FunctionalEffect | (Observable<unknown> & CreateEffectMetadata)\n      > = {};\n\n      ${code}`,\n    compilerOptions()\n  );\n\n  it('should compile without params', () => {\n    expectSnippet(`\n      provideEffects();\n    `).toSucceed();\n  });\n\n  it('should compile with a single effects class', () => {\n    expectSnippet(`\n      provideEffects(ClassEffects);\n    `).toSucceed();\n  });\n\n  it('should compile with an array of effects classes', () => {\n    expectSnippet(`\n      provideEffects([ClassEffects]);\n    `).toSucceed();\n  });\n\n  it('should compile with a sequence of effects classes', () => {\n    expectSnippet(`\n      provideEffects(ClassEffects, ClassEffects);\n    `).toSucceed();\n  });\n\n  it('should compile with a single functional effects record', () => {\n    expectSnippet(`\n      provideEffects(fnEffectsRecord);\n    `).toSucceed();\n  });\n\n  it('should compile with an array of functional effects records', () => {\n    expectSnippet(`\n      provideEffects([fnEffectsRecord, fnEffectsRecord]);\n    `).toSucceed();\n  });\n\n  it('should compile with a sequence of functional effects records', () => {\n    expectSnippet(`\n      provideEffects(fnEffectsRecord, fnEffectsRecord, fnEffectsRecord);\n    `).toSucceed();\n  });\n\n  it('should compile with an array of functional effects records and classes', () => {\n    expectSnippet(`\n      provideEffects([fnEffectsRecord, ClassEffects, fnEffectsRecord]);\n    `).toSucceed();\n  });\n\n  it('should compile with a sequence of functional effects records and classes', () => {\n    expectSnippet(`\n      provideEffects(ClassEffects, fnEffectsRecord, ClassEffects);\n    `).toSucceed();\n  });\n\n  it('should fail with a single non-functional effects record', () => {\n    expectSnippet(`\n      provideEffects(nonFnEffectsRecord);\n    `).toFail();\n  });\n\n  it('should fail with an array of classes, functional, and non-functional effects records', () => {\n    expectSnippet(`\n      provideEffects([fnEffectsRecord, ClassEffects, nonFnEffectsRecord]);\n    `).toFail();\n  });\n\n  it('should fail with a sequence of classes, functional, and non-functional effects records', () => {\n    expectSnippet(`\n      provideEffects(nonFnEffectsRecord, fnEffectsRecord, ClassEffects);\n    `).toFail();\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/effects/spec/types/utils.ts",
    "content": "export const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  target: 'ES2022',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  paths: {\n    '@ngrx/store': ['./modules/store'],\n    '@ngrx/effects': ['./modules/effects'],\n    '@ngrx/operators': ['./modules/operators'],\n    rxjs: ['../npm/node_modules/rxjs', './node_modules/rxjs'],\n  },\n});\n"
  },
  {
    "path": "modules/effects/spec/utils.spec.ts",
    "content": "import {\n  getClasses,\n  getSourceForInstance,\n  isClass,\n  isClassInstance,\n  isToken,\n} from '../src/utils';\nimport { InjectionToken } from '@angular/core';\n\ndescribe('getSourceForInstance', () => {\n  it('gets the prototype for an instance of a source', () => {\n    class Fixture {}\n    const instance = new Fixture();\n\n    const proto = getSourceForInstance(instance);\n\n    expect(proto).toBe(Fixture.prototype);\n  });\n});\n\ndescribe('isClassInstance', () => {\n  it('returns true for a class instance', () => {\n    class C {}\n    expect(isClassInstance(new C())).toBe(true);\n  });\n\n  it('returns false for a class', () => {\n    expect(isClassInstance(class C {})).toBe(false);\n  });\n\n  it('returns false for a record', () => {\n    expect(isClassInstance({ foo: 'bar' })).toBe(false);\n  });\n\n  it('returns false for a function', () => {\n    expect(isClassInstance(() => {})).toBe(false);\n  });\n\n  it('returns false for an object without prototype', () => {\n    const obj = Object.freeze({\n      __proto__: null,\n      foo: 'bar',\n    });\n\n    expect(isClassInstance(obj)).toBe(false);\n  });\n});\n\ndescribe('isClass', () => {\n  it('returns true for a class', () => {\n    expect(isClass(class C {})).toBe(true);\n  });\n\n  it('returns false for a record', () => {\n    expect(isClass({ foo: 'bar' })).toBe(false);\n  });\n});\n\ndescribe('getClasses', () => {\n  it('gets classes from an array with classes and records', () => {\n    class C1 {}\n    class C2 {}\n    class C3 {}\n\n    const classes = getClasses([C1, {}, C2, C3, { foo: 'bar' }]);\n    expect(classes).toEqual([C1, C2, C3]);\n  });\n});\n\ndescribe('isToken', () => {\n  it('returns true for a class', () => {\n    expect(isToken(class C {})).toBe(true);\n  });\n\n  it('returns true for an injection token', () => {\n    expect(isToken(new InjectionToken('foo'))).toBe(true);\n  });\n\n  it('returns false for a record', () => {\n    expect(isToken({ foo: 'bar' })).toBe(false);\n  });\n});\n"
  },
  {
    "path": "modules/effects/src/actions.ts",
    "content": "import { Inject, Injectable } from '@angular/core';\nimport {\n  Action,\n  ActionCreator,\n  Creator,\n  ScannedActionsSubject,\n} from '@ngrx/store';\nimport { Observable, OperatorFunction, Operator } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\n@Injectable({ providedIn: 'root' })\nexport class Actions<V = Action> extends Observable<V> {\n  constructor(@Inject(ScannedActionsSubject) source?: Observable<V>) {\n    super();\n\n    if (source) {\n      this.source = source;\n    }\n  }\n\n  override lift<R>(operator: Operator<V, R>): Observable<R> {\n    const observable = new Actions<R>();\n    observable.source = this;\n    observable.operator = operator;\n    return observable;\n  }\n}\n\n// Module-private helper type\ntype ActionExtractor<\n  T extends string | AC,\n  AC extends ActionCreator<string, Creator>,\n  E,\n> = T extends string ? E : ReturnType<Extract<T, AC>>;\n\nexport function ofType<\n  AC extends ActionCreator<string, Creator>[],\n  U extends Action = Action,\n  V = ReturnType<AC[number]>,\n>(...allowedTypes: AC): OperatorFunction<U, V>;\n\nexport function ofType<\n  E extends Extract<U, { type: T1 }>,\n  AC extends ActionCreator<string, Creator>,\n  T1 extends string | AC,\n  U extends Action = Action,\n  V = T1 extends string ? E : ReturnType<Extract<T1, AC>>,\n>(t1: T1): OperatorFunction<U, V>;\nexport function ofType<\n  E extends Extract<U, { type: T1 | T2 }>,\n  AC extends ActionCreator<string, Creator>,\n  T1 extends string | AC,\n  T2 extends string | AC,\n  U extends Action = Action,\n  V = ActionExtractor<T1 | T2, AC, E>,\n>(t1: T1, t2: T2): OperatorFunction<U, V>;\nexport function ofType<\n  E extends Extract<U, { type: T1 | T2 | T3 }>,\n  AC extends ActionCreator<string, Creator>,\n  T1 extends string | AC,\n  T2 extends string | AC,\n  T3 extends string | AC,\n  U extends Action = Action,\n  V = ActionExtractor<T1 | T2 | T3, AC, E>,\n>(t1: T1, t2: T2, t3: T3): OperatorFunction<U, V>;\nexport function ofType<\n  E extends Extract<U, { type: T1 | T2 | T3 | T4 }>,\n  AC extends ActionCreator<string, Creator>,\n  T1 extends string | AC,\n  T2 extends string | AC,\n  T3 extends string | AC,\n  T4 extends string | AC,\n  U extends Action = Action,\n  V = ActionExtractor<T1 | T2 | T3 | T4, AC, E>,\n>(t1: T1, t2: T2, t3: T3, t4: T4): OperatorFunction<U, V>;\nexport function ofType<\n  E extends Extract<U, { type: T1 | T2 | T3 | T4 | T5 }>,\n  AC extends ActionCreator<string, Creator>,\n  T1 extends string | AC,\n  T2 extends string | AC,\n  T3 extends string | AC,\n  T4 extends string | AC,\n  T5 extends string | AC,\n  U extends Action = Action,\n  V = ActionExtractor<T1 | T2 | T3 | T4 | T5, AC, E>,\n>(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5): OperatorFunction<U, V>;\n/**\n * Fallback for more than 5 arguments.\n * There is no inference, so the return type is the same as the input -\n * Observable<Action>.\n *\n * We provide a type parameter, even though TS will not infer it from the\n * arguments, to preserve backwards compatibility with old versions of ngrx.\n */\nexport function ofType<V extends Action>(\n  ...allowedTypes: Array<string | ActionCreator<string, Creator>>\n): OperatorFunction<Action, V>;\n/**\n * `ofType` filters an Observable of `Actions` into an Observable of the actions\n * whose type strings are passed to it.\n *\n * For example, if `actions` has type `Actions<AdditionAction|SubstractionAction>`, and\n * the type of the `Addition` action is `add`, then\n * `actions.pipe(ofType('add'))` returns an `Observable<AdditionAction>`.\n *\n * Properly typing this function is hard and requires some advanced TS tricks\n * below.\n *\n * Type narrowing automatically works, as long as your `actions` object\n * starts with a `Actions<SomeUnionOfActions>` instead of generic `Actions`.\n *\n * For backwards compatibility, when one passes a single type argument\n * `ofType<T>('something')` the result is an `Observable<T>`. Note, that `T`\n * completely overrides any possible inference from 'something'.\n *\n * Unfortunately, for unknown 'actions: Actions' these types will produce\n * 'Observable<never>'. In such cases one has to manually set the generic type\n * like `actions.ofType<AdditionAction>('add')`.\n *\n * @usageNotes\n *\n * Filter the Actions stream on the \"customers page loaded\" action\n *\n * ```ts\n * import { ofType } from '@ngrx/effects';\n * import * fromCustomers from '../customers';\n *\n * this.actions$.pipe(\n *  ofType(fromCustomers.pageLoaded)\n * )\n * ```\n */\nexport function ofType(\n  ...allowedTypes: Array<string | ActionCreator<string, Creator>>\n): OperatorFunction<Action, Action> {\n  return filter((action: Action) =>\n    allowedTypes.some((typeOrActionCreator) => {\n      if (typeof typeOrActionCreator === 'string') {\n        // Comparing the string to type\n        return typeOrActionCreator === action.type;\n      }\n\n      // We are filtering by ActionCreator\n      return typeOrActionCreator.type === action.type;\n    })\n  );\n}\n"
  },
  {
    "path": "modules/effects/src/effect_creator.ts",
    "content": "import { Observable } from 'rxjs';\nimport { Action, ActionCreator } from '@ngrx/store';\nimport {\n  CREATE_EFFECT_METADATA_KEY,\n  CreateEffectMetadata,\n  DEFAULT_EFFECT_CONFIG,\n  EffectConfig,\n  EffectMetadata,\n  FunctionalEffect,\n} from './models';\n\ntype DispatchType<T> = T extends { dispatch: infer U } ? U : true;\ntype ObservableType<T, OriginalType> = T extends false ? OriginalType : Action;\ntype EffectResult<OT> = Observable<OT> | ((...args: any[]) => Observable<OT>);\ntype ConditionallyDisallowActionCreator<DT, Result> = DT extends false\n  ? unknown // If DT (DispatchType is false, then we don't enforce any return types)\n  : Result extends EffectResult<infer OT>\n    ? OT extends ActionCreator\n      ? 'ActionCreator cannot be dispatched. Did you forget to call the action creator function?'\n      : unknown\n    : unknown;\n\nexport function createEffect<\n  C extends EffectConfig & { functional?: false },\n  DT extends DispatchType<C>,\n  OTP,\n  R extends EffectResult<OT>,\n  OT extends ObservableType<DT, OTP>,\n>(\n  source: () => R & ConditionallyDisallowActionCreator<DT, R>,\n  config?: C\n): R & CreateEffectMetadata;\nexport function createEffect<Source extends () => Observable<unknown>>(\n  source: Source,\n  config: EffectConfig & { functional: true; dispatch: false }\n): FunctionalEffect<Source>;\nexport function createEffect<Source extends () => Observable<Action>>(\n  source: Source & ConditionallyDisallowActionCreator<true, ReturnType<Source>>,\n  config: EffectConfig & { functional: true; dispatch?: true }\n): FunctionalEffect<Source>;\n/**\n * @description\n *\n * Creates an effect from a source and an `EffectConfig`.\n *\n * @param source A function which returns an observable or observable factory.\n * @param config A `EffectConfig` to configure the effect. By default,\n * `dispatch` is true, `functional` is false, and `useEffectsErrorHandler` is\n * true.\n * @returns If `EffectConfig`#`functional` is true, returns the source function.\n * Else, returns the source function result. When `EffectConfig`#`dispatch` is\n * true, the source function result needs to be `Observable<Action>`.\n *\n * @usageNotes\n *\n * ### Class Effects\n *\n * ```ts\n * @Injectable()\n * export class FeatureEffects {\n *   // mapping to a different action\n *   readonly effect1$ = createEffect(\n *     () => this.actions$.pipe(\n *       ofType(FeatureActions.actionOne),\n *       map(() => FeatureActions.actionTwo())\n *     )\n *   );\n *\n *   // non-dispatching effect\n *   readonly effect2$ = createEffect(\n *     () => this.actions$.pipe(\n *       ofType(FeatureActions.actionTwo),\n *       tap(() => console.log('Action Two Dispatched'))\n *     ),\n *     { dispatch: false } // FeatureActions.actionTwo is not dispatched\n *   );\n *\n *   constructor(private readonly actions$: Actions) {}\n * }\n * ```\n *\n * ### Functional Effects\n *\n * ```ts\n * // mapping to a different action\n * export const loadUsers = createEffect(\n *   (actions$ = inject(Actions), usersService = inject(UsersService)) => {\n *     return actions$.pipe(\n *       ofType(UsersPageActions.opened),\n *       exhaustMap(() => {\n *         return usersService.getAll().pipe(\n *           map((users) => UsersApiActions.usersLoadedSuccess({ users })),\n *           catchError((error) =>\n *             of(UsersApiActions.usersLoadedFailure({ error }))\n *           )\n *         );\n *       })\n *     );\n *   },\n *   { functional: true }\n * );\n *\n * // non-dispatching functional effect\n * export const logDispatchedActions = createEffect(\n *   () => inject(Actions).pipe(tap(console.log)),\n *   { functional: true, dispatch: false }\n * );\n * ```\n */\nexport function createEffect<\n  Result extends EffectResult<unknown>,\n  Source extends () => Result,\n>(\n  source: Source,\n  config: EffectConfig = {}\n): (Source | Result) & CreateEffectMetadata {\n  const effect = config.functional ? source : source();\n  const value: EffectConfig = {\n    ...DEFAULT_EFFECT_CONFIG,\n    ...config, // Overrides any defaults if values are provided\n  };\n  Object.defineProperty(effect, CREATE_EFFECT_METADATA_KEY, {\n    value,\n  });\n  return effect as typeof effect & CreateEffectMetadata;\n}\n\nexport function getCreateEffectMetadata<T extends Record<keyof T, Object>>(\n  instance: T\n): EffectMetadata<T>[] {\n  const propertyNames = Object.getOwnPropertyNames(instance) as Array<keyof T>;\n\n  const metadata: EffectMetadata<T>[] = propertyNames\n    .filter((propertyName) => {\n      if (\n        instance[propertyName] &&\n        instance[propertyName].hasOwnProperty(CREATE_EFFECT_METADATA_KEY)\n      ) {\n        // If the property type has overridden `hasOwnProperty` we need to ensure\n        // that the metadata is valid (containing a `dispatch` property)\n        // https://github.com/ngrx/platform/issues/2975\n        const property = instance[propertyName] as any;\n        return property[CREATE_EFFECT_METADATA_KEY].hasOwnProperty('dispatch');\n      }\n      return false;\n    })\n    .map((propertyName) => {\n      const metaData = (instance[propertyName] as any)[\n        CREATE_EFFECT_METADATA_KEY\n      ];\n      return {\n        propertyName,\n        ...metaData,\n      };\n    });\n\n  return metadata;\n}\n"
  },
  {
    "path": "modules/effects/src/effect_notification.ts",
    "content": "import { ErrorHandler } from '@angular/core';\nimport { Action } from '@ngrx/store';\nimport { Observable } from 'rxjs';\nimport { ObservableNotification } from './utils';\n\nexport interface EffectNotification {\n  effect: Observable<any> | (() => Observable<any>);\n  propertyName: PropertyKey;\n  sourceName: string | null;\n  sourceInstance: any;\n  notification: ObservableNotification<Action | null | undefined>;\n}\n\nexport function reportInvalidActions(\n  output: EffectNotification,\n  reporter: ErrorHandler\n) {\n  if (output.notification.kind === 'N') {\n    const action = output.notification.value;\n    const isInvalidAction = !isAction(action);\n\n    if (isInvalidAction) {\n      reporter.handleError(\n        new Error(\n          `Effect ${getEffectName(\n            output\n          )} dispatched an invalid action: ${stringify(action)}`\n        )\n      );\n    }\n  }\n}\n\nfunction isAction(action: any): action is Action {\n  return (\n    typeof action !== 'function' &&\n    action &&\n    action.type &&\n    typeof action.type === 'string'\n  );\n}\n\nfunction getEffectName({\n  propertyName,\n  sourceInstance,\n  sourceName,\n}: EffectNotification) {\n  const isMethod = typeof sourceInstance[propertyName] === 'function';\n  const isClassBasedEffect = !!sourceName;\n\n  return isClassBasedEffect\n    ? `\"${sourceName}.${String(propertyName)}${isMethod ? '()' : ''}\"`\n    : `\"${String(propertyName)}()\"`;\n}\n\nfunction stringify(action: Action | null | undefined) {\n  try {\n    return JSON.stringify(action);\n  } catch {\n    return action;\n  }\n}\n"
  },
  {
    "path": "modules/effects/src/effect_sources.ts",
    "content": "import { ErrorHandler, Inject, Injectable } from '@angular/core';\nimport { Action } from '@ngrx/store';\nimport { Observable, Subject, merge } from 'rxjs';\nimport {\n  dematerialize,\n  exhaustMap,\n  filter,\n  groupBy,\n  map,\n  mergeMap,\n  take,\n} from 'rxjs/operators';\n\nimport {\n  reportInvalidActions,\n  EffectNotification,\n} from './effect_notification';\nimport { EffectsErrorHandler } from './effects_error_handler';\nimport { mergeEffects } from './effects_resolver';\nimport {\n  isOnIdentifyEffects,\n  isOnRunEffects,\n  isOnInitEffects,\n} from './lifecycle_hooks';\nimport { EFFECTS_ERROR_HANDLER } from './tokens';\nimport {\n  getSourceForInstance,\n  isClassInstance,\n  ObservableNotification,\n} from './utils';\n\n@Injectable({ providedIn: 'root' })\nexport class EffectSources extends Subject<any> {\n  constructor(\n    private errorHandler: ErrorHandler,\n    @Inject(EFFECTS_ERROR_HANDLER)\n    private effectsErrorHandler: EffectsErrorHandler\n  ) {\n    super();\n  }\n\n  addEffects(effectSourceInstance: any): void {\n    this.next(effectSourceInstance);\n  }\n\n  /**\n   * @internal\n   */\n  toActions(): Observable<Action> {\n    return this.pipe(\n      groupBy((effectsInstance) =>\n        isClassInstance(effectsInstance)\n          ? getSourceForInstance(effectsInstance)\n          : effectsInstance\n      ),\n      mergeMap((source$) => {\n        return source$.pipe(groupBy(effectsInstance));\n      }),\n      mergeMap((source$) => {\n        const effect$ = source$.pipe(\n          exhaustMap((sourceInstance) => {\n            return resolveEffectSource(\n              this.errorHandler,\n              this.effectsErrorHandler\n            )(sourceInstance);\n          }),\n          map((output) => {\n            reportInvalidActions(output, this.errorHandler);\n            return output.notification;\n          }),\n          filter(\n            (notification): notification is ObservableNotification<Action> =>\n              notification.kind === 'N' && notification.value != null\n          ),\n          dematerialize()\n        );\n\n        // start the stream with an INIT action\n        // do this only for the first Effect instance\n        const init$ = source$.pipe(\n          take(1),\n          filter(isOnInitEffects),\n          map((instance) => instance.ngrxOnInitEffects())\n        );\n\n        return merge(effect$, init$);\n      })\n    );\n  }\n}\n\nfunction effectsInstance(sourceInstance: any) {\n  if (isOnIdentifyEffects(sourceInstance)) {\n    return sourceInstance.ngrxOnIdentifyEffects();\n  }\n\n  return '';\n}\n\nfunction resolveEffectSource(\n  errorHandler: ErrorHandler,\n  effectsErrorHandler: EffectsErrorHandler\n): (sourceInstance: any) => Observable<EffectNotification> {\n  return (sourceInstance) => {\n    const mergedEffects$ = mergeEffects(\n      sourceInstance,\n      errorHandler,\n      effectsErrorHandler\n    );\n\n    if (isOnRunEffects(sourceInstance)) {\n      return sourceInstance.ngrxOnRunEffects(mergedEffects$);\n    }\n\n    return mergedEffects$;\n  };\n}\n"
  },
  {
    "path": "modules/effects/src/effects_actions.ts",
    "content": "import { createAction } from '@ngrx/store';\n\nexport const ROOT_EFFECTS_INIT = '@ngrx/effects/init';\nexport const rootEffectsInit = createAction(ROOT_EFFECTS_INIT);\n"
  },
  {
    "path": "modules/effects/src/effects_error_handler.ts",
    "content": "import { ErrorHandler } from '@angular/core';\nimport { Action } from '@ngrx/store';\nimport { Observable } from 'rxjs';\nimport { catchError } from 'rxjs/operators';\n\nexport type EffectsErrorHandler = <T extends Action>(\n  observable$: Observable<T>,\n  errorHandler: ErrorHandler\n) => Observable<T>;\n\nconst MAX_NUMBER_OF_RETRY_ATTEMPTS = 10;\n\nexport function defaultEffectsErrorHandler<T extends Action>(\n  observable$: Observable<T>,\n  errorHandler: ErrorHandler,\n  retryAttemptLeft: number = MAX_NUMBER_OF_RETRY_ATTEMPTS\n): Observable<T> {\n  return observable$.pipe(\n    catchError((error) => {\n      if (errorHandler) errorHandler.handleError(error);\n      if (retryAttemptLeft <= 1) {\n        return observable$; // last attempt\n      }\n      // Return observable that produces this particular effect\n      return defaultEffectsErrorHandler(\n        observable$,\n        errorHandler,\n        retryAttemptLeft - 1\n      );\n    })\n  );\n}\n"
  },
  {
    "path": "modules/effects/src/effects_feature_module.ts",
    "content": "import { NgModule, Inject, Optional } from '@angular/core';\nimport { StoreRootModule, StoreFeatureModule } from '@ngrx/store';\nimport { EffectsRootModule } from './effects_root_module';\nimport { _FEATURE_EFFECTS_INSTANCE_GROUPS } from './tokens';\n\n@NgModule({})\nexport class EffectsFeatureModule {\n  constructor(\n    effectsRootModule: EffectsRootModule,\n    @Inject(_FEATURE_EFFECTS_INSTANCE_GROUPS)\n    effectsInstanceGroups: unknown[][],\n    @Optional() storeRootModule: StoreRootModule,\n    @Optional() storeFeatureModule: StoreFeatureModule\n  ) {\n    const effectsInstances = effectsInstanceGroups.flat();\n    for (const effectsInstance of effectsInstances) {\n      effectsRootModule.addEffects(effectsInstance);\n    }\n  }\n}\n"
  },
  {
    "path": "modules/effects/src/effects_metadata.ts",
    "content": "import { EffectMetadata, EffectsMetadata } from './models';\nimport { getCreateEffectMetadata } from './effect_creator';\n\nexport function getEffectsMetadata<T extends Record<keyof T, Object>>(\n  instance: T\n): EffectsMetadata<T> {\n  return getSourceMetadata(instance).reduce(\n    (\n      acc: EffectsMetadata<T>,\n      { propertyName, dispatch, useEffectsErrorHandler }\n    ) => {\n      acc[propertyName] = { dispatch, useEffectsErrorHandler };\n      return acc;\n    },\n    {}\n  );\n}\n\nexport function getSourceMetadata<T extends { [props in keyof T]: object }>(\n  instance: T\n): EffectMetadata<T>[] {\n  return getCreateEffectMetadata(instance);\n}\n"
  },
  {
    "path": "modules/effects/src/effects_module.ts",
    "content": "import {\n  inject,\n  InjectionToken,\n  ModuleWithProviders,\n  NgModule,\n  Type,\n} from '@angular/core';\nimport { EffectsFeatureModule } from './effects_feature_module';\nimport { EffectsRootModule } from './effects_root_module';\nimport { EffectsRunner } from './effects_runner';\nimport {\n  _FEATURE_EFFECTS,\n  _ROOT_EFFECTS,\n  _ROOT_EFFECTS_GUARD,\n  _FEATURE_EFFECTS_INSTANCE_GROUPS,\n  _ROOT_EFFECTS_INSTANCES,\n  USER_PROVIDED_EFFECTS,\n} from './tokens';\nimport { FunctionalEffect } from './models';\nimport { getClasses, isToken } from './utils';\n\n@NgModule({})\nexport class EffectsModule {\n  static forFeature(\n    featureEffects: Array<Type<unknown> | Record<string, FunctionalEffect>>\n  ): ModuleWithProviders<EffectsFeatureModule>;\n  static forFeature(\n    ...featureEffects: Array<Type<unknown> | Record<string, FunctionalEffect>>\n  ): ModuleWithProviders<EffectsFeatureModule>;\n  static forFeature(\n    ...featureEffects:\n      | Array<Type<unknown> | Record<string, FunctionalEffect>>\n      | [Array<Type<unknown> | Record<string, FunctionalEffect>>]\n  ): ModuleWithProviders<EffectsFeatureModule> {\n    const effects = featureEffects.flat();\n    const effectsClasses = getClasses(effects);\n    return {\n      ngModule: EffectsFeatureModule,\n      providers: [\n        effectsClasses,\n        {\n          provide: _FEATURE_EFFECTS,\n          multi: true,\n          useValue: effects,\n        },\n        {\n          provide: USER_PROVIDED_EFFECTS,\n          multi: true,\n          useValue: [],\n        },\n        {\n          provide: _FEATURE_EFFECTS_INSTANCE_GROUPS,\n          multi: true,\n          useFactory: createEffectsInstances,\n          deps: [_FEATURE_EFFECTS, USER_PROVIDED_EFFECTS],\n        },\n      ],\n    };\n  }\n\n  static forRoot(\n    rootEffects: Array<Type<unknown> | Record<string, FunctionalEffect>>\n  ): ModuleWithProviders<EffectsRootModule>;\n  static forRoot(\n    ...rootEffects: Array<Type<unknown> | Record<string, FunctionalEffect>>\n  ): ModuleWithProviders<EffectsRootModule>;\n  static forRoot(\n    ...rootEffects:\n      | Array<Type<unknown> | Record<string, FunctionalEffect>>\n      | [Array<Type<unknown> | Record<string, FunctionalEffect>>]\n  ): ModuleWithProviders<EffectsRootModule> {\n    const effects = rootEffects.flat();\n    const effectsClasses = getClasses(effects);\n    return {\n      ngModule: EffectsRootModule,\n      providers: [\n        effectsClasses,\n        {\n          provide: _ROOT_EFFECTS,\n          useValue: [effects],\n        },\n        {\n          provide: _ROOT_EFFECTS_GUARD,\n          useFactory: _provideForRootGuard,\n        },\n        {\n          provide: USER_PROVIDED_EFFECTS,\n          multi: true,\n          useValue: [],\n        },\n        {\n          provide: _ROOT_EFFECTS_INSTANCES,\n          useFactory: createEffectsInstances,\n          deps: [_ROOT_EFFECTS, USER_PROVIDED_EFFECTS],\n        },\n      ],\n    };\n  }\n}\n\nfunction createEffectsInstances(\n  effectsGroups: Array<Type<unknown> | Record<string, FunctionalEffect>>[],\n  userProvidedEffectsGroups: Array<Type<unknown> | InjectionToken<unknown>>[]\n): unknown[] {\n  const effects: Array<\n    Type<unknown> | Record<string, FunctionalEffect> | InjectionToken<unknown>\n  > = [];\n\n  for (const effectsGroup of effectsGroups) {\n    effects.push(...effectsGroup);\n  }\n\n  for (const userProvidedEffectsGroup of userProvidedEffectsGroups) {\n    effects.push(...userProvidedEffectsGroup);\n  }\n\n  return effects.map((effectsTokenOrRecord) =>\n    isToken(effectsTokenOrRecord)\n      ? inject(effectsTokenOrRecord)\n      : effectsTokenOrRecord\n  );\n}\n\nfunction _provideForRootGuard(): unknown {\n  const runner = inject(EffectsRunner, { optional: true, skipSelf: true });\n  const rootEffects = inject(_ROOT_EFFECTS, { self: true });\n\n  // check whether any effects are actually passed\n  const hasEffects = !(rootEffects.length === 1 && rootEffects[0].length === 0);\n  if (hasEffects && runner) {\n    throw new TypeError(\n      `EffectsModule.forRoot() called twice. Feature modules should use EffectsModule.forFeature() instead.`\n    );\n  }\n  return 'guarded';\n}\n"
  },
  {
    "path": "modules/effects/src/effects_resolver.ts",
    "content": "import { Action } from '@ngrx/store';\nimport { merge, Observable } from 'rxjs';\nimport { ignoreElements, map, materialize } from 'rxjs/operators';\n\nimport { EffectNotification } from './effect_notification';\nimport { getSourceMetadata } from './effects_metadata';\nimport { EffectsErrorHandler } from './effects_error_handler';\nimport { getSourceForInstance } from './utils';\nimport { ErrorHandler } from '@angular/core';\n\nexport function mergeEffects(\n  sourceInstance: any,\n  globalErrorHandler: ErrorHandler,\n  effectsErrorHandler: EffectsErrorHandler\n): Observable<EffectNotification> {\n  const source = getSourceForInstance(sourceInstance);\n  const isClassBasedEffect = !!source && source.constructor.name !== 'Object';\n  const sourceName = isClassBasedEffect ? source.constructor.name : null;\n\n  const observables$: Observable<any>[] = getSourceMetadata(sourceInstance).map(\n    ({\n      propertyName,\n      dispatch,\n      useEffectsErrorHandler,\n    }): Observable<EffectNotification> => {\n      const observable$: Observable<any> =\n        typeof sourceInstance[propertyName] === 'function'\n          ? sourceInstance[propertyName]()\n          : sourceInstance[propertyName];\n\n      const effectAction$ = useEffectsErrorHandler\n        ? effectsErrorHandler(observable$, globalErrorHandler)\n        : observable$;\n\n      if (dispatch === false) {\n        return effectAction$.pipe(ignoreElements());\n      }\n\n      const materialized$ = effectAction$.pipe(materialize<Action>());\n\n      return materialized$.pipe(\n        map(\n          (notification): EffectNotification => ({\n            effect: sourceInstance[propertyName],\n            notification,\n            propertyName,\n            sourceName,\n            sourceInstance,\n          })\n        )\n      );\n    }\n  );\n\n  return merge(...observables$);\n}\n"
  },
  {
    "path": "modules/effects/src/effects_root_module.ts",
    "content": "import { NgModule, Inject, Optional } from '@angular/core';\nimport { Store, StoreRootModule, StoreFeatureModule } from '@ngrx/store';\nimport { EffectsRunner } from './effects_runner';\nimport { EffectSources } from './effect_sources';\nimport { _ROOT_EFFECTS_GUARD, _ROOT_EFFECTS_INSTANCES } from './tokens';\nimport { ROOT_EFFECTS_INIT } from './effects_actions';\n\n@NgModule({})\nexport class EffectsRootModule {\n  constructor(\n    private sources: EffectSources,\n    runner: EffectsRunner,\n    store: Store,\n    @Inject(_ROOT_EFFECTS_INSTANCES) rootEffectsInstances: unknown[],\n    @Optional() storeRootModule: StoreRootModule,\n    @Optional() storeFeatureModule: StoreFeatureModule,\n    @Optional()\n    @Inject(_ROOT_EFFECTS_GUARD)\n    guard: unknown\n  ) {\n    runner.start();\n\n    for (const effectsInstance of rootEffectsInstances) {\n      sources.addEffects(effectsInstance);\n    }\n\n    store.dispatch({ type: ROOT_EFFECTS_INIT });\n  }\n\n  addEffects(effectsInstance: unknown): void {\n    this.sources.addEffects(effectsInstance);\n  }\n}\n"
  },
  {
    "path": "modules/effects/src/effects_runner.ts",
    "content": "import { Injectable, OnDestroy } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { Subscription } from 'rxjs';\n\nimport { EffectSources } from './effect_sources';\n\n@Injectable({ providedIn: 'root' })\nexport class EffectsRunner implements OnDestroy {\n  private effectsSubscription: Subscription | null = null;\n\n  get isStarted(): boolean {\n    return !!this.effectsSubscription;\n  }\n\n  constructor(\n    private effectSources: EffectSources,\n    private store: Store<any>\n  ) {}\n\n  start() {\n    if (!this.effectsSubscription) {\n      this.effectsSubscription = this.effectSources\n        .toActions()\n        .subscribe(this.store);\n    }\n  }\n\n  ngOnDestroy() {\n    if (this.effectsSubscription) {\n      this.effectsSubscription.unsubscribe();\n      this.effectsSubscription = null;\n    }\n  }\n}\n"
  },
  {
    "path": "modules/effects/src/index.ts",
    "content": "export { createEffect } from './effect_creator';\nexport { EffectConfig } from './models';\nexport { getEffectsMetadata } from './effects_metadata';\nexport { mergeEffects } from './effects_resolver';\nexport {\n  EffectsErrorHandler,\n  defaultEffectsErrorHandler,\n} from './effects_error_handler';\nexport {\n  EffectsMetadata,\n  CreateEffectMetadata,\n  FunctionalEffect,\n} from './models';\nexport { Actions, ofType } from './actions';\nexport { EffectsModule } from './effects_module';\nexport { EffectSources } from './effect_sources';\nexport { ROOT_EFFECTS_INIT, rootEffectsInit } from './effects_actions';\nexport { EffectsRunner } from './effects_runner';\nexport { EffectNotification } from './effect_notification';\nexport { EffectsFeatureModule } from './effects_feature_module';\nexport { EffectsRootModule } from './effects_root_module';\nexport { EFFECTS_ERROR_HANDLER } from './tokens';\nexport {\n  OnIdentifyEffects,\n  OnRunEffects,\n  OnInitEffects,\n} from './lifecycle_hooks';\nexport { USER_PROVIDED_EFFECTS } from './tokens';\nexport { provideEffects } from './provide_effects';\n"
  },
  {
    "path": "modules/effects/src/lifecycle_hooks.ts",
    "content": "import { Observable } from 'rxjs';\nimport { EffectNotification } from '.';\nimport { Action } from '@ngrx/store';\n\n/**\n * @description\n * Interface to set an identifier for effect instances.\n *\n * By default, each Effects class is registered\n * once regardless of how many times the Effect class\n * is loaded. By implementing this interface, you define\n * a unique identifier to register an Effects class instance\n * multiple times.\n *\n * @usageNotes\n *\n * ### Set an identifier for an Effects class\n *\n * ```ts\n * class EffectWithIdentifier implements OnIdentifyEffects {\n *  constructor(private effectIdentifier: string) {}\n *\n *  ngrxOnIdentifyEffects() {\n *    return this.effectIdentifier;\n *  }\n *\n * ```\n */\nexport declare interface OnIdentifyEffects {\n  /**\n   * @description\n   * String identifier to differentiate effect instances.\n   */\n  ngrxOnIdentifyEffects(): string;\n}\n\nexport const onIdentifyEffectsKey: keyof OnIdentifyEffects =\n  'ngrxOnIdentifyEffects';\n\nexport function isOnIdentifyEffects(\n  instance: any\n): instance is OnIdentifyEffects {\n  return isFunction(instance, onIdentifyEffectsKey);\n}\n\n/**\n * @description\n * Interface to control the lifecycle of effects.\n *\n * By default, effects are merged and subscribed to the store. Implement the OnRunEffects interface to control the lifecycle of the resolved effects.\n *\n * @usageNotes\n *\n * ### Implement the OnRunEffects interface on an Effects class\n *\n * ```ts\n * export class UserEffects implements OnRunEffects {\n *   constructor(private actions$: Actions) {}\n *\n *   ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>) {\n *     return this.actions$.pipe(\n *       ofType('LOGGED_IN'),\n *       exhaustMap(() =>\n *         resolvedEffects$.pipe(\n *           takeUntil(this.actions$.pipe(ofType('LOGGED_OUT')))\n *         )\n *       )\n *     );\n *   }\n * }\n * ```\n */\nexport declare interface OnRunEffects {\n  /**\n   * @description\n   * Method to control the lifecycle of effects.\n   */\n  ngrxOnRunEffects(\n    resolvedEffects$: Observable<EffectNotification>\n  ): Observable<EffectNotification>;\n}\n\nexport const onRunEffectsKey: keyof OnRunEffects = 'ngrxOnRunEffects';\n\nexport function isOnRunEffects(instance: any): instance is OnRunEffects {\n  return isFunction(instance, onRunEffectsKey);\n}\n\n/**\n * @description\n * Interface to dispatch an action after effect registration.\n *\n * Implement this interface to dispatch a custom action after\n * the effect has been added. You can listen to this action\n * in the rest of the application to execute something after\n * the effect is registered.\n *\n * @usageNotes\n *\n * ### Set an identifier for an Effects class\n *\n * ```ts\n * class EffectWithInitAction implements OnInitEffects {\n *  ngrxOnInitEffects() {\n *    return { type: '[EffectWithInitAction] Init' };\n *  }\n * ```\n */\nexport declare interface OnInitEffects {\n  /**\n   * @description\n   * Action to be dispatched after the effect is registered.\n   */\n  ngrxOnInitEffects(): Action;\n}\n\nexport const onInitEffects: keyof OnInitEffects = 'ngrxOnInitEffects';\n\nexport function isOnInitEffects(instance: any): instance is OnInitEffects {\n  return isFunction(instance, onInitEffects);\n}\n\nfunction isFunction(instance: any, functionName: string) {\n  return (\n    instance &&\n    functionName in instance &&\n    typeof instance[functionName] === 'function'\n  );\n}\n"
  },
  {
    "path": "modules/effects/src/models.ts",
    "content": "import { Observable } from 'rxjs';\n\n/**\n * Configures an effect created by `createEffect`.\n */\nexport interface EffectConfig {\n  /**\n   * Determines if the action emitted by the effect is dispatched to the store.\n   * If false, effect does not need to return type `Observable<Action>`.\n   */\n  dispatch?: boolean;\n  /**\n   * Determines whether the functional effect will be created.\n   * If true, the effect can be created outside the effects class.\n   */\n  functional?: boolean;\n  /**\n   * Determines if the effect will be resubscribed to if an error occurs in the main actions stream.\n   */\n  useEffectsErrorHandler?: boolean;\n}\n\nexport const DEFAULT_EFFECT_CONFIG: Readonly<Required<EffectConfig>> = {\n  dispatch: true,\n  functional: false,\n  useEffectsErrorHandler: true,\n};\n\nexport const CREATE_EFFECT_METADATA_KEY = '__@ngrx/effects_create__';\n\nexport interface CreateEffectMetadata {\n  [CREATE_EFFECT_METADATA_KEY]: EffectConfig;\n}\n\nexport interface FunctionalCreateEffectMetadata extends CreateEffectMetadata {\n  [CREATE_EFFECT_METADATA_KEY]: EffectConfig & { functional: true };\n}\n\nexport type FunctionalEffect<\n  Source extends () => Observable<unknown> = () => Observable<unknown>,\n> = Source & FunctionalCreateEffectMetadata;\n\nexport type EffectPropertyKey<T extends Record<keyof T, Object>> = Exclude<\n  keyof T,\n  keyof Object\n>;\n\nexport interface EffectMetadata<T extends Record<keyof T, Object>>\n  extends Required<EffectConfig> {\n  propertyName: EffectPropertyKey<T>;\n}\n\nexport type EffectsMetadata<T extends Record<keyof T, Object>> = {\n  [Key in EffectPropertyKey<T>]?: EffectConfig;\n};\n"
  },
  {
    "path": "modules/effects/src/provide_effects.ts",
    "content": "import {\n  EnvironmentProviders,\n  inject,\n  makeEnvironmentProviders,\n  provideEnvironmentInitializer,\n  Type,\n} from '@angular/core';\nimport {\n  FEATURE_STATE_PROVIDER,\n  ROOT_STORE_PROVIDER,\n  Store,\n} from '@ngrx/store';\nimport { EffectsRunner } from './effects_runner';\nimport { EffectSources } from './effect_sources';\nimport { rootEffectsInit as effectsInit } from './effects_actions';\nimport { FunctionalEffect } from './models';\nimport { getClasses, isClass } from './utils';\n\n/**\n * Runs the provided effects.\n * Can be called at the root and feature levels.\n */\nexport function provideEffects(\n  effects: Array<Type<unknown> | Record<string, FunctionalEffect>>\n): EnvironmentProviders;\n/**\n * Runs the provided effects.\n * Can be called at the root and feature levels.\n */\nexport function provideEffects(\n  ...effects: Array<Type<unknown> | Record<string, FunctionalEffect>>\n): EnvironmentProviders;\n/**\n * @usageNotes\n *\n * ### Providing effects at the root level\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n *   providers: [provideEffects(RouterEffects)],\n * });\n * ```\n *\n * ### Providing effects at the feature level\n *\n * ```ts\n * const booksRoutes: Route[] = [\n *   {\n *     path: '',\n *     providers: [provideEffects(BooksApiEffects)],\n *     children: [\n *       { path: '', component: BookListComponent },\n *       { path: ':id', component: BookDetailsComponent },\n *     ],\n *   },\n * ];\n * ```\n */\nexport function provideEffects(\n  ...effects:\n    | Array<Type<unknown> | Record<string, FunctionalEffect>>\n    | [Array<Type<unknown> | Record<string, FunctionalEffect>>]\n): EnvironmentProviders {\n  const effectsClassesAndRecords = effects.flat();\n  const effectsClasses = getClasses(effectsClassesAndRecords);\n\n  return makeEnvironmentProviders([\n    effectsClasses,\n    provideEnvironmentInitializer(() => {\n      inject(ROOT_STORE_PROVIDER);\n      inject(FEATURE_STATE_PROVIDER, { optional: true });\n\n      const effectsRunner = inject(EffectsRunner);\n      const effectSources = inject(EffectSources);\n      const shouldInitEffects = !effectsRunner.isStarted;\n\n      if (shouldInitEffects) {\n        effectsRunner.start();\n      }\n\n      for (const effectsClassOrRecord of effectsClassesAndRecords) {\n        const effectsInstance = isClass(effectsClassOrRecord)\n          ? inject(effectsClassOrRecord)\n          : effectsClassOrRecord;\n        effectSources.addEffects(effectsInstance);\n      }\n\n      if (shouldInitEffects) {\n        const store = inject(Store);\n        store.dispatch(effectsInit());\n      }\n    }),\n  ]);\n}\n"
  },
  {
    "path": "modules/effects/src/tokens.ts",
    "content": "import { InjectionToken, Type } from '@angular/core';\nimport {\n  defaultEffectsErrorHandler,\n  EffectsErrorHandler,\n} from './effects_error_handler';\nimport { FunctionalEffect } from './models';\n\nexport const _ROOT_EFFECTS_GUARD = new InjectionToken<void>(\n  '@ngrx/effects Internal Root Guard'\n);\nexport const USER_PROVIDED_EFFECTS = new InjectionToken<\n  Array<Type<unknown> | InjectionToken<unknown>>[]\n>('@ngrx/effects User Provided Effects');\nexport const _ROOT_EFFECTS = new InjectionToken<\n  [Array<Type<unknown> | Record<string, FunctionalEffect>>]\n>('@ngrx/effects Internal Root Effects');\nexport const _ROOT_EFFECTS_INSTANCES = new InjectionToken<unknown[]>(\n  '@ngrx/effects Internal Root Effects Instances'\n);\nexport const _FEATURE_EFFECTS = new InjectionToken<\n  Array<Type<unknown> | Record<string, FunctionalEffect>>[]\n>('@ngrx/effects Internal Feature Effects');\nexport const _FEATURE_EFFECTS_INSTANCE_GROUPS = new InjectionToken<unknown[][]>(\n  '@ngrx/effects Internal Feature Effects Instance Groups'\n);\nexport const EFFECTS_ERROR_HANDLER = new InjectionToken<EffectsErrorHandler>(\n  '@ngrx/effects Effects Error Handler',\n  { providedIn: 'root', factory: () => defaultEffectsErrorHandler }\n);\n"
  },
  {
    "path": "modules/effects/src/utils.ts",
    "content": "import { InjectionToken, Type } from '@angular/core';\n\nexport function getSourceForInstance<T>(instance: T): T {\n  return Object.getPrototypeOf(instance);\n}\n\nexport function isClassInstance(obj: object): boolean {\n  return (\n    !!obj.constructor &&\n    obj.constructor.name !== 'Object' &&\n    obj.constructor.name !== 'Function'\n  );\n}\n\nexport function isClass(\n  classOrRecord: Type<unknown> | Record<string, unknown>\n): classOrRecord is Type<unknown> {\n  return typeof classOrRecord === 'function';\n}\n\nexport function getClasses(\n  classesAndRecords: Array<Type<unknown> | Record<string, unknown>>\n): Type<unknown>[] {\n  return classesAndRecords.filter(isClass);\n}\n\nexport function isToken(\n  tokenOrRecord:\n    | Type<unknown>\n    | InjectionToken<unknown>\n    | Record<string, unknown>\n): tokenOrRecord is Type<unknown> | InjectionToken<unknown> {\n  return tokenOrRecord instanceof InjectionToken || isClass(tokenOrRecord);\n}\n\n// TODO: replace with RxJS interfaces when possible\n// needs dependency on RxJS >=7\nexport interface NextNotification<T> {\n  kind: 'N';\n  value: T;\n}\n\nexport interface ErrorNotification {\n  kind: 'E';\n  error: any;\n}\n\nexport interface CompleteNotification {\n  kind: 'C';\n}\n\nexport type ObservableNotification<T> =\n  | NextNotification<T>\n  | ErrorNotification\n  | CompleteNotification;\n"
  },
  {
    "path": "modules/effects/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/effects/testing/index.ts",
    "content": "export * from './src/public_api';\n"
  },
  {
    "path": "modules/effects/testing/ng-package.json",
    "content": "{}\n"
  },
  {
    "path": "modules/effects/testing/spec/mock_actions.spec.ts",
    "content": "import { of } from 'rxjs';\nimport { TestBed } from '@angular/core/testing';\nimport { provideMockActions } from '..';\nimport { Actions } from '@ngrx/effects';\nimport { Injector } from '@angular/core';\n\ndescribe('Mock Actions', () => {\n  describe('with TestBed', () => {\n    it('should provide Actions from source', (done: any) => {\n      TestBed.configureTestingModule({\n        providers: [provideMockActions(of({ type: 'foo' }))],\n      });\n\n      const actions$ = TestBed.inject(Actions);\n      actions$.subscribe((action) => {\n        expect(action.type).toBe('foo');\n        done();\n      });\n    });\n\n    it('should provide Actions from factory', (done: any) => {\n      TestBed.configureTestingModule({\n        providers: [provideMockActions(() => of({ type: 'bar' }))],\n      });\n\n      const actions$ = TestBed.inject(Actions);\n      actions$.subscribe((action) => {\n        expect(action.type).toBe('bar');\n        done();\n      });\n    });\n  });\n\n  describe('with Injector', () => {\n    it('should provide Actions from source', (done: any) => {\n      const injector = Injector.create({\n        providers: [provideMockActions(of({ type: 'foo' }))],\n      });\n\n      const actions$ = injector.get(Actions);\n      actions$.subscribe((action) => {\n        expect(action.type).toBe('foo');\n        done();\n      });\n    });\n\n    it('should provide Actions from factory', (done: any) => {\n      const injector = Injector.create({\n        providers: [provideMockActions(() => of({ type: 'bar' }))],\n      });\n\n      const actions$ = injector.get(Actions);\n      actions$.subscribe((action) => {\n        expect(action.type).toBe('bar');\n        done();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/effects/testing/src/public_api.ts",
    "content": "export * from './testing';\n"
  },
  {
    "path": "modules/effects/testing/src/testing.ts",
    "content": "import { FactoryProvider } from '@angular/core';\nimport { Actions } from '@ngrx/effects';\nimport { defer, Observable } from 'rxjs';\n\nexport function provideMockActions(source: Observable<any>): FactoryProvider;\nexport function provideMockActions(\n  factory: () => Observable<any>\n): FactoryProvider;\n/**\n * @description\n * Creates mock actions provider.\n *\n * @param factoryOrSource Actions' source or source creation function\n *\n * @usageNotes\n *\n * **With `TestBed.configureTestingModule`**\n *\n * ```ts\n * describe('Books Effects', () => {\n *   let actions$ = new Observable<Action>();\n *   let effects: BooksEffects;\n *\n *   beforeEach(() => {\n *     TestBed.configureTestingModule({\n *       providers: [\n *         provideMockActions(() => actions$),\n *         BooksEffects,\n *       ],\n *     });\n *\n *     actions$ = TestBed.inject(Actions);\n *     effects = TestBed.inject(BooksEffects);\n *   });\n * });\n * ```\n *\n * **With `Injector.create`**\n *\n * ```ts\n * describe('Counter Effects', () => {\n *   let injector: Injector;\n *   let actions$ = new Observable<Action>();\n *   let effects: CounterEffects;\n *\n *   beforeEach(() => {\n *     injector = Injector.create({\n *       providers: [\n *         provideMockActions(() => actions$),\n *         CounterEffects,\n *       ],\n *     });\n *\n *     actions$ = injector.get(Actions);\n *     effects = injector.get(CounterEffects);\n *   });\n * });\n * ```\n */\nexport function provideMockActions(\n  factoryOrSource: (() => Observable<any>) | Observable<any>\n): FactoryProvider {\n  return {\n    provide: Actions,\n    useFactory: (): Observable<any> => {\n      if (typeof factoryOrSource === 'function') {\n        return new Actions(defer(factoryOrSource));\n      }\n\n      return new Actions(factoryOrSource);\n    },\n    deps: [],\n  };\n}\n"
  },
  {
    "path": "modules/effects/testing/tsconfig.build.json",
    "content": "{\n  \"extends\": \"../tsconfig.build\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@ngrx/store\": [\"../../dist/packages/store\"],\n      \"@ngrx/effects\": [\"../../dist/packages/effects\"]\n    }\n  },\n  \"files\": [\"index.ts\"],\n  \"angularCompilerOptions\": {}\n}\n"
  },
  {
    "path": "modules/effects/testing/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.spec\",\n  \"files\": [\"index.ts\"]\n}\n"
  },
  {
    "path": "modules/effects/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../../\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmitOnError\": false,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/effects\",\n    \"paths\": {\n      \"@ngrx/operators\": [\"./dist/modules/operators\"]\n    },\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"skipLibCheck\": true\n  },\n  \"files\": [\"public_api.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/effects\"\n  }\n}\n"
  },
  {
    "path": "modules/effects/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/effects\",\n    \"paths\": {\n      \"@ngrx/effects/schematics-core\": [\"./schematics-core\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/effects/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"node\", \"vitest/globals\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/effects/vitest.config.ts",
    "content": "/// <reference types=\"vitest\" />\nimport angular from '@analogjs/vite-plugin-angular';\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig(({ mode }) => ({\n  root: __dirname,\n  plugins: [angular(), nxViteTsPaths()],\n  test: {\n    name: 'Effects',\n    globals: true,\n    pool: 'forks',\n    environment: 'jsdom',\n    setupFiles: ['test-setup.ts'],\n    include: ['spec/**/*.spec.ts'],\n    reporters: ['default'],\n    coverage: {\n      reportsDirectory: '../../coverage/modules/effects',\n    },\n  },\n  define: {\n    'import.meta.vitest': mode !== 'production',\n  },\n}));\n"
  },
  {
    "path": "modules/entity/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/entity/README.md",
    "content": "# @ngrx/entity\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/entity/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist', '**/jest.config.ts', '**/schematics-core/**/*.ts'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/entity/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/entity/index.ts",
    "content": "/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\nexport * from './public_api';\n"
  },
  {
    "path": "modules/entity/migrations/6_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport {\n  createPackageJson,\n  packagePath,\n} from '@ngrx/schematics-core/testing/create-package';\nimport {\n  upgradeVersion,\n  versionPrefixes,\n} from '@ngrx/schematics-core/testing/update';\n\nconst collectionPath = path.join(__dirname, '../migration.json');\n\ndescribe('Entity Migration 6_0_0', () => {\n  let appTree;\n  const pkgName = 'entity';\n\n  versionPrefixes.forEach((prefix) => {\n    it(`should install version ${prefix}6.0.0`, async () => {\n      appTree = new UnitTestTree(Tree.empty());\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n      const tree = createPackageJson(prefix, pkgName, appTree);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-01`,\n        {},\n        tree\n      );\n      const pkg = JSON.parse(newTree.readContent(packagePath));\n      expect(pkg.dependencies[`@ngrx/${pkgName}`]).toBe(\n        `${prefix}${upgradeVersion}`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "modules/entity/migrations/6_0_0/index.ts",
    "content": "import { Rule } from '@angular-devkit/schematics';\nimport { updatePackage } from '../../schematics-core';\n\nexport default function (): Rule {\n  return updatePackage('entity');\n}\n"
  },
  {
    "path": "modules/entity/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"ngrx-entity-migration-01\": {\n      \"description\": \"The road to v6\",\n      \"version\": \"5.2\",\n      \"factory\": \"./6_0_0/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/entity/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/entity\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/entity/package.json",
    "content": "{\n  \"name\": \"@ngrx/entity\",\n  \"version\": \"21.0.1\",\n  \"description\": \"Common utilities for entity reducers\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"Angular\",\n    \"Redux\",\n    \"Entity\",\n    \"Schematics\",\n    \"Angular CLI\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"@angular/core\": \"^21.0.0\",\n    \"@ngrx/store\": \"21.0.1\",\n    \"rxjs\": \"^6.5.3 || ^7.5.0\"\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"sideEffects\": false,\n  \"dependencies\": {\n    \"tslib\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "modules/entity/project.json",
    "content": "{\n  \"name\": \"entity\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/entity/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/entity/tsconfig.build.json\",\n        \"project\": \"modules/entity/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package entity\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/entity/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/entity\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/entity\"\n          },\n          {\n            \"command\": \"ncp dist/modules/entity node_modules/@ngrx/entity\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/entity\"\n          }\n        ]\n      },\n      \"outputs\": [\n        \"{workspaceRoot}/dist/modules/entity\",\n        \"{workspaceRoot}/node_modules/@ngrx/entity\"\n      ]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/entity/*/**/*.ts\",\n          \"modules/entity/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/entity\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/entity/public_api.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/entity/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Add @ngrx/entity to your application\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/entity/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as EntityOptions } from './schema';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('Entity ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/entity',\n    path.join(__dirname, '../collection.json')\n  );\n  const defaultOptions: EntityOptions = {\n    skipPackageJson: false,\n  };\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/entity']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/entity']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "modules/entity/schematics/ng-add/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  chain,\n  noop,\n} from '@angular-devkit/schematics';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\nimport {\n  addPackageToPackageJson,\n  platformVersion,\n} from '../../schematics-core';\nimport { Schema as EntityOptions } from './schema';\n\nfunction addNgRxEntityToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/entity',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nexport default function (options: EntityOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    return chain([\n      options && options.skipPackageJson\n        ? noop()\n        : addNgRxEntityToPackageJson(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/entity/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxEntity\",\n  \"title\": \"NgRx Entity Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/entity as dependency to package.json (e.g., --skipPackageJson).\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/entity/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  skipPackageJson?: boolean;\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/entity/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/entity/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/entity/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/entity/spec/entity_state.spec.ts",
    "content": "import { createEntityAdapter, EntityAdapter } from '../src';\nimport { BookModel } from './fixtures/book';\n\ndescribe('Entity State', () => {\n  let adapter: EntityAdapter<BookModel>;\n\n  beforeEach(() => {\n    adapter = createEntityAdapter({\n      selectId: (book: BookModel) => book.id,\n    });\n  });\n\n  it('should let you get the initial state', () => {\n    const initialState = adapter.getInitialState();\n\n    expect(initialState).toEqual({\n      ids: [],\n      entities: {},\n    });\n  });\n\n  it('should let you provide additional initial state properties', () => {\n    const additionalProperties = { isHydrated: true };\n\n    const initialState = adapter.getInitialState(additionalProperties);\n\n    expect(initialState).toEqual({\n      ...additionalProperties,\n      ids: [],\n      entities: {},\n    });\n  });\n});\n"
  },
  {
    "path": "modules/entity/spec/fixtures/book.ts",
    "content": "// eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-require-imports\nconst deepFreeze = require('deep-freeze');\n\nexport interface BookModel {\n  id: string;\n  title: string;\n  description?: string;\n}\n\nexport const AClockworkOrange: BookModel = deepFreeze({\n  id: 'aco',\n  title: 'A Clockwork Orange',\n});\n\nexport const AnimalFarm: BookModel = deepFreeze({\n  id: 'af',\n  title: 'Animal Farm',\n});\n\nexport const TheGreatGatsby: BookModel = deepFreeze({\n  id: 'tgg',\n  title: 'The Great Gatsby',\n  description: 'A 1925 novel written by American author F. Scott Fitzgerald',\n});\n"
  },
  {
    "path": "modules/entity/spec/sorted_state_adapter.spec.ts",
    "content": "import { EntityStateAdapter, EntityState, Update } from '../src/models';\nimport { createEntityAdapter } from '../src/create_adapter';\nimport {\n  BookModel,\n  TheGreatGatsby,\n  AClockworkOrange,\n  AnimalFarm,\n} from './fixtures/book';\n\ndescribe('Sorted State Adapter', () => {\n  let adapter: EntityStateAdapter<BookModel>;\n  let state: EntityState<BookModel>;\n\n  beforeAll(() => {\n    Object.defineProperty(Array.prototype, 'unwantedField', {\n      enumerable: true,\n      configurable: true,\n      value: 'This should not appear anywhere',\n    });\n  });\n\n  afterAll(() => {\n    delete (Array.prototype as any).unwantedField;\n  });\n\n  beforeEach(() => {\n    adapter = createEntityAdapter({\n      selectId: (book: BookModel) => book.id,\n      sortComparer: (a, b) => a.title.localeCompare(b.title),\n    });\n\n    state = { ids: [], entities: {} };\n  });\n\n  it('should let you add one entity to the state', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    expect(withOneEntity).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n      },\n    });\n  });\n\n  it('should not change state if you attempt to re-add an entity', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    const readded = adapter.addOne(TheGreatGatsby, withOneEntity);\n\n    expect(readded).toBe(withOneEntity);\n  });\n\n  it('should let you add many entities to the state', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    const withManyMore = adapter.addMany(\n      [AClockworkOrange, AnimalFarm],\n      withOneEntity\n    );\n\n    expect(withManyMore).toEqual({\n      ids: [AClockworkOrange.id, AnimalFarm.id, TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n        [AClockworkOrange.id]: AClockworkOrange,\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n\n  it('should let you set many entities in the state', () => {\n    const firstChange = { title: 'First Change' };\n    const withMany = adapter.setAll([TheGreatGatsby], state);\n\n    const withUpserts = adapter.setMany(\n      [{ ...TheGreatGatsby, ...firstChange }, AClockworkOrange],\n      withMany\n    );\n\n    expect(withUpserts).toEqual({\n      ids: [AClockworkOrange.id, TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...firstChange,\n        },\n        [AClockworkOrange.id]: AClockworkOrange,\n      },\n    });\n  });\n\n  it('should remove existing and add new ones on setAll', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    const withAll = adapter.setAll(\n      [AClockworkOrange, AnimalFarm],\n      withOneEntity\n    );\n\n    expect(withAll).toEqual({\n      ids: [AClockworkOrange.id, AnimalFarm.id],\n      entities: {\n        [AClockworkOrange.id]: AClockworkOrange,\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n\n  it('should let you add remove an entity from the state', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    const withoutOne = adapter.removeOne(TheGreatGatsby.id, state);\n\n    expect(withoutOne).toEqual({\n      ids: [],\n      entities: {},\n    });\n  });\n\n  it('should let you remove many entities by id from the state', () => {\n    const withAll = adapter.setAll(\n      [TheGreatGatsby, AClockworkOrange, AnimalFarm],\n      state\n    );\n\n    const withoutMany = adapter.removeMany(\n      [TheGreatGatsby.id, AClockworkOrange.id],\n      withAll\n    );\n\n    expect(withoutMany).toEqual({\n      ids: [AnimalFarm.id],\n      entities: {\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n\n  it('should let you remove many entities by a predicate from the state', () => {\n    const withAll = adapter.setAll(\n      [TheGreatGatsby, AClockworkOrange, AnimalFarm],\n      state\n    );\n\n    const withoutMany = adapter.removeMany(\n      (p) => p.id.startsWith('a'),\n      withAll\n    );\n\n    expect(withoutMany).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n      },\n    });\n  });\n\n  it('should let you remove all entities from the state', () => {\n    const withAll = adapter.setAll(\n      [TheGreatGatsby, AClockworkOrange, AnimalFarm],\n      state\n    );\n\n    const withoutAll = adapter.removeAll(withAll);\n\n    expect(withoutAll).toEqual({\n      ids: [],\n      entities: {},\n    });\n  });\n\n  it('should let you update an entity in the state', () => {\n    const withOne = adapter.addOne(TheGreatGatsby, state);\n    const changes = { title: 'A New Hope' };\n\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes,\n      },\n      withOne\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...changes,\n        },\n      },\n    });\n  });\n\n  it('should not change state if you attempt to update an entity that has not been added', () => {\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes: { title: 'A New Title' },\n      },\n      state\n    );\n\n    expect(withUpdates).toBe(state);\n  });\n\n  it('should not change ids state if you attempt to update an entity that does not impact sorting', () => {\n    const withAll = adapter.setAll(\n      [TheGreatGatsby, AClockworkOrange, AnimalFarm],\n      state\n    );\n    const changes = { title: 'The Great Gatsby II' };\n\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes,\n      },\n      withAll\n    );\n\n    expect(withAll.ids).toBe(withUpdates.ids);\n  });\n\n  it('should let you update the id of entity', () => {\n    const withOne = adapter.addOne(TheGreatGatsby, state);\n    const changes = { id: 'A New Id' };\n\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes,\n      },\n      withOne\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [changes.id],\n      entities: {\n        [changes.id]: {\n          ...TheGreatGatsby,\n          ...changes,\n        },\n      },\n    });\n  });\n\n  it('should resort correctly if same id but sort key update', () => {\n    const withAll = adapter.setAll(\n      [TheGreatGatsby, AnimalFarm, AClockworkOrange],\n      state\n    );\n    const changes = { title: 'A New Hope' };\n\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes,\n      },\n      withAll\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [AClockworkOrange.id, TheGreatGatsby.id, AnimalFarm.id],\n      entities: {\n        [AClockworkOrange.id]: AClockworkOrange,\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...changes,\n        },\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n\n  it('should resort correctly if the id and sort key update', () => {\n    const withOne = adapter.setAll(\n      [TheGreatGatsby, AnimalFarm, AClockworkOrange],\n      state\n    );\n    const changes = { id: 'A New Id', title: AnimalFarm.title };\n\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes,\n      },\n      withOne\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [AClockworkOrange.id, changes.id, AnimalFarm.id],\n      entities: {\n        [AClockworkOrange.id]: AClockworkOrange,\n        [changes.id]: {\n          ...TheGreatGatsby,\n          ...changes,\n        },\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n\n  it('should let you update many entities by id in the state', () => {\n    const firstChange = { title: 'Zack' };\n    const secondChange = { title: 'Aaron' };\n    const withMany = adapter.setAll([TheGreatGatsby, AClockworkOrange], state);\n\n    const withUpdates = adapter.updateMany(\n      [\n        { id: TheGreatGatsby.id, changes: firstChange },\n        { id: AClockworkOrange.id, changes: secondChange },\n      ],\n      withMany\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [AClockworkOrange.id, TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...firstChange,\n        },\n        [AClockworkOrange.id]: {\n          ...AClockworkOrange,\n          ...secondChange,\n        },\n      },\n    });\n  });\n\n  it('should let you map over entities in the state', () => {\n    const firstChange = { ...TheGreatGatsby, title: 'First change' };\n    const secondChange = { ...AClockworkOrange, title: 'Second change' };\n\n    const withMany = adapter.setAll(\n      [TheGreatGatsby, AClockworkOrange, AnimalFarm],\n      state\n    );\n\n    const withUpdates = adapter.map(\n      (book) =>\n        book.title === TheGreatGatsby.title\n          ? firstChange\n          : book.title === AClockworkOrange.title\n            ? secondChange\n            : book,\n      withMany\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [AnimalFarm.id, TheGreatGatsby.id, AClockworkOrange.id],\n      entities: {\n        [AnimalFarm.id]: AnimalFarm,\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...firstChange,\n        },\n        [AClockworkOrange.id]: {\n          ...AClockworkOrange,\n          ...secondChange,\n        },\n      },\n    });\n  });\n\n  it('should let you map over one entity by id in the state', () => {\n    const withMany = adapter.setAll([TheGreatGatsby, AClockworkOrange], state);\n\n    const withUpdates = adapter.mapOne(\n      {\n        id: TheGreatGatsby.id,\n        map: (entity) => ({ ...entity, title: 'Updated ' + entity.title }),\n      },\n      withMany\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [AClockworkOrange.id, TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          title: 'Updated ' + TheGreatGatsby.title,\n        },\n        [AClockworkOrange.id]: AClockworkOrange,\n      },\n    });\n  });\n\n  it('should let you add one entity to the state with upsert()', () => {\n    const withOneEntity = adapter.upsertOne(TheGreatGatsby, state);\n    expect(withOneEntity).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n      },\n    });\n  });\n\n  it('should let you update an entity in the state with upsert()', () => {\n    const withOne = adapter.addOne(TheGreatGatsby, state);\n    const changes = { title: 'A New Hope' };\n\n    const withUpdates = adapter.upsertOne(\n      { ...TheGreatGatsby, ...changes },\n      withOne\n    );\n    expect(withUpdates).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...changes,\n        },\n      },\n    });\n  });\n\n  it('should let you upsert many entities in the state', () => {\n    const firstChange = { title: 'Zack' };\n    const withMany = adapter.setAll([TheGreatGatsby], state);\n\n    const withUpserts = adapter.upsertMany(\n      [{ ...TheGreatGatsby, ...firstChange }, AClockworkOrange],\n      withMany\n    );\n\n    expect(withUpserts).toEqual({\n      ids: [AClockworkOrange.id, TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...firstChange,\n        },\n        [AClockworkOrange.id]: AClockworkOrange,\n      },\n    });\n  });\n\n  it('should let you add one entity to the state with setOne()', () => {\n    const withOneEntity = adapter.setOne(TheGreatGatsby, state);\n    expect(withOneEntity).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n      },\n    });\n  });\n\n  it('should let you replace an entity in the state with setOne()', () => {\n    const withMany = adapter.addOne(\n      TheGreatGatsby,\n      adapter.addOne(AnimalFarm, adapter.addOne(AClockworkOrange, state))\n    );\n    const updatedBook = {\n      id: TheGreatGatsby.id,\n      title: 'A New Hope',\n      /* description property is not provided */\n    };\n\n    const withUpdates = adapter.setOne(updatedBook, withMany);\n    expect(withUpdates).toEqual({\n      ids: [AClockworkOrange.id, updatedBook.id, AnimalFarm.id],\n      entities: {\n        [AClockworkOrange.id]: AClockworkOrange,\n        [updatedBook.id]: updatedBook,\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "modules/entity/spec/state_selectors.spec.ts",
    "content": "import { createEntityAdapter, EntityAdapter, EntityState } from '../src';\nimport { EntitySelectors } from '../src/models';\nimport {\n  BookModel,\n  AClockworkOrange,\n  AnimalFarm,\n  TheGreatGatsby,\n} from './fixtures/book';\nimport { MemoizedSelector, createSelector } from '@ngrx/store';\n\ndescribe('Entity State Selectors', () => {\n  describe('Composed Selectors', () => {\n    interface State {\n      books: EntityState<BookModel>;\n    }\n\n    let adapter: EntityAdapter<BookModel>;\n    let selectors: EntitySelectors<BookModel, State>;\n    let state: State;\n\n    beforeEach(() => {\n      adapter = createEntityAdapter({\n        selectId: (book: BookModel) => book.id,\n      });\n\n      state = {\n        books: adapter.setAll(\n          [AClockworkOrange, AnimalFarm, TheGreatGatsby],\n          adapter.getInitialState()\n        ),\n      };\n\n      selectors = adapter.getSelectors((state: State) => state.books);\n    });\n\n    it('should create a selector for selecting the ids', () => {\n      const ids = selectors.selectIds(state);\n\n      expect(ids).toEqual(state.books.ids);\n    });\n\n    it('should create a selector for selecting the entities', () => {\n      const entities = selectors.selectEntities(state);\n\n      expect(entities).toEqual(state.books.entities);\n    });\n\n    it('should create a selector for selecting the list of models', () => {\n      const models = selectors.selectAll(state);\n\n      expect(models).toEqual([AClockworkOrange, AnimalFarm, TheGreatGatsby]);\n    });\n\n    it('should create a selector for selecting the count of models', () => {\n      const total = selectors.selectTotal(state);\n\n      expect(total).toEqual(3);\n    });\n  });\n\n  describe('Uncomposed Selectors', () => {\n    type State = EntityState<BookModel>;\n\n    let adapter: EntityAdapter<BookModel>;\n    let selectors: EntitySelectors<BookModel, EntityState<BookModel>>;\n    let state: State;\n\n    beforeEach(() => {\n      adapter = createEntityAdapter({\n        selectId: (book: BookModel) => book.id,\n      });\n\n      state = adapter.setAll(\n        [AClockworkOrange, AnimalFarm, TheGreatGatsby],\n        adapter.getInitialState()\n      );\n\n      selectors = adapter.getSelectors();\n    });\n\n    it('should create a selector for selecting the ids', () => {\n      const ids = selectors.selectIds(state);\n\n      expect(ids).toEqual(state.ids);\n    });\n\n    it('should create a selector for selecting the entities', () => {\n      const entities = selectors.selectEntities(state);\n\n      expect(entities).toEqual(state.entities);\n    });\n\n    it('should type single entity from Dictionary as entity type or undefined', () => {\n      // MemoizedSelector acts like a type checker\n      const singleEntity: MemoizedSelector<\n        EntityState<BookModel>,\n        BookModel | undefined\n      > = createSelector(selectors.selectEntities, (enitites) => enitites[0]);\n    });\n\n    it('should create a selector for selecting the list of models', () => {\n      const models = selectors.selectAll(state);\n\n      expect(models).toEqual([AClockworkOrange, AnimalFarm, TheGreatGatsby]);\n    });\n\n    it('should create a selector for selecting the count of models', () => {\n      const total = selectors.selectTotal(state);\n\n      expect(total).toEqual(3);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/entity/spec/types/entity_selectors.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('EntitySelectors', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import { Selector } from '@ngrx/store';\n        import { EntitySelectors } from './modules/entity/src/models';\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('is compatible with a dictionary of selectors', () => {\n    expectSnippet(`\n      type SelectorsDictionary = Record<\n        string,\n        | Selector<Record<string, any>, unknown>\n        | ((...args: any[]) => Selector<Record<string, any>, unknown>)\n      >;\n      type ExtendsSelectorsDictionary<T> = T extends SelectorsDictionary\n        ? true\n        : false;\n\n      let result: ExtendsSelectorsDictionary<\n        EntitySelectors<unknown, Record<string, any>>\n      >;\n    `).toInfer('result', 'true');\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/entity/spec/types/entity_state.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('EntityState Types', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import { EntityState, createEntityAdapter, EntityAdapter } from '@ngrx/entity';\n\n        interface Book {\n          id: string;\n          title: string;\n        }\n\n        interface BookState extends EntityState<Book> {\n          selectedBookId: string | null;\n        }\n\n        export const adapter: EntityAdapter<Book> = createEntityAdapter<Book>();\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  describe('getInitialState', () => {\n    it('can set the initial state', () => {\n      expectSnippet(`\n        export const initialState: BookState = adapter.getInitialState({\n          selectedBookId: '1',\n        });\n\n      `).toSucceed();\n    });\n\n    it('can set the initial state with additional properties', () => {\n      expectSnippet(`\n        export const initialState: BookState = adapter.getInitialState({\n          selectedBookId: '1',\n        });\n\n      `).toSucceed();\n    });\n\n    it('throws when setting the initial state with unknown properties', () => {\n      expectSnippet(`\n        export const initialState: BookState = adapter.getInitialState({\n          selectedBookId: '1',\n          otherProperty: 'value',\n        });\n      `).toFail(\n        /Object literal may only specify known properties, and 'otherProperty' does not exist in type 'Omit<BookState, keyof EntityState<T>>'/i\n      );\n    });\n\n    it('can set the initial state with unknown properties when the state is untyped', () => {\n      expectSnippet(`\n        export const initialState = adapter.getInitialState({\n          selectedBookId: '1',\n          otherProperty: 'value',\n        });\n      `).toSucceed();\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/entity/spec/types/utils.ts",
    "content": "export const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  target: 'ES2022',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  strict: true,\n  paths: {\n    '@ngrx/entity': ['./modules/entity'],\n    '@ngrx/store': ['./modules/store'],\n  },\n});\n"
  },
  {
    "path": "modules/entity/spec/unsorted_state_adapter.spec.ts",
    "content": "import { EntityStateAdapter, EntityState } from '../src/models';\nimport { createEntityAdapter } from '../src/create_adapter';\nimport {\n  BookModel,\n  TheGreatGatsby,\n  AClockworkOrange,\n  AnimalFarm,\n} from './fixtures/book';\n\ndescribe('Unsorted State Adapter', () => {\n  let adapter: EntityStateAdapter<BookModel>;\n  let state: EntityState<BookModel>;\n\n  beforeAll(() => {\n    Object.defineProperty(Array.prototype, 'unwantedField', {\n      enumerable: true,\n      configurable: true,\n      value: 'This should not appear anywhere',\n    });\n  });\n\n  afterAll(() => {\n    delete (Array.prototype as any).unwantedField;\n  });\n\n  beforeEach(() => {\n    adapter = createEntityAdapter({\n      selectId: (book: BookModel) => book.id,\n    });\n\n    state = { ids: [], entities: {} };\n  });\n\n  it('should let you add one entity to the state', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    expect(withOneEntity).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n      },\n    });\n  });\n\n  it('should not change state if you attempt to re-add an entity', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    const readded = adapter.addOne(TheGreatGatsby, withOneEntity);\n\n    expect(readded).toBe(withOneEntity);\n  });\n\n  it('should let you add many entities to the state', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    const withManyMore = adapter.addMany(\n      [AClockworkOrange, AnimalFarm],\n      withOneEntity\n    );\n\n    expect(withManyMore).toEqual({\n      ids: [TheGreatGatsby.id, AClockworkOrange.id, AnimalFarm.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n        [AClockworkOrange.id]: AClockworkOrange,\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n\n  it('should let you set many entities in the state', () => {\n    const firstChange = { title: 'First Change' };\n    const withMany = adapter.setAll([TheGreatGatsby], state);\n\n    const withUpserts = adapter.setMany(\n      [{ ...TheGreatGatsby, ...firstChange }, AClockworkOrange],\n      withMany\n    );\n\n    expect(withUpserts).toEqual({\n      ids: [TheGreatGatsby.id, AClockworkOrange.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...firstChange,\n        },\n        [AClockworkOrange.id]: AClockworkOrange,\n      },\n    });\n  });\n\n  it('should remove existing and add new ones on setAll', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    const withAll = adapter.setAll(\n      [AClockworkOrange, AnimalFarm],\n      withOneEntity\n    );\n\n    expect(withAll).toEqual({\n      ids: [AClockworkOrange.id, AnimalFarm.id],\n      entities: {\n        [AClockworkOrange.id]: AClockworkOrange,\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n\n  it('should let you add remove an entity from the state', () => {\n    const withOneEntity = adapter.addOne(TheGreatGatsby, state);\n\n    const withoutOne = adapter.removeOne(TheGreatGatsby.id, state);\n\n    expect(withoutOne).toEqual({\n      ids: [],\n      entities: {},\n    });\n  });\n\n  it('should let you remove many entities by id from the state', () => {\n    const withAll = adapter.setAll(\n      [TheGreatGatsby, AClockworkOrange, AnimalFarm],\n      state\n    );\n\n    const withoutMany = adapter.removeMany(\n      [TheGreatGatsby.id, AClockworkOrange.id],\n      withAll\n    );\n\n    expect(withoutMany).toEqual({\n      ids: [AnimalFarm.id],\n      entities: {\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n\n  it('should let you remove many entities by a predicate from the state', () => {\n    const withAll = adapter.setAll(\n      [TheGreatGatsby, AClockworkOrange, AnimalFarm],\n      state\n    );\n\n    const withoutMany = adapter.removeMany(\n      (p) => p.id.startsWith('a'),\n      withAll\n    );\n\n    expect(withoutMany).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n      },\n    });\n  });\n\n  it('should let you remove all entities from the state', () => {\n    const withAll = adapter.setAll(\n      [TheGreatGatsby, AClockworkOrange, AnimalFarm],\n      state\n    );\n\n    const withoutAll = adapter.removeAll(withAll);\n\n    expect(withoutAll).toEqual({\n      ids: [],\n      entities: {},\n    });\n  });\n\n  it('should let you update an entity in the state', () => {\n    const withOne = adapter.addOne(TheGreatGatsby, state);\n    const changes = { title: 'A New Hope' };\n\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes,\n      },\n      withOne\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...changes,\n        },\n      },\n    });\n  });\n\n  it('should not change state if you attempt to update an entity that has not been added', () => {\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes: { title: 'A New Title' },\n      },\n      state\n    );\n\n    expect(withUpdates).toBe(state);\n  });\n\n  it('should not change ids state if you attempt to update an entity that has already been added', () => {\n    const withOne = adapter.addOne(TheGreatGatsby, state);\n    const changes = { title: 'A New Hope' };\n\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes,\n      },\n      withOne\n    );\n\n    expect(withOne.ids).toBe(withUpdates.ids);\n  });\n\n  it('should let you update the id of entity', () => {\n    const withOne = adapter.addOne(TheGreatGatsby, state);\n    const changes = { id: 'A New Id' };\n\n    const withUpdates = adapter.updateOne(\n      {\n        id: TheGreatGatsby.id,\n        changes,\n      },\n      withOne\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [changes.id],\n      entities: {\n        [changes.id]: {\n          ...TheGreatGatsby,\n          ...changes,\n        },\n      },\n    });\n  });\n\n  it('should let you update many entities by id in the state', () => {\n    const firstChange = { title: 'First Change' };\n    const secondChange = { title: 'Second Change' };\n    const withMany = adapter.setAll([TheGreatGatsby, AClockworkOrange], state);\n\n    const withUpdates = adapter.updateMany(\n      [\n        { id: TheGreatGatsby.id, changes: firstChange },\n        { id: AClockworkOrange.id, changes: secondChange },\n      ],\n      withMany\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [TheGreatGatsby.id, AClockworkOrange.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...firstChange,\n        },\n        [AClockworkOrange.id]: {\n          ...AClockworkOrange,\n          ...secondChange,\n        },\n      },\n    });\n  });\n\n  it('should let you map over entities in the state', () => {\n    const firstChange = { ...TheGreatGatsby, title: 'First change' };\n    const secondChange = { ...AClockworkOrange, title: 'Second change' };\n\n    const withMany = adapter.setAll(\n      [TheGreatGatsby, AClockworkOrange, AnimalFarm],\n      state\n    );\n\n    const withUpdates = adapter.map(\n      (book) =>\n        book.title === TheGreatGatsby.title\n          ? firstChange\n          : book.title === AClockworkOrange.title\n            ? secondChange\n            : book,\n      withMany\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [TheGreatGatsby.id, AClockworkOrange.id, AnimalFarm.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...firstChange,\n        },\n        [AClockworkOrange.id]: {\n          ...AClockworkOrange,\n          ...secondChange,\n        },\n        [AnimalFarm.id]: AnimalFarm,\n      },\n    });\n  });\n\n  it('should let you map over one entity by id in the state', () => {\n    const withMany = adapter.setAll([TheGreatGatsby, AClockworkOrange], state);\n\n    const withUpdates = adapter.mapOne(\n      {\n        id: TheGreatGatsby.id,\n        map: (entity) => ({ ...entity, title: 'Updated ' + entity.title }),\n      },\n      withMany\n    );\n\n    expect(withUpdates).toEqual({\n      ids: [TheGreatGatsby.id, AClockworkOrange.id],\n      entities: {\n        [AClockworkOrange.id]: AClockworkOrange,\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          title: 'Updated ' + TheGreatGatsby.title,\n        },\n      },\n    });\n  });\n\n  it('should let you add one entity to the state with upsert()', () => {\n    const withOneEntity = adapter.upsertOne(TheGreatGatsby, state);\n    expect(withOneEntity).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n      },\n    });\n  });\n\n  it('should let you update an entity in the state with upsert()', () => {\n    const withOne = adapter.addOne(TheGreatGatsby, state);\n    const changes = { title: 'A New Hope' };\n\n    const withUpdates = adapter.upsertOne(\n      { ...TheGreatGatsby, ...changes },\n      withOne\n    );\n    expect(withUpdates).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...changes,\n        },\n      },\n    });\n  });\n\n  it('should let you upsert many entities in the state', () => {\n    const firstChange = { title: 'First Change' };\n    const withMany = adapter.setAll([TheGreatGatsby], state);\n\n    const withUpserts = adapter.upsertMany(\n      [{ ...TheGreatGatsby, ...firstChange }, AClockworkOrange],\n      withMany\n    );\n\n    expect(withUpserts).toEqual({\n      ids: [TheGreatGatsby.id, AClockworkOrange.id],\n      entities: {\n        [TheGreatGatsby.id]: {\n          ...TheGreatGatsby,\n          ...firstChange,\n        },\n        [AClockworkOrange.id]: AClockworkOrange,\n      },\n    });\n  });\n\n  it('should let you add one entity to the state with setOne()', () => {\n    const withOneEntity = adapter.setOne(TheGreatGatsby, state);\n    expect(withOneEntity).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: TheGreatGatsby,\n      },\n    });\n  });\n\n  it('should let you replace an entity in the state with setOne()', () => {\n    const withOne = adapter.addOne(TheGreatGatsby, state);\n    const updatedBook = {\n      id: TheGreatGatsby.id,\n      title: 'A New Hope',\n      /* description property is not provided */\n    };\n\n    const withUpdates = adapter.setOne(updatedBook, withOne);\n    expect(withUpdates).toEqual({\n      ids: [TheGreatGatsby.id],\n      entities: {\n        [TheGreatGatsby.id]: updatedBook,\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "modules/entity/spec/utils.spec.ts",
    "content": "import { selectIdValue } from '../src/utils';\nimport { BookModel, AClockworkOrange } from './fixtures/book';\n\n// Mock isDevMode at the top level - use a simple wrapper function\nvi.mock('@angular/core', async () => {\n  const actual =\n    await vi.importActual<typeof import('@angular/core')>('@angular/core');\n  return {\n    ...actual,\n    isDevMode: vi.fn(() => true),\n  };\n});\n\ndescribe('Entity utils', () => {\n  describe(`selectIdValue()`, () => {\n    beforeEach(async () => {\n      const { isDevMode } = await import('@angular/core');\n      vi.mocked(isDevMode).mockReturnValue(true);\n      vi.clearAllMocks();\n    });\n\n    afterEach(() => {\n      vi.restoreAllMocks();\n    });\n\n    it('should not warn when key does exist', () => {\n      const spy = vi.spyOn(console, 'warn');\n\n      const key = selectIdValue(AClockworkOrange, (book) => book.id);\n\n      expect(spy).not.toHaveBeenCalled();\n    });\n\n    it('should warn when key does not exist in dev mode', () => {\n      const spy = vi.spyOn(console, 'warn');\n\n      const key = selectIdValue(AClockworkOrange, (book: any) => book.foo);\n\n      expect(spy).toHaveBeenCalled();\n    });\n\n    it('should warn when key is undefined in dev mode', () => {\n      const spy = vi.spyOn(console, 'warn');\n\n      const undefinedAClockworkOrange = { ...AClockworkOrange, id: undefined };\n      const key = selectIdValue(\n        undefinedAClockworkOrange,\n        (book: any) => book.id\n      );\n\n      expect(spy).toHaveBeenCalled();\n    });\n\n    it('should not warn when key does not exist in prod mode', async () => {\n      const { isDevMode } = await import('@angular/core');\n      vi.mocked(isDevMode).mockReturnValue(false);\n      const spy = vi.spyOn(console, 'warn');\n\n      const key = selectIdValue(AClockworkOrange, (book: any) => book.foo);\n\n      expect(spy).not.toHaveBeenCalled();\n    });\n\n    it('should not warn when key is undefined in prod mode', async () => {\n      const { isDevMode } = await import('@angular/core');\n      vi.mocked(isDevMode).mockReturnValue(false);\n      const spy = vi.spyOn(console, 'warn');\n\n      const undefinedAClockworkOrange = { ...AClockworkOrange, id: undefined };\n      const key = selectIdValue(\n        undefinedAClockworkOrange,\n        (book: any) => book.id\n      );\n\n      expect(spy).not.toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/entity/src/create_adapter.ts",
    "content": "import {\n  EntityDefinition,\n  Comparer,\n  IdSelector,\n  EntityAdapter,\n} from './models';\nimport { createInitialStateFactory } from './entity_state';\nimport { createSelectorsFactory } from './state_selectors';\nimport { createSortedStateAdapter } from './sorted_state_adapter';\nimport { createUnsortedStateAdapter } from './unsorted_state_adapter';\n\nexport function createEntityAdapter<T>(\n  options: {\n    selectId?: IdSelector<T>;\n    sortComparer?: false | Comparer<T>;\n  } = {}\n): EntityAdapter<T> {\n  const { selectId, sortComparer }: EntityDefinition<T> = {\n    selectId: options.selectId ?? ((entity: any) => entity.id),\n    sortComparer: options.sortComparer ?? false,\n  };\n\n  const stateFactory = createInitialStateFactory<T>();\n  const selectorsFactory = createSelectorsFactory<T>();\n  const stateAdapter = sortComparer\n    ? createSortedStateAdapter(selectId, sortComparer)\n    : createUnsortedStateAdapter(selectId);\n\n  return {\n    selectId,\n    sortComparer,\n    ...stateFactory,\n    ...selectorsFactory,\n    ...stateAdapter,\n  };\n}\n"
  },
  {
    "path": "modules/entity/src/entity_state.ts",
    "content": "import { EntityState } from './models';\n\nexport function getInitialEntityState<V>(): EntityState<V> {\n  return {\n    ids: [],\n    entities: {},\n  };\n}\n\nexport function createInitialStateFactory<V>() {\n  function getInitialState(): EntityState<V>;\n  function getInitialState<S extends EntityState<V>>(\n    additionalState: Omit<S, keyof EntityState<V>>\n  ): S;\n  function getInitialState(additionalState: any = {}): any {\n    return Object.assign(getInitialEntityState(), additionalState);\n  }\n\n  return { getInitialState };\n}\n"
  },
  {
    "path": "modules/entity/src/index.ts",
    "content": "export { createEntityAdapter } from './create_adapter';\nexport {\n  Comparer,\n  Dictionary,\n  DictionaryNum,\n  EntityAdapter,\n  EntityMap,\n  EntityMapOne,\n  EntitySelectors,\n  EntityState,\n  IdSelector,\n  MemoizedEntitySelectors,\n  Predicate,\n  Update,\n} from './models';\n"
  },
  {
    "path": "modules/entity/src/models.ts",
    "content": "import { MemoizedSelector } from '@ngrx/store';\n\nexport type Comparer<T> = (a: T, b: T) => number;\n\nexport type IdSelectorStr<T> = (model: T) => string;\nexport type IdSelectorNum<T> = (model: T) => number;\n\nexport type IdSelector<T> = IdSelectorStr<T> | IdSelectorNum<T>;\n\nexport interface DictionaryNum<T> {\n  [id: number]: T | undefined;\n}\n\nexport abstract class Dictionary<T> implements DictionaryNum<T> {\n  [id: string]: T | undefined;\n}\n\nexport interface UpdateStr<T> {\n  id: string;\n  changes: Partial<T>;\n}\n\nexport interface UpdateNum<T> {\n  id: number;\n  changes: Partial<T>;\n}\n\nexport type Update<T> = UpdateStr<T> | UpdateNum<T>;\n\nexport type Predicate<T> = (entity: T) => boolean;\n\nexport type EntityMap<T> = (entity: T) => T;\n\nexport interface EntityMapOneNum<T> {\n  id: number;\n  map: EntityMap<T>;\n}\n\nexport interface EntityMapOneStr<T> {\n  id: string;\n  map: EntityMap<T>;\n}\n\nexport type EntityMapOne<T> = EntityMapOneNum<T> | EntityMapOneStr<T>;\n\nexport interface EntityState<T> {\n  ids: string[] | number[];\n  entities: Dictionary<T>;\n}\n\nexport interface EntityDefinition<T> {\n  selectId: IdSelector<T>;\n  sortComparer: false | Comparer<T>;\n}\n\nexport interface EntityStateAdapter<T> {\n  addOne<S extends EntityState<T>>(entity: T, state: S): S;\n  addMany<S extends EntityState<T>>(entities: T[], state: S): S;\n\n  setAll<S extends EntityState<T>>(entities: T[], state: S): S;\n  setOne<S extends EntityState<T>>(entity: T, state: S): S;\n  setMany<S extends EntityState<T>>(entities: T[], state: S): S;\n\n  removeOne<S extends EntityState<T>>(key: string, state: S): S;\n  removeOne<S extends EntityState<T>>(key: number, state: S): S;\n\n  removeMany<S extends EntityState<T>>(keys: string[], state: S): S;\n  removeMany<S extends EntityState<T>>(keys: number[], state: S): S;\n  removeMany<S extends EntityState<T>>(predicate: Predicate<T>, state: S): S;\n\n  removeAll<S extends EntityState<T>>(state: S): S;\n\n  updateOne<S extends EntityState<T>>(update: Update<T>, state: S): S;\n  updateMany<S extends EntityState<T>>(updates: Update<T>[], state: S): S;\n\n  upsertOne<S extends EntityState<T>>(entity: T, state: S): S;\n  upsertMany<S extends EntityState<T>>(entities: T[], state: S): S;\n\n  mapOne<S extends EntityState<T>>(map: EntityMapOne<T>, state: S): S;\n  map<S extends EntityState<T>>(map: EntityMap<T>, state: S): S;\n}\n\nexport type EntitySelectors<T, V> = {\n  selectIds: (state: V) => string[] | number[];\n  selectEntities: (state: V) => Dictionary<T>;\n  selectAll: (state: V) => T[];\n  selectTotal: (state: V) => number;\n};\n\nexport type MemoizedEntitySelectors<T, V> = {\n  selectIds: MemoizedSelector<\n    V,\n    string[] | number[],\n    (entityState: EntityState<T>) => string[] | number[]\n  >;\n  selectEntities: MemoizedSelector<\n    V,\n    Dictionary<T>,\n    (entityState: EntityState<T>) => Dictionary<T>\n  >;\n  selectAll: MemoizedSelector<V, T[], (entityState: EntityState<T>) => T[]>;\n  selectTotal: MemoizedSelector<\n    V,\n    number,\n    (entityState: EntityState<T>) => number\n  >;\n};\n\nexport interface EntityAdapter<T> extends EntityStateAdapter<T> {\n  selectId: IdSelector<T>;\n  sortComparer: false | Comparer<T>;\n  getInitialState(): EntityState<T>;\n  getInitialState<S extends EntityState<T>>(\n    state: Omit<S, keyof EntityState<T>>\n  ): S;\n  getSelectors(): EntitySelectors<T, EntityState<T>>;\n  getSelectors<V>(\n    selectState: (state: V) => EntityState<T>\n  ): MemoizedEntitySelectors<T, V>;\n}\n"
  },
  {
    "path": "modules/entity/src/sorted_state_adapter.ts",
    "content": "import {\n  EntityState,\n  IdSelector,\n  Comparer,\n  EntityStateAdapter,\n  Update,\n  EntityMap,\n  EntityMapOneNum,\n  EntityMapOneStr,\n} from './models';\nimport { createStateOperator, DidMutate } from './state_adapter';\nimport { createUnsortedStateAdapter } from './unsorted_state_adapter';\nimport { selectIdValue } from './utils';\n\nexport function createSortedStateAdapter<T>(\n  selectId: IdSelector<T>,\n  sort: Comparer<T>\n): EntityStateAdapter<T>;\nexport function createSortedStateAdapter<T>(selectId: any, sort: any): any {\n  type R = EntityState<T>;\n\n  const { removeOne, removeMany, removeAll } =\n    createUnsortedStateAdapter(selectId);\n\n  function addOneMutably(entity: T, state: R): DidMutate;\n  function addOneMutably(entity: any, state: any): DidMutate {\n    return addManyMutably([entity], state);\n  }\n\n  function addManyMutably(newModels: T[], state: R): DidMutate;\n  function addManyMutably(newModels: any[], state: any): DidMutate {\n    const models = newModels.filter(\n      (model) => !(selectIdValue(model, selectId) in state.entities)\n    );\n\n    if (models.length === 0) {\n      return DidMutate.None;\n    } else {\n      merge(models, state);\n      return DidMutate.Both;\n    }\n  }\n\n  function setAllMutably(models: T[], state: R): DidMutate;\n  function setAllMutably(models: any[], state: any): DidMutate {\n    state.entities = {};\n    state.ids = [];\n\n    addManyMutably(models, state);\n\n    return DidMutate.Both;\n  }\n\n  function setOneMutably(entity: T, state: R): DidMutate;\n  function setOneMutably(entity: any, state: any): DidMutate {\n    const id = selectIdValue(entity, selectId);\n    if (id in state.entities) {\n      state.ids = state.ids.filter((val: string | number) => val !== id);\n      merge([entity], state);\n      return DidMutate.Both;\n    } else {\n      return addOneMutably(entity, state);\n    }\n  }\n\n  function setManyMutably(entities: T[], state: R): DidMutate;\n  function setManyMutably(entities: any[], state: any): DidMutate {\n    const didMutateSetOne = entities.map((entity) =>\n      setOneMutably(entity, state)\n    );\n\n    switch (true) {\n      case didMutateSetOne.some((didMutate) => didMutate === DidMutate.Both):\n        return DidMutate.Both;\n      case didMutateSetOne.some(\n        (didMutate) => didMutate === DidMutate.EntitiesOnly\n      ):\n        return DidMutate.EntitiesOnly;\n      default:\n        return DidMutate.None;\n    }\n  }\n\n  function updateOneMutably(update: Update<T>, state: R): DidMutate;\n  function updateOneMutably(update: any, state: any): DidMutate {\n    return updateManyMutably([update], state);\n  }\n\n  function takeUpdatedModel(models: T[], update: Update<T>, state: R): boolean;\n  function takeUpdatedModel(models: any[], update: any, state: any): boolean {\n    if (!(update.id in state.entities)) {\n      return false;\n    }\n\n    const original = state.entities[update.id];\n    const updated = Object.assign({}, original, update.changes);\n    const newKey = selectIdValue(updated, selectId);\n\n    delete state.entities[update.id];\n\n    models.push(updated);\n\n    return newKey !== update.id;\n  }\n\n  function updateManyMutably(updates: Update<T>[], state: R): DidMutate;\n  function updateManyMutably(updates: any[], state: any): DidMutate {\n    const models: T[] = [];\n\n    const didMutateIds =\n      updates.filter((update) => takeUpdatedModel(models, update, state))\n        .length > 0;\n\n    if (models.length === 0) {\n      return DidMutate.None;\n    } else {\n      const originalIds = state.ids;\n      const updatedIndexes: any[] = [];\n      state.ids = state.ids.filter((id: any, index: number) => {\n        if (id in state.entities) {\n          return true;\n        } else {\n          updatedIndexes.push(index);\n          return false;\n        }\n      });\n\n      merge(models, state);\n\n      if (\n        !didMutateIds &&\n        updatedIndexes.every((i: number) => state.ids[i] === originalIds[i])\n      ) {\n        return DidMutate.EntitiesOnly;\n      } else {\n        return DidMutate.Both;\n      }\n    }\n  }\n\n  function mapMutably(map: EntityMap<T>, state: R): DidMutate;\n  function mapMutably(updatesOrMap: any, state: any): DidMutate {\n    const updates: Update<T>[] = state.ids.reduce(\n      (changes: any[], id: string | number) => {\n        const change = updatesOrMap(state.entities[id]);\n        if (change !== state.entities[id]) {\n          changes.push({ id, changes: change });\n        }\n        return changes;\n      },\n      []\n    );\n\n    return updateManyMutably(updates, state);\n  }\n\n  function mapOneMutably(map: EntityMapOneNum<T>, state: R): DidMutate;\n  function mapOneMutably(map: EntityMapOneStr<T>, state: R): DidMutate;\n  function mapOneMutably({ map, id }: any, state: any): DidMutate {\n    const entity = state.entities[id];\n    if (!entity) {\n      return DidMutate.None;\n    }\n\n    const updatedEntity = map(entity);\n    return updateOneMutably(\n      {\n        id: id,\n        changes: updatedEntity,\n      },\n      state\n    );\n  }\n\n  function upsertOneMutably(entity: T, state: R): DidMutate;\n  function upsertOneMutably(entity: any, state: any): DidMutate {\n    return upsertManyMutably([entity], state);\n  }\n\n  function upsertManyMutably(entities: T[], state: R): DidMutate;\n  function upsertManyMutably(entities: any[], state: any): DidMutate {\n    const added: any[] = [];\n    const updated: any[] = [];\n\n    for (const entity of entities) {\n      const id = selectIdValue(entity, selectId);\n      if (id in state.entities) {\n        updated.push({ id, changes: entity });\n      } else {\n        added.push(entity);\n      }\n    }\n\n    const didMutateByUpdated = updateManyMutably(updated, state);\n    const didMutateByAdded = addManyMutably(added, state);\n\n    switch (true) {\n      case didMutateByAdded === DidMutate.None &&\n        didMutateByUpdated === DidMutate.None:\n        return DidMutate.None;\n      case didMutateByAdded === DidMutate.Both ||\n        didMutateByUpdated === DidMutate.Both:\n        return DidMutate.Both;\n      default:\n        return DidMutate.EntitiesOnly;\n    }\n  }\n\n  function merge(models: T[], state: R): void;\n  function merge(models: any[], state: any): void {\n    models.sort(sort);\n\n    const ids: any[] = [];\n\n    let i = 0;\n    let j = 0;\n\n    while (i < models.length && j < state.ids.length) {\n      const model = models[i];\n      const modelId = selectIdValue(model, selectId);\n      const entityId = state.ids[j];\n      const entity = state.entities[entityId];\n\n      if (sort(model, entity) <= 0) {\n        ids.push(modelId);\n        i++;\n      } else {\n        ids.push(entityId);\n        j++;\n      }\n    }\n\n    if (i < models.length) {\n      state.ids = ids.concat(models.slice(i).map(selectId));\n    } else {\n      state.ids = ids.concat(state.ids.slice(j));\n    }\n\n    models.forEach((model, i) => {\n      state.entities[selectId(model)] = model;\n    });\n  }\n\n  return {\n    removeOne,\n    removeMany,\n    removeAll,\n    addOne: createStateOperator(addOneMutably),\n    updateOne: createStateOperator(updateOneMutably),\n    upsertOne: createStateOperator(upsertOneMutably),\n    setAll: createStateOperator(setAllMutably),\n    setOne: createStateOperator(setOneMutably),\n    setMany: createStateOperator(setManyMutably),\n    addMany: createStateOperator(addManyMutably),\n    updateMany: createStateOperator(updateManyMutably),\n    upsertMany: createStateOperator(upsertManyMutably),\n    map: createStateOperator(mapMutably),\n    mapOne: createStateOperator(mapOneMutably),\n  };\n}\n"
  },
  {
    "path": "modules/entity/src/state_adapter.ts",
    "content": "import { EntityState } from './models';\n\nexport enum DidMutate {\n  EntitiesOnly,\n  Both,\n  None,\n}\n\nexport function createStateOperator<V, R>(\n  mutator: (arg: R, state: EntityState<V>) => DidMutate\n): EntityState<V>;\nexport function createStateOperator<V, R>(\n  mutator: (arg: any, state: any) => DidMutate\n): any {\n  return function operation<S extends EntityState<V>>(arg: R, state: any): S {\n    const clonedEntityState: EntityState<V> = {\n      ids: [...state.ids],\n      entities: { ...state.entities },\n    };\n\n    const didMutate = mutator(arg, clonedEntityState);\n\n    if (didMutate === DidMutate.Both) {\n      return Object.assign({}, state, clonedEntityState);\n    }\n\n    if (didMutate === DidMutate.EntitiesOnly) {\n      return {\n        ...state,\n        entities: clonedEntityState.entities,\n      };\n    }\n\n    return state;\n  };\n}\n"
  },
  {
    "path": "modules/entity/src/state_selectors.ts",
    "content": "import { createSelector } from '@ngrx/store';\nimport {\n  EntityState,\n  EntitySelectors,\n  MemoizedEntitySelectors,\n} from './models';\n\nexport function createSelectorsFactory<T>() {\n  function getSelectors(): EntitySelectors<T, EntityState<T>>;\n  function getSelectors<V>(\n    selectState: (state: V) => EntityState<T>\n  ): MemoizedEntitySelectors<T, V>;\n  function getSelectors(\n    selectState?: (state: any) => EntityState<T>\n  ): EntitySelectors<T, any> {\n    const selectIds = (state: any) => state.ids;\n    const selectEntities = (state: EntityState<T>) => state.entities;\n    const selectAll = createSelector(\n      selectIds,\n      selectEntities,\n      (ids, entities): any => ids.map((id: any) => (entities as any)[id])\n    );\n\n    const selectTotal = createSelector(selectIds, (ids) => ids.length);\n\n    if (!selectState) {\n      return {\n        selectIds,\n        selectEntities,\n        selectAll,\n        selectTotal,\n      };\n    }\n\n    return {\n      selectIds: createSelector(selectState, selectIds),\n      selectEntities: createSelector(selectState, selectEntities),\n      selectAll: createSelector(selectState, selectAll),\n      selectTotal: createSelector(selectState, selectTotal),\n    };\n  }\n\n  return { getSelectors };\n}\n"
  },
  {
    "path": "modules/entity/src/unsorted_state_adapter.ts",
    "content": "import {\n  EntityState,\n  EntityStateAdapter,\n  IdSelector,\n  Update,\n  Predicate,\n  EntityMap,\n  EntityMapOneNum,\n  EntityMapOneStr,\n} from './models';\nimport { createStateOperator, DidMutate } from './state_adapter';\nimport { selectIdValue } from './utils';\n\nexport function createUnsortedStateAdapter<T>(\n  selectId: IdSelector<T>\n): EntityStateAdapter<T>;\nexport function createUnsortedStateAdapter<T>(selectId: IdSelector<T>): any {\n  type R = EntityState<T>;\n\n  function addOneMutably(entity: T, state: R): DidMutate;\n  function addOneMutably(entity: any, state: any): DidMutate {\n    const key = selectIdValue(entity, selectId);\n\n    if (key in state.entities) {\n      return DidMutate.None;\n    }\n\n    state.ids.push(key);\n    state.entities[key] = entity;\n\n    return DidMutate.Both;\n  }\n\n  function addManyMutably(entities: T[], state: R): DidMutate;\n  function addManyMutably(entities: any[], state: any): DidMutate {\n    let didMutate = false;\n\n    for (const entity of entities) {\n      didMutate = addOneMutably(entity, state) !== DidMutate.None || didMutate;\n    }\n\n    return didMutate ? DidMutate.Both : DidMutate.None;\n  }\n\n  function setAllMutably(entities: T[], state: R): DidMutate;\n  function setAllMutably(entities: any[], state: any): DidMutate {\n    state.ids = [];\n    state.entities = {};\n\n    addManyMutably(entities, state);\n\n    return DidMutate.Both;\n  }\n\n  function setOneMutably(entity: T, state: R): DidMutate;\n  function setOneMutably(entity: any, state: any): DidMutate {\n    const key = selectIdValue(entity, selectId);\n\n    if (key in state.entities) {\n      state.entities[key] = entity;\n      return DidMutate.EntitiesOnly;\n    }\n\n    state.ids.push(key);\n    state.entities[key] = entity;\n\n    return DidMutate.Both;\n  }\n\n  function setManyMutably(entities: T[], state: R): DidMutate;\n  function setManyMutably(entities: any[], state: any): DidMutate {\n    const didMutateSetOne = entities.map((entity) =>\n      setOneMutably(entity, state)\n    );\n\n    switch (true) {\n      case didMutateSetOne.some((didMutate) => didMutate === DidMutate.Both):\n        return DidMutate.Both;\n      case didMutateSetOne.some(\n        (didMutate) => didMutate === DidMutate.EntitiesOnly\n      ):\n        return DidMutate.EntitiesOnly;\n      default:\n        return DidMutate.None;\n    }\n  }\n\n  function removeOneMutably(key: string | number, state: R): DidMutate;\n  function removeOneMutably(key: any, state: any): DidMutate {\n    return removeManyMutably([key], state);\n  }\n\n  function removeManyMutably(keys: string[] | number[], state: R): DidMutate;\n  function removeManyMutably(predicate: Predicate<T>, state: R): DidMutate;\n  function removeManyMutably(\n    keysOrPredicate: any[] | Predicate<T>,\n    state: any\n  ): DidMutate {\n    const keys =\n      keysOrPredicate instanceof Array\n        ? keysOrPredicate\n        : state.ids.filter((key: any) => keysOrPredicate(state.entities[key]));\n\n    const didMutate =\n      keys\n        .filter((key: any) => key in state.entities)\n        .map((key: any) => delete state.entities[key]).length > 0;\n\n    if (didMutate) {\n      state.ids = state.ids.filter((id: any) => id in state.entities);\n    }\n\n    return didMutate ? DidMutate.Both : DidMutate.None;\n  }\n\n  function removeAll<S extends R>(state: S): S;\n  function removeAll<S extends R>(state: any): S {\n    return Object.assign({}, state, {\n      ids: [],\n      entities: {},\n    });\n  }\n\n  function takeNewKey(\n    keys: { [id: string]: string },\n    update: Update<T>,\n    state: R\n  ): void;\n  function takeNewKey(\n    keys: { [id: string]: any },\n    update: Update<T>,\n    state: any\n  ): boolean {\n    const original = state.entities[update.id];\n    const updated: T = Object.assign({}, original, update.changes);\n    const newKey = selectIdValue(updated, selectId);\n    const hasNewKey = newKey !== update.id;\n\n    if (hasNewKey) {\n      keys[update.id] = newKey;\n      delete state.entities[update.id];\n    }\n\n    state.entities[newKey] = updated;\n\n    return hasNewKey;\n  }\n\n  function updateOneMutably(update: Update<T>, state: R): DidMutate;\n  function updateOneMutably(update: any, state: any): DidMutate {\n    return updateManyMutably([update], state);\n  }\n\n  function updateManyMutably(updates: Update<T>[], state: R): DidMutate;\n  function updateManyMutably(updates: any[], state: any): DidMutate {\n    const newKeys: { [id: string]: string } = {};\n\n    updates = updates.filter((update) => update.id in state.entities);\n\n    const didMutateEntities = updates.length > 0;\n\n    if (didMutateEntities) {\n      const didMutateIds =\n        updates.filter((update) => takeNewKey(newKeys, update, state)).length >\n        0;\n\n      if (didMutateIds) {\n        state.ids = state.ids.map((id: any) => newKeys[id] || id);\n        return DidMutate.Both;\n      } else {\n        return DidMutate.EntitiesOnly;\n      }\n    }\n\n    return DidMutate.None;\n  }\n\n  function mapMutably(map: EntityMap<T>, state: R): DidMutate;\n  function mapMutably(map: any, state: any): DidMutate {\n    const changes: Update<T>[] = state.ids.reduce(\n      (changes: any[], id: string | number) => {\n        const change = map(state.entities[id]);\n        if (change !== state.entities[id]) {\n          changes.push({ id, changes: change });\n        }\n        return changes;\n      },\n      []\n    );\n    const updates = changes.filter(({ id }) => id in state.entities);\n\n    return updateManyMutably(updates, state);\n  }\n\n  function mapOneMutably(map: EntityMapOneNum<T>, state: R): DidMutate;\n  function mapOneMutably(map: EntityMapOneStr<T>, state: R): DidMutate;\n  function mapOneMutably({ map, id }: any, state: any): DidMutate {\n    const entity = state.entities[id];\n    if (!entity) {\n      return DidMutate.None;\n    }\n\n    const updatedEntity = map(entity);\n    return updateOneMutably(\n      {\n        id: id,\n        changes: updatedEntity,\n      },\n      state\n    );\n  }\n\n  function upsertOneMutably(entity: T, state: R): DidMutate;\n  function upsertOneMutably(entity: any, state: any): DidMutate {\n    return upsertManyMutably([entity], state);\n  }\n\n  function upsertManyMutably(entities: T[], state: R): DidMutate;\n  function upsertManyMutably(entities: any[], state: any): DidMutate {\n    const added: any[] = [];\n    const updated: any[] = [];\n\n    for (const entity of entities) {\n      const id = selectIdValue(entity, selectId);\n      if (id in state.entities) {\n        updated.push({ id, changes: entity });\n      } else {\n        added.push(entity);\n      }\n    }\n\n    const didMutateByUpdated = updateManyMutably(updated, state);\n    const didMutateByAdded = addManyMutably(added, state);\n\n    switch (true) {\n      case didMutateByAdded === DidMutate.None &&\n        didMutateByUpdated === DidMutate.None:\n        return DidMutate.None;\n      case didMutateByAdded === DidMutate.Both ||\n        didMutateByUpdated === DidMutate.Both:\n        return DidMutate.Both;\n      default:\n        return DidMutate.EntitiesOnly;\n    }\n  }\n\n  return {\n    removeAll,\n    addOne: createStateOperator(addOneMutably),\n    addMany: createStateOperator(addManyMutably),\n    setAll: createStateOperator(setAllMutably),\n    setOne: createStateOperator(setOneMutably),\n    setMany: createStateOperator(setManyMutably),\n    updateOne: createStateOperator(updateOneMutably),\n    updateMany: createStateOperator(updateManyMutably),\n    upsertOne: createStateOperator(upsertOneMutably),\n    upsertMany: createStateOperator(upsertManyMutably),\n    removeOne: createStateOperator(removeOneMutably),\n    removeMany: createStateOperator(removeManyMutably),\n    map: createStateOperator(mapMutably),\n    mapOne: createStateOperator(mapOneMutably),\n  };\n}\n"
  },
  {
    "path": "modules/entity/src/utils.ts",
    "content": "import { isDevMode } from '@angular/core';\nimport { IdSelector } from './models';\n\nexport function selectIdValue<T>(entity: T, selectId: IdSelector<T>) {\n  const key = selectId(entity);\n\n  if (isDevMode() && key === undefined) {\n    console.warn(\n      '@ngrx/entity: The entity passed to the `selectId` implementation returned undefined.',\n      'You should probably provide your own `selectId` implementation.',\n      'The entity that was passed:',\n      entity,\n      'The `selectId` implementation:',\n      selectId.toString()\n    );\n  }\n\n  return key;\n}\n"
  },
  {
    "path": "modules/entity/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/entity/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../../\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmitOnError\": false,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/entity\",\n    \"paths\": {},\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"skipLibCheck\": true\n  },\n  \"files\": [\"public_api.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/entity\"\n  }\n}\n"
  },
  {
    "path": "modules/entity/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/entity\",\n    \"paths\": {\n      \"@ngrx/entity/schematics-core\": [\"./schematics-core\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/entity/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"vitest/globals\", \"node\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/entity/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [angular(), nxViteTsPaths()],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['spec/**/*.spec.ts'],\n      reporters: ['default'],\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "modules/eslint-plugin/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/eslint-plugin/README.md",
    "content": "# @ngrx/eslint-plugin\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/eslint-plugin/eslint.config.mjs",
    "content": "import baseConfig from '../../eslint.config.mjs';\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n];\n"
  },
  {
    "path": "modules/eslint-plugin/migrations/migration.json",
    "content": "{\n  \"schematics\": {}\n}\n"
  },
  {
    "path": "modules/eslint-plugin/package.json",
    "content": "{\n  \"name\": \"@ngrx/eslint-plugin\",\n  \"version\": \"21.0.1\",\n  \"description\": \"NgRx ESLint Plugin\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"RxJS\",\n    \"Angular\",\n    \"Redux\",\n    \"NgRx\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"schematics\": \"./schematics/collection.json\",\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"ng-add\": {\n    \"save\": \"devDependencies\"\n  },\n  \"sideEffects\": false,\n  \"dependencies\": {\n    \"strip-json-comments\": \"3.1.1\"\n  },\n  \"peerDependencies\": {\n    \"eslint\": \"^8.57.0 || ^9.0.0 || ^10.0.0\",\n    \"typescript\": \"*\",\n    \"typescript-eslint\": \"^8.0.0\",\n    \"@typescript-eslint/utils\": \"^8.0.0\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/project.json",
    "content": "{\n  \"name\": \"eslint-plugin\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/eslint-plugin/src\",\n  \"tags\": [],\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@nx/js:tsc\",\n      \"options\": {\n        \"outputPath\": \"dist/modules/eslint-plugin\",\n        \"tsConfig\": \"modules/eslint-plugin/tsconfig.build.json\",\n        \"packageJson\": \"modules/eslint-plugin/package.json\",\n        \"main\": \"modules/eslint-plugin/src/index.ts\",\n        \"additionalEntryPoints\": [\"modules/eslint-plugin/v9/index.ts\"],\n        \"generateExportsField\": true,\n        \"updateBuildableProjectDepsInPackageJson\": false,\n        \"sourceMap\": false,\n        \"assets\": [\n          \"collection.json\",\n          {\n            \"input\": \"./modules/eslint-plugin/src\",\n            \"glob\": \"**/*.!(ts)\",\n            \"output\": \"./src\"\n          },\n          {\n            \"input\": \"./modules/eslint-plugin\",\n            \"glob\": \"collection.json\",\n            \"output\": \".\"\n          }\n        ],\n        \"srcRootForCompilationRoot\": \"modules/eslint-plugin\"\n      },\n      \"outputs\": [\"{options.outputPath}\"]\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package eslint-plugin\"\n          },\n          {\n            \"command\": \"cpy modules/eslint-plugin/migrations/migration.json dist\"\n          },\n          {\n            \"command\": \"cpy modules/eslint-plugin/schematics/collection.json dist\"\n          },\n          {\n            \"command\": \"cpy modules/eslint-plugin/schematics/ng-add/schema.json dist\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/eslint-plugin\"\n          }\n        ]\n      },\n      \"outputs\": [\"{workspaceRoot}/dist/modules/eslint-plugin\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/eslint-plugin\"]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/eslint-plugin/*/**/*.ts\",\n          \"modules/eslint-plugin/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Add @ngrx/eslint-plugin to your application\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/schematics/ng-add/index.ts",
    "content": "import type { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport stripJsonComments from 'strip-json-comments';\nimport type { Schema } from './schema';\nimport * as ts from 'typescript';\n\nexport const possibleFlatConfigPaths = [\n  'eslint.config.js',\n  'eslint.config.mjs',\n  'eslint.config.cjs',\n];\n\nexport default function (schema: Schema): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    const jsonConfigPath = '.eslintrc.json';\n    const flatConfigPath = possibleFlatConfigPaths.find((path) =>\n      host.exists(path)\n    );\n    const docs = 'https://ngrx.io/guide/eslint-plugin';\n\n    if (flatConfigPath) {\n      updateFlatConfig(host, context, flatConfigPath, schema, docs);\n      return host;\n    }\n\n    if (!host.exists(jsonConfigPath)) {\n      context.logger.warn(`\nCould not find an ESLint config at any of ${possibleFlatConfigPaths.join(\n        ', '\n      )} or \\`${jsonConfigPath}\\`.\nThe NgRx ESLint Plugin is installed but not configured.\nPlease see ${docs} to configure the NgRx ESLint Plugin.\n      `);\n      return host;\n    }\n\n    updateJsonConfig(host, context, jsonConfigPath, schema, docs);\n    return host;\n  };\n}\n\nfunction updateFlatConfig(\n  host: Tree,\n  context: SchematicContext,\n  flatConfigPath: string,\n  schema: Schema,\n  docs: string\n): void {\n  const ngrxPlugin = '@ngrx/eslint-plugin/v9';\n  const content = host.read(flatConfigPath)?.toString('utf-8');\n  if (!content) {\n    context.logger.error(\n      `Could not read the ESLint flat config at \\`${flatConfigPath}\\`.`\n    );\n    return;\n  }\n\n  if (content.includes(ngrxPlugin)) {\n    context.logger.info(\n      `Skipping installation, the NgRx ESLint Plugin is already installed in your flat config.`\n    );\n    return;\n  }\n\n  if (!content.includes('tseslint.config')) {\n    context.logger.warn(\n      `No tseslint found, skipping the installation of the NgRx ESLint Plugin in your flat config.`\n    );\n    return;\n  }\n\n  const source = ts.createSourceFile(\n    flatConfigPath,\n    content,\n    ts.ScriptTarget.Latest,\n    true\n  );\n\n  const recorder = host.beginUpdate(flatConfigPath);\n  addImport();\n  addNgRxPlugin();\n\n  host.commitUpdate(recorder);\n  context.logger.info(`\nThe NgRx ESLint Plugin is installed and configured using the '${schema.config}' configuration in your flat config.\nSee ${docs} for more details.\n  `);\n\n  function addImport() {\n    const isESM = content!.includes('export default');\n    if (isESM) {\n      const lastImport = source.statements\n        .filter((statement) => ts.isImportDeclaration(statement))\n        .reverse()[0];\n      recorder.insertRight(\n        lastImport?.end ?? 0,\n        `\\nimport ngrx from '${ngrxPlugin}';`\n      );\n    } else {\n      const lastRequireVariableDeclaration = source.statements\n        .filter((statement) => {\n          if (!ts.isVariableStatement(statement)) return false;\n          const decl = statement.declarationList.declarations[0];\n          if (!decl.initializer) return false;\n          return (\n            ts.isCallExpression(decl.initializer) &&\n            decl.initializer.expression.getText() === 'require'\n          );\n        })\n        .reverse()[0];\n\n      recorder.insertRight(\n        lastRequireVariableDeclaration?.end ?? 0,\n        `\\nconst ngrx = require('${ngrxPlugin}');`\n      );\n    }\n  }\n\n  function addNgRxPlugin() {\n    let tseslintConfigCall: ts.CallExpression | null = null;\n    function findTsEslintConfigCalls(node: ts.Node) {\n      if (tseslintConfigCall) {\n        return;\n      }\n\n      if (\n        ts.isCallExpression(node) &&\n        node.expression.getText() === 'tseslint.config'\n      ) {\n        tseslintConfigCall = node;\n      }\n      ts.forEachChild(node, findTsEslintConfigCalls);\n    }\n    findTsEslintConfigCalls(source);\n\n    if (tseslintConfigCall) {\n      tseslintConfigCall = tseslintConfigCall as ts.CallExpression;\n      const lastArgument =\n        tseslintConfigCall.arguments[tseslintConfigCall.arguments.length - 1];\n      const plugin = `  {\n    files: ['**/*.ts'],\n    extends: [\n      ...ngrx.configs.${schema.config},\n    ],\n    rules: {},\n  }`;\n\n      if (lastArgument) {\n        recorder.remove(lastArgument.pos, lastArgument.end - lastArgument.pos);\n        recorder.insertRight(\n          lastArgument.pos,\n          `${lastArgument.getFullText()},\\n${plugin}`\n        );\n      } else {\n        recorder.insertRight(tseslintConfigCall.end - 1, `\\n${plugin}\\n`);\n      }\n    }\n  }\n}\n\nfunction updateJsonConfig(\n  host: Tree,\n  context: SchematicContext,\n  jsonConfigPath: string,\n  schema: Schema,\n  docs: string\n): void {\n  const eslint = host.read(jsonConfigPath)?.toString('utf-8');\n  if (!eslint) {\n    context.logger.error(`\nCould not find the ESLint config at \\`${jsonConfigPath}\\`.\nThe NgRx ESLint Plugin is installed but not configured.\nPlease see ${docs} to configure the NgRx ESLint Plugin.\n`);\n    return;\n  }\n\n  try {\n    const json = JSON.parse(stripJsonComments(eslint));\n    const plugin = {\n      files: ['*.ts'],\n      extends: [`plugin:@ngrx/${schema.config}`],\n    };\n    if (json.overrides) {\n      if (\n        !json.overrides.some((override: any) =>\n          override.extends?.some((extend: any) =>\n            extend.startsWith('plugin:@ngrx')\n          )\n        )\n      ) {\n        json.overrides.push(plugin);\n      }\n    } else if (\n      !json.extends?.some((extend: any) => extend.startsWith('plugin:@ngrx'))\n    ) {\n      json.overrides = [plugin];\n    }\n\n    host.overwrite(jsonConfigPath, JSON.stringify(json, null, 2));\n\n    context.logger.info(`\nThe NgRx ESLint Plugin is installed and configured with the '${schema.config}' config.\nTake a look at the docs at ${docs} if you want to change the default configuration.\n`);\n  } catch (err) {\n    const detailsContent =\n      err instanceof Error\n        ? `\nDetails:\n${err.message}\n`\n        : '';\n    context.logger.warn(`\nSomething went wrong while adding the NgRx ESLint Plugin.\nThe NgRx ESLint Plugin is installed but not configured.\nPlease see ${docs} to configure the NgRx ESLint Plugin.\n${detailsContent}\n`);\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsEslintPluginNgRx\",\n  \"title\": \"eslint-plugin-ngrx schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"config\": {\n      \"description\": \"The config to be used.\",\n      \"type\": \"string\",\n      \"default\": \"all\",\n      \"enum\": [\n        \"all\",\n        \"allTypedChecked\",\n        \"component-store\",\n        \"effects\",\n        \"effectsTypedChecked\",\n        \"operators\",\n        \"signals\",\n        \"signalsTypedChecked\",\n        \"store\"\n      ],\n      \"x-prompt\": {\n        \"message\": \"Which ESLint configuration would you like to use?\",\n        \"type\": \"list\",\n        \"items\": [\n          {\n            \"value\": \"all\",\n            \"label\": \"all\"\n          },\n          {\n            \"value\": \"allTypedChecked\",\n            \"label\": \"allTypedChecked\"\n          },\n          {\n            \"value\": \"component-store\",\n            \"label\": \"component-store\"\n          },\n          {\n            \"value\": \"effects\",\n            \"label\": \"effects\"\n          },\n          {\n            \"value\": \"effectsTypedChecked\",\n            \"label\": \"effectsTypedChecked\"\n          },\n          {\n            \"value\": \"operators\",\n            \"label\": \"operators\"\n          },\n          {\n            \"value\": \"signals\",\n            \"label\": \"signals\"\n          },\n          {\n            \"value\": \"signalsTypedChecked\",\n            \"label\": \"signalsTypedChecked\"\n          },\n          {\n            \"value\": \"store\",\n            \"label\": \"store\"\n          }\n        ]\n      }\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/eslint-plugin/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  config: 'all' | 'store' | 'effects' | 'component-store' | 'signals';\n}\n"
  },
  {
    "path": "modules/eslint-plugin/scripts/generate-config.ts",
    "content": "import { writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { format, resolveConfig } from 'prettier';\nimport { rulesForGenerate } from '../src/utils/helper-functions/rules';\nimport { NgRxRule } from '../src/rule-creator';\n\n(async () => {\n  const prettierConfig = await resolveConfig(__dirname);\n  const RULE_MODULE = '@ngrx';\n  const CONFIG_DIRECTORY = './modules/eslint-plugin/src/configs/';\n\n  const isModule = (rule: NgRxRule, moduleName: string) =>\n    rule.meta.docs?.ngrxModule === moduleName;\n  const isTypeChecked = (rule: NgRxRule) =>\n    rule.meta.docs?.requiresTypeChecking === true;\n\n  writeConfig('all', (rule) => !isTypeChecked(rule));\n  writeConfig('all-type-checked', (_rule) => true);\n\n  writeConfig(\n    'store',\n    (rule) => isModule(rule, 'store') && !isTypeChecked(rule)\n  );\n\n  writeConfig(\n    'effects',\n    (rule) => isModule(rule, 'effects') && !isTypeChecked(rule)\n  );\n  writeConfig('effects-type-checked', (rule) => isModule(rule, 'effects'));\n\n  writeConfig(\n    'component-store',\n    (rule) => isModule(rule, 'component-store') && !isTypeChecked(rule)\n  );\n\n  writeConfig(\n    'operators',\n    (rule) => isModule(rule, 'operators') && !isTypeChecked(rule)\n  );\n\n  writeConfig(\n    'signals',\n    (rule) => isModule(rule, 'signals') && !isTypeChecked(rule)\n  );\n  writeConfig('signals-type-checked', (rule) => isModule(rule, 'signals'));\n\n  async function writeConfig(\n    configName:\n      | 'all'\n      | 'all-type-checked'\n      | 'store'\n      | 'effects'\n      | 'effects-type-checked'\n      | 'component-store'\n      | 'operators'\n      | 'signals'\n      | 'signals-type-checked',\n    predicate: (rule: NgRxRule) => boolean\n  ) {\n    const rulesForConfig = Object.entries(rulesForGenerate).filter(\n      ([_, rule]) => predicate(rule)\n    );\n    const configRules = rulesForConfig.reduce<Record<string, string>>(\n      (rules, [ruleName, _rule]) => {\n        rules[`${RULE_MODULE}/${ruleName}`] = 'error';\n        return rules;\n      },\n      {}\n    );\n\n    const tsCode = `\n      /**\n     * DO NOT EDIT\n     * This file is generated\n     */\n\n      import type { TSESLint } from '@typescript-eslint/utils';\n\n      export default (\n        plugin: TSESLint.FlatConfig.Plugin,\n        parser: TSESLint.FlatConfig.Parser,\n      ): TSESLint.FlatConfig.ConfigArray => [\n        {\n          name: 'ngrx/base',\n          languageOptions: {\n            parser,\n          },\n          plugins: {\n            '@ngrx': plugin,\n          },\n        },\n        {\n          name: 'ngrx/${configName}',\n          languageOptions: {\n            parser,\n          },\n          rules: ${JSON.stringify(configRules, null, 2)}\n        },\n      ];`;\n    const tsConfigFormatted = await format(tsCode, {\n      parser: 'typescript',\n      ...prettierConfig,\n    });\n    writeFileSync(\n      join(CONFIG_DIRECTORY, `${configName}.ts`),\n      tsConfigFormatted\n    );\n\n    const jsonConfig: { [key: string]: any } = {\n      parser: '@typescript-eslint/parser',\n      plugins: ['@ngrx'],\n      rules: configRules,\n    };\n    const jsonConfigFormatted = await format(\n      JSON.stringify(jsonConfig, null, 2),\n      {\n        parser: 'json',\n        ...prettierConfig,\n      }\n    );\n\n    writeFileSync(\n      join(CONFIG_DIRECTORY, `${configName}.json`),\n      jsonConfigFormatted\n    );\n  }\n})();\n"
  },
  {
    "path": "modules/eslint-plugin/scripts/generate-docs.ts",
    "content": "import { readFileSync, writeFileSync, existsSync } from 'fs';\nimport * as path from 'path';\nimport { format, resolveConfig } from 'prettier';\nimport { rulesForGenerate } from '../src/utils/helper-functions/rules';\n\n(async () => {\n  const prettierConfig = await resolveConfig(__dirname);\n  const PLACEHOLDER = '<!-- MANUAL-DOC:START -->';\n  const RULES_PATHS = [\n    './projects/www/src/app/pages/guide/eslint-plugin/rules',\n  ];\n\n  for (const rules of RULES_PATHS) {\n    for (const [ruleName, { meta }] of Object.entries(rulesForGenerate)) {\n      const docPath = path.join(rules, `${ruleName}.md`);\n      if (!existsSync(docPath)) {\n        writeFileSync(docPath, ``);\n      }\n      const doc = readFileSync(docPath, 'utf-8');\n      const docContent = doc.substring(\n        doc.indexOf(PLACEHOLDER) + PLACEHOLDER.length\n      );\n      const newDoc = await format(\n        `# ${ruleName}\n\n${meta.docs?.description}\n\n- **Type**: ${meta.type}\n- **Fixable**: ${meta.fixable ? 'Yes' : 'No'}\n- **Suggestion**: ${meta.hasSuggestions ? 'Yes' : 'No'}\n- **Requires type checking**: ${meta.docs?.requiresTypeChecking ? 'Yes' : 'No'}\n- **Configurable**: ${\n          Array.isArray(meta.schema) && meta.schema.length ? 'Yes' : 'No'\n        }\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n${docContent}`,\n        {\n          parser: 'markdown',\n          ...prettierConfig,\n        }\n      );\n\n      writeFileSync(docPath, newDoc);\n    }\n  }\n})();\n"
  },
  {
    "path": "modules/eslint-plugin/scripts/generate-overview.ts",
    "content": "import { readFileSync, writeFileSync } from 'fs';\nimport { EOL } from 'os';\nimport { format, resolveConfig } from 'prettier';\nimport {\n  configsForGenerate,\n  rulesForGenerate,\n} from '../src/utils/helper-functions/rules';\n\n(async () => {\n  const prettierConfig = await resolveConfig(__dirname);\n  const OVERVIEW = './projects/www/src/app/pages/guide/eslint-plugin/index.md';\n  const GH_CONFIGS =\n    'https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs';\n\n  await generateRules();\n  await generateConfigurations();\n\n  async function generateRules() {\n    const moduleRules = Object.entries(rulesForGenerate).reduce<\n      Record<string, string[][]>\n    >((all, [ruleName, { meta }]) => {\n      if (!meta.docs) {\n        throw new Error(`Rule ${ruleName} is missing meta.docs information`);\n      }\n\n      all[meta.docs.ngrxModule] = (all[meta.docs.ngrxModule] ?? []).concat([\n        [\n          `[@ngrx/${ruleName}]${\n            meta.docs?.url\n              ? '(' +\n                meta.docs.url\n                  .replace('https://ngrx.io', '')\n                  .replace('.md', '') +\n                ')'\n              : ''\n          }`,\n          meta.docs?.description ?? 'TODO',\n          meta.type,\n          meta.fixable ? 'Yes' : 'No',\n          meta.hasSuggestions ? 'Yes' : 'No',\n          Array.isArray(meta.schema) && meta.schema.length ? 'Yes' : 'No',\n          meta.docs?.requiresTypeChecking ? 'Yes' : 'No',\n        ],\n      ]);\n      return all;\n    }, {});\n\n    const tableHeader = `| Name | Description | Category | Fixable | Has suggestions | Configurable | Requires type information\n| --- | --- | --- | --- | --- | --- | --- |`;\n\n    const configTable = Object.entries(moduleRules).map(\n      ([ngrxModule, pluginRules]) => {\n        const tableBody = pluginRules\n          .map((rule) => `|${rule.join('|')}|`)\n          .join(EOL);\n        const table = [tableHeader, tableBody].join(EOL);\n\n        return [`### ${ngrxModule}`, table].join(EOL);\n      }\n    );\n\n    const overview = readFileSync(OVERVIEW, 'utf-8');\n    const start = overview.indexOf('<!-- RULES-CONFIG:START -->');\n    const end = overview.indexOf('<!-- RULES-CONFIG:END -->');\n\n    const newOverview = await format(\n      `${overview.substring(0, start + '<!-- RULES-CONFIG:START -->'.length)}\n${configTable.join(EOL)}\n${overview.substring(end)}`,\n      {\n        ...prettierConfig,\n        parser: 'markdown',\n      }\n    );\n\n    writeFileSync(OVERVIEW, newOverview);\n  }\n\n  async function generateConfigurations() {\n    const tableHeader = `| Name |\n    | --- |`;\n\n    const overview = readFileSync(OVERVIEW, 'utf-8');\n    const start = overview.indexOf('<!-- CONFIGURATIONS-CONFIG:START -->');\n    const end = overview.indexOf('<!-- CONFIGURATIONS-CONFIG:END -->');\n\n    const configTable = configsForGenerate.map(\n      (configName) => `| [${configName}](${GH_CONFIGS}/${configName}.json) |`\n    );\n    const newOverview = await format(\n      `${overview.substring(\n        0,\n        start + '<!-- CONFIGURATIONS-CONFIG:START -->'.length\n      )}\n${[tableHeader, ...configTable].join(EOL)}\n${overview.substring(end)}`,\n      {\n        ...prettierConfig,\n        parser: 'markdown',\n      }\n    );\n\n    writeFileSync(OVERVIEW, newOverview);\n  }\n})();\n"
  },
  {
    "path": "modules/eslint-plugin/spec/exported-rules.spec.ts",
    "content": "import * as path from 'path';\nimport * as lib from '../src/rules';\nimport { traverseFolder } from '../src/utils';\nimport { configs } from '../v9';\n\nconst rulesDirectory = path.join(__dirname, '../src/rules');\nconst configsDirectory = path.join(__dirname, '../src/configs');\n\nfunction getAllRules() {\n  return [...traverseFolder(rulesDirectory, ['.ts'])]\n    .map((rule) => rule.file)\n    .filter((rule) => rule !== 'index');\n}\nfunction getAllConfigs() {\n  return [...traverseFolder(configsDirectory, ['.ts'])];\n}\n\ndescribe('ESLint V8', () => {\n  test('exports all rules', () => {\n    const rules = getAllRules();\n    expect(Object.keys(lib.rules).length).toBe(rules.length);\n  });\n  test('exports all configurations', () => {\n    const configs = getAllConfigs();\n    expect(configs.length).toBe(9);\n  });\n});\n\ndescribe('ESLint V9', () => {\n  test('exports all rules ', () => {\n    const rules = getAllRules();\n    expect(Object.keys((configs.allTypeChecked[1] as any).rules).length).toBe(\n      rules.length\n    );\n  });\n  test('there is a difference between typed checked rules ', () => {\n    expect(\n      Object.keys((configs.allTypeChecked[1] as any).rules).length\n    ).toBeGreaterThan(Object.keys((configs.all[1] as any).rules).length);\n  });\n  test('exports all configurations', () => {\n    expect(Object.keys(configs).length).toBe(9);\n  });\n});\n"
  },
  {
    "path": "modules/eslint-plugin/spec/fixtures/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"esnext\"],\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"skipLibCheck\": true,\n    \"target\": \"esnext\",\n    \"paths\": {\n      \"@ngrx/component-store\": [\"../../../../modules/component-store\"],\n      \"@ngrx/effects\": [\"../../../../modules/effects\"],\n      \"@ngrx/store\": [\"../../../../modules/store\"]\n    }\n  },\n  \"files\": [\"file.ts\"]\n}\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/component-store/avoid-combining-component-store-selectors.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/component-store/avoid-combining-component-store-selectors';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { ComponentStore } from '@ngrx/component-store'\nclass Ok extends ComponentStore<MoviesState> {\n  movies$ = this.select((state) => state.movies);\n  selectedId$ = this.select((state) => state.selectedId);\n  movie$ = this.select(\n    this.movies$,\n    this.selectedId$,\n    ([movies, selectedId]) => movies[selectedId]\n  );\n\n  constructor() {\n    super({ movies: [] })\n  }\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\nclass Ok {\n  readonly movies$ = this.store.select((state) => state.movies);\n  readonly selectedId$ = this.store.select((state) => state.selectedId);\n  readonly movie$ = this.store.select(\n    this.movies$,\n    this.selectedId$,\n    ([movies, selectedId]) => movies[selectedId]\n  );\n\n  constructor(private readonly store: ComponentStore<MoviesState>) {}\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\nclass Ok {\n  movie$: Observable<unknown>\n\n  constructor(customStore: ComponentStore<MoviesState>) {\n    const movies = customStore.select((state) => state.movies);\n    const selectedId = this.customStore.select((state) => state.selectedId);\n\n    this.movie$ = this.customStore.select(\n      this.movies$,\n      this.selectedId$,\n      ([movies, selectedId]) => movies[selectedId]\n    );\n  }\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\nclass Ok {\n  vm$ = combineLatest(this.somethingElse(), this.customStore.select(selectItems))\n\n  constructor(customStore: ComponentStore<MoviesState>) {}\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\nclass Ok extends ComponentStore<MoviesState> {\n  vm$ = combineLatest(this.select(selectItems), this.somethingElse())\n}`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { inject } from '@angular/core'\nimport { ComponentStore } from '@ngrx/component-store'\nclass Ok {\n  readonly store = inject(ComponentStore<MoviesState>)\n  readonly movies$ = this.store.select((state) => state.movies);\n  readonly selectedId$ = this.store.select((state) => state.selectedId);\n  readonly movie$ = this.store.select(\n    this.movies$,\n    this.selectedId$,\n    ([movies, selectedId]) => movies[selectedId]\n  );\n}`,\n  `\nimport { inject } from '@angular/core'\nimport { ComponentStore } from '@ngrx/component-store'\nclass Ok {\n  readonly store = inject(ComponentStore<MoviesState>)\n  readonly vm$ = combineLatest(this.store.select(selectItems), somethingElse())\n}`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass NotOk extends ComponentStore<MoviesState> {\n  movie$ = combineLatest(\n    this.select((state) => state.movies),\n    this.select((state) => state.selectedId),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  );\n\n  constructor() {\n    super({ movies: [] })\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass NotOk extends ComponentStore<MoviesState> {\n  movie$ = combineLatest(\n    this.select((state) => state.movies),\n    this.select((state) => state.selectedId),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.select((state) => state.selectedId),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  );\n\n  constructor() {\n    super({ movies: [] })\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\nclass NotOk {\n  movie$ = combineLatest(\n    this.moviesState.select((state) => state.movies),\n    this.moviesState.select((state) => state.selectedId),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  );\n\n  constructor(private readonly moviesState: ComponentStore<MoviesState>) {}\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\nclass NotOk {\n  movie$ = combineLatest(\n    this.moviesState.select((state) => state.movies),\n    this.moviesState.select((state) => state.selectedId),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.moviesState.select((state) => state.selectedId),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  );\n\n  constructor(private readonly moviesState: ComponentStore<MoviesState>) {}\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\nclass NotOk {\n  movie$: Observable<unknown>\n\n  constructor(store: ComponentStore<MoviesState>) {\n    this.movie$ = combineLatest(\n      store.select((state) => state.movies),\n      store.select((state) => state.selectedId)\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    );\n  }\n}\n`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\nclass NotOk {\n  movie$: Observable<unknown>\n\n  constructor(store: ComponentStore<MoviesState>) {\n    this.movie$ = combineLatest(\n      store.select((state) => state.movies),\n      store.select((state) => state.selectedId),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n      store.select((state) => state.selectedId)\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    );\n  }\n}\n`),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { inject } from '@angular/core'\nimport { ComponentStore } from '@ngrx/component-store'\nclass NotOk {\n  readonly componentStore = inject(ComponentStore<MoviesState>)\n  readonly movie$ = combineLatest(\n    this.componentStore.select((state) => state.movies),\n    this.componentStore.select((state) => state.selectedId),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  );\n}`),\n  fromFixture(`\nimport { inject } from '@angular/core'\nimport { ComponentStore } from '@ngrx/component-store'\nclass NotOk {\n  readonly store = inject(ComponentStore<MoviesState>)\n  readonly movie$ = combineLatest(\n    this.store.select((state) => state.movies),\n    this.store.select((state) => state.selectedId),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.store.select((state) => state.selectedId),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  );\n}`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/component-store/avoid-mapping-component-store-selectors.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/component-store/avoid-mapping-component-store-selectors';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass Ok extends ComponentStore<MoviesState> {\n  movies$ = this.select((state) => state.movies);\n  firstMovie$ = this.select(this.movies$, (movies) => movies[0]);\n\n  constructor() {\n    super({ movies: [] })\n  }\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass Ok {\n  readonly movies$ = this.store.select((state) => state.movies);\n  firstMovie$ = this.store.select(this.movies$, (movies) => movies[0]);\n\n  constructor(private readonly store: ComponentStore<MoviesState>) {}\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass Ok {\n  readonly movies$: Observable<unknown>\n  readonly firstMovie$: Observable<unknown>\n\n  constructor(private customStore: ComponentStore<MoviesState>) {\n    const movies = customStore.select((state) => state.movies);\n    this.firstMovie$ = customStore.select(movies, (movies) => movies[0]);\n\n    this.movies$ = this.customStore.select((state) => state.movies);\n    this.firstMovie$ = this.customStore.select(this.movies$, (movies) => movies[0]);\n  }\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\n\nexport class UserStore extends ComponentStore<UserState> {\n  loggedInUser$ = this.select((state) => state.loggedInUser);\n  name$ = this.select(this.loggedInUser$, (user) => user.name);\n}\n`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { inject } from '@angular/core'\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass Ok {\n  readonly store = inject(ComponentStore<MoviesState>)\n  readonly movies$ = this.store.select((state) => state.movies)\n  readonly firstMovie$ = this.store.select(this.movies$, (movies) => movies[0]);\n}`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass NotOk extends ComponentStore<MoviesState> {\n  movies$ = this.select((state) => state.movies).pipe(map((movies) => movies))\n                                                      ~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n\n  constructor() {\n    super({ movies: [] })\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass NotOk {\n  readonly movies$ = this.customStore.select((state) => state.movies).pipe(\n      filter(Boolean),\n      map((movies) => movies)\n      ~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  )\n\n  constructor(private readonly customStore: ComponentStore<MoviesState>) {}\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass NotOk {\n  readonly movies$: Observable<unknown>\n\n  constructor(private customStore: ComponentStore<MoviesState>) {\n    this.movies$ = customStore.select((state) => state.movies).pipe(map((movies) => movies), filter(Boolean));\n                                                                    ~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.movies$ = this.customStore.select((state) => state.movies).pipe(map((movies) => movies));\n                                                                         ~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nexport class UserStore extends ComponentStore<UserState> {\n  name$ = this.select((state) => state.loggedInUser).pipe(map((user) => user.name));\n                                                          ~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n}`),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\n  import { inject } from '@angular/core'\n  import { ComponentStore } from '@ngrx/component-store'\n\n  class NotOk {\n    readonly otherStoreName = inject(ComponentStore<MoviesState>)\n    readonly movie$ = this.otherStoreName.select((state) => state.movies).pipe(\n      map((m) => m),\n      ~~~~~~~~~~~~~ [${messageId}]\n      filter(Boolean),\n      map((movies) => movies[0]),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    )\n  }`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/component-store/require-super-ondestroy.spec.ts",
    "content": "import { ESLintUtils } from '@typescript-eslint/utils';\nimport { InvalidTestCase, ValidTestCase } from '@typescript-eslint/rule-tester';\nimport rule, {\n  messageId,\n} from '../../../src/rules/component-store/require-super-ondestroy';\nimport { fromFixture, ruleTester } from '../../utils';\nimport * as path from 'path';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { ComponentStore } from '@ngrx/component-store';\n\nclass BooksStore extends ComponentStore<BooksState>\n{\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store';\n\nclass BooksStore extends ComponentStore<BooksState> implements OnDestroy\n{\n  override ngOnDestroy(): void {\n    super.ngOnDestroy();\n  }\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store';\n\nclass BooksStore extends ComponentStore<BooksState> implements OnDestroy\n{\n  cleanUp() {}\n\n  override ngOnDestroy(): void {\n    this.cleanUp();\n    super.ngOnDestroy();\n  }\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store';\n\nclass BooksStore extends ComponentStore<BooksState> implements OnDestroy\n{\n  cleanUp() {}\n\n  override ngOnDestroy(): void {\n    super.ngOnDestroy();\n    this.cleanUp();\n  }\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store';\n\nclass BooksStore extends ComponentStore<BooksState> implements OnDestroy\n{\n  cleanUp() {}\n\n  override ngOnDestroy(): void {\n    this.cleanUp();\n    super.ngOnDestroy();\n    this.cleanUp();\n  }\n}`,\n  `\nimport { ComponentStore } from '../components/component-store';\n\nclass BooksStore extends ComponentStore implements OnDestroy\n{\n  cleanUp() {}\n\n  override ngOnDestroy(): void {\n    this.cleanUp();\n  }\n}`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store';\n\nclass BooksStore extends ComponentStore<BooksState> implements OnDestroy {\n  override ngOnDestroy(): void {\n           ~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store';\n\nclass BooksStore extends ComponentStore<BooksState> implements OnDestroy {\n  cleanUp() {}\n\n  override ngOnDestroy(): void {\n           ~~~~~~~~~~~ [${messageId}]\n    this.cleanUp();\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store';\n\nclass BooksStore extends ComponentStore<BooksState> implements OnDestroy {\n  override ngOnDestroy(): void {\n           ~~~~~~~~~~~ [${messageId}]\n    super.ngOnDestroy;\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store';\n\nclass BooksStore extends ComponentStore<BooksState> implements OnDestroy {\n  override ngOnDestroy(): void {\n           ~~~~~~~~~~~ [${messageId}]\n    super.get();\n  }\n}`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/component-store/updater-explicit-return-type.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/component-store/updater-explicit-return-type';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass Ok extends ComponentStore<MoviesState> {\n  readonly addMovie = this.updater(\n    (state, movie): MoviesState => ({ movies: [...state.movies, movie] }),\n  )\n\n  constructor() {\n    super({ movies: [] })\n  }\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass Ok1 extends ComponentStore<MoviesState> {\n  readonly addMovie = this.updater<Movie>(\n    (state, movie): MoviesState => ({ movies: [...state.movies, movie] }),\n  )\n\n  constructor() {\n    super({ movies: [] })\n  }\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass Ok2 {\n  readonly addMovie = this.store.updater<Movie>(\n    (state, movie): MoviesState => ({\n      movies: [...state.movies, movie],\n    }),\n  )\n\n  constructor(private readonly store: ComponentStore<MoviesState>) {}\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass Ok3 {\n  readonly addMovie: Observable<unknown>\n\n  constructor(customStore: ComponentStore<MoviesState>) {\n    this.addMovie = customStore.updater<Movie>(\n      (state, movie): MoviesState => ({\n        movies: [...state.movies, movie],\n      }),\n    )\n  }\n}`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { ComponentStore } from '@ngrx/component-store'\nimport { inject } from '@angular/core'\n\nclass Ok4 {\n  private readonly store = inject(ComponentStore<MoviesState>);\n  readonly addMovie = this.store.updater<Movie>(\n    (state, movie): MoviesState => ({\n      movies: [...state.movies, movie],\n    }),\n  )\n}`,\n  `\nimport { ComponentStore } from '@ngrx/component-store'\nimport { inject } from '@angular/core'\n\nclass Ok5 {\n  readonly addMovie: Observable<unknown>\n  customStore = inject(ComponentStore<MoviesState>)\n\n  constructor() {\n    this.addMovie = this.customStore.updater<Movie>(\n      (state, movie): MoviesState => ({\n        movies: [...state.movies, movie],\n      }),\n    )\n  }\n}`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass NotOk extends ComponentStore<MoviesState> {\n  readonly addMovie = this.updater((state, movie) => ({ movies: [...state.movies, movie] }))\n                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n\n  constructor() {\n    super({ movies: [] })\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass NotOk1 extends ComponentStore<MoviesState> {\n  readonly updateMovie: Observable<unknown>\n  readonly addMovie = this.updater<Movie | null>((state, movie) => movie ? ({ movies: [...state.movies, movie] }) : ({ movies }))\n                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n\n  constructor(componentStore: ComponentStore<MoviesState>) {\n    super({ movies: [] })\n    this.updateMovie = componentStore.updater(() => ({ movies: MOVIES }))\n                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass NotOk2 {\n  readonly addMovie = this.store.updater((state, movie) => ({ movies: [...state.movies, movie] }))\n                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n\n  constructor(private readonly store: ComponentStore<MoviesState>) {}\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\n\nclass NotOk3 {\n  readonly addMovie: Observable<unknown>\n  readonly updateMovie: Observable<unknown>\n\n  constructor(\n    customStore: ComponentStore<MoviesState>,\n    private readonly store: ComponentStore<MoviesState>\n  ) {\n    this.addMovie = customStore.updater<Movie>((state, movie) => ({ movies: [...state.movies, movie] }))\n                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.updateMovie = this.store.updater(() => ({ movies: MOVIES }))\n                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n\n  ngOnInit() {\n    const updater = (item: Movie) => item\n    updater()\n  }\n}`),\n  fromFixture(`\n@Injectable()\nexport class CompetitorsStore2 extends CompetitorsStore1 {\n  override updateName = this.updater<string>((state, name: string) => ({ ...state, name }));\n                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n\n\n  updateName2 = this.updater(() => ({ name: 'test' }));\n                             ~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n}`),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\nimport { inject } from '@angular/core'\n\nclass NotOk4 {\n  componentStore = inject(ComponentStore<MoviesState>)\n  readonly updateMovie: Observable<unknown>\n\n  constructor() {\n    this.updateMovie = this.componentStore.updater(() => ({ movies: MOVIES }))\n                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\nimport { inject } from '@angular/core'\n\nclass NotOk5 {\n  private readonly store = inject(ComponentStore<MoviesState>)\n  readonly addMovie = this.store.updater((state, movie) => ({ movies: [...state.movies, movie] }))\n                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n}`),\n  fromFixture(`\nimport { ComponentStore } from '@ngrx/component-store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  customStore = inject(ComponentStore<MoviesState>)\n  private readonly store = inject(ComponentStore<MoviesState>)\n  readonly addMovie: Observable<unknown>\n  readonly updateMovie: Observable<unknown>\n\n  constructor() {\n    this.addMovie = this.customStore.updater<Movie>((state, movie) => ({ movies: [...state.movies, movie] }))\n                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.updateMovie = this.store.updater(() => ({ movies: MOVIES }))\n                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n\n  ngOnInit() {\n    const updater = (item: Movie) => item\n    updater()\n  }\n}`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/effects/avoid-cyclic-effects.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/effects/avoid-cyclic-effects';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst setup = `\nimport type { OnRunEffects } from '@ngrx/effects'\nimport { EffectConfig } from '@ngrx/effects'\nimport { Actions, createEffect, ofType } from '@ngrx/effects'\nimport { createAction } from '@ngrx/store'\nimport { map, tap, timer, takeUntil, merge,  } from 'rxjs'\nimport { inject } from '@angular/core';\n\nconst foo = createAction('FOO')\nconst bar = createAction('BAR')\n\nconst fromFoo = {\n  foo,\n  bar\n}\nconst subject = 'SUBJECT'\nconst genericFoo = createAction(\\`$\\{subject} FOO\\`)\nconst genericBar = createAction(\\`$\\{subject} BAR\\`)\n`;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n${setup}\nclass Effect {\n  foo$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(foo),\n      map(() => bar()),\n    ),\n  )\n\n  constructor(\n    private actions$: Actions,\n  ) {}\n}`,\n  `\n${setup}\nclass Effect {\n  foo$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(foo),\n      map(() => bar()),\n    )\n  })\n\n  constructor(\n    private actions$: Actions,\n  ) {}\n}`,\n  `\n${setup}\nclass Effect {\n  foo$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(fromFoo.foo),\n      map(() => fromFoo.bar()),\n    )\n  })\n\n  constructor(\n    private actions$: Actions,\n  ) {}\n}`,\n  `\n${setup}\nclass Effect {\n  foo$ = createEffect(() => {\n    return this.actions.pipe(\n      ofType(foo),\n      mapTo(bar()),\n    )\n  })\n\n  constructor(\n    private actions: Actions,\n  ) {}\n}`,\n  `\n${setup}\nclass Effect {\n  foo$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(foo),\n      tap(() => alert('hi'))\n    )\n    }, { dispatch: false }\n  )\n\n  constructor(\n    private actions$: Actions,\n  ) {}\n}`,\n  `\n${setup}\nclass Effect {\n  foo$: CreateEffectMetadata\n\n  constructor(\n    private actions$: Actions,\n  ) {\n    this.foo$ = createEffect(() =>\n      this.actions$.pipe(\n        ofType(genericFoo),\n        map(() => genericBar()),\n      ),\n    )\n  }\n}`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/223\n  `\nimport { Injectable } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { Action } from '@ngrx/store';\nimport { of } from 'rxjs';\nimport { switchMap } from 'rxjs/operators';\n\nenum OrderEntityActionTypes {\n  postPortInData = '[Order Entity] Post PortIn Data',\n  postPortInDataSuccess = '[Order Entity] Post PortIn Data Success',\n}\n\nclass PostPortInData implements Action {\n  readonly type = OrderEntityActionTypes.postPortInData\n}\n\nclass PostPortInDataSuccess implements Action {\n  readonly type = OrderEntityActionTypes.postPortInDataSuccess\n}\n\n@Injectable()\nclass Effect {\n  readonly postPortInData$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType<PostPortInData>(OrderEntityActionTypes.postPortInData),\n      switchMap(() => of(new PostPortInDataSuccess())),\n    ),\n  )\n\n  constructor(private readonly actions$: Actions) {}\n}`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n  ${setup}\n  class Effect {\n    private actions$ = inject(Actions);\n    foo$ = createEffect(() =>\n      this.actions$.pipe(\n        ofType(foo),\n        map(() => bar()),\n      ),\n    )\n  }`,\n  `\n  ${setup}\n  class Effect {\n    private actions$ = inject(Actions);\n    foo$ = createEffect(() => {\n      return this.actions$.pipe(\n        ofType(foo),\n        map(() => bar()),\n      )\n    })\n  }`,\n  `\n  ${setup}\n  class Effect {\n    private actions$ = inject(Actions);\n    foo$ = createEffect(() => {\n      return this.actions$.pipe(\n        ofType(fromFoo.foo),\n        map(() => fromFoo.bar()),\n      )\n    })\n  }`,\n  `\n  ${setup}\n  class Effect {\n    private actions = inject(Actions);\n    foo$ = createEffect(() => {\n      return this.actions.pipe(\n        ofType(foo),\n        mapTo(bar()),\n      )\n    })\n  }`,\n  `\n  ${setup}\n  class Effect {\n    private actions$ = inject(Actions);\n    foo$ = createEffect(() => {\n      return this.actions$.pipe(\n        ofType(foo),\n        tap(() => alert('hi'))\n      )\n      }, { dispatch: false }\n    )\n  }`,\n  `\n  ${setup}\n  class Effect {\n    foo$: CreateEffectMetadata\n    private actions$ = inject(Actions);\n\n    constructor() {\n      this.foo$ = createEffect(() =>\n        this.actions$.pipe(\n          ofType(genericFoo),\n          map(() => genericBar()),\n        ),\n      )\n    }\n  }`,\n  `\n  ${setup}\n  class Effect {\n    private actions$ = otherInject(Actions);\n    foo$ = createEffect(() => {\n      return this.actions$.pipe(\n        ofType(foo),\n        tap(() => alert('hi'))\n      )\n      }, { dispatch: false }\n    )\n  }`,\n  `\n  ${setup}\n  class Effect {\n    private actions$ = inject(OtherActions);\n    foo$ = createEffect(() => {\n      return this.actions$.pipe(\n        ofType(foo),\n        tap(() => alert('hi'))\n      )\n      }, { dispatch: false }\n    )\n  }`,\n  // https://github.com/ngrx/platform/issues/4168\n  `\n${setup}\nclass Effect {\n  actions$: Actions = inject(Actions);\n  foo$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(foo),\n      switchMap(() => {\n        return timer(500).pipe(\n          map(() => {\n            return bar();\n          }),\n          takeUntil(merge(timer(1000), this.actions$.pipe(ofType(foo))))\n        );\n      })\n    );\n  });\n}`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\n${setup}\nclass Effect {\n  foo$ = createEffect(() =>\n    this.actions$.pipe(\n    ~~~~~~~~~~~~~~~~~~ [${messageId}]\n      ofType(foo),\n      tap(() => alert('hi'))\n    ),\n  )\n\n  constructor(\n    private actions$: Actions,\n  ) {}\n}`),\n  fromFixture(`\n${setup}\nclass Effect {\n  foo$ = createEffect(() => {\n    return this.actions$.pipe(\n           ~~~~~~~~~~~~~~~~~~ [${messageId}]\n      ofType(foo),\n      tap(() => alert('hi'))\n    )\n  })\n\n  constructor(\n    private actions$: Actions,\n  ) {}\n}`),\n  fromFixture(`\n${setup}\nclass Effect {\n  foo$ = createEffect(() => {\n    return this.actions$.pipe(\n           ~~~~~~~~~~~~~~~~~~ [${messageId}]\n      ofType(foo),\n      tap(() => alert('hi'))\n    )\n  }, { dispatch: true })\n\n  constructor(\n    private actions$: Actions,\n  ) {}\n}`),\n  fromFixture(`\n${setup}\nclass Effect {\n  foo$ = createEffect(\n    () =>\n      ({ debounce = 100 } = {}) =>\n        debounce\n          ? this.actions$.pipe(\n            ~~~~~~~~~~~~~~~~~~ [${messageId}]\n              ofType(fromFoo.foo),\n              tap(() => alert('hi')),\n            )\n          : this.actions$.pipe(),\n  )\n\n  constructor(private actions$: Actions) {}\n}`),\n  fromFixture(`\n${setup}\nclass Effect {\n  foo$: CreateEffectMetadata\n\n  constructor(\n    private actions$: Actions,\n  ) {\n    this.foo$ = createEffect(() =>\n      this.actions$.pipe(\n      ~~~~~~~~~~~~~~~~~~ [${messageId}]\n        ofType(genericFoo),\n      ),\n    )\n  }\n}`),\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/223\n  fromFixture(`\nimport { Injectable } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { Action } from '@ngrx/store';\nimport { of } from 'rxjs';\nimport { switchMap } from 'rxjs/operators';\n\nenum OrderEntityActionTypes {\n  postPortInData = '[Order Entity] Post PortIn Data',\n  postPortInDataSuccess = '[Order Entity] Post PortIn Data Success',\n}\n\nclass PostPortInData implements Action {\n  readonly type = OrderEntityActionTypes.postPortInData\n}\n\nclass PostPortInDataSuccess implements Action {\n  readonly type = OrderEntityActionTypes.postPortInDataSuccess\n}\n\n@Injectable()\nclass Effect {\n  readonly postPortInData$ = createEffect(() =>\n    this.actions$.pipe(\n    ~~~~~~~~~~~~~~~~~~ [${messageId}]\n      ofType<PostPortInData>(OrderEntityActionTypes.postPortInData),\n      switchMap(() => of(new PostPortInData())),\n    ),\n  )\n\n  constructor(private readonly actions$: Actions) {}\n}`),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\n${setup}\nclass Effect {\n  private actions$ = inject(Actions);\n  foo$ = createEffect(() =>\n    this.actions$.pipe(\n    ~~~~~~~~~~~~~~~~~~ [${messageId}]\n      ofType(foo),\n      tap(() => alert('hi'))\n    ),\n  )\n}`),\n  fromFixture(`\n${setup}\nclass Effect {\n  private actions$ = inject(Actions);\n  foo$ = createEffect(() => {\n    return this.actions$.pipe(\n           ~~~~~~~~~~~~~~~~~~ [${messageId}]\n      ofType(foo),\n      tap(() => alert('hi'))\n    )\n  })\n}`),\n  fromFixture(`\n${setup}\nclass Effect {\n  private actions$ = inject(Actions);\n  foo$ = createEffect(() => {\n    return this.actions$.pipe(\n           ~~~~~~~~~~~~~~~~~~ [${messageId}]\n      ofType(foo),\n      tap(() => alert('hi'))\n    )\n  }, { dispatch: true });\n}`),\n  fromFixture(`\n${setup}\nclass Effect {\n  private actions$ = inject(Actions);\n  foo$ = createEffect(\n    () =>\n      ({ debounce = 100 } = {}) =>\n        debounce\n          ? this.actions$.pipe(\n            ~~~~~~~~~~~~~~~~~~ [${messageId}]\n              ofType(fromFoo.foo),\n              tap(() => alert('hi')),\n            )\n          : this.actions$.pipe(),\n  );\n}`),\n  fromFixture(`\n${setup}\nclass Effect {\n  foo$: CreateEffectMetadata\n  private actions$ = inject(Actions);\n\n  constructor() {\n    this.foo$ = createEffect(() =>\n      this.actions$.pipe(\n      ~~~~~~~~~~~~~~~~~~ [${messageId}]\n        ofType(genericFoo),\n      ),\n    )\n  }\n}`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/effects/no-dispatch-in-effects.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  noDispatchInEffects,\n  noDispatchInEffectsSuggest,\n} from '../../../src/rules/effects/no-dispatch-in-effects';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok {\n    readonly effect = somethingOutside();\n  }`,\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok1 {\n    effect = createEffect(() => this.actions.pipe(\n      ofType('PING'),\n      tap(() => ({ type: 'PONG' }))\n    ))\n\n    constructor(private actions: Actions, private store: Store) {}\n  }`,\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok2 {\n    readonly effect: CreateEffectMetadata\n\n    constructor(private actions: Actions, private store$: Store) {\n        this.effect = createEffect(\n        () => ({ scheduler = asyncScheduler } = {}) =>\n          this.actions.pipe(\n            ofType(customerActions.remove),\n            tap(() => {\n              customObject.dispatch({ somethingElse: true })\n              return customerActions.removeSuccess()\n            }),\n          ),\n        { dispatch: false },\n      )\n    }\n  }`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok3 {\n  private readonly actions = inject(Actions);\n  private readonly store = inject(Store);\n\n  effect = createEffect(() => this.actions.pipe(\n    ofType('PING'),\n    tap(() => ({ type: 'PONG' }))\n  ))\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/inject'\n\nclass Ok4 {\n  private readonly actions = inject(Actions);\n  private readonly store = inject(Store);\n\n  effect = createEffect(() => this.actions.pipe(\n    ofType('PING'),\n    tap(() => ({ type: 'PONG' }))\n  ))\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok5 {\n  readonly effect: CreateEffectMetadata\n  private readonly actions = inject(Actions);\n  private readonly store$ = inject(Store);\n\n  constructor() {\n      this.effect = createEffect(\n      () => ({ scheduler = asyncScheduler } = {}) =>\n        this.actions.pipe(\n          ofType(customerActions.remove),\n          tap(() => {\n            customObject.dispatch({ somethingElse: true })\n            return customerActions.removeSuccess()\n          }),\n        ),\n      { dispatch: false },\n    )\n  }\n}`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  effect = createEffect(\n    () => {\n      return this.actions.pipe(\n        ofType(someAction),\n        tap(() => this.store.dispatch(awesomeAction())),\n                  ~~~~~~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest]\n      )\n    },\n    { dispatch: false },\n  )\n\n  constructor(private actions: Actions, private store: Store) {}\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  effect = createEffect(\n    () => {\n      return this.actions.pipe(\n        ofType(someAction),\n        tap(() => (awesomeAction())),\n      )\n    },\n    { dispatch: false },\n  )\n\n  constructor(private actions: Actions, private store: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  readonly effect = createEffect(() => condition ? this.actions.pipe(\n    ofType(userActions.add),\n    tap(() => {\n      return this.store.dispatch(userActions.addSuccess)\n             ~~~~~~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest]\n    })\n  ) : this.actions.pipe())\n\n  constructor(private actions: Actions, private store: Store) {}\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  readonly effect = createEffect(() => condition ? this.actions.pipe(\n    ofType(userActions.add),\n    tap(() => {\n      return (userActions.addSuccess)\n    })\n  ) : this.actions.pipe())\n\n  constructor(private actions: Actions, private store: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  effect = createEffect(\n    () => ({ debounce = 200 } = {}) =>\n      this.actions.pipe(\n        ofType(actions.ping),\n        tap(() => {\n          return this.customName.dispatch(/* you shouldn't do this */ actions.pong())\n                 ~~~~~~~~~~~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest]\n        }),\n      ),\n  )\n\n  constructor(private actions: Actions, private customName: Store) {}\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  effect = createEffect(\n    () => ({ debounce = 200 } = {}) =>\n      this.actions.pipe(\n        ofType(actions.ping),\n        tap(() => {\n          return (/* you shouldn't do this */ actions.pong())\n        }),\n      ),\n  )\n\n  constructor(private actions: Actions, private customName: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  readonly effect : CreateEffectMetadata\n  readonly effect : CreateEffectMetadata\n\n  constructor(private actions: Actions, store: Store, private readonly store$: Store) {\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          map(() => {\n            this.store$.dispatch(bookActions.loadSuccess());// you shouldn't do this\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest 0]\n            return somethingElse()\n          }),\n        ),\n      { dispatch: true, useEffectsErrorHandler: false, ...options },\n    )\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          tap(() => store.dispatch(bookActions.loadSuccess()))\n                    ~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest 1]\n        ),\n    )\n  }\n\n  ngOnDestroy() {\n    store.dispatch()\n  }\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  readonly effect : CreateEffectMetadata\n  readonly effect : CreateEffectMetadata\n\n  constructor(private actions: Actions, store: Store, private readonly store$: Store) {\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          map(() => {\n            ;// you shouldn't do this\n            return somethingElse()\n          }),\n        ),\n      { dispatch: true, useEffectsErrorHandler: false, ...options },\n    )\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          tap(() => store.dispatch(bookActions.loadSuccess()))\n        ),\n    )\n  }\n\n  ngOnDestroy() {\n    store.dispatch()\n  }\n}`,\n        },\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  readonly effect : CreateEffectMetadata\n  readonly effect : CreateEffectMetadata\n\n  constructor(private actions: Actions, store: Store, private readonly store$: Store) {\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          map(() => {\n            this.store$.dispatch(bookActions.loadSuccess());// you shouldn't do this\n            return somethingElse()\n          }),\n        ),\n      { dispatch: true, useEffectsErrorHandler: false, ...options },\n    )\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          tap(() => (bookActions.loadSuccess()))\n        ),\n    )\n  }\n\n  ngOnDestroy() {\n    store.dispatch()\n  }\n}`,\n        },\n      ],\n    }\n  ),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk4 {\n  private actions = inject(Actions);\n  private store = inject(Store);\n\n  effect = createEffect(\n    () => {\n      return this.actions.pipe(\n        ofType(someAction),\n        tap(() => this.store.dispatch(awesomeAction())),\n                  ~~~~~~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest]\n      )\n    },\n    { dispatch: false },\n  )\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk4 {\n  private actions = inject(Actions);\n  private store = inject(Store);\n\n  effect = createEffect(\n    () => {\n      return this.actions.pipe(\n        ofType(someAction),\n        tap(() => (awesomeAction())),\n      )\n    },\n    { dispatch: false },\n  )\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk5 {\n  private readonly actions = inject(Actions);\n  private readonly store = inject(Store);\n  readonly effect = createEffect(() => condition ? this.actions.pipe(\n    ofType(userActions.add),\n    tap(() => {\n      return this.store.dispatch(userActions.addSuccess)\n             ~~~~~~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest]\n    })\n  ) : this.actions.pipe())\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk5 {\n  private readonly actions = inject(Actions);\n  private readonly store = inject(Store);\n  readonly effect = createEffect(() => condition ? this.actions.pipe(\n    ofType(userActions.add),\n    tap(() => {\n      return (userActions.addSuccess)\n    })\n  ) : this.actions.pipe())\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  private readonly actions = inject(Actions);\n  private readonly customName = inject(Store);\n  effect = createEffect(\n    () => ({ debounce = 200 } = {}) =>\n      this.actions.pipe(\n        ofType(actions.ping),\n        tap(() => {\n          return this.customName.dispatch(/* you shouldn't do this */ actions.pong())\n                 ~~~~~~~~~~~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest]\n        }),\n      ),\n  )\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  private readonly actions = inject(Actions);\n  private readonly customName = inject(Store);\n  effect = createEffect(\n    () => ({ debounce = 200 } = {}) =>\n      this.actions.pipe(\n        ofType(actions.ping),\n        tap(() => {\n          return (/* you shouldn't do this */ actions.pong())\n        }),\n      ),\n  )\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\n  readonly effect : CreateEffectMetadata\n  readonly effect : CreateEffectMetadata\n  private readonly actions = inject(Actions);\n  private readonly store = inject(Store);\n  private readonly store$ = inject(Store);\n\n  constructor() {\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          map(() => {\n            this.store$.dispatch(bookActions.loadSuccess());// you shouldn't do this\n            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest 0]\n            return somethingElse()\n          }),\n        ),\n      { dispatch: true, useEffectsErrorHandler: false, ...options },\n    )\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          tap(() => store.dispatch(bookActions.loadSuccess()))\n                    ~~~~~~~~~~~~~~ [${noDispatchInEffects} suggest 1]\n        ),\n    )\n  }\n\n  ngOnDestroy() {\n    store.dispatch()\n  }\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\n  readonly effect : CreateEffectMetadata\n  readonly effect : CreateEffectMetadata\n  private readonly actions = inject(Actions);\n  private readonly store = inject(Store);\n  private readonly store$ = inject(Store);\n\n  constructor() {\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          map(() => {\n            ;// you shouldn't do this\n            return somethingElse()\n          }),\n        ),\n      { dispatch: true, useEffectsErrorHandler: false, ...options },\n    )\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          tap(() => store.dispatch(bookActions.loadSuccess()))\n        ),\n    )\n  }\n\n  ngOnDestroy() {\n    store.dispatch()\n  }\n}`,\n        },\n        {\n          messageId: noDispatchInEffectsSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\n  readonly effect : CreateEffectMetadata\n  readonly effect : CreateEffectMetadata\n  private readonly actions = inject(Actions);\n  private readonly store = inject(Store);\n  private readonly store$ = inject(Store);\n\n  constructor() {\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          map(() => {\n            this.store$.dispatch(bookActions.loadSuccess());// you shouldn't do this\n            return somethingElse()\n          }),\n        ),\n      { dispatch: true, useEffectsErrorHandler: false, ...options },\n    )\n    this.effect = createEffect(\n      () =>\n        this.actions.pipe(\n          ofType(bookActions.load),\n          tap(() => (bookActions.loadSuccess()))\n        ),\n    )\n  }\n\n  ngOnDestroy() {\n    store.dispatch()\n  }\n}`,\n        },\n      ],\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/effects/no-effects-in-providers.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/effects/no-effects-in-providers';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n@NgModule({\n  imports: [\n    StoreModule.forFeature('persons', {\"foo\": \"bar\"}),\n    EffectsModule.forRoot([RootEffectOne]),\n    EffectsModule.forFeature([FeatEffectOne]),\n  ],\n  providers: [FeatEffectTwo, UnRegisteredEffect, FeatEffectThree, RootEffectTwo],\n})\nexport class AppModule {}`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\n@NgModule({\n  imports: [EffectsModule.forFeature([RegisteredEffect])],\n  providers: [RegisteredEffect]\n              ~~~~~~~~~~~~~~~~ [${messageId}]\n})\nexport class AppModule {}`,\n    {\n      output: `\n@NgModule({\n  imports: [EffectsModule.forFeature([RegisteredEffect])],\n  providers: []\n})\nexport class AppModule {}`,\n    }\n  ),\n  fromFixture(\n    `\n@NgModule({\n  imports: [EffectsModule.forRoot([RegisteredEffect])],\n  providers: [\n    RegisteredEffect// Let's see what happens with this comment?\n    ~~~~~~~~~~~~~~~~ [${messageId}]\n    ,\n  ],\n})\nexport class AppModule {}`,\n    {\n      output: `\n@NgModule({\n  imports: [EffectsModule.forRoot([RegisteredEffect])],\n  providers: [\n    // Let's see what happens with this comment?\n    \n  ],\n})\nexport class AppModule {}`,\n    }\n  ),\n  fromFixture(\n    `\n@NgModule({\n  providers: [\n    UnRegisteredEffect,\n    FeatEffectTwo,\n    ~~~~~~~~~~~~~ [${messageId}]\n\n\n    UnRegisteredEffect2,\n  ],\n  'imports': [\n    EffectsModule.forFeature([FeatEffectOne, FeatEffectTwo]),\n  ],\n})\nexport class AppModule {}\n\n@NgModule({\n  imports: [EffectsModule.forFeature([UnRegisteredEffect])],\n  providers: [],\n})\nexport class SharedModule {}`,\n    {\n      output: `\n@NgModule({\n  providers: [\n    UnRegisteredEffect,\n    \n\n\n    UnRegisteredEffect2,\n  ],\n  'imports': [\n    EffectsModule.forFeature([FeatEffectOne, FeatEffectTwo]),\n  ],\n})\nexport class AppModule {}\n\n@NgModule({\n  imports: [EffectsModule.forFeature([UnRegisteredEffect])],\n  providers: [],\n})\nexport class SharedModule {}`,\n    }\n  ),\n  fromFixture(\n    `\n@NgModule({\n  imports: [\n    StoreModule.forFeature('persons', {\"foo\": \"bar\"}),\n    EffectsModule.forRoot([RootEffectOne, RootEffectTwo]),\n    EffectsModule.forFeature([FeatEffectOne, FeatEffectTwo]),\n    EffectsModule.forFeature([FeatEffectThree]),\n  ],\n  ['providers']: [\n    FeatEffectTwo,\n    ~~~~~~~~~~~~~   [${messageId}]\n    UnRegisteredEffect,\n    FeatEffectThree/* Deprecated effect */,\n    ~~~~~~~~~~~~~~~ [${messageId}]\n    RootEffectTwo\n    ~~~~~~~~~~~~~   [${messageId}]\n  ],\n})\nexport class AppModule {}`,\n    {\n      output: `\n@NgModule({\n  imports: [\n    StoreModule.forFeature('persons', {\"foo\": \"bar\"}),\n    EffectsModule.forRoot([RootEffectOne, RootEffectTwo]),\n    EffectsModule.forFeature([FeatEffectOne, FeatEffectTwo]),\n    EffectsModule.forFeature([FeatEffectThree]),\n  ],\n  ['providers']: [\n    \n    UnRegisteredEffect,\n    /* Deprecated effect */\n    \n  ],\n})\nexport class AppModule {}`,\n    }\n  ),\n  fromFixture(\n    `\n@NgModule({\n  [\\`providers\\`]: [\n    FeatEffectTwo,\n    ~~~~~~~~~~~~~   [${messageId}]\n    FeatEffectThree,\n    ~~~~~~~~~~~~~~~ [${messageId}]\n    RootEffectTwo,\n    ~~~~~~~~~~~~~   [${messageId}]\n    UnRegisteredEffect\n  ],\n  ['imports']: [\n    StoreModule.forFeature('persons', {\"foo\": \"bar\"}),\n    EffectsModule.forRoot([RootEffectOne, RootEffectTwo]),\n    EffectsModule.forFeature([FeatEffectOne, FeatEffectTwo]),\n    EffectsModule.forFeature([FeatEffectThree]),\n  ],\n})\nexport class AppModule {}`,\n    {\n      output: `\n@NgModule({\n  [\\`providers\\`]: [\n    \n    \n    \n    UnRegisteredEffect\n  ],\n  ['imports']: [\n    StoreModule.forFeature('persons', {\"foo\": \"bar\"}),\n    EffectsModule.forRoot([RootEffectOne, RootEffectTwo]),\n    EffectsModule.forFeature([FeatEffectOne, FeatEffectTwo]),\n    EffectsModule.forFeature([FeatEffectThree]),\n  ],\n})\nexport class AppModule {}`,\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/effects/no-multiple-actions-in-effects.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/effects/no-multiple-actions-in-effects';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n@Injectable()\nexport class Effects {\n  effectOK$ = createEffect(() =>\n    this.actions$.pipe(map(() => foo()))\n  )\n}`,\n  `\n@Injectable()\nexport class Effects {\n  effectOK1$ = createEffect(() =>\n    this.actions$.pipe(switchMap(() => {\n      return of(foo())\n    }))\n  )\n}`,\n  `\nconst action = () => foo()\n@Injectable()\nexport class Effects {\n  effectOK2$ = createEffect(() => ({ debounce = 700 } = {}) =>\n    this.actions$.pipe(mapTo(action()))\n  )\n}`,\n  // This specific test ensures that we only care about built-in `rxjs` operators.\n  `\n@Injectable()\nexport class Effects {\n  effectOK3$ = createEffect(() =>\n    this.actions$.pipe(\n      aconcatMapTo([foo()]),\n      switchMapTop([bar()]),\n    )\n  )\n}`,\n  `\nconst foo = {type: 'foo'}\n@Injectable()\nexport class Effects {\n  effectOK4$: CreateEffectMetadata\n\n  constructor() {\n    this.effectOK4$ = createEffect(() =>\n      this.actions$.pipe(\n        exhaustMap(() => {\n          return of({}).pipe(\n            map(() => foo),\n            catchError(() => of(bar()))\n          );\n        })\n      )\n    )\n  }\n}`,\n  `\nexport const saveSearchCriteria$ = createEffect(\n  (actions$ = inject(Actions$), store = inject(Store), saveLoadService = inject(SaveLoadService)) => {\n    return actions$.pipe(\n      ofType(SearchCriteriaActions.save),\n      concatLatestFrom(() => store.select(inventoryFeature.selectInventoryItems)),\n      concatMap(([{ searchCriteriaName }, inventoryItems]) => {\n        const tags = Object.keys(inventoryItems)\n          .filter((inventoryType) => {\n            const [, inventorySearchType] = splitInventoryType(inventoryType as InventoryType);\n            return inventorySearchType === 'costCenter' || inventorySearchType === 'wbs';\n          })\n          .map((inventoryType) => splitInventoryType(inventoryType as InventoryType))\n          .map(([value, type]) => ({ value, type }));\n        return saveLoadService.saveSearch(searchCriteriaName, tags, false).pipe(\n          map(() => SearchCriteriaActions.saveSucceeded()),\n          catchError((error: Error) => {\n            if (error instanceof HttpErrorResponse && error.status === 409) {\n              return of(SearchCriteriaActions.saveAlreadyExists({ searchCriteriaName, tags }));\n            }\n\n            return defaultErrorHandler(error, 'inventoryDomain.messages.saveSearchFailed', SearchCriteriaActions.saveFailed());\n          })\n        );\n      })\n    );\n  },\n  { functional: true }\n);\n  `,\n  `\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { createAction, props } from '@ngrx/store';\nimport { of, switchMap } from 'rxjs';\nconst foo = createAction('foo', props<{ payload: string }>());\nconst bar = createAction('bar');\nconst baz = createAction('baz');\n\n@Injectable()\nexport class Effects {\n  constructor(private actions$: Actions) {}\n  effect$ = createEffect(() =>\n        this.actions$.pipe(\n            ofType(foo),\n            switchMap(({ payload }: { payload: string }) => (payload === 'value' ? of(bar()) : of(baz()))),\n        ),\n    );\n}`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\n@Injectable()\nexport class Effects {\n  effectNOK$ = createEffect(() =>\n    this.actions$.pipe(flatMap(_ => [foo(), bar()])),\n                                    ~~~~~~~~~~~~~~  [${messageId}]\n  )\n}`\n  ),\n  fromFixture(\n    `\n@Injectable()\nexport class Effects {\n  effectNOK1$ = createEffect(() =>\n    this.actions$.pipe(mergeMap(_ => { return [foo(), bar()] }))\n                                              ~~~~~~~~~~~~~~  [${messageId}]\n  )\n}`\n  ),\n  fromFixture(\n    `\n@Injectable()\nexport class Effects {\n  effectNOK2$ = createEffect(() =>\n    this.actions$.pipe(exhaustMap(function() { return [foo(), bar()] }))\n                                                      ~~~~~~~~~~~~~~  [${messageId}]\n  )\n}`\n  ),\n  fromFixture(\n    `\nimport { of } from 'rxjs'\n@Injectable()\nexport class Effects {\n  readonly effectNOK3$ = createEffect(() =>\n    this.actions$.pipe(concatMapTo(condition ? [foo(), bar()] : of(foo())))\n                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  [${messageId}]\n  )\n}`\n  ),\n  fromFixture(\n    `\nconst actions = [foo(), bar()]\n@Injectable()\nexport class Effects {\n  readonly effectNOK4$ = createEffect(() =>\n    this.actions$.pipe(concatMapTo(actions))\n                                   ~~~~~~~  [${messageId}]\n  )\n}`\n  ),\n  fromFixture(\n    `\nconst actions = () => [foo(), bar()]\n@Injectable()\nexport class Effects {\n  effectNOK5$ = createEffect(() =>\n    condition\n      ? this.actions$.pipe(\n          exhaustMap(() => {\n            return of({}).pipe(\n              switchMap(() => actions()),\n                              ~~~~~~~~~  [${messageId}]\n              catchError(() => of(bar())),\n            )\n          }),\n        )\n      : this.actions.pipe(),\n  )\n}`\n  ),\n  fromFixture(\n    `\nimport { Action } from '@ngrx/store'\n@Injectable()\nexport class Effects {\n  effectNOK6$ = createEffect(() =>\n    this.actions$.pipe(concatMap(() => {\n      let actions: Action[] = [];\n      return actions;\n             ~~~~~~~  [${messageId}]\n    }))\n  )\n}`\n  ),\n  fromFixture(\n    `\nimport { Action } from '@ngrx/store'\nimport { of } from 'rxjs'\n@Injectable()\nexport class Effects {\n  readonly effectNOK7$: CreateEffectMetadata\n\n  constructor() {\n    effectNOK7$ = createEffect(() => ({ debounce = 300 } = {}) =>\n      this.actions$.pipe(switchMap(() => {\n        let actions: Action[] | null;\n        return actions ?? of(foo());\n               ~~~~~~~~~~~~~~~~~~~~  [${messageId}]\n      }))\n    )\n  }\n}`\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/effects/prefer-effect-callback-in-block-statement.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/effects/prefer-effect-callback-in-block-statement';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n  @Injectable()\n  class Effect {\n    effectOK = createEffect(() => {\n      return this.actions.pipe(\n        ofType('PING'),\n        map(() => ({ type: 'PONG' }))\n      )\n    })\n  }`,\n  `\n@Injectable()\nclass Effect {\n  effectOK1 = createEffect(() => {\n    return this.actions.pipe(\n      ofType('PING'),\n      tap(() => doSomething())\n    )\n  }, {dispatch: false})\n}`,\n  `\n@Injectable()\nclass Effect {\n  effectOK2 = createEffect(() => ({ debounce = 200 } = {}) => {\n    return this.actions$.pipe()\n  })\n}`,\n  `\n@Injectable()\nclass Effect {\n  readonly effectOK3: CreateEffectMetadata;\n\n  constructor() {\n    this.effectOK3 = createEffect(function () {\n      return ({ debounce = defaultDebounce } = {}) => {\n        return this.actions$.pipe()\n      }\n    })\n  }\n}`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\n@Injectable()\nclass Effect {\n  effectNOK = createEffect(() => this.actions.pipe(ofType('PING'),map(() => ({ type: 'PONG' }))))\n                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n}`,\n    {\n      output: `\n@Injectable()\nclass Effect {\n  effectNOK = createEffect(() => { return this.actions.pipe(ofType('PING'),map(() => ({ type: 'PONG' }))) })\n}`,\n    }\n  ),\n  fromFixture(\n    `\n@Injectable()\nclass Effect {\n  effectNOK1 = createEffect(() =>\n    (condition ? this.actions.pipe() : of({})),\n     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    { dispatch: false }\n  )\n}`,\n    {\n      output: `\n@Injectable()\nclass Effect {\n  effectNOK1 = createEffect(() =>\n    { return (condition ? this.actions.pipe() : of({})) },\n    { dispatch: false }\n  )\n}`,\n    }\n  ),\n  fromFixture(\n    `\n@Injectable()\nclass Effect {\n  effectNOK2 = createEffect(\n    () => ({ debounce = 500 } = {}) => (this.actions$.pipe())\n                                        ~~~~~~~~~~~~~~~~~~~~  [${messageId}]\n  )\n}`,\n    {\n      output: `\n@Injectable()\nclass Effect {\n  effectNOK2 = createEffect(\n    () => ({ debounce = 500 } = {}) => { return (this.actions$.pipe()) }\n  )\n}`,\n    }\n  ),\n  fromFixture(\n    `\n@Injectable()\nclass Effect {\n  effectNOK3 = createEffect(() => {\n    return ({ scheduler = asyncScheduler } = {}) => this.actions$.pipe()\n                                                    ~~~~~~~~~~~~~~~~~~~~  [${messageId}]\n  })\n}`,\n    {\n      output: `\n@Injectable()\nclass Effect {\n  effectNOK3 = createEffect(() => {\n    return ({ scheduler = asyncScheduler } = {}) => { return this.actions$.pipe() }\n  })\n}`,\n    }\n  ),\n  fromFixture(\n    `\n@Injectable()\nclass Effect {\n  readonly effectNOK4: CreateEffectMetadata;\n\n  constructor() {\n    this.effectNOK4 = createEffect(\n      () =>\n        ({ debounce = 700 } = {}) =>\n          this.actions$.pipe(),\n          ~~~~~~~~~~~~~~~~~~~~  [${messageId}]\n    )\n  }\n}`,\n    {\n      output: `\n@Injectable()\nclass Effect {\n  readonly effectNOK4: CreateEffectMetadata;\n\n  constructor() {\n    this.effectNOK4 = createEffect(\n      () =>\n        ({ debounce = 700 } = {}) =>\n          { return this.actions$.pipe() },\n    )\n  }\n}`,\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/effects/use-effects-lifecycle-interface.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/effects/use-effects-lifecycle-interface';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `class Foo {}`,\n  `\nclass UserEffects implements OnInitEffects {\n  ngrxOnInitEffects(): Action {\n    return { type: '[UserEffects]: Init' }\n  }\n}`,\n  `\nexport class UserEffects implements OnRunEffects {\n  constructor(private actions$: Actions) {}\n  updateUser$ = createEffect(() =>\n      this.actions$.pipe(\n        ofType('UPDATE_USER'),\n        tap(action => {\n          console.log(action)\n        })\n      ),\n    { dispatch: false })\n  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>) {\n    return this.actions$.pipe(\n      ofType('LOGGED_IN'),\n      exhaustMap(() =>\n        resolvedEffects$.pipe(\n          takeUntil(this.actions$.pipe(ofType('LOGGED_OUT')))\n        )\n      )\n    )\n  }\n}`,\n  `\n    class EffectWithIdentifier implements OnIdentifyEffects {\n      constructor(private effectIdentifier: string) {}\n      ngrxOnIdentifyEffects() {\n        return this.effectIdentifier\n      }\n    }`,\n  `\nclass UserEffects implements ngrx.OnInitEffects, OnIdentifyEffects {\n  ngrxOnInitEffects(): Action {\n    return { type: '[UserEffects]: Init' }\n  }\n  ngrxOnIdentifyEffects(): string {\n    return ''\n  }\n}`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nclass UserEffects {\n  ngrxOnInitEffects() {}\n  ~~~~~~~~~~~~~~~~~ [${messageId} { \"interfaceName\": \"OnInitEffects\", \"methodName\": \"ngrxOnInitEffects\" }]\n}`,\n    {\n      output: `import { OnInitEffects } from '@ngrx/effects';\nclass UserEffects implements OnInitEffects {\n  ngrxOnInitEffects() {}\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport Effects from '@ngrx/effects'\nclass UserEffects {\n  ngrxOnIdentifyEffects() {}\n  ~~~~~~~~~~~~~~~~~~~~~ [${messageId} { \"interfaceName\": \"OnIdentifyEffects\", \"methodName\": \"ngrxOnIdentifyEffects\" }]\n}`,\n    {\n      output: `\nimport Effects, { OnIdentifyEffects } from '@ngrx/effects'\nclass UserEffects implements OnIdentifyEffects {\n  ngrxOnIdentifyEffects() {}\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { Injectable } from '@angular/core'\nclass UserEffects {\n  ngrxOnRunEffects() {}\n  ~~~~~~~~~~~~~~~~ [${messageId} { \"interfaceName\": \"OnRunEffects\", \"methodName\": \"ngrxOnRunEffects\" }]\n}`,\n    {\n      output: `import { OnRunEffects } from '@ngrx/effects';\nimport { Injectable } from '@angular/core'\nclass UserEffects implements OnRunEffects {\n  ngrxOnRunEffects() {}\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport * as ngrx from '@ngrx/effects'\nclass UserEffects {\n  ngrxOnInitEffects() {}\n  ~~~~~~~~~~~~~~~~~ [${messageId} { \"interfaceName\": \"OnInitEffects\", \"methodName\": \"ngrxOnInitEffects\" }]\n}`,\n    {\n      output: `import { OnInitEffects } from '@ngrx/effects';\nimport * as ngrx from '@ngrx/effects'\nclass UserEffects implements OnInitEffects {\n  ngrxOnInitEffects() {}\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport type { OnInitEffects, OnRunEffects } from '@ngrx/effects'\nclass UserEffects implements OnInitEffects, OnRunEffects {\n  ngrxOnInitEffects() {}\n\n  ngrxOnIdentifyEffects() {}\n  ~~~~~~~~~~~~~~~~~~~~~ [${messageId} { \"interfaceName\": \"OnIdentifyEffects\", \"methodName\": \"ngrxOnIdentifyEffects\" }]\n\n  ngrxOnRunEffects() {}\n}`,\n    {\n      output: `\nimport type { OnInitEffects, OnRunEffects, OnIdentifyEffects } from '@ngrx/effects'\nclass UserEffects implements OnInitEffects, OnRunEffects, OnIdentifyEffects {\n  ngrxOnInitEffects() {}\n\n  ngrxOnIdentifyEffects() {}\n\n  ngrxOnRunEffects() {}\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { Injectable, inject } from \"@angular/core\";\nimport { Actions, EffectNotification, ofType } from \"@ngrx/effects\";\nimport { Observable, exhaustMap, takeUntil } from \"rxjs\";\n\n@Injectable()\nexport class Effects {\n  private actions$ = inject(Actions);\n\n  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>) {\n  ~~~~~~~~~~~~~~~~ [${messageId} { \"interfaceName\": \"OnRunEffects\", \"methodName\": \"ngrxOnRunEffects\" }]\n    return this.actions$.pipe(\n      ofType(\"Login\"),\n      exhaustMap(() =>\n        resolvedEffects$.pipe(\n          takeUntil(\n            this.actions$.pipe(ofType(\"Logout\"))\n          )\n        )\n      )\n    );\n  }\n}`,\n    {\n      output: `\nimport { Injectable, inject } from \"@angular/core\";\nimport { Actions, EffectNotification, ofType, OnRunEffects } from \"@ngrx/effects\";\nimport { Observable, exhaustMap, takeUntil } from \"rxjs\";\n\n@Injectable()\nexport class Effects implements OnRunEffects {\n  private actions$ = inject(Actions);\n\n  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>) {\n    return this.actions$.pipe(\n      ofType(\"Login\"),\n      exhaustMap(() =>\n        resolvedEffects$.pipe(\n          takeUntil(\n            this.actions$.pipe(ofType(\"Logout\"))\n          )\n        )\n      )\n    );\n  }\n}`,\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/operators/prefer-concat-latest-from.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/operators/prefer-concat-latest-from';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = readonly ESLintUtils.InferOptionsTypeFromRule<typeof rule>[0][];\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  {\n    code: `\n  import { of, withLatestFrom } from 'rxjs'\n  class Ok {\n    effect = createEffect(\n      () =>\n        this.actions$.pipe(\n          ofType(CollectionApiActions.addBookSuccess),\n          concatLatestFrom(action => this.store.select(fromBooks.getCollectionBookIds)),\n          switchMap(([, bookCollection]) => {\n            return of({ type: 'noop' })\n          })\n        ),\n    );\n  }`,\n    options: [{ strict: true }],\n  },\n  `\n  import { Actions } from '@ngrx/effects'\n  import { of, withLatestFrom } from 'rxjs'\n  class Ok1 {\n    readonly effect: CreateEffectMetadata\n    constructor(actions$: Actions) {\n      this.effect = createEffect(() => ({ scheduler = asyncScheduler } = {}) => {\n        return actions$.pipe(\n          ofType(ProductDetailPage.loaded),\n          concatMap((action) =>\n            of(action).pipe(withLatestFrom(this.store.select(selectProducts))),\n          ),\n          mergeMapTo(of({ type: 'noop' })),\n        )\n      }, { dispatch: false })\n    }\n  }`,\n  `\n  import { Actions } from '@ngrx/effects'\n  import { of, withLatestFrom } from 'rxjs'\n  class Ok2 {\n    effect = createEffect(() =>\n      condition\n        ? this.actions$.pipe(\n            ofType(ProductDetailPage.loaded),\n            concatMap((action) =>\n              of(action).pipe(\n                withLatestFrom(this.store.select$(something), (one, other) =>\n                  somethingElse(),\n                ),\n              ),\n            ),\n            mergeMap(([action, products]) => of(products)),\n          )\n        : this.actions$.pipe(),\n    )\n    constructor(private readonly actions$: Actions) {}\n  }`,\n  `\n  import { Actions } from '@ngrx/effects'\n  import { of, withLatestFrom } from 'rxjs'\n  import { inject } from '@angular/core'\n  class Ok3 {\n    readonly effect: CreateEffectMetadata\n    readonly actions$ = inject(Actions)\n    constructor() {\n      this.effect = createEffect(() => ({ scheduler = asyncScheduler } = {}) => {\n        return actions$.pipe(\n          ofType(ProductDetailPage.loaded),\n          concatMap((action) =>\n            of(action).pipe(withLatestFrom(this.store.select(selectProducts))),\n          ),\n          mergeMapTo(of({ type: 'noop' })),\n        )\n      }, { dispatch: false })\n    }\n  }`,\n  `\n  import { Actions } from '@ngrx/effects'\n  import { of, withLatestFrom } from 'rxjs'\n  import { inject } from '@angular/core'\n  class Ok4 {\n    private readonly actions$ = inject(Actions)\n    effect = createEffect(() =>\n      condition\n        ? this.actions$.pipe(\n            ofType(ProductDetailPage.loaded),\n            concatMap((action) =>\n              of(action).pipe(\n                withLatestFrom(this.store.select$(something), (one, other) =>\n                  somethingElse(),\n                ),\n              ),\n            ),\n            mergeMap(([action, products]) => of(products)),\n          )\n        : this.actions$.pipe(),\n    )\n  }`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Actions } from '@ngrx/effects'\nimport { of, withLatestFrom } from 'rxjs'\n\nclass NotOk {\n  effect = createEffect(() =>\n    this.actions$.pipe(\n      ofType(CollectionApiActions.addBookSuccess),\n      withLatestFrom((action) =>\n      ~~~~~~~~~~~~~~ [${messageId}]\n        this.store.select(fromBooks.selectCollectionBookIds),\n      ),\n      switchMap(([action, bookCollection]) => {\n        return of({ type: 'noop' })\n      }),\n    ),\n  )\n\n  constructor(private readonly actions$: Actions) {}\n}`,\n    {\n      output: `import { concatLatestFrom } from '@ngrx/operators';\nimport { Actions } from '@ngrx/effects'\nimport { of, withLatestFrom } from 'rxjs'\n\nclass NotOk {\n  effect = createEffect(() =>\n    this.actions$.pipe(\n      ofType(CollectionApiActions.addBookSuccess),\n      concatLatestFrom((action) =>\n        this.store.select(fromBooks.selectCollectionBookIds),\n      ),\n      switchMap(([action, bookCollection]) => {\n        return of({ type: 'noop' })\n      }),\n    ),\n  )\n\n  constructor(private readonly actions$: Actions) {}\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { Actions } from '@ngrx/effects'\nimport { of, withLatestFrom } from 'rxjs'\n\nclass NotOk {\n  effect = createEffect(() =>\n    this.actions$.pipe(\n      ofType(CollectionApiActions.addBookSuccess),\n      withLatestFrom((action) =>\n      ~~~~~~~~~~~~~~ [${messageId}]\n        this.store.select(fromBooks.selectCollectionBookIds),\n        this.store.select(fromBooks.selectWishlist),\n      ),\n      switchMap(([action, bookCollection, wishlist]) => {\n        return of({ type: 'noop' })\n      }),\n    ),\n  )\n\n  constructor(private readonly actions$: Actions) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Actions } from '@ngrx/effects'\nimport { tapResponse } from '@ngrx/operators'\nimport { of, withLatestFrom } from 'rxjs'\n\nclass NotOk1 {\n  readonly effect: CreateEffectMetadata\n\n  constructor(actions$: Actions) {\n    this.effect = createEffect(\n      () =>\n        ({ debounce = 300 } = {}) =>\n          condition\n            ? actions$.pipe()\n            : actions$.pipe(\n                ofType(CollectionApiActions.addBookSuccess),\n                withLatestFrom(() => this.store.select(fromBooks.selectCollectionBookIds)),\n                ~~~~~~~~~~~~~~ [${messageId}]\n                switchMap(([action, bookCollection]) => {\n                  return of({ type: 'noop' })\n                }),\n              ),\n    )\n  }\n}`,\n    {\n      output: `\nimport { Actions } from '@ngrx/effects'\nimport { tapResponse, concatLatestFrom } from '@ngrx/operators'\nimport { of, withLatestFrom } from 'rxjs'\n\nclass NotOk1 {\n  readonly effect: CreateEffectMetadata\n\n  constructor(actions$: Actions) {\n    this.effect = createEffect(\n      () =>\n        ({ debounce = 300 } = {}) =>\n          condition\n            ? actions$.pipe()\n            : actions$.pipe(\n                ofType(CollectionApiActions.addBookSuccess),\n                concatLatestFrom(() => this.store.select(fromBooks.selectCollectionBookIds)),\n                switchMap(([action, bookCollection]) => {\n                  return of({ type: 'noop' })\n                }),\n              ),\n    )\n  }\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { of, withLatestFrom } from 'rxjs'\n\nclass NotOk2 {\n  effect = createEffect(() => ({ debounce = 700 } = {}) => {\n    return this.actions$.pipe(\n      ofType(ProductDetailPage.loaded),\n      concatMap((action) =>\n        of(action).pipe(withLatestFrom(this.store.select(selectProducts))),\n                        ~~~~~~~~~~~~~~ [${messageId}]\n      ),\n      mergeMapTo(of({ type: 'noop' })),\n    )\n  }, { dispatch: false })\n}`,\n    {\n      options: [{ strict: true }],\n      output: `import { concatLatestFrom } from '@ngrx/operators';\nimport { of, withLatestFrom } from 'rxjs'\n\nclass NotOk2 {\n  effect = createEffect(() => ({ debounce = 700 } = {}) => {\n    return this.actions$.pipe(\n      ofType(ProductDetailPage.loaded),\n      concatMap((action) =>\n        of(action).pipe(concatLatestFrom(() => this.store.select(selectProducts))),\n      ),\n      mergeMapTo(of({ type: 'noop' })),\n    )\n  }, { dispatch: false })\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { concatLatestFrom } from '@ngrx/operators'\nimport { of, withLatestFrom } from 'rxjs'\n\nclass NotOk3 {\n  effect = createEffect(() => {\n    return condition\n      ? this.actions$.pipe(\n          ofType(ProductDetailPage.loaded),\n          concatMap((action) =>\n            of(action).pipe(\n              withLatestFrom(this.store.select$(something), (one, other) => somethingElse()),\n              ~~~~~~~~~~~~~~ [${messageId}]\n            ),\n          ),\n          mergeMap(([action, products]) => of(products)),\n        )\n      : this.actions$.pipe()\n  })\n}`,\n    {\n      options: [{ strict: true }],\n      output: `import { map } from 'rxjs/operators';\nimport { concatLatestFrom } from '@ngrx/operators'\nimport { of, withLatestFrom } from 'rxjs'\n\nclass NotOk3 {\n  effect = createEffect(() => {\n    return condition\n      ? this.actions$.pipe(\n          ofType(ProductDetailPage.loaded),\n          concatMap((action) =>\n            of(action).pipe(\n              concatLatestFrom(() => this.store.select$(something),), map( (one, other) => somethingElse()),\n            ),\n          ),\n          mergeMap(([action, products]) => of(products)),\n        )\n      : this.actions$.pipe()\n  })\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { Actions } from '@ngrx/effects'\nimport { of, withLatestFrom } from 'rxjs'\nimport { inject } from '@angular/core'\n\nclass NotOk4 {\n  private readonly actions$ = inject(Actions);\n  effect = createEffect(() =>\n    this.actions$.pipe(\n      ofType(CollectionApiActions.addBookSuccess),\n      withLatestFrom((action) =>\n      ~~~~~~~~~~~~~~ [${messageId}]\n        this.store.select(fromBooks.selectCollectionBookIds),\n      ),\n      switchMap(([action, bookCollection]) => {\n        return of({ type: 'noop' })\n      }),\n    ),\n  )\n}`,\n    {\n      output: `import { concatLatestFrom } from '@ngrx/operators';\nimport { Actions } from '@ngrx/effects'\nimport { of, withLatestFrom } from 'rxjs'\nimport { inject } from '@angular/core'\n\nclass NotOk4 {\n  private readonly actions$ = inject(Actions);\n  effect = createEffect(() =>\n    this.actions$.pipe(\n      ofType(CollectionApiActions.addBookSuccess),\n      concatLatestFrom((action) =>\n        this.store.select(fromBooks.selectCollectionBookIds),\n      ),\n      switchMap(([action, bookCollection]) => {\n        return of({ type: 'noop' })\n      }),\n    ),\n  )\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { Actions } from '@ngrx/effects'\nimport { of, withLatestFrom } from 'rxjs'\nimport { inject } from '@angular/core'\n\nclass NotOk5 {\n  readonly effect: CreateEffectMetadata\n  readonly actions$ = inject(Actions)\n\n  constructor() {\n    this.effect = createEffect(\n      () =>\n        ({ debounce = 300 } = {}) =>\n          condition\n            ? actions$.pipe()\n            : actions$.pipe(\n                ofType(CollectionApiActions.addBookSuccess),\n                withLatestFrom(() => this.store.select(fromBooks.selectCollectionBookIds)),\n                ~~~~~~~~~~~~~~ [${messageId}]\n                switchMap(([action, bookCollection]) => {\n                  return of({ type: 'noop' })\n                }),\n              ),\n    )\n  }\n}`,\n    {\n      output: `import { concatLatestFrom } from '@ngrx/operators';\nimport { Actions } from '@ngrx/effects'\nimport { of, withLatestFrom } from 'rxjs'\nimport { inject } from '@angular/core'\n\nclass NotOk5 {\n  readonly effect: CreateEffectMetadata\n  readonly actions$ = inject(Actions)\n\n  constructor() {\n    this.effect = createEffect(\n      () =>\n        ({ debounce = 300 } = {}) =>\n          condition\n            ? actions$.pipe()\n            : actions$.pipe(\n                ofType(CollectionApiActions.addBookSuccess),\n                concatLatestFrom(() => this.store.select(fromBooks.selectCollectionBookIds)),\n                switchMap(([action, bookCollection]) => {\n                  return of({ type: 'noop' })\n                }),\n              ),\n    )\n  }\n}`,\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/signals/enforce-type-call.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport { ruleTester } from '../../utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  enforceTypeCall,\n} from '../../../src/rules/signals/enforce-type-call';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = readonly ESLintUtils.InferOptionsTypeFromRule<typeof rule>[];\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  {\n    code: `\n      import { type } from '@ngrx/signals';\n      const stateType = type<{ count: number }>();\n    `,\n  },\n  {\n    code: `\n      import { type as typeFn } from '@ngrx/signals';\n      const stateType = typeFn<{ count: number; name: string }>();\n    `,\n  },\n  {\n    code: `\n      import { type } from '@none/of/business';\n      const stateType = type<{ count: number }>;\n    `,\n  },\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  {\n    code: `\n      import { type } from '@ngrx/signals';\n      const stateType = type<{ count: number }>;\n    `,\n    output: `\n      import { type } from '@ngrx/signals';\n      const stateType = type<{ count: number }>();\n    `,\n    errors: [\n      {\n        messageId: enforceTypeCall,\n        data: { name: 'type' },\n      },\n    ],\n  },\n  {\n    code: `\n      import { type as typeFn } from '@ngrx/signals';\n      const stateType = typeFn<{ count: number }>;\n    `,\n    output: `\n      import { type as typeFn } from '@ngrx/signals';\n      const stateType = typeFn<{ count: number }>();\n    `,\n    errors: [\n      {\n        messageId: enforceTypeCall,\n        data: { name: 'typeFn' },\n      },\n    ],\n  },\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/signals/prefer-protected-state.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  preferProtectedState,\n  preferProtectedStateSuggest,\n} from '../../../src/rules/signals/prefer-protected-state';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = readonly ESLintUtils.InferOptionsTypeFromRule<typeof rule>[];\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `const mySignalStore = signalStore();`,\n  `const mySignalStore = signalStore({ protectedState: true });`,\n  `const mySignalStore = signalStore({ providedIn: 'root' });`,\n  `const mySignalStore = signalStore({ providedIn: 'root', protectedState: true });`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nconst mySignalStore = signalStore({ providedIn: 'root', protectedState: false, });\n                                                        ~~~~~~~~~~~~~~~~~~~~~ [${preferProtectedState} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: preferProtectedStateSuggest,\n          output: `\nconst mySignalStore = signalStore({ providedIn: 'root',  });`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nconst mySignalStore = signalStore({ providedIn: 'root', protectedState: false  ,  });\n                                                        ~~~~~~~~~~~~~~~~~~~~~ [${preferProtectedState} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: preferProtectedStateSuggest,\n          output: `\nconst mySignalStore = signalStore({ providedIn: 'root',   });`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nconst mySignalStore = signalStore({ protectedState: false, });\n                                    ~~~~~~~~~~~~~~~~~~~~~ [${preferProtectedState} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: preferProtectedStateSuggest,\n          output: `\nconst mySignalStore = signalStore();`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nconst mySignalStore = signalStore({ protectedState: false, providedIn: 'root' });\n                                    ~~~~~~~~~~~~~~~~~~~~~ [${preferProtectedState} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: preferProtectedStateSuggest,\n          output: `\nconst mySignalStore = signalStore({  providedIn: 'root' });`,\n        },\n      ],\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/signals/signal-state-no-arrays-at-root-level.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/signals/signal-state-no-arrays-at-root-level';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `const state = signalState({ numbers: [1, 2, 3] });`,\n  `const state = signalState({ date: new Date() });`,\n  `const state = signalState({ set: new Set() });`,\n  `const state = signalState({ map: new Map() });`,\n  `const state = state([1, 2, 3]);`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nconst state = signalState([1, 2, 3]);\n                          ~~~~~~~~~ [${messageId} { \"property\": \"Array\" }]`),\n  fromFixture(`\nclass Fixture {\n  state = signalState([{ foo: 'bar' }]);\n                      ~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Array\" }]\n}`),\n  fromFixture(`\nconst state = signalState<string[]>([]);\n                                    ~~ [${messageId} { \"property\": \"Array\" }]`),\n  fromFixture(`\nconst initialState: string[] = [];\nconst state = signalState(initialState);\n                          ~~~~~~~~~~~~ [${messageId} { \"property\": \"Array\" }]`),\n  fromFixture(`\nconst state = signalState(new Set());\n                          ~~~~~~~~~ [${messageId} { \"property\": \"Set\" }]`),\n  fromFixture(`\nconst initialState = new Set<string>();\nconst state = signalState(initialState);\n                          ~~~~~~~~~~~~ [${messageId} { \"property\": \"Set\" }]`),\n  fromFixture(`\nconst state = signalState(new Map());\n                          ~~~~~~~~~ [${messageId} { \"property\": \"Map\" }]`),\n  fromFixture(`\nconst initialState = new Map<string, number>();\nconst state = signalState(initialState);\n                          ~~~~~~~~~~~~ [${messageId} { \"property\": \"Map\" }]`),\n  fromFixture(`\nconst state = signalState(new WeakSet());\n                          ~~~~~~~~~~~~~ [${messageId} { \"property\": \"WeakSet\" }]`),\n  fromFixture(`\nconst state = signalState(new WeakMap());\n                          ~~~~~~~~~~~~~ [${messageId} { \"property\": \"WeakMap\" }]`),\n  fromFixture(`\nconst state = signalState(new Date());\n                          ~~~~~~~~~~ [${messageId} { \"property\": \"Date\" }]`),\n  fromFixture(`\nconst initialState = new Date();\nconst state = signalState(initialState);\n                          ~~~~~~~~~~~~ [${messageId} { \"property\": \"Date\" }]`),\n  fromFixture(`\nconst state = signalState(new Error());\n                          ~~~~~~~~~~~ [${messageId} { \"property\": \"Error\" }]`),\n  fromFixture(`\nconst state = signalState(new RegExp('test'));\n                          ~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"RegExp\" }]`),\n  fromFixture(`\nconst state = signalState(/test/);\n                          ~~~~~~ [${messageId} { \"property\": \"RegExp\" }]`),\n  fromFixture(`\nconst state = signalState(new ArrayBuffer(8));\n                          ~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"ArrayBuffer\" }]`),\n  fromFixture(`\nconst buffer = new ArrayBuffer(8);\nconst state = signalState(new DataView(buffer));\n                          ~~~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"DataView\" }]`),\n  fromFixture(`\nconst state = signalState(new Promise(() => {}));\n                          ~~~~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Promise\" }]`),\n  fromFixture(`\nconst state = signalState(Promise.resolve({}));\n                          ~~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Promise\" }]`),\n  fromFixture(`\nconst state = signalState(function() {});\n                          ~~~~~~~~~~~~~ [${messageId} { \"property\": \"Function\" }]`),\n  fromFixture(`\nconst state = signalState(() => {});\n                          ~~~~~~~~ [${messageId} { \"property\": \"Function\" }]`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/signals/signal-store-feature-should-use-generic-type.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/signals/signal-store-feature-should-use-generic-type';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = readonly ESLintUtils.InferOptionsTypeFromRule<typeof rule>[];\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `const withY = <Y>() => signalStoreFeature({ state: type<{ y: Y }>() }, withState({}));`,\n  `export const withY = <Y>() => signalStoreFeature(type<{ state: { y: Y } }>(), withState({}));`,\n  `const withY = <_>() => { return signalStoreFeature({ state: type<{ y: number }>() }, withState({})); }`,\n  `export const withY = <_>() => { return signalStoreFeature(type<{ state: { y: number } }>(), withState({})); }`,\n  `function withY<Y>() { return signalStoreFeature({ state: type<{ y: Y }>() }, withState({})); }`,\n  `export function withY<_>() { return signalStoreFeature(type<{ state: { y: number } }>(), withState({})); }`,\n  `function withY<_>() {\n    const feature = signalStoreFeature(type<{ state: { y: number } }>(), withState({}));\n    return feature;\n  }`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nconst withY = () => signalStoreFeature({ state: type<{ y: number }>() }, withState({}));\n                    ~~~~~~~~~~~~~~~~~~ [${messageId}]`,\n    {\n      output: `\nconst withY = <_>() => signalStoreFeature({ state: type<{ y: number }>() }, withState({}));`,\n    }\n  ),\n  fromFixture(\n    `\nconst withY = () => signalStoreFeature(type<{ state: { y: number } }>(), withState({}));\n                    ~~~~~~~~~~~~~~~~~~ [${messageId}]`,\n    {\n      output: `\nconst withY = <_>() => signalStoreFeature(type<{ state: { y: number } }>(), withState({}));`,\n    }\n  ),\n  fromFixture(\n    `\nconst withY = () => { return signalStoreFeature({ state: type<{ y: number }>() }, withState({})); }\n                             ~~~~~~~~~~~~~~~~~~ [${messageId}]`,\n    {\n      output: `\nconst withY = <_>() => { return signalStoreFeature({ state: type<{ y: number }>() }, withState({})); }`,\n    }\n  ),\n  fromFixture(\n    `\nconst withY = () => { return signalStoreFeature(type<{ state: { y: number } }>(), withState({})); }\n                             ~~~~~~~~~~~~~~~~~~ [${messageId}]`,\n    {\n      output: `\nconst withY = <_>() => { return signalStoreFeature(type<{ state: { y: number } }>(), withState({})); }`,\n    }\n  ),\n  fromFixture(\n    `\nfunction withY() { return signalStoreFeature(type<{ state: { y: number } }>(), withState({})); }\n                          ~~~~~~~~~~~~~~~~~~ [${messageId}]`,\n    {\n      output: `\nfunction withY<_>() { return signalStoreFeature(type<{ state: { y: number } }>(), withState({})); }`,\n    }\n  ),\n  fromFixture(\n    `\nfunction withY() { return signalStoreFeature({ state: type<{ y: number }>() }, withState({})); }\n                          ~~~~~~~~~~~~~~~~~~ [${messageId}]`,\n    {\n      output: `\nfunction withY<_>() { return signalStoreFeature({ state: type<{ y: number }>() }, withState({})); }`,\n    }\n  ),\n  fromFixture(\n    `\nfunction withY() {\n  const feature = signalStoreFeature({ state: type<{ y: number }>() }, withState({}));\n                  ~~~~~~~~~~~~~~~~~~ [${messageId}]\n  return feature;\n}`,\n    {\n      output: `\nfunction withY<_>() {\n  const feature = signalStoreFeature({ state: type<{ y: number }>() }, withState({}));\n  return feature;\n}`,\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/signals/with-state-no-arrays-at-root-level.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/signals/with-state-no-arrays-at-root-level';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `const store = withState({ foo: 'bar' })`,\n  `const store = withState({ date: new Date() })`,\n  `const store = withState({ set: new Set() })`,\n  `const store = withState({ map: new Map() })`,\n  `const Store = signalStore(withState(initialState));`,\n  `\n    const initialState = {};\n    const Store = signalStore(withState(initialState));\n  `,\n  `const store = withState(() => ({ foo: 'bar' }))`,\n  `const store = withState(function() { return { foo: 'bar' }; })`,\n  `\n    const initialState = { books: [] };\n    const store = withState(() => initialState);\n  `,\n  `\n    const initialState = { books: [] };\n    const store = withState(function() { return initialState; });\n  `,\n  `\n    function getState() { return { count: 0 }; }\n    const store = withState(getState);\n  `,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nconst store = withState([1, 2, 3]);\n                        ~~~~~~~~~ [${messageId} { \"property\": \"Array\" }]`),\n  fromFixture(`\nclass Fixture {\n  store = withState([{ foo: 'bar' }]);\n                    ~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Array\" }]\n}`),\n  fromFixture(`\nconst store = withState<string[]>([]);\n                                  ~~ [${messageId} { \"property\": \"Array\" }]`),\n  fromFixture(`\nconst initialState = [];\nconst store = withState(initialState);\n                        ~~~~~~~~~~~~ [${messageId} { \"property\": \"Array\" }]`),\n  fromFixture(`\nconst store = withState(new Set());\n                        ~~~~~~~~~ [${messageId} { \"property\": \"Set\" }]`),\n  fromFixture(`\nconst initialState = new Set<string>();\nconst store = withState(initialState);\n                        ~~~~~~~~~~~~ [${messageId} { \"property\": \"Set\" }]`),\n  fromFixture(`\nconst store = withState(new Map());\n                        ~~~~~~~~~ [${messageId} { \"property\": \"Map\" }]`),\n  fromFixture(`\nconst initialState = new Map<string, number>();\nconst store = withState(initialState);\n                        ~~~~~~~~~~~~ [${messageId} { \"property\": \"Map\" }]`),\n  fromFixture(`\nconst store = withState(new WeakSet());\n                        ~~~~~~~~~~~~~ [${messageId} { \"property\": \"WeakSet\" }]`),\n  fromFixture(`\nconst store = withState(new WeakMap());\n                        ~~~~~~~~~~~~~ [${messageId} { \"property\": \"WeakMap\" }]`),\n  fromFixture(`\nconst store = withState(new Date());\n                        ~~~~~~~~~~ [${messageId} { \"property\": \"Date\" }]`),\n  fromFixture(`\nconst initialState = new Date();\nconst store = withState(initialState);\n                        ~~~~~~~~~~~~ [${messageId} { \"property\": \"Date\" }]`),\n  fromFixture(`\nconst store = withState(new Error());\n                        ~~~~~~~~~~~ [${messageId} { \"property\": \"Error\" }]`),\n  fromFixture(`\nconst store = withState(new RegExp('test'));\n                        ~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"RegExp\" }]`),\n  fromFixture(`\nconst store = withState(/test/);\n                        ~~~~~~ [${messageId} { \"property\": \"RegExp\" }]`),\n  fromFixture(`\nconst store = withState(new ArrayBuffer(8));\n                        ~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"ArrayBuffer\" }]`),\n  fromFixture(`\nconst buffer = new ArrayBuffer(8);\nconst store = withState(new DataView(buffer));\n                        ~~~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"DataView\" }]`),\n  fromFixture(`\nconst store = withState(new Promise(() => {}));\n                        ~~~~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Promise\" }]`),\n  fromFixture(`\nconst store = withState(Promise.resolve({}));\n                        ~~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Promise\" }]`),\n  fromFixture(`\nconst store = withState(function() {});\n                        ~~~~~~~~~~~~~ [${messageId} { \"property\": \"Function\" }]`),\n  fromFixture(`\nconst store = withState(() => {});\n                        ~~~~~~~~ [${messageId} { \"property\": \"Function\" }]`),\n  fromFixture(`\nconst store = withState(() => () => ({ foo: 'bar' }));\n                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Function\" }]`),\n  fromFixture(`\nconst store = withState(() => [1, 2, 3]);\n                        ~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Array\" }]`),\n  fromFixture(`\nconst initialState: string[] = [];\nconst store = withState(() => initialState);\n                        ~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Array\" }]`),\n  fromFixture(`\nconst store = withState(() => new Set());\n                        ~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Set\" }]`),\n  fromFixture(`\nconst store = withState(() => new Map());\n                        ~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Map\" }]`),\n  fromFixture(`\nconst store = withState(function() { return function() { return { foo: 'bar' }; }; });\n                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId} { \"property\": \"Function\" }]`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/avoid-combining-selectors.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/store/avoid-combining-selectors';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok {\n    readonly test$ = somethingOutside();\n  }`,\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok1 {\n    readonly vm$: Observable<unknown>\n\n    constructor(store: Store) {\n      this.vm$ = store.select(selectItems)\n    }\n  }`,\n  `\n  import { Store, select } from '@ngrx/store'\n\n  class Ok2 {\n    vm$ = this.store.pipe(select(selectItems))\n\n    constructor(private store: Store) {}\n  }`,\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok3 {\n    vm$ = combineLatest(this.store$.select(selectItems), this.somethingElse())\n\n    constructor(private store$: Store) {}\n  }`,\n  `\n  import { Store } from '@ngrx/store'\n\n  @Pipe()\n  class Ok4 {\n    vm$ = combineLatest(this.somethingElse(), this.store.select(selectItems))\n\n    constructor(private readonly store: Store) {}\n  }`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok5 {\n  readonly vm$: Observable<unknown>\n  readonly store = inject(Store)\n\n  constructor() {\n    this.vm$ = store.select(selectItems)\n  }\n}`,\n  `\nimport { Store, select } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok6 {\n  readonly store = inject(Store)\n  vm$ = this.store.pipe(select(selectItems))\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok7 {\n  readonly store = inject(Store)\n  vm$ = combineLatest(this.store.select(selectItems), this.somethingElse())\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\n@Pipe()\nclass Ok8 {\n  private readonly store = inject(Store)\n  vm$ = combineLatest(this.somethingElse(), this.store.select(selectItems))\n}`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  readonly vm$: Observable<unknown>\n\n  constructor(store: Store, private store2: Store) {\n    this.vm$ = combineLatest(\n      store.select(selectItems),\n      store.select(selectOtherItems),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n      this.store2.select(selectOtherItems),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    )\n  }\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOkWithArrays {\n  readonly vm$: Observable<unknown>\n\n  constructor(store: Store, private store2: Store) {\n    this.vm$ = combineLatest([\n      store.select(selectItems),\n      store.select(selectOtherItems),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n      this.store2.select(selectOtherItems),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    ])\n  }\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  vm$ = combineLatest(\n    this.store.pipe(select(selectItems)),\n    this.store.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  )\n\n  constructor(private store: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOkWithArray {\n  vm$ = combineLatest([\n    this.store.pipe(select(selectItems)),\n    this.store.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  ])\n\n  constructor(private store: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  vm$ = combineLatest(\n    this.customName.select(selectItems),\n    this.customName.select(selectOtherItems),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.somethingElse(),\n  )\n\n  constructor(private customName: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\n@Pipe()\nclass NotOk3 {\n  vm$ = combineLatest(\n    this.customName.select(selectItems),\n    this.customName.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.somethingElse(),\n  )\n\n  constructor(private readonly customName: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk4 {\n  vm$ = combineLatest(\n    this.store.pipe(select(selectItems)),\n    this.store.select(selectOtherItems),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  )\n\n  constructor(private store: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk5 {\n  vm$ = combineLatest(\n    this.store.select(selectItems),\n    this.store.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.store2.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  )\n\n  constructor(private store: Store, private store2: Store) {}\n}`\n  ),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  readonly vm$: Observable<unknown>\n  readonly store = inject(Store)\n  readonly store2 = inject(Store)\n\n  constructor() {\n    this.vm$ = combineLatest(\n      store.select(selectItems),\n      store.select(selectOtherItems),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n      this.store2.select(selectOtherItems),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    )\n  }\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOkWithArrays1 {\n  readonly vm$: Observable<unknown>\n  readonly store = inject(Store)\n  readonly store2 = inject(Store)\n\n  constructor() {\n    this.vm$ = combineLatest([\n      store.select(selectItems),\n      store.select(selectOtherItems),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n      this.store2.select(selectOtherItems),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    ])\n  }\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\n  private readonly store = inject(Store)\n  vm$ = combineLatest(\n    this.store.pipe(select(selectItems)),\n    this.store.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  )\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOkWithArray1 {\n  private readonly store = inject(Store)\n  vm$ = combineLatest([\n    this.store.pipe(select(selectItems)),\n    this.store.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  ])\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk8 {\n  private readonly customName = inject(Store)\n  vm$ = combineLatest(\n    this.customName.select(selectItems),\n    this.customName.select(selectOtherItems),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.somethingElse(),\n  )\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\n@Pipe()\nclass NotOk9 {\n  private readonly customName = inject(Store)\n  vm$ = combineLatest(\n    this.customName.select(selectItems),\n    this.customName.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.somethingElse(),\n  )\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk10 {\n  private readonly store = inject(Store)\n  vm$ = combineLatest(\n    this.store.pipe(select(selectItems)),\n    this.store.select(selectOtherItems),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  )\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk11 {\n  private readonly store = inject(Store)\n  private readonly store2 = inject(Store)\n  vm$ = combineLatest(\n    this.store.select(selectItems),\n    this.store.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.store2.pipe(select(selectOtherItems)),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  )\n}`\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/avoid-dispatching-multiple-actions-sequentially.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/store/avoid-dispatching-multiple-actions-sequentially';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok {\n  ngOnInit() {\n    this.dispatch(UserActions.add())\n  }\n  }`,\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok1 {\n  constructor(private store: Store) {}\n\n  ping() {\n    this.store.dispatch(GameActions.ping())\n  }\n  }`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/47\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok2 {\n  constructor(private store: Store) {}\n\n  pingPong() {\n    if (condition) {\n      this.store.dispatch(GameActions.ping())\n    } else {\n      this.store.dispatch(GameActions.pong())\n    }\n  }\n  }`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/86\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok3 {\n  constructor(private store: Store) {}\n\n  ngOnInit() {\n    this.store.subscribe(() => {\n      this.store.dispatch(one());\n    });\n    this.store.subscribe(() => {\n      this.store.dispatch(anotherOne());\n    });\n  }\n  }`,\n  // https://github.com/ngrx/platform/issues/3513\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok4 {\n  constructor(private store: Store) {}\n\n  ngOnInit() {\n    this.store.dispatch(one());\n\n    this.store.subscribe(() => {\n      this.store.dispatch(anotherOne());\n    });\n  }\n  }`,\n  // https://github.com/ngrx/platform/issues/3513\n  `\n  import { Store } from '@ngrx/store'\n\n  class Ok5 {\n  constructor(private store: Store) {}\n\n  ngOnInit() {\n    this.store.dispatch(anotherOne());\n\n    this.store.subscribe(() => {\n      this.store.dispatch(one());\n    });\n  }\n  }`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok6 {\nprivate readonly store = inject(Store)\n\nping() {\n  this.store.dispatch(GameActions.ping())\n}\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok7 {\nprivate readonly store = inject(Store)\n\npingPong() {\n  if (condition) {\n    this.store.dispatch(GameActions.ping())\n  } else {\n    this.store.dispatch(GameActions.pong())\n  }\n}\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok8 {\nprivate readonly store = inject(Store)\n\nngOnInit() {\n  this.store.subscribe(() => {\n    this.store.dispatch(one());\n  });\n  this.store.subscribe(() => {\n    this.store.dispatch(anotherOne());\n  });\n}\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok9 {\nprivate readonly store = inject(Store)\n\nngOnInit() {\n  this.store.dispatch(one());\n\n  this.store.subscribe(() => {\n    this.store.dispatch(anotherOne());\n  });\n}\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok10 {\nprivate readonly store = inject(Store)\n\nngOnInit() {\n  this.store.dispatch(anotherOne());\n\n  this.store.subscribe(() => {\n    this.store.dispatch(one());\n  });\n}\n}`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\n  import { Store } from '@ngrx/store'\n\n  class NotOk {\n  constructor(private store: Store) {}\n\n  pingPong() {\n    this.store.dispatch(GameActions.ping())\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.store.dispatch(GameActions.pong())\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n  }`),\n  fromFixture(`\n  import { Store } from '@ngrx/store'\n\n  class NotOk1 {\n  constructor(store: Store, private readonly store$: Store) {\n    store.dispatch(GameActions.ping())\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.ping();\n    this.name = 'Bob'\n    this.store$.dispatch(GameActions.pong())\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n  }`),\n  fromFixture(`\n  import { Store } from '@ngrx/store'\n\n  class NotOk2 {\n  constructor(private store: Store) {}\n\n  pingPongPong() {\n    this.store.dispatch(GameActions.ping())\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.store.dispatch(GameActions.pong())\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.store.dispatch(GameActions.pong())\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n  }`),\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/44\n  fromFixture(`\n  import { Store } from '@ngrx/store'\n\n  class NotOk3 {\n  constructor(private customName: Store) {}\n\n  ngOnInit() {\n    customName.dispatch()\n  }\n\n  pingPong() {\n    this.customName.dispatch(GameActions.ping())\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.customName.dispatch(GameActions.pong())\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n  }`),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk4 {\nprivate readonly store = inject(Store)\n\npingPong() {\n  this.store.dispatch(GameActions.ping())\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  this.store.dispatch(GameActions.pong())\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n}\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk5 {\nprivate readonly store = inject(Store)\nprivate readonly store$ = inject(Store)\nconstructor() {\n  store.dispatch(GameActions.ping())\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  this.ping();\n  this.name = 'Bob'\n  this.store$.dispatch(GameActions.pong())\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n}\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\nprivate readonly store = inject(Store)\n\npingPongPong() {\n  this.store.dispatch(GameActions.ping())\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  this.store.dispatch(GameActions.pong())\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  this.store.dispatch(GameActions.pong())\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n}\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\nprivate readonly customName = inject(Store)\n\nngOnInit() {\n  customName.dispatch()\n}\n\npingPong() {\n  this.customName.dispatch(GameActions.ping())\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  this.customName.dispatch(GameActions.pong())\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n}\n}`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/avoid-duplicate-actions-in-reducer.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  avoidDuplicateActionsInReducer,\n  avoidDuplicateActionsInReducerSuggest,\n} from '../../../src/rules/store/avoid-duplicate-actions-in-reducer';\nimport { ruleTester } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nexport const reducer = createReducer(\n  {},\n  on(abc, state => state),\n  on(def, state => state),\n  on(ghi, state => state),\n)`,\n  `\nexport const reducer = createReducer(\n  {},\n  on(abc, state => state),\n  on(def, state => state),\n  on(ghi, state => state),\n)\n\nexport const reducerTwo = createReducer(\n  {},\n  on(abc, state => state),\n  on(def, state => state),\n  on(ghi, state => state),\n)`,\n  // does not crash when no arguments present\n  `\nexport const reducer = createReducer(\n  {},\n  on(),\n)`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  {\n    code: `\nexport const reducer = createReducer(\n  {},\n  on(abc, state => state),\n  on(def, state => state),\n  on(abc, (state, props) => state),\n)`,\n    errors: [\n      {\n        column: 6,\n        endColumn: 9,\n        line: 4,\n        messageId: avoidDuplicateActionsInReducer,\n        suggestions: [\n          {\n            messageId: avoidDuplicateActionsInReducerSuggest,\n            data: {\n              actionName: 'abc',\n            },\n            output: `\nexport const reducer = createReducer(\n  {},\n  \n  on(def, state => state),\n  on(abc, (state, props) => state),\n)`,\n          },\n        ],\n      },\n      {\n        column: 6,\n        endColumn: 9,\n        line: 6,\n        messageId: avoidDuplicateActionsInReducer,\n        suggestions: [\n          {\n            messageId: avoidDuplicateActionsInReducerSuggest,\n            data: {\n              actionName: 'abc',\n            },\n            output: `\nexport const reducer = createReducer(\n  {},\n  on(abc, state => state),\n  on(def, state => state),\n  \n)`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `\nexport const reducer = createReducer(\n  {},\n  on(foo, state => state),\n  on(foo2, state => state),\n  on(foo, (state, props) => state),\n  on(foo, state => state),\n  on(foo3, state => state),\n)`,\n    errors: [\n      {\n        column: 6,\n        endColumn: 9,\n        line: 4,\n        messageId: avoidDuplicateActionsInReducer,\n        suggestions: [\n          {\n            messageId: avoidDuplicateActionsInReducerSuggest,\n            data: {\n              actionName: 'foo',\n            },\n            output: `\nexport const reducer = createReducer(\n  {},\n  \n  on(foo2, state => state),\n  on(foo, (state, props) => state),\n  on(foo, state => state),\n  on(foo3, state => state),\n)`,\n          },\n        ],\n      },\n      {\n        column: 6,\n        endColumn: 9,\n        line: 6,\n        messageId: avoidDuplicateActionsInReducer,\n        suggestions: [\n          {\n            messageId: avoidDuplicateActionsInReducerSuggest,\n            data: {\n              actionName: 'foo',\n            },\n            output: `\nexport const reducer = createReducer(\n  {},\n  on(foo, state => state),\n  on(foo2, state => state),\n  \n  on(foo, state => state),\n  on(foo3, state => state),\n)`,\n          },\n        ],\n      },\n      {\n        column: 6,\n        endColumn: 9,\n        line: 7,\n        messageId: avoidDuplicateActionsInReducer,\n        suggestions: [\n          {\n            messageId: avoidDuplicateActionsInReducerSuggest,\n            data: {\n              actionName: 'foo',\n            },\n            output: `\nexport const reducer = createReducer(\n  {},\n  on(foo, state => state),\n  on(foo2, state => state),\n  on(foo, (state, props) => state),\n  \n  on(foo3, state => state),\n)`,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/avoid-mapping-selectors.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/store/avoid-mapping-selectors';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok {\n  readonly test$ = somethingOutside();\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok1 {\n  foo$ = this.store.select(selectItems)\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok2 {\n  foo$ = this.store.select(selectItems).pipe(filter(x => !!x))\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok3 {\n  foo$ = this.store.pipe(select(selectItems))\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok4 {\n  foo$ = this.store.pipe(select(selectItems)).pipe(filter(x => !!x))\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok5 {\n  loginUserSuccess$ = createEffect(() => {\n      return this.actions$.pipe(\n        ofType(AuthActions.loginUserSuccess),\n        concatLatestFrom(action => this.store.select(startUrl)),\n        map(([action, url]) => AuthActions.setStartUrl({ data: '' })),\n      )\n    }\n  )\n\n  constructor(private store: Store) {}\n}`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/174\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok6 {\n  loginUserSuccess$ = combineLatest([\n    this.store.select(selectAuthorizations), this.hasAuthorization$\n  ]).pipe(map((val) => !isEmpty(intersection(val))))\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok7 {\n  readonly customers$ = this.store$.select(({ customers }) => customers).pipe()\n  readonly users$ = this.store$\n    .select('users')\n    .pipe(switchMap(() => of(items.map(parseItem))))\n\n  constructor(private readonly store$: Store) {}\n\n  ngOnInit() {\n    this.store$.pipe()\n  }\n}\n`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/282\n  `\nimport { Store } from '@ngrx/store'\n\nexport class CollectionService {\n\n  constructor(\n    private service: Service,\n    private store: Store\n  ) {\n  }\n\n  getEntities$(): Observable<Entity[]> {\n    return this.store.select(selectUser).pipe(\n      switchMap(user => this.service.getCollection(user).pipe(\n        map(transformCollection)\n      )),\n    );\n  }\n}\n`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/285\n  `\nimport { Store } from '@ngrx/store'\n\nclass OkBecauseOfThis {\n  storeSelect$ = this.store.select(selectIsAuthenticated).pipe(\n      take(1),\n      map((isAuthenticated: boolean) => (isAuthenticated ? true : this.router.parseUrl('/login'))),\n  )\n\n  pipeSelect$ = this.store.pipe(\n      select(selectIsAuthenticated),\n      take(1),\n      map((isAuthenticated: boolean) => (isAuthenticated ? true : this.router.parseUrl('/login'))),\n  )\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass OkBecauseOfEffect {\n  storeSelect$ = createEffect(() => {\n    return this.store.select(selectObj).pipe(\n      map((obj) => obj.prop),\n      distinctUntilChanged(),\n      map(() => anAction())\n    )\n  })\n\n  pipeSelect$ = createEffect(() => {\n    return this.store.pipe(\n      select(selectObj),\n      map((obj) => obj.prop),\n      distinctUntilChanged(),\n      map(() => anAction())\n    )\n  })\n\n  constructor(private store: Store) {}\n}`,\n  // https://github.com/ngrx/platform/issues/3511\n  `\nimport { Store } from '@ngrx/store'\n\nclass OkBecauseOfEffect {\n  storeSelect$ = createEffect(() => {\n    return this.store.select(selectObj).pipe(\n      switchMap(() => this.service.something()),\n      map((obj) => obj.prop),\n    )\n  })\n\n  pipeSelect$ = createEffect(() => {\n    return this.store.pipe(\n      select(selectObj),\n      exhaustMap(() => this.service.something()),\n      map((obj) => obj.prop),\n    )\n  })\n\n  somethingElse$ = createEffect(() => {\n    return this.store.pipe(\n      select(selectObj),\n      customOperator(() => this.prop),\n      map((obj) => obj.prop),\n    )\n  })\n\n  constructor(private store: Store, private service: Service) {}\n}`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  vm$ = this.store\n    .select(selectItems)\n    .pipe(map((item) => item.select()))\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n\n  constructor(private store: Store) {}\n}\n`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  vm$ = this.customStore.select(selectItems).pipe(\n    filter((x) => !!x),\n    map(getName),\n    ~~~~~~~~~~~~ [${messageId}]\n  )\n\n  constructor(private customStore: Store) {}\n}\n`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  readonly vm$ = this.store.pipe(\n    select(selectItems),\n    map((item) => ({ name: item.name, ...item.pipe() })),\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  )\n\n  constructor(private readonly store: Store) {}\n}\n`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  readonly test$: Observable<unknown>\n  readonly vm$: Observable<Name>\n\n  constructor(store$: Store, private readonly store: Store) {\n    this.vm$ = store$.pipe(\n      select(selectItems),\n      filter(Boolean),\n      map(({ name }) => ({ name })),\n      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    )\n  }\n\n  buildSomething() {\n    this.test$ = this.store\n    .select(selectItems)\n    .pipe(map((item) => item.select()))\n          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}\n`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/good-action-hygiene.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, { messageId } from '../../../src/rules/store/good-action-hygiene';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `export const loadCustomer = createAction('[Customer Page] Load Customer')`,\n  `export const loadCustomerSuccess = createAction('[Customer API] Load Customer Success', props<{ customer: Customer }>())`,\n  `export const loadCustomerFail = createAction('[Customer API] Load Customer Fail', (error: string) => ({ error, timestamp: +Date.now() }))`,\n  `export const computed = createAction(iDoNotCrash)`,\n  `export const withIncorrectFunction = createActionType('Just testing')`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\n        export const loadCustomer = createAction('Load Customer')\n                                                 ~~~~~~~~~~~~~~~ [${messageId} { \"actionType\": \"Load Customer\" }]\n    `\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/no-multiple-global-stores.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  noMultipleGlobalStores,\n  noMultipleGlobalStoresSuggest,\n} from '../../../src/rules/store/no-multiple-global-stores';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nclass Ok {}`,\n  `\nclass Ok1 {\n  constructor() {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok2 {\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok3 {\n  constructor(store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok4 {\n  constructor(private store: Store, data: Service) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok5 {\n  constructor(private store: Store) {}\n}\n\nclass Ok6 {\n  constructor(private readonly store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok7 {\n  constructor(store: Store, apiService: ApiService) {}\n}\n\nclass Ok8 {\n  constructor(public store$: Store) {}\n}`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass ShouldNotBreakLaterReports {\n  constructor(store: Store, apiService: ApiService) {}\n}\n\nclass ShouldNotBreakLaterReports1 {\n  constructor(public store$: Store) {}\n}\n\nclass NotOk {\n  constructor(store: Store, store2: Store) {}\n              ~~~~~~~~~~~~ [${noMultipleGlobalStores} suggest 0]\n                            ~~~~~~~~~~~~~ [${noMultipleGlobalStores} suggest 1]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noMultipleGlobalStoresSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass ShouldNotBreakLaterReports {\n  constructor(store: Store, apiService: ApiService) {}\n}\n\nclass ShouldNotBreakLaterReports1 {\n  constructor(public store$: Store) {}\n}\n\nclass NotOk {\n  constructor( store2: Store) {}\n}`,\n        },\n        {\n          messageId: noMultipleGlobalStoresSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass ShouldNotBreakLaterReports {\n  constructor(store: Store, apiService: ApiService) {}\n}\n\nclass ShouldNotBreakLaterReports1 {\n  constructor(public store$: Store) {}\n}\n\nclass NotOk {\n  constructor(store: Store, ) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  constructor(store: Store /* first store */, private readonly actions$: Actions, private store2: Store, b: B) {}\n              ~~~~~~~~~~~~ [${noMultipleGlobalStores} suggest 0]\n                                                                                  ~~~~~~~~~~~~~~~~~~~~~ [${noMultipleGlobalStores} suggest 1]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noMultipleGlobalStoresSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  constructor( /* first store */ private readonly actions$: Actions, private store2: Store, b: B) {}\n}`,\n        },\n        {\n          messageId: noMultipleGlobalStoresSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  constructor(store: Store /* first store */, private readonly actions$: Actions,  b: B) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(\n    a: A,\n    store: Store,// a comment\n    ~~~~~~~~~~~~ [${noMultipleGlobalStores} suggest 0]\n    private readonly actions$: Actions,\n    private store2: Store,\n    ~~~~~~~~~~~~~~~~~~~~~ [${noMultipleGlobalStores} suggest 1]\n    private readonly store3: Store,\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${noMultipleGlobalStores} suggest 2]\n  ) {}\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noMultipleGlobalStoresSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(\n    a: A,\n    // a comment\n    private readonly actions$: Actions,\n    private store2: Store,\n    private readonly store3: Store,\n  ) {}\n}`,\n        },\n        {\n          messageId: noMultipleGlobalStoresSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(\n    a: A,\n    store: Store,// a comment\n    private readonly actions$: Actions,\n    \\n    private readonly store3: Store,\n  ) {}\n}`,\n        },\n        {\n          messageId: noMultipleGlobalStoresSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(\n    a: A,\n    store: Store,// a comment\n    private readonly actions$: Actions,\n    private store2: Store,\n    \\n  ) {}\n}`,\n        },\n      ],\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/no-reducer-in-key-names.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  noReducerInKeyNames,\n  noReducerInKeyNamesSuggest,\n} from '../../../src/rules/store/no-reducer-in-key-names';\nimport { ruleTester } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n@NgModule({\n  imports: [\n    StoreModule.forRoot({\n      foo,\n      persons: personsReducer,\n      'people': peopleReducer,\n    }),\n  ],\n})\nexport class AppModule {}`,\n  `\n  @NgModule({\n    imports: [\n      StoreModule.forFeature({\n        foo,\n        persons: personsReducer,\n        'people': peopleReducer,\n      }),\n    ],\n  })\n  export class AppModule {}`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/91\n  `\n@NgModule({\n  imports: [\n    StoreModule.forRoot(reducers, {metaReducers}),\n  ],\n})\nexport class AppModule {}`,\n  `\nexport const reducers: ActionReducerMap<AppState> = {\n  foo,\n  persons: personsReducer,\n  'people': peopleReducer,\n};`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  {\n    code: `\n@NgModule({\n  imports: [\n    StoreModule.forRoot({\n      feeReducer,\n    }),\n  ],\n})\nexport class AppModule {}`,\n    errors: [\n      {\n        column: 7,\n        endColumn: 17,\n        line: 5,\n        messageId: noReducerInKeyNames,\n        suggestions: [\n          {\n            messageId: noReducerInKeyNamesSuggest,\n            output: `\n@NgModule({\n  imports: [\n    StoreModule.forRoot({\n      fee,\n    }),\n  ],\n})\nexport class AppModule {}`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `\n@NgModule({\n  imports: [\n    StoreModule.forFeature({\n      'foo-reducer': foo,\n      FoeReducer: FoeReducer,\n    }),\n  ],\n})\nexport class AppModule {}`,\n    errors: [\n      {\n        column: 7,\n        endColumn: 20,\n        line: 5,\n        messageId: noReducerInKeyNames,\n        suggestions: [\n          {\n            messageId: noReducerInKeyNamesSuggest,\n            output: `\n@NgModule({\n  imports: [\n    StoreModule.forFeature({\n      'foo-': foo,\n      FoeReducer: FoeReducer,\n    }),\n  ],\n})\nexport class AppModule {}`,\n          },\n        ],\n      },\n      {\n        column: 7,\n        endColumn: 17,\n        line: 6,\n        messageId: noReducerInKeyNames,\n        suggestions: [\n          {\n            messageId: noReducerInKeyNamesSuggest,\n            output: `\n@NgModule({\n  imports: [\n    StoreModule.forFeature({\n      'foo-reducer': foo,\n      Foe: FoeReducer,\n    }),\n  ],\n})\nexport class AppModule {}`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `\nexport const reducers: ActionReducerMap<AppState> = {\n  feeReducer,\n  'fieReducer': fie,\n  ['fooReducerName']: foo,\n  [\\`ReducerFoe\\`]: FoeReducer,\n};`,\n    errors: [\n      {\n        column: 3,\n        endColumn: 13,\n        line: 3,\n        messageId: noReducerInKeyNames,\n        suggestions: [\n          {\n            messageId: noReducerInKeyNamesSuggest,\n            output: `\nexport const reducers: ActionReducerMap<AppState> = {\n  fee,\n  'fieReducer': fie,\n  ['fooReducerName']: foo,\n  [\\`ReducerFoe\\`]: FoeReducer,\n};`,\n          },\n        ],\n      },\n      {\n        column: 3,\n        endColumn: 15,\n        line: 4,\n        messageId: noReducerInKeyNames,\n        suggestions: [\n          {\n            messageId: noReducerInKeyNamesSuggest,\n            output: `\nexport const reducers: ActionReducerMap<AppState> = {\n  feeReducer,\n  'fie': fie,\n  ['fooReducerName']: foo,\n  [\\`ReducerFoe\\`]: FoeReducer,\n};`,\n          },\n        ],\n      },\n      {\n        column: 4,\n        endColumn: 20,\n        line: 5,\n        messageId: noReducerInKeyNames,\n        suggestions: [\n          {\n            messageId: noReducerInKeyNamesSuggest,\n            output: `\nexport const reducers: ActionReducerMap<AppState> = {\n  feeReducer,\n  'fieReducer': fie,\n  ['fooName']: foo,\n  [\\`ReducerFoe\\`]: FoeReducer,\n};`,\n          },\n        ],\n      },\n      {\n        column: 4,\n        endColumn: 16,\n        line: 6,\n        messageId: noReducerInKeyNames,\n        suggestions: [\n          {\n            messageId: noReducerInKeyNamesSuggest,\n            output: `\nexport const reducers: ActionReducerMap<AppState> = {\n  feeReducer,\n  'fieReducer': fie,\n  ['fooReducerName']: foo,\n  [\\`Foe\\`]: FoeReducer,\n};`,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/no-store-subscription.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/store/no-store-subscription';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok {\n  readonly test$ = somethingOutside();\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok1 {\n  vm$ = this.store.select(selectItems)\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok2 {\n  vm$ = this.store.pipe(select(selectItems))\n\n  constructor(private store: Store) {}\n}`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/175\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok3 {\n  readonly formControlChangesSubscription$ = this.formCtrlOrg.valueChanges\n    .pipe(takeUntil(this.drop))\n    .subscribe((orgName) => {\n      this.store.dispatch(UserPageActions.checkOrgNameInput({ orgName }))\n    })\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok4 {\n  readonly items$: Observable<readonly Item[]>\n  readonly metrics$: Observable<Metric>\n\n  constructor(store: Store) {\n    this.items$ = store.pipe(select(selectItems))\n    this.metrics$ = store.select(selectMetrics)\n  }\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok5 {\n  readonly items$: Observable<readonly Item[]>\n  readonly metrics$: Observable<Metric>\n  readonly store = inject(Store)\n\n  constructor() {\n    this.items$ = store.pipe(select(selectItems))\n    this.metrics$ = store.select(selectMetrics)\n  }\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from 'some-other-package'\n\nclass Ok6 {\n  readonly items$: Observable<readonly Item[]>\n  readonly metrics$: Observable<Metric>\n  readonly store = inject(Store)\n\n  constructor() {\n    store.pipe(select(selectItems)).subscribe()\n  }\n}`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  constructor(store: Store) {\n    store.subscribe()\n          ~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  constructor(store: Store) {\n    store.pipe(map(selectItems)).subscribe()\n                                 ~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  sub = this.store.subscribe()\n                   ~~~~~~~~~ [${messageId}]\n\n  constructor(private store: Store) {}\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  sub = this.store.select(selectItems).subscribe()\n                                       ~~~~~~~~~ [${messageId}]\n\n  constructor(private store: Store) {}\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk4 {\n  sub = this.store.pipe(select(selectItems)).subscribe()\n                                             ~~~~~~~~~ [${messageId}]\n\n  constructor(private store: Store) {}\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk5 {\n  constructor(private store: Store) {}\n\n  ngOnInit() {\n    this.store.select(selectItems).subscribe()\n                                   ~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk6 {\n  items: readonly Item[]\n\n  constructor(private store: Store) {}\n\n  ngOnInit() {\n    this.store.pipe(select(selectItems)).subscribe((items) => {\n                                         ~~~~~~~~~ [${messageId}]\n      this.items = items\n    })\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk7 {\n  readonly items: readonly Item[]\n\n  constructor(store: Store) {\n    store.pipe(select(selectItems)).subscribe((items) => this.items = items)\n                                    ~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk8 {\n  readonly control = new FormControl()\n\n  constructor(store: Store) {\n    this.control.valueChanges.subscribe(() => {\n      store.pipe(select(selectItems)).subscribe()\n                                      ~~~~~~~~~ [${messageId}]\n    })\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk9 {\n  readonly control = new FormControl()\n  store = inject(Store)\n\n  constructor() {\n    this.control.valueChanges.subscribe(() => {\n      store.pipe(select(selectItems)).subscribe()\n                                      ~~~~~~~~~ [${messageId}]\n    })\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk10 {\n  readonly items$: Observable<readonly Item[]>\n  readonly metrics$: Observable<Metric>\n  readonly store = inject(Store)\n\n  constructor() {\n    store.pipe(select(selectItems)).subscribe()\n                                    ~~~~~~~~~ [${messageId}]\n  }\n}`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/no-typed-global-store.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  noTypedStore,\n  noTypedStoreSuggest,\n} from '../../../src/rules/store/no-typed-global-store';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\n\nexport class Ok {\n  constructor(store: Store) {}\n}`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  // https://github.com/ngrx/platform/issues/3950\n  `\nimport { inject } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\nexport class AppComponent {\n  store = inject(Store);\n  otherName = inject(Store);\n}`,\n  `\nimport { somethingElse } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\nexport class AppComponent {\n  store = somethingElse(Store<{}>);\n}\n`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  constructor(store: Store<PersonsState>) {}\n                          ~~~~~~~~~~~~~~ [${noTypedStore} suggest]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noTypedStoreSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  constructor(store: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  constructor(cdr: ChangeDetectorRef, private store: Store<CustomersState>) {}\n                                                          ~~~~~~~~~~~~~~~~ [${noTypedStore} suggest]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noTypedStoreSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  constructor(cdr: ChangeDetectorRef, private store: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(private readonly store: Store<any>, private personsService: PersonsService) {}\n                                           ~~~~~ [${noTypedStore} suggest]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noTypedStoreSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(private readonly store: Store, private personsService: PersonsService) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  constructor(store: Store<{}>, private customStore: Store<object>) {}\n                          ~~~~ [${noTypedStore} suggest 0]\n                                                          ~~~~~~~~ [${noTypedStore} suggest 1]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noTypedStoreSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  constructor(store: Store, private customStore: Store<object>) {}\n}`,\n        },\n        {\n          messageId: noTypedStoreSuggest,\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  constructor(store: Store<{}>, private customStore: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { inject } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\nexport class NotOk4 {\n  store = inject(Store<{}>);\n                      ~~~~ [${noTypedStore} suggest]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: noTypedStoreSuggest,\n          output: `\nimport { inject } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\nexport class NotOk4 {\n  store = inject(Store);\n}`,\n        },\n      ],\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/on-function-explicit-return-type.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  onFunctionExplicitReturnType,\n  onFunctionExplicitReturnTypeSuggest,\n} from '../../../src/rules/store/on-function-explicit-return-type';\nimport { ruleTester } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nconst reducer = createReducer(\n  initialState,\n  on(\n    increment,\n    (s): State => ({\n      ...s,\n      counter: s.counter + 1,\n    }),\n  ),\n)`,\n  `\nconst reducer = createReducer(\n  initialState,\n  on(increment, incrementFunc),\n  on(increment, (s): State => incrementFunc(s)),\n)`,\n  `\nconst reducer = createReducer(\n  initialState,\n  on(\n    increment,\n    produce((draft: State, action) => {\n        draft.counter++;\n    }),\n  ),\n)`,\n  // https://github.com/timdeschryver/ngrx-tslint-rules/pull/37\n  `\nconst reducer = createReducer(\n  on(increment, (s): State => ({\n    ...s,\n    counter: (s => s.counter + 1)(s),\n  })),\n)`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  {\n    code: `\nconst reducer = createReducer(\n  initialState,\n  on(increment, s => s),\n)`,\n    errors: [\n      {\n        column: 17,\n        endColumn: 23,\n        line: 4,\n        messageId: onFunctionExplicitReturnType,\n        suggestions: [\n          {\n            messageId: onFunctionExplicitReturnTypeSuggest,\n            output: `\nconst reducer = createReducer(\n  initialState,\n  on(increment, (s): State => s),\n)`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `\nconst reducer = createReducer(\n  initialState,\n  on(increment, s => ({ ...s, counter: s.counter + 1 })),\n)`,\n    errors: [\n      {\n        column: 17,\n        endColumn: 56,\n        line: 4,\n        messageId: onFunctionExplicitReturnType,\n        suggestions: [\n          {\n            messageId: onFunctionExplicitReturnTypeSuggest,\n            output: `\nconst reducer = createReducer(\n  initialState,\n  on(increment, (s): State => ({ ...s, counter: s.counter + 1 })),\n)`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `\nconst reducer = createReducer(\n  initialState,\n  on(increase, (s, action) => ({ ...s, counter: s.counter + action.value })),\n)`,\n    errors: [\n      {\n        column: 16,\n        endColumn: 76,\n        line: 4,\n        messageId: onFunctionExplicitReturnType,\n        suggestions: [\n          {\n            messageId: onFunctionExplicitReturnTypeSuggest,\n            output: `\nconst reducer = createReducer(\n  initialState,\n  on(increase, (s, action): State => ({ ...s, counter: s.counter + action.value })),\n)`,\n          },\n        ],\n      },\n    ],\n  },\n\n  {\n    code: `\nconst reducer = createReducer(\n  initialState,\n  on(increase, (s, { value }) =>   (   { ...s, counter: s.counter + value   }  )   ),\n)`,\n    errors: [\n      {\n        column: 16,\n        endColumn: 81,\n        line: 4,\n        messageId: onFunctionExplicitReturnType,\n        suggestions: [\n          {\n            messageId: onFunctionExplicitReturnTypeSuggest,\n            output: `\nconst reducer = createReducer(\n  initialState,\n  on(increase, (s, { value }): State =>   (   { ...s, counter: s.counter + value   }  )   ),\n)`,\n          },\n        ],\n      },\n    ],\n  },\n\n  {\n    code: `\nconst reducer = createReducer(\n  initialState,\n  on(reset, () =>   initialState  ),\n)`,\n    errors: [\n      {\n        column: 13,\n        endColumn: 33,\n        line: 4,\n        messageId: onFunctionExplicitReturnType,\n        suggestions: [\n          {\n            messageId: onFunctionExplicitReturnTypeSuggest,\n            output: `\nconst reducer = createReducer(\n  initialState,\n  on(reset, (): State =>   initialState  ),\n)`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `\nconst reducer = createReducer(\n  initialState,\n  on(reset, s => foo(s)),\n)`,\n    errors: [\n      {\n        column: 13,\n        endColumn: 24,\n        line: 4,\n        messageId: onFunctionExplicitReturnType,\n        suggestions: [\n          {\n            messageId: onFunctionExplicitReturnTypeSuggest,\n            output: `\nconst reducer = createReducer(\n  initialState,\n  on(reset, (s): State => foo(s)),\n)`,\n          },\n        ],\n      },\n    ],\n  },\n  // https://github.com/ngrx/platform/issues/4901\n  {\n    code: `\nconst reducer = createReducer(\n  initialState,\n  on(reset, s => ({ ...s, counter: Number(1) })),\n)`,\n    errors: [\n      {\n        column: 13,\n        endColumn: 48,\n        line: 4,\n        messageId: onFunctionExplicitReturnType,\n        suggestions: [\n          {\n            messageId: onFunctionExplicitReturnTypeSuggest,\n            output: `\nconst reducer = createReducer(\n  initialState,\n  on(reset, (s): State => ({ ...s, counter: Number(1) })),\n)`,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/prefer-action-creator-in-dispatch.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/store/prefer-action-creator-in-dispatch';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nclass Ok {\n  readonly viewModel$ = somethingElse()\n\n  constructor(private readonly appFacade: AppFacade) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok1 {\n  constructor(store$: Store) {\n    store$.dispatch(action)\n  }\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok2 {\n  constructor(private readonly store$: Store) {\n    this.store$.dispatch(BookActions.load())\n  }\n}`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/255\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok3 {\n  constructor(private readonly store: Store) {\n    this.store.dispatch(login({ payload }));\n    this.store.dispatch(AuthActions.dispatch({ type: 'SUCCESS' }));\n    nonStore.dispatch(AuthActions.dispatch({ type: 'FAIL' }));\n  }\n}`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok4 {\n  store$ = inject(Store)\n\n  constructor() {\n    this.store$.dispatch(action)\n  }\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok5 {\n  private readonly store$ = inject(Store)\n\n  constructor() {\n    this.store$.dispatch(BookActions.load())\n  }\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok6 {\n  private readonly store = inject(Store)\n\n  constructor() {\n    this.store.dispatch(login({ payload }));\n    this.store.dispatch(AuthActions.dispatch({ type: 'SUCCESS' }));\n    nonStore.dispatch(AuthActions.dispatch({ type: 'FAIL' }));\n  }\n}`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  constructor(store$: Store) {\n    store$.dispatch(new CustomAction())\n                    ~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  constructor(private readonly store$: Store) {\n    this.store$.dispatch({ type: 'custom' })\n                         ~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(store: Store, private readonly store$: Store) {\n    store.dispatch(new Login({ payload }));\n                   ~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.store$.dispatch(new AuthActions.dispatch({ type: 'SUCCESS' }));\n                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    nonStore.dispatch(new AuthActions.dispatch({ type: 'FAIL' }));\n  }\n\n  ngOnInit() {\n    const store = { dispatch: () => void 0 }\n    store.dispatch()\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  constructor(private readonly store$: Store) {}\n\n  ngOnInit() {\n    this.store$.dispatch(useObject ? { type: 'custom' } : new CustomAction())\n                                     ~~~~~~~~~~~~~~~~~~ [${messageId}]\n                                                          ~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk4 {\n  store$ = inject(Store)\n\n  constructor() {\n    this.store$.dispatch(new CustomAction())\n                         ~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk5 {\n  private readonly store$ = inject(Store)\n\n  constructor() {\n    this.store$.dispatch({ type: 'custom' })\n                         ~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  store = inject(Store)\n  private readonly store$ = inject(Store)\n\n  constructor() {\n    this.store.dispatch(new Login({ payload }));\n                        ~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    this.store$.dispatch(new AuthActions.dispatch({ type: 'SUCCESS' }));\n                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]\n    nonStore.dispatch(new AuthActions.dispatch({ type: 'FAIL' }));\n  }\n\n  ngOnInit() {\n    const store = { dispatch: () => void 0 }\n    store.dispatch()\n  }\n}`),\n  fromFixture(`\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\n  private readonly store$ = inject(Store)\n\n  constructor() {}\n\n  ngOnInit() {\n    this.store$.dispatch(useObject ? { type: 'custom' } : new CustomAction())\n                                     ~~~~~~~~~~~~~~~~~~ [${messageId}]\n                                                          ~~~~~~~~~~~~~~~~~~ [${messageId}]\n  }\n}`),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/prefer-action-creator-in-of-type.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/effects/prefer-action-creator-in-of-type';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `\n@Injectable()\nclass Test {\n  effectOK = createEffect(() => this.actions$.pipe(ofType(userActions.ping)))\n\n  constructor(private readonly actions$: Actions) {}\n}`,\n  `\n@Injectable()\nclass Test {\n  effectOK1 = createEffect(() => this.actions$.pipe(ofType(userActions.ping.type)))\n\n  constructor(private readonly actions$: Actions) {}\n}`,\n  `\n@Injectable()\nclass Test {\n  effectOK2 = createEffect(() => this.actions$.pipe(ofType(method())))\n\n  constructor(private readonly actions$: Actions) {}\n}`,\n  `\n@Injectable()\nclass Test {\n  effectOK3 = createEffect(() => this.actions$.pipe(ofType(condition ? methodA() : bookActions.load)))\n\n  constructor(private readonly actions$: Actions) {}\n}`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\n@Injectable()\nclass Test {\n  effectNOK = createEffect(() => this.actions$.pipe(ofType('PING')))\n                                                           ~~~~~~ [${messageId}]\n\n  constructor(private readonly actions$: Actions) {}\n}`\n  ),\n  fromFixture(\n    `\n@Injectable()\nclass Test {\n  effectNOK1 = createEffect(() => this.actions$.pipe(ofType(BookActions.load, 'PONG')))\n                                                                              ~~~~~~ [${messageId}]\n\n  constructor(private readonly actions$: Actions) {}\n}`\n  ),\n  fromFixture(\n    `\n@Injectable()\nclass Test {\n  effectNOK2 = createEffect(() =>\n    this.actions$.pipe(ofType(legacy ? 'error here' : myAction)),\n                                       ~~~~~~~~~~~~ [${messageId}]\n  )\n\n  constructor(private readonly actions$: Actions) {}\n}`\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/prefer-action-creator.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/store/prefer-action-creator';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `const loadUser = createAction('[User Page] Load User')`,\n  `\n    class Test {\n      type = '[Customer Page] Load Customer'\n    }`,\n  `\n    class Test implements Action {\n      member = '[Customer Page] Load Customer'\n    }`,\n  `\n    class Test {\n      readonly type = ActionTypes.success\n\n      constructor(\n        currentState: string,\n        newState: string,\n        params?: RawParams,\n        options?: TransitionOptions\n      ) {}\n    }`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nclass Test implements Action { type = '[Customer Page] Load Customer' }\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]`\n  ),\n  fromFixture(\n    `\nclass Test implements ngrx.Action { type = ActionTypes.success; constructor(readonly payload: Payload) {} }\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]`\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/prefer-inline-action-props.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  preferInlineActionProps,\n  preferInlineActionPropsSuggest,\n} from '../../../src/rules/store/prefer-inline-action-props';\nimport { ruleTester } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `const ok0 = createAction('ok0', props<{ id: number, name: string }>())`,\n  `const ok1 = createAction('ok1', props<Readonly<{ description: string }>>())`,\n  `const ok2 = createAction('ok2', props<Readonly<HttpErrorResponse & { description: string }>>())`,\n  `const ok3 = createAction('ok3')`,\n  `const ok4 = createAction('[Users] Add User', props<{ user: User }>())`,\n  `const ok5 = createAction('[API/User] Save user success', (user: User<number>) => ({\n      kind: 'SAVE_SUCCESS',\n      message,\n      user,\n    }));`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  {\n    code: `const notOk0 = createAction('notOk0', props<number>())`,\n    errors: [\n      {\n        messageId: preferInlineActionProps,\n        suggestions: [\n          {\n            messageId: preferInlineActionPropsSuggest,\n            output: `const notOk0 = createAction('notOk0', props<{name: number}>())`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `const notOk1 = createAction('notOk1', props<Person>())`,\n    errors: [\n      {\n        messageId: preferInlineActionProps,\n        suggestions: [\n          {\n            messageId: preferInlineActionPropsSuggest,\n            output: `const notOk1 = createAction('notOk1', props<{name: Person}>())`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `const notOk2 = createAction('notOk2', props<Customer<T>>())`,\n    errors: [\n      {\n        messageId: preferInlineActionProps,\n        suggestions: [\n          {\n            messageId: preferInlineActionPropsSuggest,\n            output: `const notOk2 = createAction('notOk2', props<{name: Customer<T>}>())`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `const notOk3 = createAction('notOk3', props<ReadonlyArray<Test>>())`,\n    errors: [\n      {\n        messageId: preferInlineActionProps,\n        suggestions: [\n          {\n            messageId: preferInlineActionPropsSuggest,\n            output: `const notOk3 = createAction('notOk3', props<{name: ReadonlyArray<Test>}>())`,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    code: `const notOk4 = createAction('notOk4', props<Test[]>())`,\n    errors: [\n      {\n        messageId: preferInlineActionProps,\n        suggestions: [\n          {\n            messageId: preferInlineActionPropsSuggest,\n            output: `const notOk4 = createAction('notOk4', props<{name: Test[]}>())`,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/prefer-one-generic-in-create-for-feature-selector.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  preferOneGenericInCreateForFeatureSelector,\n  preferOneGenericInCreateForFeatureSelectorSuggest,\n} from '../../../src/rules/store/prefer-one-generic-in-create-for-feature-selector';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `const createFeatureSelector = test('feature-state')`,\n  `const featureOk = createFeatureSelector('feature-state')`,\n  `const featureOk1 = createFeatureSelector<FeatureState>('feature-state')`,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nconst featureNotOk = createFeatureSelector<GlobalState, FeatureState>('feature-state')\n                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${preferOneGenericInCreateForFeatureSelector} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: preferOneGenericInCreateForFeatureSelectorSuggest,\n          output: `\nconst featureNotOk = createFeatureSelector< FeatureState>('feature-state')`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nconst featureNotOk1 = createFeatureSelector<AppState, readonly string[]>('feature-state')\n                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${preferOneGenericInCreateForFeatureSelector} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: preferOneGenericInCreateForFeatureSelectorSuggest,\n          output: `\nconst featureNotOk1 = createFeatureSelector< readonly string[]>('feature-state')`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nconst featureNotOk2 = createFeatureSelector<GlobalState  , StateA & StateB>('feature-state')\n                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [${preferOneGenericInCreateForFeatureSelector} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: preferOneGenericInCreateForFeatureSelectorSuggest,\n          output: `\nconst featureNotOk2 = createFeatureSelector< StateA & StateB>('feature-state')`,\n        },\n      ],\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/prefer-selector-in-select.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  messageId,\n} from '../../../src/rules/store/prefer-selector-in-select';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nclass Ok {\n  readonly test$ = somethingOutside();\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok1 {\n  view$: Observable<unknown>\n\n  constructor(store: Store) {\n    this.view$ = store.pipe(select(selectCustomers))\n  }\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok2 {\n  view$: Observable<unknown>\n\n  constructor(private store: Store) {\n    this.view$ = store.select(selectCustomers)\n  }\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok3 {\n  view$ = this.store.pipe(select(CustomerSelectors.selectCustomers))\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok4 {\n  view$ = this.store.select(selectCustomers)\n\n  constructor(private readonly store: Store) {}\n}`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/41\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok5 {\n  view$ = this.store.pipe(select(selectQueryParam('parameter')))\n\n  constructor(private store: Store) {}\n}`,\n  // https://github.com/timdeschryver/eslint-plugin-ngrx/issues/135\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok6 {\n  view$ = this.store.select(this.store.select(hasAuthorization, 'ADMIN'));\n\n  constructor(private readonly store: Store) {}\n}`,\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok7 {\n  store = inject(Store)\n  view$ = this.store.pipe(select(selectCustomers))\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok8 {\n  private store = inject(Store)\n  view$ = this.store.select(selectCustomers)\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok9 {\n  private store = inject(Store)\n  view$ = this.store.pipe(select(CustomerSelectors.selectCustomers))\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok10 {\n  private readonly store = inject(Store)\n  view$ = this.store.select(selectCustomers)\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok11 {\n  private store = inject(Store)\n  view$ = this.store.pipe(select(selectQueryParam('parameter')))\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok12 {\n  private readonly store = inject(Store)\n  view$ = this.store.select(this.store.select(hasAuthorization, 'ADMIN'));\n}`,\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  view$: Observable<unknown>\n\n  constructor(store: Store) {\n    this.view$ = this.store.pipe(select('customers'))\n                                        ~~~~~~~~~~~  [${messageId}]\n  }\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  view$ = this.store.select('customers')\n                            ~~~~~~~~~~~  [${messageId}]\n\n  constructor(private store: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  view$ = this.store.pipe(select('customers', 'orders'))\n                                 ~~~~~~~~~~~            [${messageId}]\n                                              ~~~~~~~~  [${messageId}]\n\n  constructor(private readonly store: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  view$ = this.store.select('customers', 'orders')\n                            ~~~~~~~~~~~               [${messageId}]\n                                         ~~~~~~~~     [${messageId}]\n\n  constructor(private store: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk4 {\n  view$ = this.store.pipe(select(s => s.customers))\n                                 ~~~~~~~~~~~~~~~~  [${messageId}]\n\n  constructor(private store: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk5 {\n  view$ = this.store.select(s => s.customers)\n                            ~~~~~~~~~~~~~~~~  [${messageId}]\n\n  constructor(readonly store: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk6 {\n  view$ = this.store$.select(s => s.customers)\n                             ~~~~~~~~~~~~~~~~  [${messageId}]\n\n  constructor(private store$: Store) {}\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk7 {\n  view$ = this.store$.pipe(select('customers'))\n                                  ~~~~~~~~~~~  [${messageId}]\n\n  constructor(private readonly store$: Store) {}\n}`\n  ),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk8 {\n  store = inject(Store)\n  view$ = this.store.pipe(select('customers'))\n                                 ~~~~~~~~~~~  [${messageId}]\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk9 {\n  private store = inject(Store)\n  view$ = this.store.select('customers')\n                            ~~~~~~~~~~~  [${messageId}]\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk10 {\n  private readonly store = inject(Store)\n  view$ = this.store.pipe(select('customers', 'orders'))\n                                 ~~~~~~~~~~~            [${messageId}]\n                                              ~~~~~~~~  [${messageId}]\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk11 {\n  private store = inject(Store)\n  view$ = this.store.select('customers', 'orders')\n                            ~~~~~~~~~~~               [${messageId}]\n                                         ~~~~~~~~     [${messageId}]\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk12 {\n  private store = inject(Store)\n  view$ = this.store.pipe(select(s => s.customers))\n                                 ~~~~~~~~~~~~~~~~  [${messageId}]\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk13 {\n  readonly store = inject(Store)\n  view$ = this.store.select(s => s.customers)\n                            ~~~~~~~~~~~~~~~~  [${messageId}]\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk14 {\n  private store$ = inject(Store)\n  view$ = this.store$.select(s => s.customers)\n                             ~~~~~~~~~~~~~~~~  [${messageId}]\n}`\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk15 {\n  private readonly store$ = inject(Store)\n  view$ = this.store$.pipe(select('customers'))\n                                  ~~~~~~~~~~~  [${messageId}]\n}`\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/prefix-selectors-with-select.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  prefixSelectorsWithSelect,\n  prefixSelectorsWithSelectSuggest,\n} from '../../../src/rules/store/prefix-selectors-with-select';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;\n\nconst valid: () => (string | ValidTestCase<Options>)[] = () => [\n  `export const selectFeature: MemoizedSelector<any, any> = (state: AppState) => state.feature`,\n  `export const selectFeature: MemoizedSelectorWithProps<any, any> = ({ feature }) => feature`,\n  `export const selectFeature = createSelector((state: AppState) => state.feature)`,\n  `export const selectFeature = createFeatureSelector<FeatureState>(featureKey)`,\n  `export const selectFeature = createFeatureSelector<AppState, FeatureState>(featureKey)`,\n  `export const selectThing = (id: string) => createSelector(selectThings, things => things[id])`,\n  `export const selectFeature = createSelectorFactory(factoryFn)`,\n  `export const select_feature = createSelectorFactory(factoryFn)`,\n  `export const select$feature = createSelectorFactory(factoryFn)`,\n  `export const selectF01 = createSelector(factoryFn)`,\n  `\n    export const authFeature = createFeature({\n      name: 'auth',\n      reducer: authReducer,\n      extraSelectors: ({selectToken}) => ({\n        selectIsAuthorized: createSelector(selectToken, token => !!token)\n      }),\n    })\n  `,\n  `\n    export const { selectAll: selectAllBooks } = booksAdapter.getSelectors(createSelector(selectBookInfo, (state) => state.books));\n  `,\n  `\n    const { selectAll, selectEntities } = getSelectors(adapter);\n  `,\n  `\n    const { selectAll: selectAllItems, selectEntities: selectEntitiesMap } = getSelectors(adapter);\n  `,\n  `\n    const { selectItems, ...rest } = getSelectors(adapter);\n  `,\n  // Issue #4447: Should not flag custom types that end with 'Selector' but are not NgRx selectors\n  `type RowSelector<T> = { row: T };\ninterface ItemData {\n  id: number;\n  name: string;\n  active: boolean;\n}\nconst item: RowSelector<ItemData> = {\n  row: {\n    id: 1,\n    name: 'Test',\n    active: false,\n  },\n};`,\n  `\n    interface CustomSelector {\n      data: string;\n    }\n    const config: CustomSelector = { data: 'test' };\n  `,\n];\n\nconst invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nexport const getCount: MemoizedSelector<any, any> = (state: AppState) => state.feature\n             ~~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: { name: 'selectCount' },\n          output: `\nexport const selectCount: MemoizedSelector<any, any> = (state: AppState) => state.feature`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nexport const getF01 = createSelector((state: AppState) => state.feature)\n             ~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          output: `\nexport const selectF01 = createSelector((state: AppState) => state.feature)`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nexport const get_f01 = createSelector((state: AppState) => state.feature)\n             ~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          output: `\nexport const select_f01 = createSelector((state: AppState) => state.feature)`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nexport const select = (id: string) => createSelector(selectThings, things => things[id])\n             ~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          data: {\n            name: 'selectSelect',\n          },\n          messageId: prefixSelectorsWithSelectSuggest,\n          output: `\nexport const selectSelect = (id: string) => createSelector(selectThings, things => things[id])`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nexport const SELECT_TEST = (id: string) => {\n             ~~~~~~~~~~~ [${prefixSelectorsWithSelect} suggest]\n  return createSelector(selectThings, things => things[id])\n}`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: {\n            name: 'selectSELECT_TEST',\n          },\n          output: `\nexport const selectSELECT_TEST = (id: string) => {\n  return createSelector(selectThings, things => things[id])\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nexport const feature = createFeatureSelector<AppState, FeatureState>(featureKey)\n             ~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: {\n            name: 'selectFeature',\n          },\n          output: `\nexport const selectFeature = createFeatureSelector<AppState, FeatureState>(featureKey)`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nexport const selectfeature = createSelector((state: AppState) => state.feature)\n             ~~~~~~~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: {\n            name: 'selectFeature',\n          },\n          output: `\nexport const selectFeature = createSelector((state: AppState) => state.feature)`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nexport const createSelect = createSelectorFactory((projectionFun) =>\n             ~~~~~~~~~~~~ [${prefixSelectorsWithSelect} suggest]\n  defaultMemoize(\n    projectionFun,\n    orderDoesNotMatterComparer,\n    orderDoesNotMatterComparer,\n  ),\n)`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: {\n            name: 'selectCreateSelect',\n          },\n          output: `\nexport const selectCreateSelect = createSelectorFactory((projectionFun) =>\n  defaultMemoize(\n    projectionFun,\n    orderDoesNotMatterComparer,\n    orderDoesNotMatterComparer,\n  ),\n)`,\n        },\n      ],\n    }\n  ),\n  // https://github.com/ngrx/platform/issues/3956\n  fromFixture(\n    `\nimport {createFeatureSelector} from '@ngrx/store';\n\nexport interface FileListResponseState {\n  loading: boolean;\n}\n\nconst featureSelector = createFeatureSelector<FileListResponseState>(\"name\");\n      ~~~~~~~~~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: {\n            name: 'selectFeatureSelector',\n          },\n          output: `\nimport {createFeatureSelector} from '@ngrx/store';\n\nexport interface FileListResponseState {\n  loading: boolean;\n}\n\nconst selectFeatureSelector = createFeatureSelector<FileListResponseState>(\"name\");`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nexport const { selectAll: allBooks } = booksAdapter.getSelectors(createSelector(selectBookInfo, (state) => state.books));\n                          ~~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: { name: 'selectAllBooks' },\n          output: `\nexport const { selectAll: selectAllBooks } = booksAdapter.getSelectors(createSelector(selectBookInfo, (state) => state.books));`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nconst { selectAll: allItems } = getSelectors(adapter);\n                   ~~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: { name: 'selectAllItems' },\n          output: `\nconst { selectAll: selectAllItems } = getSelectors(adapter);`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nconst { selectEntities: entitiesMap } = getSelectors(adapter);\n                        ~~~~~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: { name: 'selectEntitiesMap' },\n          output: `\nconst { selectEntities: selectEntitiesMap } = getSelectors(adapter);`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nconst { allItems } = getSelectors(adapter);\n        ~~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: { name: 'selectAllItems' },\n          output: `\nconst { selectAllItems } = getSelectors(adapter);`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nconst { entitiesMap } = getSelectors(adapter);\n        ~~~~~~~~~~~ [${prefixSelectorsWithSelect} suggest]`,\n    {\n      suggestions: [\n        {\n          messageId: prefixSelectorsWithSelectSuggest,\n          data: { name: 'selectEntitiesMap' },\n          output: `\nconst { selectEntitiesMap } = getSelectors(adapter);`,\n        },\n      ],\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: valid(),\n    invalid: invalid(),\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/select-style.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, { SelectStyle } from '../../../src/rules/store/select-style';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = readonly ESLintUtils.InferOptionsTypeFromRule<typeof rule>[0][];\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok {\n  readonly test$ = somethingOutside();\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok1 {\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store, select } from '@ngrx/store'\n\nclass Ok2 {\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store, select } from '@ngrx/store'\n\nclass Ok3 {\n  foo$ = select(selector)\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { select } from '@my-org/framework'\nimport { Store } from '@ngrx/store'\n\nclass Ok4 {\n  foo$ = this.store.pipe(select(selector))\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok5 {\n  foo$ = this.store.select(selector)\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok6 {\n  foo$ = this.store.select(selector)\n\n  constructor(private store: Store) {}\n}`,\n  `\nimport { Store, select } from '@ngrx/store'\n\nclass Ok7 {\n  foo$ = this.customName.select(selector)\n  constructor(private customName: Store) {}\n}`,\n  {\n    code: `\nimport { Store, select } from '@ngrx/store'\n\nclass Ok8 {\n  foo$ = this.store.pipe(select(selector))\n\n  constructor(private store: Store) {}\n}`,\n    options: [SelectStyle.Operator],\n  },\n  {\n    code: `\nimport { select, Store } from '@ngrx/store'\n\nclass Ok9 {\n  foo$ = this.store.select(selector)\n\n  constructor(private store: Store) {}\n}`,\n    options: [SelectStyle.Method],\n  },\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok10 {\n  private store = inject(Store)\n}`,\n  `\nimport { Store, select } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok11 {\n  private store = inject(Store)\n}`,\n  `\nimport { Store, select } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok12 {\n  private store = inject(Store)\n  foo$ = select(selector)\n}`,\n  `\nimport { select } from '@my-org/framework'\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok13 {\n  private store = inject(Store)\n  foo$ = this.store.pipe(select(selector))\n}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok14 {\n  private store = inject(Store)\n  foo$ = this.store.select(selector)\n}`,\n  `\nimport { Store, select } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok15 {\n  private customName = inject(Store)\n  foo$ = this.customName.select(selector)\n}`,\n  {\n    code: `\nimport { Store, select } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok16 {\n  private store = inject(Store)\n  foo$ = this.store.pipe(select(selector))\n}`,\n    options: [SelectStyle.Operator],\n  },\n  {\n    code: `\nimport { select, Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok17 {\n  private store = inject(Store)\n  foo$ = this.store.select(selector)\n}`,\n    options: [SelectStyle.Method],\n  },\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { select, Store } from '@ngrx/store'\n         ~~~~~~ [${SelectStyle.Method}]\n\nclass NotOk {\n  foo$ = this.store.pipe( select(selector), )\n                          ~~~~~~ [${SelectStyle.Method}]\n\n  constructor(private store: Store) {}\n}`,\n    {\n      output: `\nimport {  Store } from '@ngrx/store'\n\nclass NotOk {\n  foo$ = this.store. select((selector), )\n\n  constructor(private store: Store) {}\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { Store, select } from '@ngrx/store'\n                ~~~~~~ [${SelectStyle.Method}]\n\nclass NotOk1 {\n  foo$ = this.store.pipe  (select\n                           ~~~~~~ [${SelectStyle.Method}]\n    (selector, selector2), filter(Boolean))\n\n  constructor(private store: Store) {}\n}`,\n    {\n      options: [SelectStyle.Method],\n      output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  foo$ = this.store.select\n    (selector, selector2).pipe  ( filter(Boolean))\n\n  constructor(private store: Store) {}\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { select, Store } from '@ngrx/store'\n\nclass NotOk2 {\n  bar$: Observable<unknown>\n  foo$: Observable<unknown>\n\n  constructor(store: Store, private readonly customStore: Store) {\n    this.foo$ = store.select(\n                      ~~~~~~ [${SelectStyle.Operator}]\n      selector,\n    )\n  }\n\n  ngOnInit() {\n    this.bar$ = this.customStore.select(\n                                 ~~~~~~ [${SelectStyle.Operator}]\n      selector,\n    )\n  }\n}`,\n    {\n      options: [SelectStyle.Operator],\n      output: `\nimport { select, Store } from '@ngrx/store'\n\nclass NotOk2 {\n  bar$: Observable<unknown>\n  foo$: Observable<unknown>\n\n  constructor(store: Store, private readonly customStore: Store) {\n    this.foo$ = store.pipe(select(\n      selector,\n    ))\n  }\n\n  ngOnInit() {\n    this.bar$ = this.customStore.pipe(select(\n      selector,\n    ))\n  }\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport {\n  Store,\n  select,\n  ~~~~~~ [${SelectStyle.Method}]\n} from '@ngrx/store'\n\nclass NotOk3 {\n  foo$ = this.store.pipe(select(selector), map(toItem)).pipe()\n                         ~~~~~~ [${SelectStyle.Method}]\n  bar$ = this.store.\n    select(selector).pipe()\n  baz$ = this.store.pipe(\n    select(({ customers }) => customers), map(toItem),\n    ~~~~~~ [${SelectStyle.Method}]\n  ).pipe()\n\n  constructor(private store: Store) {}\n}\n\nclass NotOk4 {\n  foo$ = this.store.pipe(select(selector), map(toItem)).pipe()\n                         ~~~~~~ [${SelectStyle.Method}]\n\n  constructor(private readonly store: Store) {}\n}`,\n    {\n      options: [SelectStyle.Method],\n      output: `\nimport {\n  Store,\n} from '@ngrx/store'\n\nclass NotOk3 {\n  foo$ = this.store.select(selector).pipe( map(toItem)).pipe()\n  bar$ = this.store.\n    select(selector).pipe()\n  baz$ = this.store.select(({ customers }) => customers).pipe(\n     map(toItem),\n  ).pipe()\n\n  constructor(private store: Store) {}\n}\n\nclass NotOk4 {\n  foo$ = this.store.select(selector).pipe( map(toItem)).pipe()\n\n  constructor(private readonly store: Store) {}\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport type {Creator} from '@ngrx/store'\nimport { Store } from '@ngrx/store'\n\nclass NotOk5 {\n  foo$ = this.store.select(selector)\n                    ~~~~~~ [${SelectStyle.Operator}]\n\n  constructor(private store: Store) {}\n}`,\n    {\n      options: [SelectStyle.Operator],\n      output: `\nimport type {Creator} from '@ngrx/store'\nimport { Store, select } from '@ngrx/store'\n\nclass NotOk5 {\n  foo$ = this.store.pipe(select(selector))\n\n  constructor(private store: Store) {}\n}`,\n    }\n  ),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { select, Store } from '@ngrx/store'\n         ~~~~~~ [${SelectStyle.Method}]\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  private store = inject(Store)\n  foo$ = this.store.pipe( select(selector), )\n                          ~~~~~~ [${SelectStyle.Method}]\n}`,\n    {\n      output: `\nimport {  Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  private store = inject(Store)\n  foo$ = this.store. select((selector), )\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { Store, select } from '@ngrx/store'\n                ~~~~~~ [${SelectStyle.Method}]\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\n  private store = inject(Store)\n  foo$ = this.store.pipe  (select\n                           ~~~~~~ [${SelectStyle.Method}]\n    (selector, selector2), filter(Boolean))\n}`,\n    {\n      options: [SelectStyle.Method],\n      output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\n  private store = inject(Store)\n  foo$ = this.store.select\n    (selector, selector2).pipe  ( filter(Boolean))\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport { select, Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk8 {\n  private store = inject(Store)\n  private customStore = inject(Store)\n  foo$ = this.store.select(\n                    ~~~~~~ [${SelectStyle.Operator}]\n    selector,\n  )\n  bar$: Observable<unknown>\n\n  ngOnInit() {\n    this.bar$ = this.customStore.select(\n                                 ~~~~~~ [${SelectStyle.Operator}]\n      selector,\n    )\n  }\n}`,\n    {\n      options: [SelectStyle.Operator],\n      output: `\nimport { select, Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk8 {\n  private store = inject(Store)\n  private customStore = inject(Store)\n  foo$ = this.store.pipe(select(\n    selector,\n  ))\n  bar$: Observable<unknown>\n\n  ngOnInit() {\n    this.bar$ = this.customStore.pipe(select(\n      selector,\n    ))\n  }\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport {\n  Store,\n  select,\n  ~~~~~~ [${SelectStyle.Method}]\n} from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk9 {\n  private store = inject(Store)\n  foo$ = this.store.pipe(select(selector), map(toItem)).pipe()\n                         ~~~~~~ [${SelectStyle.Method}]\n  bar$ = this.store.\n    select(selector).pipe()\n  baz$ = this.store.pipe(\n    select(({ customers }) => customers), map(toItem),\n    ~~~~~~ [${SelectStyle.Method}]\n  ).pipe()\n}\n\nclass NotOk10 {\n  private readonly store = inject(Store)\n  foo$ = this.store.pipe(select(selector), map(toItem)).pipe()\n                         ~~~~~~ [${SelectStyle.Method}]\n}`,\n    {\n      options: [SelectStyle.Method],\n      output: `\nimport {\n  Store,\n} from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk9 {\n  private store = inject(Store)\n  foo$ = this.store.select(selector).pipe( map(toItem)).pipe()\n  bar$ = this.store.\n    select(selector).pipe()\n  baz$ = this.store.select(({ customers }) => customers).pipe(\n     map(toItem),\n  ).pipe()\n}\n\nclass NotOk10 {\n  private readonly store = inject(Store)\n  foo$ = this.store.select(selector).pipe( map(toItem)).pipe()\n}`,\n    }\n  ),\n  fromFixture(\n    `\nimport type {Creator} from '@ngrx/store'\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk11 {\n  private store = inject(Store)\n  foo$ = this.store.select(selector)\n                    ~~~~~~ [${SelectStyle.Operator}]\n}`,\n    {\n      options: [SelectStyle.Operator],\n      output: `\nimport type {Creator} from '@ngrx/store'\nimport { Store, select } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk11 {\n  private store = inject(Store)\n  foo$ = this.store.pipe(select(selector))\n}`,\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/rules/store/use-consistent-global-store-name.spec.ts",
    "content": "import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type {\n  InvalidTestCase,\n  ValidTestCase,\n} from '@typescript-eslint/rule-tester';\nimport * as path from 'path';\nimport rule, {\n  useConsistentGlobalStoreName,\n  useConsistentGlobalStoreNameSuggest,\n} from '../../../src/rules/store/use-consistent-global-store-name';\nimport { ruleTester, fromFixture } from '../../utils';\n\ntype MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;\ntype Options = readonly ESLintUtils.InferOptionsTypeFromRule<typeof rule>[0][];\n\nconst validConstructor: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nclass Ok {}`,\n  `\nimport { Store } from '@ngrx/store'\n\nclass Ok1 {\n  constructor(store: Store) {}\n}`,\n  {\n    code: `\nimport { Store } from '@ngrx/store'\n\nclass Ok2 {\n  constructor(private customName: Store) {}\n}`,\n    options: ['customName'],\n  },\n];\n\nconst validInject: () => (string | ValidTestCase<Options>)[] = () => [\n  `\nclass Ok3 {}`,\n  `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok4 {\n  readonly store = inject(Store)\n}`,\n  {\n    code: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass Ok5 {\n  readonly customName = inject(Store)\n}`,\n    options: ['customName'],\n  },\n];\n\nconst invalidConstructor: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  constructor(private readonly somethingElse$: Store) {}\n                               ~~~~~~~~~~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"store\" } suggest]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'store',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk {\n  constructor(private readonly store: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  constructor(private readonly store1: Store, private readonly store: Store) {}\n                               ~~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"store\" } suggest]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'store',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk1 {\n  constructor(private readonly store: Store, private readonly store: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(private readonly store1: Store, private readonly store2: Store) {}\n                               ~~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"store\" } suggest 0]\n                                                               ~~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"store\" } suggest 1]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'store',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(private readonly store: Store, private readonly store2: Store) {}\n}`,\n        },\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'store',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk2 {\n  constructor(private readonly store1: Store, private readonly store: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  constructor(private store: Store) {}\n                      ~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"customName\" } suggest]\n}`,\n    {\n      options: ['customName'],\n      suggestions: [\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'customName',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\n\nclass NotOk3 {\n  constructor(private customName: Store) {}\n}`,\n        },\n      ],\n    }\n  ),\n];\n\nconst invalidInject: () => InvalidTestCase<MessageIds, Options>[] = () => [\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk4 {\n  readonly somethingElse$: Store = inject(Store)\n           ~~~~~~~~~~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"store\" } suggest]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'store',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk4 {\n  readonly store: Store = inject(Store)\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk5 {\n  private readonly store1 = inject(Store)\n                   ~~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"store\" } suggest]\n  private readonly store = inject(Store)\n}`,\n    {\n      suggestions: [\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'store',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk5 {\n  private readonly store = inject(Store)\n  private readonly store = inject(Store)\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  private readonly store1 = inject(Store)\n                   ~~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"store\" } suggest 0]\n  private readonly store2 = inject(Store)\n                   ~~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"store\" } suggest 1]\n}`,\n    {\n      suggestions: [\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'store',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  private readonly store = inject(Store)\n  private readonly store2 = inject(Store)\n}`,\n        },\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'store',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk6 {\n  private readonly store1 = inject(Store)\n  private readonly store = inject(Store)\n}`,\n        },\n      ],\n    }\n  ),\n  fromFixture(\n    `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\n  private store = inject(Store)\n          ~~~~~ [${useConsistentGlobalStoreName} { \"storeName\": \"customName\" } suggest]\n}`,\n    {\n      options: ['customName'],\n      suggestions: [\n        {\n          messageId: useConsistentGlobalStoreNameSuggest,\n          data: {\n            storeName: 'customName',\n          },\n          output: `\nimport { Store } from '@ngrx/store'\nimport { inject } from '@angular/core'\n\nclass NotOk7 {\n  private customName = inject(Store)\n}`,\n        },\n      ],\n    }\n  ),\n];\n\nruleTester(rule.meta.docs?.requiresTypeChecking).run(\n  path.parse(__filename).name,\n  rule,\n  {\n    valid: [...validConstructor(), ...validInject()],\n    invalid: [...invalidConstructor(), ...invalidInject()],\n  }\n);\n"
  },
  {
    "path": "modules/eslint-plugin/spec/schematics/ng-add.spec.ts",
    "content": "import { Tree, HostTree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\n\nconst schematicRunner = new SchematicTestRunner(\n  '@ngrx/eslint-plugin',\n  path.join(\n    process.cwd(),\n    'dist/modules/eslint-plugin/schematics/collection.json'\n  )\n);\n\ndescribe('addNgRxESLintPlugin for ESLint < v9 (JSON)', () => {\n  test('registers the plugin with the all config', async () => {\n    const appTree = new UnitTestTree(Tree.empty());\n\n    const initialConfig = {};\n    appTree.create('./.eslintrc.json', JSON.stringify(initialConfig, null, 2));\n\n    await schematicRunner.runSchematic('ng-add', {}, appTree);\n\n    const eslintContent = appTree.readContent(`.eslintrc.json`);\n    const eslintJson = JSON.parse(eslintContent);\n    expect(eslintJson).toEqual({\n      overrides: [{ files: ['*.ts'], extends: [`plugin:@ngrx/all`] }],\n    });\n  });\n\n  test('registers the plugin with a different config', async () => {\n    const appTree = new UnitTestTree(Tree.empty());\n\n    const initialConfig = {};\n    appTree.create('./.eslintrc.json', JSON.stringify(initialConfig, null, 2));\n\n    const options = { config: 'store' };\n    await schematicRunner.runSchematic('ng-add', options, appTree);\n\n    const eslintContent = appTree.readContent(`.eslintrc.json`);\n    const eslintJson = JSON.parse(eslintContent);\n    expect(eslintJson).toEqual({\n      overrides: [\n        {\n          files: ['*.ts'],\n          extends: [`plugin:@ngrx/${options.config}`],\n        },\n      ],\n    });\n  });\n\n  test('registers the plugin in overrides when it supports TS', async () => {\n    const appTree = new UnitTestTree(Tree.empty());\n\n    const initialConfig = {\n      overrides: [\n        {\n          files: ['*.ts'],\n          parserOptions: {\n            project: ['tsconfig.eslint.json'],\n            createDefaultProgram: true,\n          },\n          extends: [\n            'plugin:@angular-eslint/recommended',\n            'eslint:recommended',\n            'plugin:@typescript-eslint/recommended',\n            'plugin:@typescript-eslint/recommended-requiring-type-checking',\n            'plugin:@angular-eslint/template/process-inline-templates',\n            'plugin:prettier/recommended',\n          ],\n        },\n        {\n          files: ['*.html'],\n          extends: [\n            'plugin:@angular-eslint/template/recommended',\n            'plugin:prettier/recommended',\n          ],\n          rules: {},\n        },\n      ],\n    };\n    appTree.create('.eslintrc.json', JSON.stringify(initialConfig, null, 2));\n\n    await schematicRunner.runSchematic('ng-add', {}, appTree);\n\n    const eslintContent = appTree.readContent(`.eslintrc.json`);\n    const eslintJson = JSON.parse(eslintContent);\n    expect(eslintJson).toEqual({\n      overrides: [\n        {\n          files: ['*.ts'],\n          parserOptions: {\n            project: ['tsconfig.eslint.json'],\n            createDefaultProgram: true,\n          },\n          extends: [\n            'plugin:@angular-eslint/recommended',\n            'eslint:recommended',\n            'plugin:@typescript-eslint/recommended',\n            'plugin:@typescript-eslint/recommended-requiring-type-checking',\n            'plugin:@angular-eslint/template/process-inline-templates',\n            'plugin:prettier/recommended',\n          ],\n        },\n        {\n          files: ['*.html'],\n          extends: [\n            'plugin:@angular-eslint/template/recommended',\n            'plugin:prettier/recommended',\n          ],\n          rules: {},\n        },\n        {\n          files: ['*.ts'],\n          extends: [`plugin:@ngrx/all`],\n        },\n      ],\n    });\n  });\n\n  test('does not add the plugin if it is already added manually', async () => {\n    const appTree = new UnitTestTree(Tree.empty());\n\n    const initialConfig = {\n      extends: ['plugin:@ngrx/all'],\n    };\n    appTree.create('.eslintrc.json', JSON.stringify(initialConfig, null, 2));\n\n    await schematicRunner.runSchematic('ng-add', {}, appTree);\n\n    const eslintContent = appTree.readContent(`.eslintrc.json`);\n    const eslintJson = JSON.parse(eslintContent);\n    expect(eslintJson).toEqual(initialConfig);\n  });\n\n  test('does not add the plugin if it is already added manually as an override', async () => {\n    const appTree = new UnitTestTree(Tree.empty());\n\n    const initialConfig = {\n      overrides: [\n        {\n          extends: ['plugin:@ngrx/all'],\n        },\n      ],\n    };\n    appTree.create('.eslintrc.json', JSON.stringify(initialConfig, null, 2));\n\n    await schematicRunner.runSchematic('ng-add', {}, appTree);\n\n    const eslintContent = appTree.readContent(`.eslintrc.json`);\n    const eslintJson = JSON.parse(eslintContent);\n    expect(eslintJson).toEqual(initialConfig);\n  });\n});\n\ndescribe('addNgRxESLintPlugin for ESLint >= 9 (flat config)', () => {\n  let host: UnitTestTree;\n\n  beforeEach(() => {\n    host = new UnitTestTree(new HostTree());\n  });\n\n  it('registers the plugin with CommonJS', async () => {\n    host.create(\n      'eslint.config.js',\n      `\n// @ts-check\nconst tseslint = require('typescript-eslint');\nmodule.exports = tseslint.config(\n  {\n    files: ['**/*.ts'],\n    extends: [\n      ...tseslint.configs.recommended,\n    ]\n  }\n);`\n    );\n\n    await schematicRunner.runSchematic('ng-add', { config: 'store' }, host);\n    // verify it does not register the plugin twice\n    await schematicRunner.runSchematic('ng-add', { config: 'store' }, host);\n\n    const content = host.readText('eslint.config.js');\n    expect(content).toContain(`@ngrx/eslint-plugin`);\n    expect(content).toMatchInlineSnapshot(`\n      \"\n      // @ts-check\n      const tseslint = require('typescript-eslint');\n      const ngrx = require('@ngrx/eslint-plugin/v9');\n      module.exports = tseslint.config(\n        {\n          files: ['**/*.ts'],\n          extends: [\n            ...tseslint.configs.recommended,\n          ]\n        },\n        {\n          files: ['**/*.ts'],\n          extends: [\n            ...ngrx.configs.store,\n          ],\n          rules: {},\n        }\n      );\"\n    `);\n  });\n\n  it('registers the plugin with ESM', async () => {\n    host.create(\n      'eslint.config.mjs',\n      `\n// @ts-check\nimport tseslint from 'typescript-eslint';\nexport default tseslint.config(\n  {\n    files: ['**/*.ts'],\n    extends: [\n      ...tseslint.configs.recommended,\n    ],\n  },\n);`\n    );\n\n    await schematicRunner.runSchematic('ng-add', { config: 'effects' }, host);\n    // verify it does not register the plugin twice\n    await schematicRunner.runSchematic('ng-add', { config: 'effects' }, host);\n\n    const content = host.readText('eslint.config.mjs');\n    expect(content).toContain(`@ngrx/eslint-plugin`);\n    expect(content).toMatchInlineSnapshot(`\n      \"\n      // @ts-check\n      import tseslint from 'typescript-eslint';\n      import ngrx from '@ngrx/eslint-plugin/v9';\n      export default tseslint.config(\n        {\n          files: ['**/*.ts'],\n          extends: [\n            ...tseslint.configs.recommended,\n          ],\n        },\n        {\n          files: ['**/*.ts'],\n          extends: [\n            ...ngrx.configs.effects,\n          ],\n          rules: {},\n        },\n      );\"\n    `);\n  });\n\n  it('registers the plugin when there are no existing plugins', async () => {\n    host.create('eslint.config.cjs', `module.exports = tseslint.config();`);\n\n    await schematicRunner.runSchematic('ng-add', { config: 'all' }, host);\n\n    const content = host.readText('eslint.config.cjs');\n    expect(content).toContain(`@ngrx/eslint-plugin`);\n    expect(content).toMatchInlineSnapshot(`\n      \"\n      const ngrx = require('@ngrx/eslint-plugin/v9');module.exports = tseslint.config(\n        {\n          files: ['**/*.ts'],\n          extends: [\n            ...ngrx.configs.all,\n          ],\n          rules: {},\n        }\n      );\"\n    `);\n  });\n\n  it('does not register the plugin if tseslint is missing', async () => {\n    const originalContent = `\n        import somePlugin from 'some-plugin';\n        export default [];\n        `;\n    host.create('eslint.config.js', originalContent);\n\n    await schematicRunner.runSchematic('ng-add', { config: 'all' }, host);\n\n    const content = host.readText('eslint.config.js');\n    expect(content).toBe(originalContent);\n  });\n});\n"
  },
  {
    "path": "modules/eslint-plugin/spec/utils/from-fixture.spec.ts",
    "content": "import { fromFixture } from './from-fixture';\n\ndescribe('fromFixture', () => {\n  it('should create an invalid test with a message ID', () => {\n    const test = fromFixture(\n      `\nconst name = \"alice\";\n             ~~~~~~~ [whoops]`\n    );\n    expect(test).toHaveProperty('code');\n    expect(test).toHaveProperty('errors');\n    expect(test.errors).toEqual([\n      {\n        column: 14,\n        data: {},\n        endColumn: 21,\n        endLine: 2,\n        line: 2,\n        messageId: 'whoops',\n      },\n    ]);\n  });\n\n  it('should create an invalid test with options', () => {\n    const test = fromFixture(\n      `\nconst name = \"alice\";\n             ~~~~~~~ [whoops]`,\n      {\n        filename: 'test.ts',\n        output: `\nconst name = 'alice';`,\n      }\n    );\n    expect(test).toHaveProperty('code');\n    expect(test).toHaveProperty('errors');\n    expect(test.errors).toEqual([\n      {\n        column: 14,\n        data: {},\n        endColumn: 21,\n        endLine: 2,\n        line: 2,\n        messageId: 'whoops',\n      },\n    ]);\n    expect(test).toHaveProperty('filename', 'test.ts');\n    expect(test).toHaveProperty(\n      'output',\n      `\nconst name = 'alice';`\n    );\n  });\n\n  it('should create an invalid test with multiple errors', () => {\n    const test = fromFixture(\n      `\nconst name = \"alice\";\n             ~~~~~~~ [first]\n      ~~~~ [second]\nconst role = 'engineer';\n~~~~~ [third]`\n    );\n    expect(test).toHaveProperty('code');\n    expect(test).toHaveProperty('errors');\n    expect(test.errors).toEqual([\n      {\n        column: 14,\n        data: {},\n        endColumn: 21,\n        endLine: 2,\n        line: 2,\n        messageId: 'first',\n      },\n      {\n        column: 7,\n        data: {},\n        endColumn: 11,\n        endLine: 2,\n        line: 2,\n        messageId: 'second',\n      },\n      {\n        column: 1,\n        data: {},\n        endColumn: 6,\n        endLine: 3,\n        line: 3,\n        messageId: 'third',\n      },\n    ]);\n  });\n\n  it('should create an invalid test with data', () => {\n    const test = fromFixture(\n      `\nconst name = \"alice\";\n      ~~~~ [whoops { \"identifier\": \"name\" }]`\n    );\n    expect(test).toHaveProperty('code');\n    expect(test).toHaveProperty('errors');\n    expect(test.errors).toEqual([\n      {\n        column: 7,\n        data: {\n          identifier: 'name',\n        },\n        endColumn: 11,\n        endLine: 2,\n        line: 2,\n        messageId: 'whoops',\n      },\n    ]);\n  });\n\n  it('should support data that contains punctuation', () => {\n    const punctuation = '[]{}()';\n    const test = fromFixture(\n      `\nconst name = \"alice\";\n      ~~~~ [whoops { \"value\": \"${punctuation}\" }]`\n    );\n    expect(test).toHaveProperty('code');\n    expect(test).toHaveProperty('errors');\n    expect(test.errors).toEqual([\n      {\n        column: 7,\n        data: {\n          value: punctuation,\n        },\n        endColumn: 11,\n        endLine: 2,\n        line: 2,\n        messageId: 'whoops',\n      },\n    ]);\n  });\n\n  it('should create an invalid test with a suggestion', () => {\n    const test = fromFixture(\n      `\nconst name = \"alice\";\n             ~~~~~~~ [whoops suggest]`,\n      {\n        suggestions: [\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"bob\";\n            `,\n          },\n        ],\n      }\n    );\n    expect(test).toHaveProperty('code');\n    expect(test).toHaveProperty('errors');\n    expect(test.errors).toEqual([\n      {\n        column: 14,\n        data: {},\n        endColumn: 21,\n        endLine: 2,\n        line: 2,\n        messageId: 'whoops',\n        suggestions: [\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"bob\";\n            `,\n          },\n        ],\n      },\n    ]);\n    expect(test).not.toHaveProperty('suggestions');\n  });\n\n  it('should create an invalid test with multiple suggestions', () => {\n    const test = fromFixture(\n      `\nconst name = \"alice\";\n             ~~~~~~~ [whoops suggest]`,\n      {\n        suggestions: [\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"bob\";\n            `,\n          },\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"eve\";\n            `,\n          },\n        ],\n      }\n    );\n    expect(test).toHaveProperty('code');\n    expect(test).toHaveProperty('errors');\n    expect(test.errors).toEqual([\n      {\n        column: 14,\n        data: {},\n        endColumn: 21,\n        endLine: 2,\n        line: 2,\n        messageId: 'whoops',\n        suggestions: [\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"bob\";\n            `,\n          },\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"eve\";\n            `,\n          },\n        ],\n      },\n    ]);\n    expect(test).not.toHaveProperty('suggestions');\n  });\n\n  it('should create an invalid test with multiple errors with suggestions', () => {\n    const test = fromFixture(\n      `\nconst name = \"alice\";\n             ~~~~~~~ [whoops suggest 0]\n             ~~~~~~~ [whoops { \"identifier\": \"name\" } suggest 1 2]\n             ~~~~~~~ [whoops]`,\n      {\n        suggestions: [\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"bob\";\n            `,\n          },\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"eve\";\n            `,\n          },\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"mallory\";\n            `,\n          },\n        ],\n      } as const\n    );\n    expect(test).toHaveProperty('code');\n    expect(test).toHaveProperty('errors');\n    expect(test.errors).toEqual([\n      {\n        column: 14,\n        data: {},\n        endColumn: 21,\n        endLine: 2,\n        line: 2,\n        messageId: 'whoops',\n        suggestions: [\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"bob\";\n            `,\n          },\n        ],\n      },\n      {\n        column: 14,\n        data: { identifier: 'name' },\n        endColumn: 21,\n        endLine: 2,\n        line: 2,\n        messageId: 'whoops',\n        suggestions: [\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"eve\";\n            `,\n          },\n          {\n            messageId: 'wat',\n            output: `\n              const name = \"mallory\";\n            `,\n          },\n        ],\n      },\n      {\n        column: 14,\n        data: {},\n        endColumn: 21,\n        endLine: 2,\n        line: 2,\n        messageId: 'whoops',\n      },\n    ]);\n    expect(test).not.toHaveProperty('suggestions');\n  });\n\n  it('should throw if suggestions are not annotated', () => {\n    expect(() =>\n      fromFixture(\n        `\nconst name = \"alice\";\n              ~~~~~~~ [whoops]`,\n        {\n          suggestions: [\n            {\n              messageId: 'wat',\n              output: `\n              const name = \"bob\";\n            `,\n            },\n          ],\n        }\n      )\n    ).toThrow(/no 'suggest' annotation found/);\n  });\n});\n"
  },
  {
    "path": "modules/eslint-plugin/spec/utils/from-fixture.ts",
    "content": "import { TSESLint as eslint } from '@typescript-eslint/utils';\n\nexport function fromFixture<TMessageIds extends string>(\n  fixture: string,\n  invalidTestCase?: {\n    output?: string;\n    suggestions?: readonly eslint.SuggestionOutput<TMessageIds>[] | null;\n  }\n): eslint.InvalidTestCase<TMessageIds, never>;\n\nexport function fromFixture<\n  TMessageIds extends string,\n  TOptions extends readonly unknown[],\n>(\n  fixture: string,\n  invalidTestCase: Omit<\n    eslint.InvalidTestCase<TMessageIds, TOptions>,\n    'code' | 'errors'\n  > & {\n    suggestions?: readonly eslint.SuggestionOutput<TMessageIds>[] | null;\n  }\n): eslint.InvalidTestCase<TMessageIds, TOptions>;\n\nexport function fromFixture<\n  TMessageIds extends string,\n  TOptions extends readonly unknown[],\n>(\n  fixture: string,\n  invalidTestCase: Omit<\n    eslint.InvalidTestCase<TMessageIds, TOptions>,\n    'code' | 'errors'\n  > & {\n    suggestions?: readonly eslint.SuggestionOutput<TMessageIds>[] | null;\n  } = {}\n): eslint.InvalidTestCase<TMessageIds, TOptions> {\n  const { suggestions, ...rest } = invalidTestCase;\n  return {\n    ...rest,\n    ...parseFixture(fixture, suggestions),\n  };\n}\n\nfunction getSuggestions<TMessageIds extends string>(\n  suggestions:\n    | readonly eslint.SuggestionOutput<TMessageIds>[]\n    | null\n    | undefined,\n  suggest: boolean,\n  indices: string | undefined\n) {\n  if (!suggestions || !suggest) {\n    return {};\n  }\n  if (!indices) {\n    return { suggestions } as const;\n  }\n  return {\n    suggestions: indices\n      .split(/\\s+/)\n      .map((index) => suggestions[Number.parseInt(index, 10)]),\n  } as const;\n}\n\nfunction parseFixture<TMessageIds extends string>(\n  fixture: string,\n  suggestions?: readonly eslint.SuggestionOutput<TMessageIds>[] | null\n) {\n  const errorRegExp =\n    /^(?<indent>\\s*)(?<error>~+)\\s*\\[(?<id>\\w+)\\s*(?<data>.*?)(?:\\s*(?<suggest>suggest)\\s*(?<indices>[\\d\\s]*))?\\]\\s*$/;\n  const lines: string[] = [];\n  const errors: eslint.TestCaseError<TMessageIds>[] = [];\n  let suggestFound = false;\n  fixture.split('\\n').forEach((line) => {\n    const match = line.match(errorRegExp);\n    if (match?.groups) {\n      const column = match.groups.indent.length + 1;\n      const endColumn = column + match.groups.error.length;\n      const { length } = lines;\n      errors.push({\n        column,\n        data: JSON.parse(match.groups.data || '{}'),\n        endColumn,\n        endLine: length,\n        line: length,\n        messageId: match.groups.id as TMessageIds,\n        // TODO: Remove type assertion once https://github.com/typescript-eslint/typescript-eslint/pull/3844 is available.\n        ...(getSuggestions(\n          suggestions,\n          Boolean(match.groups.suggest),\n          match.groups.indices?.trim()\n        ) as eslint.TestCaseError<TMessageIds>['suggestions']),\n      });\n      if (match.groups.suggest) {\n        suggestFound = true;\n      }\n    } else {\n      lines.push(line);\n    }\n  });\n  if (suggestions && !suggestFound) {\n    throw new Error(\"Suggestions specified but no 'suggest' annotation found.\");\n  }\n  return {\n    code: lines.join('\\n'),\n    errors,\n  };\n}\n"
  },
  {
    "path": "modules/eslint-plugin/spec/utils/index.ts",
    "content": "export * from './from-fixture';\nexport * from './rule-tester';\n"
  },
  {
    "path": "modules/eslint-plugin/spec/utils/rule-tester.ts",
    "content": "import { RuleTester } from '@typescript-eslint/rule-tester';\nimport { resolve } from 'path';\n\nexport function ruleTester(requiresTypeChecking?: boolean) {\n  const languageOptions =\n    (requiresTypeChecking ?? false)\n      ? {\n          parserOptions: {\n            tsconfigRootDir: resolve('./modules/eslint-plugin/spec/fixtures'),\n            project: './tsconfig.json',\n          },\n        }\n      : undefined;\n  return new RuleTester({\n    languageOptions,\n  });\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/all-type-checked.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/avoid-combining-component-store-selectors\": \"error\",\n    \"@ngrx/avoid-mapping-component-store-selectors\": \"error\",\n    \"@ngrx/require-super-ondestroy\": \"error\",\n    \"@ngrx/updater-explicit-return-type\": \"error\",\n    \"@ngrx/avoid-cyclic-effects\": \"error\",\n    \"@ngrx/no-dispatch-in-effects\": \"error\",\n    \"@ngrx/no-effects-in-providers\": \"error\",\n    \"@ngrx/no-multiple-actions-in-effects\": \"error\",\n    \"@ngrx/prefer-action-creator-in-of-type\": \"error\",\n    \"@ngrx/prefer-effect-callback-in-block-statement\": \"error\",\n    \"@ngrx/use-effects-lifecycle-interface\": \"error\",\n    \"@ngrx/prefer-concat-latest-from\": \"error\",\n    \"@ngrx/enforce-type-call\": \"error\",\n    \"@ngrx/prefer-protected-state\": \"error\",\n    \"@ngrx/signal-state-no-arrays-at-root-level\": \"error\",\n    \"@ngrx/signal-store-feature-should-use-generic-type\": \"error\",\n    \"@ngrx/with-state-no-arrays-at-root-level\": \"error\",\n    \"@ngrx/avoid-combining-selectors\": \"error\",\n    \"@ngrx/avoid-dispatching-multiple-actions-sequentially\": \"error\",\n    \"@ngrx/avoid-duplicate-actions-in-reducer\": \"error\",\n    \"@ngrx/avoid-mapping-selectors\": \"error\",\n    \"@ngrx/good-action-hygiene\": \"error\",\n    \"@ngrx/no-multiple-global-stores\": \"error\",\n    \"@ngrx/no-reducer-in-key-names\": \"error\",\n    \"@ngrx/no-store-subscription\": \"error\",\n    \"@ngrx/no-typed-global-store\": \"error\",\n    \"@ngrx/on-function-explicit-return-type\": \"error\",\n    \"@ngrx/prefer-action-creator-in-dispatch\": \"error\",\n    \"@ngrx/prefer-action-creator\": \"error\",\n    \"@ngrx/prefer-inline-action-props\": \"error\",\n    \"@ngrx/prefer-one-generic-in-create-for-feature-selector\": \"error\",\n    \"@ngrx/prefer-selector-in-select\": \"error\",\n    \"@ngrx/prefix-selectors-with-select\": \"error\",\n    \"@ngrx/select-style\": \"error\",\n    \"@ngrx/use-consistent-global-store-name\": \"error\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/all-type-checked.ts",
    "content": "/**\n * DO NOT EDIT\n * This file is generated\n */\n\nimport type { TSESLint } from '@typescript-eslint/utils';\n\nexport default (\n  plugin: TSESLint.FlatConfig.Plugin,\n  parser: TSESLint.FlatConfig.Parser\n): TSESLint.FlatConfig.ConfigArray => [\n  {\n    name: 'ngrx/base',\n    languageOptions: {\n      parser,\n    },\n    plugins: {\n      '@ngrx': plugin,\n    },\n  },\n  {\n    name: 'ngrx/all-type-checked',\n    languageOptions: {\n      parser,\n    },\n    rules: {\n      '@ngrx/avoid-combining-component-store-selectors': 'error',\n      '@ngrx/avoid-mapping-component-store-selectors': 'error',\n      '@ngrx/require-super-ondestroy': 'error',\n      '@ngrx/updater-explicit-return-type': 'error',\n      '@ngrx/avoid-cyclic-effects': 'error',\n      '@ngrx/no-dispatch-in-effects': 'error',\n      '@ngrx/no-effects-in-providers': 'error',\n      '@ngrx/no-multiple-actions-in-effects': 'error',\n      '@ngrx/prefer-action-creator-in-of-type': 'error',\n      '@ngrx/prefer-effect-callback-in-block-statement': 'error',\n      '@ngrx/use-effects-lifecycle-interface': 'error',\n      '@ngrx/prefer-concat-latest-from': 'error',\n      '@ngrx/enforce-type-call': 'error',\n      '@ngrx/prefer-protected-state': 'error',\n      '@ngrx/signal-state-no-arrays-at-root-level': 'error',\n      '@ngrx/signal-store-feature-should-use-generic-type': 'error',\n      '@ngrx/with-state-no-arrays-at-root-level': 'error',\n      '@ngrx/avoid-combining-selectors': 'error',\n      '@ngrx/avoid-dispatching-multiple-actions-sequentially': 'error',\n      '@ngrx/avoid-duplicate-actions-in-reducer': 'error',\n      '@ngrx/avoid-mapping-selectors': 'error',\n      '@ngrx/good-action-hygiene': 'error',\n      '@ngrx/no-multiple-global-stores': 'error',\n      '@ngrx/no-reducer-in-key-names': 'error',\n      '@ngrx/no-store-subscription': 'error',\n      '@ngrx/no-typed-global-store': 'error',\n      '@ngrx/on-function-explicit-return-type': 'error',\n      '@ngrx/prefer-action-creator-in-dispatch': 'error',\n      '@ngrx/prefer-action-creator': 'error',\n      '@ngrx/prefer-inline-action-props': 'error',\n      '@ngrx/prefer-one-generic-in-create-for-feature-selector': 'error',\n      '@ngrx/prefer-selector-in-select': 'error',\n      '@ngrx/prefix-selectors-with-select': 'error',\n      '@ngrx/select-style': 'error',\n      '@ngrx/use-consistent-global-store-name': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/all.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/avoid-combining-component-store-selectors\": \"error\",\n    \"@ngrx/avoid-mapping-component-store-selectors\": \"error\",\n    \"@ngrx/require-super-ondestroy\": \"error\",\n    \"@ngrx/updater-explicit-return-type\": \"error\",\n    \"@ngrx/no-dispatch-in-effects\": \"error\",\n    \"@ngrx/no-effects-in-providers\": \"error\",\n    \"@ngrx/prefer-action-creator-in-of-type\": \"error\",\n    \"@ngrx/prefer-effect-callback-in-block-statement\": \"error\",\n    \"@ngrx/use-effects-lifecycle-interface\": \"error\",\n    \"@ngrx/prefer-concat-latest-from\": \"error\",\n    \"@ngrx/enforce-type-call\": \"error\",\n    \"@ngrx/prefer-protected-state\": \"error\",\n    \"@ngrx/signal-store-feature-should-use-generic-type\": \"error\",\n    \"@ngrx/avoid-combining-selectors\": \"error\",\n    \"@ngrx/avoid-dispatching-multiple-actions-sequentially\": \"error\",\n    \"@ngrx/avoid-duplicate-actions-in-reducer\": \"error\",\n    \"@ngrx/avoid-mapping-selectors\": \"error\",\n    \"@ngrx/good-action-hygiene\": \"error\",\n    \"@ngrx/no-multiple-global-stores\": \"error\",\n    \"@ngrx/no-reducer-in-key-names\": \"error\",\n    \"@ngrx/no-store-subscription\": \"error\",\n    \"@ngrx/no-typed-global-store\": \"error\",\n    \"@ngrx/on-function-explicit-return-type\": \"error\",\n    \"@ngrx/prefer-action-creator-in-dispatch\": \"error\",\n    \"@ngrx/prefer-action-creator\": \"error\",\n    \"@ngrx/prefer-inline-action-props\": \"error\",\n    \"@ngrx/prefer-one-generic-in-create-for-feature-selector\": \"error\",\n    \"@ngrx/prefer-selector-in-select\": \"error\",\n    \"@ngrx/prefix-selectors-with-select\": \"error\",\n    \"@ngrx/select-style\": \"error\",\n    \"@ngrx/use-consistent-global-store-name\": \"error\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/all.ts",
    "content": "/**\n * DO NOT EDIT\n * This file is generated\n */\n\nimport type { TSESLint } from '@typescript-eslint/utils';\n\nexport default (\n  plugin: TSESLint.FlatConfig.Plugin,\n  parser: TSESLint.FlatConfig.Parser\n): TSESLint.FlatConfig.ConfigArray => [\n  {\n    name: 'ngrx/base',\n    languageOptions: {\n      parser,\n    },\n    plugins: {\n      '@ngrx': plugin,\n    },\n  },\n  {\n    name: 'ngrx/all',\n    languageOptions: {\n      parser,\n    },\n    rules: {\n      '@ngrx/avoid-combining-component-store-selectors': 'error',\n      '@ngrx/avoid-mapping-component-store-selectors': 'error',\n      '@ngrx/require-super-ondestroy': 'error',\n      '@ngrx/updater-explicit-return-type': 'error',\n      '@ngrx/no-dispatch-in-effects': 'error',\n      '@ngrx/no-effects-in-providers': 'error',\n      '@ngrx/prefer-action-creator-in-of-type': 'error',\n      '@ngrx/prefer-effect-callback-in-block-statement': 'error',\n      '@ngrx/use-effects-lifecycle-interface': 'error',\n      '@ngrx/prefer-concat-latest-from': 'error',\n      '@ngrx/enforce-type-call': 'error',\n      '@ngrx/prefer-protected-state': 'error',\n      '@ngrx/signal-store-feature-should-use-generic-type': 'error',\n      '@ngrx/avoid-combining-selectors': 'error',\n      '@ngrx/avoid-dispatching-multiple-actions-sequentially': 'error',\n      '@ngrx/avoid-duplicate-actions-in-reducer': 'error',\n      '@ngrx/avoid-mapping-selectors': 'error',\n      '@ngrx/good-action-hygiene': 'error',\n      '@ngrx/no-multiple-global-stores': 'error',\n      '@ngrx/no-reducer-in-key-names': 'error',\n      '@ngrx/no-store-subscription': 'error',\n      '@ngrx/no-typed-global-store': 'error',\n      '@ngrx/on-function-explicit-return-type': 'error',\n      '@ngrx/prefer-action-creator-in-dispatch': 'error',\n      '@ngrx/prefer-action-creator': 'error',\n      '@ngrx/prefer-inline-action-props': 'error',\n      '@ngrx/prefer-one-generic-in-create-for-feature-selector': 'error',\n      '@ngrx/prefer-selector-in-select': 'error',\n      '@ngrx/prefix-selectors-with-select': 'error',\n      '@ngrx/select-style': 'error',\n      '@ngrx/use-consistent-global-store-name': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/component-store.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/avoid-combining-component-store-selectors\": \"error\",\n    \"@ngrx/avoid-mapping-component-store-selectors\": \"error\",\n    \"@ngrx/require-super-ondestroy\": \"error\",\n    \"@ngrx/updater-explicit-return-type\": \"error\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/component-store.ts",
    "content": "/**\n * DO NOT EDIT\n * This file is generated\n */\n\nimport type { TSESLint } from '@typescript-eslint/utils';\n\nexport default (\n  plugin: TSESLint.FlatConfig.Plugin,\n  parser: TSESLint.FlatConfig.Parser\n): TSESLint.FlatConfig.ConfigArray => [\n  {\n    name: 'ngrx/base',\n    languageOptions: {\n      parser,\n    },\n    plugins: {\n      '@ngrx': plugin,\n    },\n  },\n  {\n    name: 'ngrx/component-store',\n    languageOptions: {\n      parser,\n    },\n    rules: {\n      '@ngrx/avoid-combining-component-store-selectors': 'error',\n      '@ngrx/avoid-mapping-component-store-selectors': 'error',\n      '@ngrx/require-super-ondestroy': 'error',\n      '@ngrx/updater-explicit-return-type': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/effects-type-checked.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/avoid-cyclic-effects\": \"error\",\n    \"@ngrx/no-dispatch-in-effects\": \"error\",\n    \"@ngrx/no-effects-in-providers\": \"error\",\n    \"@ngrx/no-multiple-actions-in-effects\": \"error\",\n    \"@ngrx/prefer-action-creator-in-of-type\": \"error\",\n    \"@ngrx/prefer-effect-callback-in-block-statement\": \"error\",\n    \"@ngrx/use-effects-lifecycle-interface\": \"error\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/effects-type-checked.ts",
    "content": "/**\n * DO NOT EDIT\n * This file is generated\n */\n\nimport type { TSESLint } from '@typescript-eslint/utils';\n\nexport default (\n  plugin: TSESLint.FlatConfig.Plugin,\n  parser: TSESLint.FlatConfig.Parser\n): TSESLint.FlatConfig.ConfigArray => [\n  {\n    name: 'ngrx/base',\n    languageOptions: {\n      parser,\n    },\n    plugins: {\n      '@ngrx': plugin,\n    },\n  },\n  {\n    name: 'ngrx/effects-type-checked',\n    languageOptions: {\n      parser,\n    },\n    rules: {\n      '@ngrx/avoid-cyclic-effects': 'error',\n      '@ngrx/no-dispatch-in-effects': 'error',\n      '@ngrx/no-effects-in-providers': 'error',\n      '@ngrx/no-multiple-actions-in-effects': 'error',\n      '@ngrx/prefer-action-creator-in-of-type': 'error',\n      '@ngrx/prefer-effect-callback-in-block-statement': 'error',\n      '@ngrx/use-effects-lifecycle-interface': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/effects.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/no-dispatch-in-effects\": \"error\",\n    \"@ngrx/no-effects-in-providers\": \"error\",\n    \"@ngrx/prefer-action-creator-in-of-type\": \"error\",\n    \"@ngrx/prefer-effect-callback-in-block-statement\": \"error\",\n    \"@ngrx/use-effects-lifecycle-interface\": \"error\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/effects.ts",
    "content": "/**\n * DO NOT EDIT\n * This file is generated\n */\n\nimport type { TSESLint } from '@typescript-eslint/utils';\n\nexport default (\n  plugin: TSESLint.FlatConfig.Plugin,\n  parser: TSESLint.FlatConfig.Parser\n): TSESLint.FlatConfig.ConfigArray => [\n  {\n    name: 'ngrx/base',\n    languageOptions: {\n      parser,\n    },\n    plugins: {\n      '@ngrx': plugin,\n    },\n  },\n  {\n    name: 'ngrx/effects',\n    languageOptions: {\n      parser,\n    },\n    rules: {\n      '@ngrx/no-dispatch-in-effects': 'error',\n      '@ngrx/no-effects-in-providers': 'error',\n      '@ngrx/prefer-action-creator-in-of-type': 'error',\n      '@ngrx/prefer-effect-callback-in-block-statement': 'error',\n      '@ngrx/use-effects-lifecycle-interface': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/operators.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/prefer-concat-latest-from\": \"error\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/operators.ts",
    "content": "/**\n * DO NOT EDIT\n * This file is generated\n */\n\nimport type { TSESLint } from '@typescript-eslint/utils';\n\nexport default (\n  plugin: TSESLint.FlatConfig.Plugin,\n  parser: TSESLint.FlatConfig.Parser\n): TSESLint.FlatConfig.ConfigArray => [\n  {\n    name: 'ngrx/base',\n    languageOptions: {\n      parser,\n    },\n    plugins: {\n      '@ngrx': plugin,\n    },\n  },\n  {\n    name: 'ngrx/operators',\n    languageOptions: {\n      parser,\n    },\n    rules: {\n      '@ngrx/prefer-concat-latest-from': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/signals-type-checked.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/enforce-type-call\": \"error\",\n    \"@ngrx/prefer-protected-state\": \"error\",\n    \"@ngrx/signal-state-no-arrays-at-root-level\": \"error\",\n    \"@ngrx/signal-store-feature-should-use-generic-type\": \"error\",\n    \"@ngrx/with-state-no-arrays-at-root-level\": \"error\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/signals-type-checked.ts",
    "content": "/**\n * DO NOT EDIT\n * This file is generated\n */\n\nimport type { TSESLint } from '@typescript-eslint/utils';\n\nexport default (\n  plugin: TSESLint.FlatConfig.Plugin,\n  parser: TSESLint.FlatConfig.Parser\n): TSESLint.FlatConfig.ConfigArray => [\n  {\n    name: 'ngrx/base',\n    languageOptions: {\n      parser,\n    },\n    plugins: {\n      '@ngrx': plugin,\n    },\n  },\n  {\n    name: 'ngrx/signals-type-checked',\n    languageOptions: {\n      parser,\n    },\n    rules: {\n      '@ngrx/enforce-type-call': 'error',\n      '@ngrx/prefer-protected-state': 'error',\n      '@ngrx/signal-state-no-arrays-at-root-level': 'error',\n      '@ngrx/signal-store-feature-should-use-generic-type': 'error',\n      '@ngrx/with-state-no-arrays-at-root-level': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/signals.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/enforce-type-call\": \"error\",\n    \"@ngrx/prefer-protected-state\": \"error\",\n    \"@ngrx/signal-store-feature-should-use-generic-type\": \"error\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/signals.ts",
    "content": "/**\n * DO NOT EDIT\n * This file is generated\n */\n\nimport type { TSESLint } from '@typescript-eslint/utils';\n\nexport default (\n  plugin: TSESLint.FlatConfig.Plugin,\n  parser: TSESLint.FlatConfig.Parser\n): TSESLint.FlatConfig.ConfigArray => [\n  {\n    name: 'ngrx/base',\n    languageOptions: {\n      parser,\n    },\n    plugins: {\n      '@ngrx': plugin,\n    },\n  },\n  {\n    name: 'ngrx/signals',\n    languageOptions: {\n      parser,\n    },\n    rules: {\n      '@ngrx/enforce-type-call': 'error',\n      '@ngrx/prefer-protected-state': 'error',\n      '@ngrx/signal-store-feature-should-use-generic-type': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/store.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/avoid-combining-selectors\": \"error\",\n    \"@ngrx/avoid-dispatching-multiple-actions-sequentially\": \"error\",\n    \"@ngrx/avoid-duplicate-actions-in-reducer\": \"error\",\n    \"@ngrx/avoid-mapping-selectors\": \"error\",\n    \"@ngrx/good-action-hygiene\": \"error\",\n    \"@ngrx/no-multiple-global-stores\": \"error\",\n    \"@ngrx/no-reducer-in-key-names\": \"error\",\n    \"@ngrx/no-store-subscription\": \"error\",\n    \"@ngrx/no-typed-global-store\": \"error\",\n    \"@ngrx/on-function-explicit-return-type\": \"error\",\n    \"@ngrx/prefer-action-creator-in-dispatch\": \"error\",\n    \"@ngrx/prefer-action-creator\": \"error\",\n    \"@ngrx/prefer-inline-action-props\": \"error\",\n    \"@ngrx/prefer-one-generic-in-create-for-feature-selector\": \"error\",\n    \"@ngrx/prefer-selector-in-select\": \"error\",\n    \"@ngrx/prefix-selectors-with-select\": \"error\",\n    \"@ngrx/select-style\": \"error\",\n    \"@ngrx/use-consistent-global-store-name\": \"error\"\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/configs/store.ts",
    "content": "/**\n * DO NOT EDIT\n * This file is generated\n */\n\nimport type { TSESLint } from '@typescript-eslint/utils';\n\nexport default (\n  plugin: TSESLint.FlatConfig.Plugin,\n  parser: TSESLint.FlatConfig.Parser\n): TSESLint.FlatConfig.ConfigArray => [\n  {\n    name: 'ngrx/base',\n    languageOptions: {\n      parser,\n    },\n    plugins: {\n      '@ngrx': plugin,\n    },\n  },\n  {\n    name: 'ngrx/store',\n    languageOptions: {\n      parser,\n    },\n    rules: {\n      '@ngrx/avoid-combining-selectors': 'error',\n      '@ngrx/avoid-dispatching-multiple-actions-sequentially': 'error',\n      '@ngrx/avoid-duplicate-actions-in-reducer': 'error',\n      '@ngrx/avoid-mapping-selectors': 'error',\n      '@ngrx/good-action-hygiene': 'error',\n      '@ngrx/no-multiple-global-stores': 'error',\n      '@ngrx/no-reducer-in-key-names': 'error',\n      '@ngrx/no-store-subscription': 'error',\n      '@ngrx/no-typed-global-store': 'error',\n      '@ngrx/on-function-explicit-return-type': 'error',\n      '@ngrx/prefer-action-creator-in-dispatch': 'error',\n      '@ngrx/prefer-action-creator': 'error',\n      '@ngrx/prefer-inline-action-props': 'error',\n      '@ngrx/prefer-one-generic-in-create-for-feature-selector': 'error',\n      '@ngrx/prefer-selector-in-select': 'error',\n      '@ngrx/prefix-selectors-with-select': 'error',\n      '@ngrx/select-style': 'error',\n      '@ngrx/use-consistent-global-store-name': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "modules/eslint-plugin/src/index.ts",
    "content": "import { rules } from './rules';\nimport all from './configs/all.json';\nimport allTypeChecked from './configs/all-type-checked';\nimport componentStore from './configs/component-store.json';\nimport effects from './configs/effects.json';\nimport effectsTypeChecked from './configs/effects-type-checked';\nimport store from './configs/store.json';\nimport operators from './configs/operators.json';\nimport signals from './configs/signals.json';\nimport signalsTypeChecked from './configs/signals-type-checked';\n\nexport = {\n  configs: {\n    all,\n    'all-type-checked': allTypeChecked,\n    'component-store': componentStore,\n    effects: effects,\n    'effects-type-checked': effectsTypeChecked,\n    store: store,\n    operators: operators,\n    signals: signals,\n    'signals-type-checked': signalsTypeChecked,\n  },\n  rules,\n};\n"
  },
  {
    "path": "modules/eslint-plugin/src/rule-creator.ts",
    "content": "import { ESLintUtils } from '@typescript-eslint/utils';\nimport { NGRX_MODULE } from './utils';\n\nexport interface NgRxRuleDocs {\n  ngrxModule: NGRX_MODULE;\n  requiresTypeChecking?: boolean;\n}\n\nexport type NgRxRule = ReturnType<\n  ReturnType<typeof ESLintUtils.RuleCreator<NgRxRuleDocs>>\n>;\n\n/**\n * We need to patch the RuleCreator in order to preserve the defaultOptions\n * to use as part of documentation generation.\n */\nconst patchedRuleCreator: typeof ESLintUtils.RuleCreator = (urlCreator) => {\n  return function createRule({ name, meta, defaultOptions, create }) {\n    return {\n      meta: Object.assign(Object.assign({}, meta), {\n        docs: Object.assign(Object.assign({}, meta.docs), {\n          url: urlCreator(name),\n        }),\n      }),\n      defaultOptions,\n      create(context) {\n        const optionsWithDefault = ESLintUtils.applyDefault(\n          defaultOptions,\n          context.options\n        );\n        return create(context, optionsWithDefault);\n      },\n    };\n  };\n};\npatchedRuleCreator.withoutDocs = ESLintUtils.RuleCreator.withoutDocs;\n\nexport const createRule = patchedRuleCreator<NgRxRuleDocs>(\n  (ruleName) => `https://ngrx.io/guide/eslint-plugin/rules/${ruleName}`\n);\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/component-store/avoid-combining-component-store-selectors.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { getNgrxComponentStoreNames, namedExpression } from '../../utils';\nexport const messageId = 'avoidCombiningComponentStoreSelectors';\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'Prefer combining selectors at the selector level.',\n      ngrxModule: 'component-store',\n    },\n    schema: [],\n    messages: {\n      [messageId]: 'Combine selectors at the selector level.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const storeNames = getNgrxComponentStoreNames(context);\n\n    const thisSelects = `CallExpression[callee.object.type='ThisExpression'][callee.property.name='select']`;\n    const storeSelects = storeNames ? namedExpression(storeNames) : null;\n\n    const selectsInArray: TSESTree.CallExpression[] = [];\n    return {\n      [`ClassDeclaration[superClass.name=/Store/] CallExpression[callee.name='combineLatest'] ${thisSelects} ~ ${thisSelects}`](\n        node: TSESTree.CallExpression\n      ) {\n        selectsInArray.push(node);\n      },\n      [`CallExpression[callee.name='combineLatest'] ${storeSelects} ~ ${storeSelects}`](\n        node: TSESTree.CallExpression\n      ) {\n        selectsInArray.push(node);\n      },\n      [`CallExpression[callee.name='combineLatest']:exit`]() {\n        for (const node of selectsInArray) {\n          context.report({\n            node,\n            messageId,\n          });\n        }\n        selectsInArray.length = 0;\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/component-store/avoid-mapping-component-store-selectors.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  getNgrxComponentStoreNames,\n  namedCallableExpression,\n} from '../../utils';\n\nexport const messageId = 'avoidMappingComponentStoreSelectors';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'Avoid mapping logic outside the selector level.',\n      ngrxModule: 'component-store',\n    },\n    schema: [],\n    messages: {\n      [messageId]: 'Map logic at the selector level instead.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const storeNames = getNgrxComponentStoreNames(context);\n\n    const mapOperatorSelector = `[callee.property.name=pipe] > CallExpression[callee.name=map]`;\n    const selectors = [\n      `ClassDeclaration[superClass.name=/Store/] CallExpression:has(CallExpression[callee.object.type='ThisExpression'][callee.property.name='select'])${mapOperatorSelector}`,\n      storeNames &&\n        `${namedCallableExpression(storeNames)}${mapOperatorSelector}`,\n    ]\n      .filter(Boolean)\n      .join(',');\n\n    return {\n      [selectors](node: TSESTree.ArrowFunctionExpression) {\n        context.report({\n          node,\n          messageId,\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/component-store/require-super-ondestroy.ts",
    "content": "import * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { TSESTree } from '@typescript-eslint/types';\n\nexport const messageId = 'requireSuperOnDestroy';\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'Overriden ngOnDestroy method in component stores require a call to super.ngOnDestroy().',\n      ngrxModule: 'component-store',\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        \"Call super.ngOnDestroy() inside a component store's ngOnDestroy method.\",\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const ngOnDestroyMethodSelector = `MethodDefinition[key.name='ngOnDestroy']`;\n    const componentStoreClassName = 'ComponentStore';\n\n    let hasNgrxComponentStoreImport = false;\n\n    return {\n      [`ImportDeclaration[source.value='@ngrx/component-store'] ImportSpecifier[imported.name='${componentStoreClassName}']`](\n        _: TSESTree.ImportSpecifier\n      ) {\n        hasNgrxComponentStoreImport = true;\n      },\n      [`ClassDeclaration[superClass.name=${componentStoreClassName}] ${ngOnDestroyMethodSelector}:not(:has(CallExpression[callee.object.type='Super'][callee.property.name='ngOnDestroy'])) > .key`](\n        node: TSESTree.Identifier\n      ) {\n        if (!hasNgrxComponentStoreImport) {\n          return;\n        }\n\n        context.report({\n          node,\n          messageId,\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/component-store/updater-explicit-return-type.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { getNgrxComponentStoreNames, namedExpression } from '../../utils';\n\nexport const messageId = 'updaterExplicitReturnType';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description: '`Updater` should have an explicit return type.',\n      ngrxModule: 'component-store',\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        '`Updater` should have an explicit return type when using arrow functions: `this.store.updater((state, value): State => {}`.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const storeNames = getNgrxComponentStoreNames(context);\n    const withoutTypeAnnotation = `ArrowFunctionExpression:not([returnType.typeAnnotation])`;\n    const selectors = [\n      `ClassDeclaration[superClass.name=/Store/] CallExpression[callee.object.type='ThisExpression'][callee.property.name='updater'] > ${withoutTypeAnnotation}`,\n      storeNames &&\n        `${namedExpression(\n          storeNames\n        )}[callee.property.name='updater'] > ${withoutTypeAnnotation}`,\n    ]\n      .filter(Boolean)\n      .join(',');\n\n    return {\n      [selectors](node: TSESTree.ArrowFunctionExpression) {\n        context.report({\n          node,\n          messageId,\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/effects/avoid-cyclic-effects.ts",
    "content": "import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport { createRule } from '../../rule-creator';\nimport {\n  asPattern,\n  createEffectExpression,\n  getNgRxEffectActions,\n  isCallExpression,\n  isIdentifier,\n  isTypeReference,\n} from '../../utils';\n\nexport const messageId = 'avoidCyclicEffects';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\n// This rule is a modified version (to support dispatch: false) from the eslint-plugin-rxjs plugin.\n// The original implementation can be found at https://github.com/cartant/eslint-plugin-rxjs/blob/main/source/rules/no-cyclic-action.ts\n// Thank you Nicholas Jamieson (@cartant).\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'Avoid `Effect` that re-emit filtered actions.',\n      ngrxModule: 'effects',\n      requiresTypeChecking: true,\n    },\n    schema: [],\n    messages: {\n      [messageId]: '`Effect` that re-emit filtered actions are forbidden.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const { identifiers = [] } = getNgRxEffectActions(context);\n    const actionsNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!actionsNames) {\n      return {};\n    }\n\n    const services = ESLintUtils.getParserServices(context);\n    const typeChecker = services.program.getTypeChecker();\n\n    function checkNode(pipeCallExpression: TSESTree.CallExpression) {\n      const operatorCallExpression = pipeCallExpression.arguments.find(\n        (arg) =>\n          isCallExpression(arg) &&\n          isIdentifier(arg.callee) &&\n          arg.callee.name === 'ofType'\n      );\n      if (!operatorCallExpression) {\n        return;\n      }\n      const operatorType = services.getTypeAtLocation(operatorCallExpression);\n      const [signature] = typeChecker.getSignaturesOfType(\n        operatorType,\n        ts.SignatureKind.Call\n      );\n\n      if (!signature) {\n        return;\n      }\n      const operatorReturnType =\n        typeChecker.getReturnTypeOfSignature(signature);\n      if (!isTypeReference(operatorReturnType)) {\n        return;\n      }\n      const [operatorElementType] =\n        typeChecker.getTypeArguments(operatorReturnType);\n      if (!operatorElementType) {\n        return;\n      }\n\n      const pipeType = services.getTypeAtLocation(pipeCallExpression);\n      if (!isTypeReference(pipeType)) {\n        return;\n      }\n      const [pipeElementType] = typeChecker.getTypeArguments(pipeType);\n      if (!pipeElementType) {\n        return;\n      }\n\n      const operatorActionTypes = getActionTypes(operatorElementType);\n      const pipeActionTypes = getActionTypes(pipeElementType);\n\n      for (const actionType of operatorActionTypes) {\n        if (pipeActionTypes.includes(actionType)) {\n          context.report({\n            node: pipeCallExpression.callee,\n            messageId,\n          });\n          return;\n        }\n      }\n    }\n\n    function getActionType(symbol: ts.Symbol): ts.Type | null {\n      const { valueDeclaration } = symbol;\n\n      if (!valueDeclaration) {\n        return null;\n      }\n\n      if (valueDeclaration.kind === ts.SyntaxKind.PropertyDeclaration) {\n        const { parent } = symbol as typeof symbol & { parent: ts.Symbol };\n        return parent.valueDeclaration\n          ? typeChecker.getTypeOfSymbolAtLocation(\n              parent,\n              parent.valueDeclaration\n            )\n          : null;\n      }\n\n      return typeChecker.getTypeOfSymbolAtLocation(symbol, valueDeclaration);\n    }\n\n    function getActionTypes(type: ts.Type): string[] {\n      if (type.isUnion()) {\n        const memberActionTypes: string[] = [];\n        for (const memberType of type.types) {\n          memberActionTypes.push(...getActionTypes(memberType));\n        }\n        return memberActionTypes;\n      }\n\n      const symbol = typeChecker.getPropertyOfType(type, 'type');\n\n      if (!symbol) {\n        return [];\n      }\n\n      const actionType = getActionType(symbol);\n\n      if (!actionType) {\n        return [];\n      }\n\n      // TODO: support \"dynamic\" types\n      // e.g. const genericFoo = createAction(`${subject} FOO`); (resolves to 'string')\n      if (typeChecker.typeToString(actionType) === 'string') {\n        return [];\n      }\n      return [typeChecker.typeToString(actionType)];\n    }\n\n    let firstPipe = true;\n    return {\n      [`${createEffectExpression}:not([arguments.1]:has(Property[key.name='dispatch'][value.value=false])) CallExpression[callee.property.name='pipe'][callee.object.property.name=${actionsNames}]`](\n        node\n      ) {\n        if (firstPipe) {\n          checkNode(node);\n          firstPipe = false;\n          return;\n        }\n      },\n\n      [`${createEffectExpression}:not([arguments.1]:has(Property[key.name='dispatch'][value.value=false])) CallExpression[callee.property.name='pipe']:exit`]() {\n        firstPipe = true;\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/effects/no-dispatch-in-effects.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  asPattern,\n  dispatchInEffects,\n  getNgRxStores,\n  isArrowFunctionExpression,\n  isReturnStatement,\n} from '../../utils';\n\nexport const noDispatchInEffects = 'noDispatchInEffects';\nexport const noDispatchInEffectsSuggest = 'noDispatchInEffectsSuggest';\n\ntype MessageIds =\n  | typeof noDispatchInEffects\n  | typeof noDispatchInEffectsSuggest;\ntype Options = readonly [];\ntype MemberExpressionWithinCallExpression = TSESTree.MemberExpression & {\n  parent: TSESTree.CallExpression;\n};\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description: '`Effect` should not call `store.dispatch`.',\n      ngrxModule: 'effects',\n    },\n    schema: [],\n    messages: {\n      [noDispatchInEffects]:\n        'Calling `store.dispatch` in `Effect` is forbidden.',\n      [noDispatchInEffectsSuggest]: 'Remove `store.dispatch`.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const { identifiers = [] } = getNgRxStores(context);\n    const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!storeNames) {\n      return {};\n    }\n\n    return {\n      [dispatchInEffects(storeNames)](\n        node: MemberExpressionWithinCallExpression\n      ) {\n        const nodeToReport = getNodeToReport(node);\n        context.report({\n          node: nodeToReport,\n          messageId: noDispatchInEffects,\n          suggest: [\n            {\n              messageId: noDispatchInEffectsSuggest,\n              fix: (fixer) => fixer.remove(nodeToReport),\n            },\n          ],\n        });\n      },\n    };\n  },\n});\n\nfunction getNodeToReport(node: MemberExpressionWithinCallExpression) {\n  const { parent } = node;\n  const { parent: grandParent } = parent;\n  return grandParent &&\n    (isArrowFunctionExpression(grandParent) || isReturnStatement(grandParent))\n    ? node\n    : parent;\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/effects/no-effects-in-providers.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  effectsInNgModuleImports,\n  effectsInNgModuleProviders,\n  getNodeToCommaRemoveFix,\n  ngModuleDecorator,\n} from '../../utils';\n\nexport const messageId = 'noEffectsInProviders';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        '`Effect` should not be listed as a provider if it is added to the `EffectsModule`.',\n      ngrxModule: 'effects',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      [messageId]:\n        '`Effect` should not be listed as a provider if it is added to the `EffectsModule`.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const effectsInProviders = new Set<TSESTree.Identifier>();\n    const effectsInImports = new Set<string>();\n\n    return {\n      [effectsInNgModuleProviders](node: TSESTree.Identifier) {\n        effectsInProviders.add(node);\n      },\n      [effectsInNgModuleImports]({ name }: TSESTree.Identifier) {\n        effectsInImports.add(name);\n      },\n      [`${ngModuleDecorator}:exit`]() {\n        for (const effectInProvider of effectsInProviders) {\n          if (!effectsInImports.has(effectInProvider.name)) {\n            continue;\n          }\n\n          context.report({\n            node: effectInProvider,\n            messageId,\n            fix: (fixer) =>\n              getNodeToCommaRemoveFix(\n                context.sourceCode,\n                fixer,\n                effectInProvider\n              ),\n          });\n        }\n\n        effectsInImports.clear();\n        effectsInProviders.clear();\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/effects/no-multiple-actions-in-effects.ts",
    "content": "import {\n  AST_NODE_TYPES,\n  ESLintUtils,\n  type TSESTree,\n} from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  createEffectExpression,\n  isBlockStatement,\n  isReturnStatement,\n  mapLikeOperatorCallExpressions,\n} from '../../utils';\n\nexport const messageId = 'noMultipleActionsInEffects';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly unknown[];\ntype EffectsMapLikeOperatorsReturn =\n  | TSESTree.ArrowFunctionExpression\n  | TSESTree.CallExpression\n  | TSESTree.FunctionExpression\n  | TSESTree.ReturnStatement;\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description: '`Effect` should not return multiple actions.',\n      ngrxModule: 'effects',\n      requiresTypeChecking: true,\n    },\n    schema: [],\n    messages: {\n      [messageId]: '`Effect` should return a single action.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [`${createEffectExpression} ${mapLikeOperatorCallExpressions}`](\n        node: EffectsMapLikeOperatorsReturn\n      ) {\n        const nodeToReport = getNodeToReport(node);\n        if (!nodeToReport) {\n          return;\n        }\n\n        const services = ESLintUtils.getParserServices(context);\n        const typeChecker = services.program.getTypeChecker();\n        const type = services.getTypeAtLocation(nodeToReport);\n\n        if (typeChecker.isArrayType(type)) {\n          context.report({\n            node: nodeToReport,\n            messageId,\n          });\n        } else if (\n          type.isUnion() &&\n          type.types.some((ut) => typeChecker.isArrayType(ut))\n        ) {\n          context.report({\n            node: nodeToReport,\n            messageId,\n          });\n        }\n      },\n    };\n  },\n});\n\nfunction getNodeToReport(node: EffectsMapLikeOperatorsReturn) {\n  switch (node.type) {\n    case AST_NODE_TYPES.ArrowFunctionExpression:\n    case AST_NODE_TYPES.FunctionExpression:\n      return isBlockStatement(node.body)\n        ? findReturnStatement(node.body.body)\n        : node.body;\n    case AST_NODE_TYPES.CallExpression:\n      return findReturnStatement(node.arguments) ?? node.arguments[0];\n    default:\n      return node.argument;\n  }\n}\n\nfunction findReturnStatement(nodes: TSESTree.Node[]) {\n  const returnNode = nodes.find((n): n is TSESTree.ReturnStatement =>\n    isReturnStatement(n)\n  );\n  return returnNode?.argument;\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/effects/prefer-action-creator-in-of-type.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\n\nexport const messageId = 'preferActionCreatorInOfType';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'Using `action creator` in `ofType` is preferred over `string`.',\n      ngrxModule: 'effects',\n    },\n    schema: [],\n    messages: {\n      [messageId]: 'Using `string` is forbidden. Use `action creator` instead.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [`CallExpression[callee.name='ofType'] Literal`](node: TSESTree.Literal) {\n        context.report({\n          node,\n          messageId,\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/effects/prefer-effect-callback-in-block-statement.ts",
    "content": "import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { ASTUtils } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { createEffectExpression } from '../../utils';\n\nexport const messageId = 'preferEffectCallbackInBlockStatement';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'A block statement is easier to troubleshoot.',\n      ngrxModule: 'effects',\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        'The callback of `Effect` should be wrapped in a block statement.',\n    },\n    fixable: 'code',\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const nonParametrizedEffect =\n      `${createEffectExpression} > ArrowFunctionExpression > .body[type!=/^(ArrowFunctionExpression|BlockStatement)$/]` as const;\n    const parametrizedEffect =\n      `${createEffectExpression} > ArrowFunctionExpression > ArrowFunctionExpression > .body[type!='BlockStatement']` as const;\n    const parametrizedEffectWithinBlockStatement =\n      `${createEffectExpression} > ArrowFunctionExpression > BlockStatement > ReturnStatement > ArrowFunctionExpression > .body[type!='BlockStatement']` as const;\n\n    return {\n      [`${nonParametrizedEffect}, ${parametrizedEffect}, ${parametrizedEffectWithinBlockStatement}`](\n        node: TSESTree.ArrowFunctionExpression['body']\n      ) {\n        context.report({\n          node,\n          messageId,\n          fix: (fixer) => {\n            const [previousNode, nextNode] = getSafeNodesToApplyFix(\n              context.sourceCode,\n              node\n            );\n            return [\n              fixer.insertTextBefore(previousNode, `{ return `),\n              fixer.insertTextAfter(nextNode, ` }`),\n            ];\n          },\n        });\n      },\n    };\n  },\n});\n\nfunction getSafeNodesToApplyFix(\n  sourceCode: Readonly<TSESLint.SourceCode>,\n  node: TSESTree.Node\n) {\n  const previousToken = sourceCode.getTokenBefore(node);\n  const nextToken = sourceCode.getTokenAfter(node);\n\n  if (\n    previousToken &&\n    ASTUtils.isOpeningParenToken(previousToken) &&\n    nextToken &&\n    ASTUtils.isClosingParenToken(nextToken)\n  ) {\n    return [previousToken, nextToken] as const;\n  }\n\n  return [node, node] as const;\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/effects/use-effects-lifecycle-interface.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  getImplementsSchemaFixer,\n  getImportAddFix,\n  getInterface,\n  NGRX_MODULE_PATHS,\n} from '../../utils';\n\nexport const messageId = 'useEffectsLifecycleInterface';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'Ensures classes implement lifecycle interfaces corresponding to the declared lifecycle methods.',\n      ngrxModule: 'effects',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      [messageId]:\n        'Lifecycle interface `{{ interfaceName }}` should be implemented for method `{{ methodName }}`.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const lifecycleMapper = {\n      ngrxOnIdentifyEffects: 'OnIdentifyEffects',\n      ngrxOnInitEffects: 'OnInitEffects',\n      ngrxOnRunEffects: 'OnRunEffects',\n    } as const;\n    const lifecyclesPattern = Object.keys(lifecycleMapper).join('|');\n\n    return {\n      [`ClassDeclaration > ClassBody > MethodDefinition > Identifier[name=/${lifecyclesPattern}/]`](\n        node: TSESTree.Identifier & {\n          name: keyof typeof lifecycleMapper;\n          parent: TSESTree.MethodDefinition & {\n            parent: TSESTree.ClassBody & { parent: TSESTree.ClassDeclaration };\n          };\n        }\n      ) {\n        const classDeclaration = node.parent.parent.parent;\n        const methodName = node.name;\n        const interfaceName = lifecycleMapper[methodName];\n\n        if (getInterface(classDeclaration, interfaceName)) {\n          return;\n        }\n\n        context.report({\n          fix: (fixer) => {\n            const { implementsNodeReplace, implementsTextReplace } =\n              getImplementsSchemaFixer(classDeclaration, interfaceName);\n            return [\n              fixer.insertTextAfter(\n                implementsNodeReplace,\n                implementsTextReplace\n              ),\n            ].concat(\n              getImportAddFix({\n                compatibleWithTypeOnlyImport: true,\n                fixer,\n                importName: interfaceName,\n                moduleName: NGRX_MODULE_PATHS.effects,\n                node: classDeclaration,\n              })\n            );\n          },\n          node,\n          messageId,\n          data: {\n            interfaceName,\n            methodName,\n          },\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/index.ts",
    "content": "// component-store\nimport avoidCombiningComponentStoreSelectors from './component-store/avoid-combining-component-store-selectors';\nimport avoidMappingComponentStoreSelectors from './component-store/avoid-mapping-component-store-selectors';\nimport updaterExplicitReturnType from './component-store/updater-explicit-return-type';\nimport requireSuperOnDestroy from './component-store/require-super-ondestroy';\n// effects\nimport avoidCyclicEffects from './effects/avoid-cyclic-effects';\nimport noDispatchInEffects from './effects/no-dispatch-in-effects';\nimport noEffectsInProviders from './effects/no-effects-in-providers';\nimport noMultipleActionsInEffects from './effects/no-multiple-actions-in-effects';\nimport preferActionCreatorInOfType from './effects/prefer-action-creator-in-of-type';\nimport preferEffectCallbackInBlockStatement from './effects/prefer-effect-callback-in-block-statement';\nimport useEffectsLifecycleInterface from './effects/use-effects-lifecycle-interface';\n// store\nimport avoidCombiningSelectors from './store/avoid-combining-selectors';\nimport avoidDispatchingMultipleActionsSequentially from './store/avoid-dispatching-multiple-actions-sequentially';\nimport avoidDuplicateActionsInReducer from './store/avoid-duplicate-actions-in-reducer';\nimport avoidMappingSelectors from './store/avoid-mapping-selectors';\nimport goodActionHygiene from './store/good-action-hygiene';\nimport noMultipleGlobalStores from './store/no-multiple-global-stores';\nimport noReducerInKeyNames from './store/no-reducer-in-key-names';\nimport noStoreSubscription from './store/no-store-subscription';\nimport noTypedGlobalStore from './store/no-typed-global-store';\nimport onFunctionExplicitReturnType from './store/on-function-explicit-return-type';\nimport preferActionCreator from './store/prefer-action-creator';\nimport preferActionCreatorInDispatch from './store/prefer-action-creator-in-dispatch';\nimport preferInlineActionProps from './store/prefer-inline-action-props';\nimport preferOneGenericInCreateForFeatureSelector from './store/prefer-one-generic-in-create-for-feature-selector';\nimport preferSelectorInSelect from './store/prefer-selector-in-select';\nimport prefixSelectorsWithSelect from './store/prefix-selectors-with-select';\nimport selectStyle from './store/select-style';\nimport useConsistentGlobalStoreName from './store/use-consistent-global-store-name';\n// operators\nimport preferConcatLatestFrom from './operators/prefer-concat-latest-from';\n// signals\nimport signalStateNoArraysAtRootLevel from './signals/signal-state-no-arrays-at-root-level';\nimport signalStoreFeatureShouldUseGenericType from './signals/signal-store-feature-should-use-generic-type';\nimport withStateNoArraysAtRootLevel from './signals/with-state-no-arrays-at-root-level';\nimport preferProtectedState from './signals/prefer-protected-state';\nimport enforceTypeCall from './signals/enforce-type-call';\n\nexport const rules = {\n  // component-store\n  'avoid-combining-component-store-selectors':\n    avoidCombiningComponentStoreSelectors,\n  'avoid-mapping-component-store-selectors':\n    avoidMappingComponentStoreSelectors,\n  'updater-explicit-return-type': updaterExplicitReturnType,\n  'require-super-ondestroy': requireSuperOnDestroy,\n  //effects\n  'avoid-cyclic-effects': avoidCyclicEffects,\n  'no-dispatch-in-effects': noDispatchInEffects,\n  'no-effects-in-providers': noEffectsInProviders,\n  'no-multiple-actions-in-effects': noMultipleActionsInEffects,\n  'prefer-action-creator-in-of-type': preferActionCreatorInOfType,\n  'prefer-effect-callback-in-block-statement':\n    preferEffectCallbackInBlockStatement,\n  'use-effects-lifecycle-interface': useEffectsLifecycleInterface,\n  // store\n  'avoid-combining-selectors': avoidCombiningSelectors,\n  'avoid-dispatching-multiple-actions-sequentially':\n    avoidDispatchingMultipleActionsSequentially,\n  'avoid-duplicate-actions-in-reducer': avoidDuplicateActionsInReducer,\n  'avoid-mapping-selectors': avoidMappingSelectors,\n  'good-action-hygiene': goodActionHygiene,\n  'no-multiple-global-stores': noMultipleGlobalStores,\n  'no-reducer-in-key-names': noReducerInKeyNames,\n  'no-store-subscription': noStoreSubscription,\n  'no-typed-global-store': noTypedGlobalStore,\n  'on-function-explicit-return-type': onFunctionExplicitReturnType,\n  'prefer-action-creator': preferActionCreator,\n  'prefer-action-creator-in-dispatch': preferActionCreatorInDispatch,\n  'prefer-inline-action-props': preferInlineActionProps,\n  'prefer-one-generic-in-create-for-feature-selector':\n    preferOneGenericInCreateForFeatureSelector,\n  'prefer-selector-in-select': preferSelectorInSelect,\n  'prefix-selectors-with-select': prefixSelectorsWithSelect,\n  'select-style': selectStyle,\n  'use-consistent-global-store-name': useConsistentGlobalStoreName,\n  // operators\n  'prefer-concat-latest-from': preferConcatLatestFrom,\n  // signals\n  'signal-state-no-arrays-at-root-level': signalStateNoArraysAtRootLevel,\n  'signal-store-feature-should-use-generic-type':\n    signalStoreFeatureShouldUseGenericType,\n  'prefer-protected-state': preferProtectedState,\n  'with-state-no-arrays-at-root-level': withStateNoArraysAtRootLevel,\n  'enforce-type-call': enforceTypeCall,\n};\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/operators/prefer-concat-latest-from.ts",
    "content": "import {\n  AST_NODE_TYPES,\n  type TSESLint,\n  type TSESTree,\n} from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  asPattern,\n  createEffectExpression,\n  getImportAddFix,\n  getNgRxEffectActions,\n  namedExpression,\n  NGRX_MODULE_PATHS,\n} from '../../utils';\n\nexport const messageId = 'preferConcatLatestFrom';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [{ readonly strict: boolean }];\ntype WithLatestFromIdentifier = TSESTree.Identifier & {\n  parent: TSESTree.CallExpression;\n};\n\nconst defaultOptions: Options[number] = { strict: false };\nconst concatLatestFromKeyword = 'concatLatestFrom';\nconst withLatestFromKeyword = 'withLatestFrom';\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description: `Use \\`${concatLatestFromKeyword}\\` instead of \\`${withLatestFromKeyword}\\` to prevent the selector from firing until the correct \\`Action\\` is dispatched.`,\n      ngrxModule: 'operators',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          strict: {\n            type: 'boolean',\n            default: defaultOptions.strict,\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      [messageId]: `Use \\`${concatLatestFromKeyword}\\` instead of \\`${withLatestFromKeyword}\\`.`,\n    },\n  },\n  defaultOptions: [defaultOptions],\n  create: (context, [options]) => {\n    if (options.strict) {\n      return {\n        [`${createEffectExpression} CallExpression > Identifier[name='withLatestFrom']`](\n          node: WithLatestFromIdentifier\n        ) {\n          context.report({\n            node,\n            messageId,\n            fix: (fixer) => getFixes(context.sourceCode, fixer, node),\n          });\n        },\n      };\n    }\n\n    const { identifiers = [], sourceCode } = getNgRxEffectActions(context);\n    const actionsNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!actionsNames) {\n      return {};\n    }\n\n    return {\n      [`${createEffectExpression} ${namedExpression(\n        actionsNames\n      )} > CallExpression[arguments.length=1] > Identifier[name='${withLatestFromKeyword}']`](\n        node: WithLatestFromIdentifier\n      ) {\n        context.report({\n          node,\n          messageId,\n          fix: (fixer) => getFixes(sourceCode, fixer, node),\n        });\n      },\n      [`${createEffectExpression} ${namedExpression(\n        actionsNames\n      )} > CallExpression[arguments.length>1] > Identifier[name='${withLatestFromKeyword}']`](\n        node: WithLatestFromIdentifier\n      ) {\n        context.report({\n          node,\n          messageId,\n        });\n      },\n    };\n  },\n});\n\nfunction getFixes(\n  sourceCode: Readonly<TSESLint.SourceCode>,\n  fixer: TSESLint.RuleFixer,\n  node: WithLatestFromIdentifier\n) {\n  const { parent } = node;\n  const isUsingDeprecatedProjectorArgument = parent.arguments.length > 1;\n  const [firstArgument] = parent.arguments;\n  const nextToken =\n    isUsingDeprecatedProjectorArgument &&\n    sourceCode.getTokenAfter(firstArgument);\n  return [\n    fixer.replaceText(node, concatLatestFromKeyword),\n    ...(firstArgument.type == AST_NODE_TYPES.ArrowFunctionExpression\n      ? []\n      : [fixer.insertTextBefore(firstArgument, '() => ')]),\n  ].concat(\n    getImportAddFix({\n      fixer,\n      importName: concatLatestFromKeyword,\n      moduleName: NGRX_MODULE_PATHS.operators,\n      node,\n    }),\n    ...(isUsingDeprecatedProjectorArgument && nextToken\n      ? [\n          getImportAddFix({\n            fixer,\n            importName: 'map',\n            moduleName: 'rxjs/operators',\n            node,\n          }),\n          fixer.insertTextAfterRange(nextToken.range, '), map('),\n        ]\n      : [])\n  );\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/signals/enforce-type-call.ts",
    "content": "import { type TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { isCallExpression, isIdentifier } from '../../utils';\n\nexport const enforceTypeCall = 'enforceTypeCall';\n\ntype MessageIds = typeof enforceTypeCall;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'The `type` function must be called.',\n      ngrxModule: 'signals',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      [enforceTypeCall]: 'The `{{name}}` function must be called.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    // It's possible that we have multiple type import aliases, so we need to track them all.\n    const typeNames = new Set<string>();\n\n    return {\n      [`ImportDeclaration[source.value='@ngrx/signals'] ImportSpecifier[imported.name='type']`](\n        node: TSESTree.ImportSpecifier\n      ) {\n        typeNames.add(node.local.name);\n      },\n\n      TSInstantiationExpression(node: TSESTree.TSInstantiationExpression) {\n        const expression = node.expression;\n        if (\n          isIdentifier(expression) &&\n          typeNames.has(expression.name) &&\n          !isCallExpression(node.parent)\n        ) {\n          context.report({\n            node: expression,\n            messageId: enforceTypeCall,\n            data: { name: expression.name },\n            fix: (fixer) => fixer.insertTextAfter(node, '()'),\n          });\n        }\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/signals/prefer-protected-state.ts",
    "content": "import { type TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\n\nexport const preferProtectedState = 'preferProtectedState';\nexport const preferProtectedStateSuggest = 'preferProtectedStateSuggest';\n\ntype MessageIds =\n  | typeof preferProtectedState\n  | typeof preferProtectedStateSuggest;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description: `A Signal Store prefers protected state`,\n      ngrxModule: 'signals',\n    },\n    schema: [],\n    messages: {\n      [preferProtectedState]:\n        '{ protectedState: false } should be removed to prevent external state mutations.',\n      [preferProtectedStateSuggest]: 'Remove `{protectedState: false}`.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [`CallExpression[callee.name=signalStore][arguments.length>0] > ObjectExpression[properties.length>0] > Property[key.name=protectedState][value.value=false]`](\n        node: TSESTree.Property\n      ) {\n        context.report({\n          node,\n          messageId: preferProtectedState,\n          suggest: [\n            {\n              messageId: preferProtectedStateSuggest,\n              fix: (fixer) => {\n                const getRangeToBeRemoved = (): Parameters<\n                  typeof fixer.removeRange\n                >[0] => {\n                  const parentObject = node.parent as TSESTree.ObjectExpression;\n                  const parentObjectHasOnlyOneProperty =\n                    parentObject.properties.length === 1;\n\n                  if (parentObjectHasOnlyOneProperty) {\n                    /**\n                     * Remove the entire object if it contains only one property - the relevant one\n                     */\n                    return parentObject.range;\n                  }\n\n                  const tokenAfter = context.sourceCode.getTokenAfter(node);\n                  const tokenAfterIsComma = tokenAfter?.value?.trim() === ',';\n                  /**\n                   * Remove the specific property if there is more than one property in the parent\n                   */\n                  return [\n                    node.range[0],\n                    /**\n                     *  remove trailing comma as well\n                     */\n                    tokenAfterIsComma ? tokenAfter.range[1] : node.range[1],\n                  ];\n                };\n\n                return fixer.removeRange(getRangeToBeRemoved());\n              },\n            },\n          ],\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/signals/signal-state-no-arrays-at-root-level.ts",
    "content": "import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { isArrayExpression } from '../../utils';\n\nexport const messageId = 'signalStateNoArraysAtRootLevel';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nconst NON_RECORD_TYPES = [\n  'Array',\n  'Set',\n  'Map',\n  'WeakSet',\n  'WeakMap',\n  'Date',\n  'Error',\n  'RegExp',\n  'ArrayBuffer',\n  'DataView',\n  'Promise',\n  'Function',\n];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description: `signalState should accept a record or dictionary as an input argument.`,\n      ngrxModule: 'signals',\n      requiresTypeChecking: true,\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        'The property type `{{ property }}` is forbidden as the initial state argument, wrap the property in a record or dictionary.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [`CallExpression[callee.name=signalState]`](\n        node: TSESTree.CallExpression\n      ) {\n        const [argument] = node.arguments;\n        if (isArrayExpression(argument)) {\n          context.report({\n            node: argument,\n            messageId,\n            data: { property: 'Array' },\n          });\n        } else if (argument) {\n          const services = ESLintUtils.getParserServices(context);\n          const typeChecker = services.program.getTypeChecker();\n          const type = services.getTypeAtLocation(argument);\n\n          if (typeChecker.isArrayType(type) || typeChecker.isTupleType(type)) {\n            context.report({\n              node: argument,\n              messageId,\n              data: { property: 'Array' },\n            });\n            return;\n          }\n\n          const symbol = type.getSymbol();\n          if (symbol && NON_RECORD_TYPES.includes(symbol.getName())) {\n            context.report({\n              node: argument,\n              messageId,\n              data: { property: symbol.getName() },\n            });\n            return;\n          }\n\n          const callSignatures = type.getCallSignatures();\n          if (callSignatures.length > 0) {\n            context.report({\n              node: argument,\n              messageId,\n              data: { property: 'Function' },\n            });\n            return;\n          }\n\n          const typeString = typeChecker.typeToString(type);\n          const matchedType = NON_RECORD_TYPES.find((t) =>\n            typeString.startsWith(`${t}<`)\n          );\n          if (matchedType) {\n            context.report({\n              node: argument,\n              messageId,\n              data: { property: matchedType },\n            });\n          }\n        }\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/signals/signal-store-feature-should-use-generic-type.ts",
    "content": "import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  isArrowFunctionExpression,\n  isCallExpression,\n  isFunctionDeclaration,\n  isIdentifier,\n} from '../../utils';\n\nexport const messageId = 'signalStoreFeatureShouldUseGenericType';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description: `A custom Signal Store feature that accepts an input should define a generic type.`,\n      ngrxModule: 'signals',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      [messageId]: `Add an unused generic type to the function creating the signal store feature.`,\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    function report(\n      signalStoreFeature: TSESTree.CallExpression,\n      func?: TSESTree.Node\n    ) {\n      if (\n        !func ||\n        (!isFunctionDeclaration(func) && !isArrowFunctionExpression(func))\n      ) {\n        return;\n      }\n      const parentHasGenerics =\n        func.typeParameters && func.typeParameters.params.length > 0;\n      if (!parentHasGenerics) {\n        context.report({\n          node: signalStoreFeature.callee,\n          messageId,\n          fix(fixer) {\n            if (isFunctionDeclaration(func)) {\n              if (func.id) {\n                return fixer.insertTextAfter(func.id, '<_>');\n              }\n            }\n\n            return fixer.insertTextBefore(func, '<_>');\n          },\n        });\n      }\n    }\n\n    function hasInputAsArgument(node: TSESTree.CallExpression) {\n      const [inputArg] = node.arguments;\n      return (\n        !isCallExpression(inputArg) ||\n        (isIdentifier(inputArg.callee) && inputArg.callee.name === 'type')\n      );\n    }\n\n    return {\n      [`ArrowFunctionExpression > CallExpression[callee.name=signalStoreFeature]`](\n        node: TSESTree.CallExpression\n      ) {\n        if (hasInputAsArgument(node)) {\n          report(node, node.parent);\n        }\n      },\n      [`ArrowFunctionExpression > BlockStatement CallExpression[callee.name=signalStoreFeature]`](\n        node: TSESTree.CallExpression\n      ) {\n        if (hasInputAsArgument(node)) {\n          let parent: TSESTree.Node | undefined = node.parent;\n          while (parent && !isArrowFunctionExpression(parent)) {\n            parent = parent.parent;\n          }\n          report(node, parent);\n        }\n      },\n      [`FunctionDeclaration > BlockStatement CallExpression[callee.name=signalStoreFeature]`](\n        node: TSESTree.CallExpression\n      ) {\n        if (hasInputAsArgument(node)) {\n          let parent: TSESTree.Node | undefined = node.parent;\n          while (parent && !isFunctionDeclaration(parent)) {\n            parent = parent.parent;\n          }\n          report(node, parent);\n        }\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/signals/with-state-no-arrays-at-root-level.ts",
    "content": "import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { isArrayExpression } from '../../utils';\n\nexport const messageId = 'withStateNoArraysAtRootLevel';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nconst NON_RECORD_TYPES = [\n  'Array',\n  'Set',\n  'Map',\n  'WeakSet',\n  'WeakMap',\n  'Date',\n  'Error',\n  'RegExp',\n  'ArrayBuffer',\n  'DataView',\n  'Promise',\n  'Function',\n];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'problem',\n    docs: {\n      description: `withState should accept a record or dictionary as an input argument.`,\n      ngrxModule: 'signals',\n      requiresTypeChecking: true,\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        'The property type `{{ property }}` is forbidden as the initial state argument, wrap the property in a record or dictionary.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [`CallExpression[callee.name=withState]`](node: TSESTree.CallExpression) {\n        const [argument] = node.arguments;\n        if (isArrayExpression(argument)) {\n          context.report({\n            node: argument,\n            messageId,\n            data: { property: 'Array' },\n          });\n        } else if (argument) {\n          const services = ESLintUtils.getParserServices(context);\n          const typeChecker = services.program.getTypeChecker();\n\n          let type = services.getTypeAtLocation(argument);\n          const callSignatures = type.getCallSignatures();\n          if (callSignatures.length > 0) {\n            type = typeChecker.getReturnTypeOfSignature(callSignatures[0]);\n          }\n\n          if (typeChecker.isArrayType(type) || typeChecker.isTupleType(type)) {\n            context.report({\n              node: argument,\n              messageId,\n              data: { property: 'Array' },\n            });\n            return;\n          }\n\n          const symbol = type.getSymbol();\n          if (symbol && NON_RECORD_TYPES.includes(symbol.getName())) {\n            context.report({\n              node: argument,\n              messageId,\n              data: { property: symbol.getName() },\n            });\n            return;\n          }\n\n          if (type.getCallSignatures().length > 0) {\n            context.report({\n              node: argument,\n              messageId,\n              data: { property: 'Function' },\n            });\n            return;\n          }\n\n          const typeString = typeChecker.typeToString(type);\n\n          if (typeString === 'void') {\n            context.report({\n              node: argument,\n              messageId,\n              data: { property: 'Function' },\n            });\n            return;\n          }\n\n          const matchedType = NON_RECORD_TYPES.find((t) =>\n            typeString.startsWith(`${t}<`)\n          );\n          if (matchedType) {\n            context.report({\n              node: argument,\n              messageId,\n              data: { property: matchedType },\n            });\n          }\n        }\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/avoid-combining-selectors.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  asPattern,\n  getNgRxStores,\n  namedExpression,\n  selectExpression,\n} from '../../utils';\n\nexport const messageId = 'avoidCombiningSelectors';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'Prefer combining selectors at the selector level.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [messageId]: 'Combine selectors at the selector level.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const { identifiers = [] } = getNgRxStores(context);\n    const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!storeNames) {\n      return {};\n    }\n\n    const pipeableOrStoreSelect = `:matches(${namedExpression(\n      storeNames\n    )}[callee.property.name='pipe']:has(CallExpression[callee.name='select']), ${selectExpression(\n      storeNames\n    )})` as const;\n\n    const selectsInArray: TSESTree.CallExpression[] = [];\n    return {\n      [`CallExpression[callee.name='combineLatest'] ${pipeableOrStoreSelect} ~ ${pipeableOrStoreSelect}`](\n        node: TSESTree.CallExpression\n      ) {\n        selectsInArray.push(node);\n      },\n      [`CallExpression[callee.name='combineLatest']:exit`]() {\n        for (const node of selectsInArray) {\n          context.report({\n            node,\n            messageId,\n          });\n        }\n        selectsInArray.length = 0;\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/avoid-dispatching-multiple-actions-sequentially.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { asPattern, dispatchExpression, getNgRxStores } from '../../utils';\n\nexport const messageId = 'avoidDispatchingMultipleActionsSequentially';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'It is recommended to only dispatch one `Action` at a time.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        'Avoid dispatching many actions in a row to accomplish a larger conceptual \"transaction\".',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const { identifiers = [] } = getNgRxStores(context);\n    const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!storeNames) {\n      return {};\n    }\n\n    const collectedDispatches: TSESTree.CallExpression[] = [];\n\n    return {\n      [`BlockStatement > ExpressionStatement > ${dispatchExpression(\n        storeNames\n      )}`](node: TSESTree.CallExpression) {\n        collectedDispatches.push(node);\n      },\n      'BlockStatement:exit'() {\n        const withSameParent = collectedDispatches.filter((d1) =>\n          collectedDispatches.some(\n            (d2) => d2 !== d1 && d2.parent?.parent === d1.parent?.parent\n          )\n        );\n        if (withSameParent.length > 1) {\n          for (const node of withSameParent) {\n            context.report({\n              node,\n              messageId,\n            });\n          }\n        }\n\n        collectedDispatches.length = 0;\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/avoid-duplicate-actions-in-reducer.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { createReducer, getNodeToCommaRemoveFix } from '../../utils';\n\nexport const avoidDuplicateActionsInReducer = 'avoidDuplicateActionsInReducer';\nexport const avoidDuplicateActionsInReducerSuggest =\n  'avoidDuplicateActionsInReducerSuggest';\n\ntype MessageIds =\n  | typeof avoidDuplicateActionsInReducer\n  | typeof avoidDuplicateActionsInReducerSuggest;\ntype Options = readonly [];\ntype Action = TSESTree.Identifier & { parent: TSESTree.CallExpression };\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description: 'A `Reducer` should handle an `Action` once.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [avoidDuplicateActionsInReducer]:\n        'The `Reducer` handles a duplicate `Action` `{{ actionName }}`.',\n      [avoidDuplicateActionsInReducerSuggest]: 'Remove this duplication.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const collectedActions = new Map<string, Action[]>();\n\n    return {\n      [`${createReducer} > CallExpression[callee.name='on'][arguments.0.type='Identifier']`]({\n        arguments: [action],\n      }: TSESTree.CallExpression & {\n        arguments: Action[];\n      }) {\n        const actions = collectedActions.get(action.name) ?? [];\n        collectedActions.set(action.name, [...actions, action]);\n      },\n      [`${createReducer}:exit`]() {\n        for (const [actionName, identifiers] of collectedActions) {\n          if (identifiers.length <= 1) {\n            break;\n          }\n\n          for (const node of identifiers) {\n            context.report({\n              node,\n              messageId: avoidDuplicateActionsInReducer,\n              data: {\n                actionName,\n              },\n              suggest: [\n                {\n                  messageId: avoidDuplicateActionsInReducerSuggest,\n                  fix: (fixer) =>\n                    getNodeToCommaRemoveFix(\n                      context.sourceCode,\n                      fixer,\n                      node.parent\n                    ),\n                },\n              ],\n            });\n          }\n        }\n\n        collectedActions.clear();\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/avoid-mapping-selectors.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  asPattern,\n  getNgRxStores,\n  isCallExpression,\n  isIdentifier,\n  namedCallableExpression,\n  pipeExpression,\n} from '../../utils';\n\nexport const messageId = 'avoidMapppingSelectors';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'Avoid mapping logic outside the selector level.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [messageId]: 'Map logic at the selector level instead.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const { identifiers = [] } = getNgRxStores(context);\n    const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!storeNames) {\n      return {};\n    }\n\n    const pipeWithSelectAndMapSelector = `${pipeExpression(\n      storeNames\n    )}:has(CallExpression[callee.name='select'] ~ CallExpression[callee.name='map'])` as const;\n    const selectSelector = `${namedCallableExpression(\n      storeNames\n    )}[callee.object.callee.property.name='select']` as const;\n\n    function isInCreateEffect(node: TSESTree.CallExpression) {\n      let parent: TSESTree.Node | undefined = node.parent;\n      while (parent) {\n        if (\n          isCallExpression(parent) &&\n          isIdentifier(parent.callee) &&\n          parent.callee.name === 'createEffect'\n        ) {\n          return true;\n        }\n        parent = parent.parent;\n      }\n      return false;\n    }\n\n    let pipeHasThisExpression = false;\n\n    const selectorQuery = `:matches(${selectSelector}, ${pipeWithSelectAndMapSelector})`;\n    return {\n      [`${selectorQuery} > CallExpression:has(ThisExpression)`](\n        _node: TSESTree.CallExpression\n      ) {\n        pipeHasThisExpression = true;\n      },\n      [`${selectorQuery}[callee.property.name=pipe]:exit`](\n        node: TSESTree.CallExpression\n      ) {\n        if (pipeHasThisExpression) {\n          pipeHasThisExpression = false;\n          return;\n        }\n\n        if (isInCreateEffect(node)) {\n          return;\n        }\n\n        const operators = node.arguments;\n        const mapOperator = operators.find(\n          (operator) =>\n            isCallExpression(operator) &&\n            isIdentifier(operator.callee) &&\n            operator.callee.name === 'map'\n        );\n        if (mapOperator) {\n          context.report({\n            node: mapOperator,\n            messageId,\n          });\n        }\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/good-action-hygiene.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { actionCreatorWithLiteral } from '../../utils';\n\nexport const messageId = 'goodActionHygiene';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'Ensures the use of good action hygiene.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        'Action type `{{ actionType }}` does not follow the good action hygiene practice, use \"[Source] {{ actionType }}\" to define action types.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const sourceEventPattern = /[[].*[\\]]\\s.*/;\n\n    return {\n      [actionCreatorWithLiteral]({\n        arguments: [node],\n      }: Omit<TSESTree.CallExpression, 'arguments'> & {\n        arguments: TSESTree.StringLiteral[];\n      }) {\n        const { value: actionType } = node;\n\n        if (sourceEventPattern.test(actionType)) {\n          return;\n        }\n\n        context.report({\n          node,\n          messageId,\n          data: {\n            actionType,\n          },\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/no-multiple-global-stores.ts",
    "content": "import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  getNgRxStores,\n  getNodeToCommaRemoveFix,\n  isTSParameterProperty,\n} from '../../utils';\n\nexport const noMultipleGlobalStores = 'noMultipleGlobalStores';\nexport const noMultipleGlobalStoresSuggest = 'noMultipleGlobalStoresSuggest';\n\ntype MessageIds =\n  | typeof noMultipleGlobalStores\n  | typeof noMultipleGlobalStoresSuggest;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description: 'There should only be one global store injected.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [noMultipleGlobalStores]: 'Global store should be injected only once.',\n      [noMultipleGlobalStoresSuggest]: 'Remove this reference.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      Program() {\n        const { identifiers = [], sourceCode } = getNgRxStores(context);\n        const flattenedIdentifiers = groupBy(identifiers).values();\n\n        for (const identifiers of flattenedIdentifiers) {\n          if (identifiers.length <= 1) {\n            continue;\n          }\n\n          for (const node of identifiers) {\n            const nodeToReport = getNodeToReport(node);\n            context.report({\n              node: nodeToReport,\n              messageId: noMultipleGlobalStores,\n              suggest: [\n                {\n                  messageId: noMultipleGlobalStoresSuggest,\n                  fix: (fixer) => getFixes(sourceCode, fixer, nodeToReport),\n                },\n              ],\n            });\n          }\n        }\n      },\n    };\n  },\n});\n\nfunction getNodeToReport(node: TSESTree.Node) {\n  return node.parent && isTSParameterProperty(node.parent) ? node.parent : node;\n}\n\nfunction getFixes(\n  sourceCode: Readonly<TSESLint.SourceCode>,\n  fixer: TSESLint.RuleFixer,\n  node: TSESTree.Node\n) {\n  const { parent } = node;\n  const nodeToRemove = parent && isTSParameterProperty(parent) ? parent : node;\n  return getNodeToCommaRemoveFix(sourceCode, fixer, nodeToRemove);\n}\n\ntype Identifiers = NonNullable<ReturnType<typeof getNgRxStores>['identifiers']>;\n\nfunction groupBy(identifiers: Identifiers): Map<TSESTree.Node, Identifiers> {\n  return identifiers.reduce<Map<TSESTree.Node, Identifiers>>(\n    (accumulator, identifier) => {\n      const parent = isTSParameterProperty(identifier.parent)\n        ? identifier.parent.parent\n        : identifier.parent;\n      const collectedIdentifiers = accumulator.get(parent);\n      return accumulator.set(parent, [\n        ...(collectedIdentifiers ?? []),\n        identifier,\n      ]);\n    },\n    new Map()\n  );\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/no-reducer-in-key-names.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  actionReducerMap,\n  getRawText,\n  metadataProperty,\n  storeActionReducerMap,\n} from '../../utils';\n\nexport const noReducerInKeyNames = 'noReducerInKeyNames';\nexport const noReducerInKeyNamesSuggest = 'noReducerInKeyNamesSuggest';\n\ntype MessageIds =\n  | typeof noReducerInKeyNames\n  | typeof noReducerInKeyNamesSuggest;\ntype Options = readonly [];\n\nconst reducerKeyword = 'reducer';\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description: `Avoid the word \"${reducerKeyword}\" in the key names.`,\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [noReducerInKeyNames]: `Avoid the word \"${reducerKeyword}\" in the key names to better represent the state.`,\n      [noReducerInKeyNamesSuggest]: `Remove the word \"${reducerKeyword}\".`,\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [`:matches(${storeActionReducerMap}, ${actionReducerMap}) > ${metadataProperty(\n        /reducer/i\n      )} > .key`](node: TSESTree.Property['key']) {\n        context.report({\n          node,\n          messageId: noReducerInKeyNames,\n          suggest: [\n            {\n              messageId: noReducerInKeyNamesSuggest,\n              fix: (fixer) => {\n                const keyName = getRawText(node);\n\n                if (!keyName) {\n                  return null;\n                }\n\n                return fixer.replaceText(\n                  node,\n                  keyName.replace(new RegExp(reducerKeyword, 'i'), '')\n                );\n              },\n            },\n          ],\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/no-store-subscription.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { asPattern, getNgRxStores, namedCallableExpression } from '../../utils';\n\nexport const messageId = 'noStoreSubscription';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'Using the `async` pipe is preferred over `store` subscription.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        '`Store` subscription is forbidden. Use the `async` pipe instead.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const { identifiers = [] } = getNgRxStores(context);\n    const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!storeNames) {\n      return {};\n    }\n\n    return {\n      [`${namedCallableExpression(\n        storeNames\n      )} > MemberExpression > Identifier[name='subscribe']`](\n        node: TSESTree.Identifier\n      ) {\n        context.report({\n          node,\n          messageId,\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/no-typed-global-store.ts",
    "content": "import * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  getNgRxStores,\n  isPropertyDefinition,\n  isTSTypeReference,\n  isCallExpression,\n  isTSInstantiationExpression,\n} from '../../utils';\nimport type { TSESTree } from '@typescript-eslint/utils';\n\nexport const noTypedStore = 'noTypedStore';\nexport const noTypedStoreSuggest = 'noTypedStoreSuggest';\n\ntype MessageIds = typeof noTypedStore | typeof noTypedStoreSuggest;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description: 'The global store should not be typed.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [noTypedStore]:\n        '`Store` should not be typed, use `Store` (without generic) instead.',\n      [noTypedStoreSuggest]: 'Remove generic from `Store`.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      Program() {\n        const { identifiers = [] } = getNgRxStores(context);\n\n        for (const identifier of identifiers) {\n          // using inject()\n          if (!identifier.typeAnnotation) {\n            const { parent } = identifier;\n            if (\n              isPropertyDefinition(parent) &&\n              parent.value &&\n              isCallExpression(parent.value) &&\n              parent.value.arguments.length\n            ) {\n              const [storeArgument] = parent.value.arguments;\n              if (isTSInstantiationExpression(storeArgument)) {\n                report(storeArgument.typeArguments);\n              }\n            }\n\n            continue;\n          }\n\n          if (\n            !isTSTypeReference(identifier.typeAnnotation.typeAnnotation) ||\n            !identifier.typeAnnotation.typeAnnotation.typeArguments\n          ) {\n            continue;\n          }\n\n          report(identifier.typeAnnotation.typeAnnotation.typeArguments);\n        }\n      },\n    };\n\n    function report(typeArguments: TSESTree.TSTypeParameterInstantiation) {\n      context.report({\n        node: typeArguments,\n        messageId: noTypedStore,\n        suggest: [\n          {\n            messageId: noTypedStoreSuggest,\n            fix: (fixer) => fixer.remove(typeArguments),\n          },\n        ],\n      });\n    }\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/on-function-explicit-return-type.ts",
    "content": "import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { ASTUtils } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { getLast, onFunctionWithoutType } from '../../utils';\n\nexport const onFunctionExplicitReturnType = 'onFunctionExplicitReturnType';\nexport const onFunctionExplicitReturnTypeSuggest =\n  'onFunctionExplicitReturnTypeSuggest';\n\ntype MessageIds =\n  | typeof onFunctionExplicitReturnType\n  | typeof onFunctionExplicitReturnTypeSuggest;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description: '`On` function should have an explicit return type.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [onFunctionExplicitReturnType]:\n        '`On` functions should have an explicit return type when using arrow functions: `on(action, (state): State => {}`.',\n      [onFunctionExplicitReturnTypeSuggest]:\n        'Add the explicit return type `State` (if the interface/type is named differently you need to manually correct the return type).',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [onFunctionWithoutType](node: TSESTree.ArrowFunctionExpression) {\n        context.report({\n          node,\n          messageId: onFunctionExplicitReturnType,\n          suggest: [\n            {\n              messageId: onFunctionExplicitReturnTypeSuggest,\n              fix: (fixer) => getFixes(node, context.sourceCode, fixer),\n            },\n          ],\n        });\n      },\n    };\n  },\n});\n\nfunction getFixes(\n  node: TSESTree.ArrowFunctionExpression,\n  sourceCode: Readonly<TSESLint.SourceCode>,\n  fixer: TSESLint.RuleFixer\n) {\n  const { params } = node;\n\n  if (params.length === 0) {\n    const [, closingParen] = sourceCode.getTokens(node);\n    return fixer.insertTextAfter(closingParen, ': State');\n  }\n\n  const [firstParam] = params;\n  const lastParam = getLast(params);\n  const previousToken = sourceCode.getTokenBefore(firstParam);\n  const isParenthesized =\n    previousToken && ASTUtils.isOpeningParenToken(previousToken);\n\n  if (isParenthesized) {\n    const nextToken = sourceCode.getTokenAfter(lastParam);\n    return fixer.insertTextAfter(nextToken ?? lastParam, ': State');\n  }\n\n  return [\n    fixer.insertTextBefore(firstParam, '('),\n    fixer.insertTextAfter(lastParam, '): State'),\n  ] as const;\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/prefer-action-creator-in-dispatch.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  asPattern,\n  dispatchExpression,\n  getNearestUpperNodeFrom,\n  getNgRxStores,\n  isCallExpression,\n  isCallExpressionWith,\n} from '../../utils';\n\nexport const messageId = 'preferActionCreatorInDispatch';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'Using `action creator` in `dispatch` is preferred over `object` or old `Action`.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        'Using `object` or old `Action` is forbidden. Use `action creator` instead.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const { identifiers = [] } = getNgRxStores(context);\n    const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!storeNames) {\n      return {};\n    }\n\n    return {\n      [`${dispatchExpression(\n        storeNames\n      )} :matches(NewExpression, :not(NewExpression) > ObjectExpression)`](\n        node: TSESTree.NewExpression | TSESTree.ObjectExpression\n      ) {\n        const nearestUpperCallExpression = getNearestUpperNodeFrom(\n          node,\n          isCallExpression\n        );\n        const isStoreDispatchImmediateParent =\n          nearestUpperCallExpression !== undefined &&\n          isCallExpressionWith(\n            nearestUpperCallExpression,\n            storeNames,\n            'dispatch'\n          );\n\n        if (!isStoreDispatchImmediateParent) {\n          return;\n        }\n\n        context.report({\n          node,\n          messageId,\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/prefer-action-creator.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\n\nexport const messageId = 'preferActionCreator';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'Using `action creator` is preferred over `Action class`.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        'Using `Action class` is forbidden. Use `action creator` instead.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [`ClassDeclaration:has(TSClassImplements:matches([expression.name='Action'], [expression.property.name='Action'])):has(PropertyDefinition[key.name='type'])`](\n        node: TSESTree.ClassDeclaration\n      ) {\n        context.report({\n          node,\n          messageId,\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/prefer-inline-action-props.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { actionCreatorPropsComputed } from '../../utils';\n\nexport const preferInlineActionProps = 'preferInlineActionProps';\nexport const preferInlineActionPropsSuggest = 'preferInlineActionPropsSuggest';\n\ntype MessageIds =\n  | typeof preferInlineActionProps\n  | typeof preferInlineActionPropsSuggest;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description:\n        'Prefer using inline types instead of interfaces, types or classes.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [preferInlineActionProps]:\n        'Use inline types instead of interfaces, types or classes.',\n      [preferInlineActionPropsSuggest]: 'Change to inline types.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [actionCreatorPropsComputed](node: TSESTree.TSTypeReference) {\n        context.report({\n          node,\n          messageId: preferInlineActionProps,\n          suggest: [\n            {\n              messageId: preferInlineActionPropsSuggest,\n              fix: (fixer) => [\n                fixer.insertTextBefore(node, '{name: '),\n                fixer.insertTextAfter(node, '}'),\n              ],\n            },\n          ],\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/prefer-one-generic-in-create-for-feature-selector.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\n\nexport const preferOneGenericInCreateForFeatureSelector =\n  'preferOneGenericInCreateForFeatureSelector';\nexport const preferOneGenericInCreateForFeatureSelectorSuggest =\n  'preferOneGenericInCreateForFeatureSelectorSuggest';\n\ntype MessageIds =\n  | typeof preferOneGenericInCreateForFeatureSelector\n  | typeof preferOneGenericInCreateForFeatureSelectorSuggest;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description: 'Prefer using a single generic to define the feature state.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [preferOneGenericInCreateForFeatureSelector]:\n        'Use a single generic to define the feature state.',\n      [preferOneGenericInCreateForFeatureSelectorSuggest]:\n        'Remove the global state generic.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    return {\n      [`CallExpression[callee.name='createFeatureSelector'] > TSTypeParameterInstantiation[params.length>1]`](\n        node: TSESTree.TSTypeParameterInstantiation\n      ) {\n        context.report({\n          node,\n          messageId: preferOneGenericInCreateForFeatureSelector,\n          suggest: [\n            {\n              messageId: preferOneGenericInCreateForFeatureSelectorSuggest,\n              fix: (fixer) => {\n                const [globalState] = node.params;\n                const nextToken = context.sourceCode.getTokenAfter(globalState);\n                return fixer.removeRange([\n                  globalState.range[0],\n                  nextToken?.range[1] ?? globalState.range[1] + 1,\n                ]);\n              },\n            },\n          ],\n        });\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/prefer-selector-in-select.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  asPattern,\n  getNgRxStores,\n  isArrowFunctionExpression,\n  isFunctionExpression,\n  isLiteral,\n  pipeableSelect,\n  selectExpression,\n} from '../../utils';\n\nexport const messageId = 'preferSelectorInSelect';\n\ntype MessageIds = typeof messageId;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'Using a selector in the `select` is preferred over `string` or `props drilling`.',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [messageId]:\n        'Using `string` or `props drilling` is forbidden. Use a selector instead.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    const { identifiers = [] } = getNgRxStores(context);\n    const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!storeNames) {\n      return {};\n    }\n\n    return {\n      [`${pipeableSelect(storeNames)}, ${selectExpression(storeNames)}`](\n        node: TSESTree.CallExpression\n      ) {\n        for (const argument of node.arguments) {\n          if (\n            !isLiteral(argument) &&\n            !isArrowFunctionExpression(argument) &&\n            !isFunctionExpression(argument)\n          ) {\n            break;\n          }\n\n          context.report({\n            node: argument,\n            messageId,\n          });\n        }\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/prefix-selectors-with-select.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { capitalize } from '../../utils';\n\nexport const prefixSelectorsWithSelect = 'prefixSelectorsWithSelect';\nexport const prefixSelectorsWithSelectSuggest =\n  'prefixSelectorsWithSelectSuggest';\n\ntype MessageIds =\n  | typeof prefixSelectorsWithSelect\n  | typeof prefixSelectorsWithSelectSuggest;\ntype Options = readonly [];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description:\n        'The selector should start with \"select\", for example \"selectEntity\".',\n      ngrxModule: 'store',\n    },\n    schema: [],\n    messages: {\n      [prefixSelectorsWithSelect]: 'The selector should start with \"select\".',\n      [prefixSelectorsWithSelectSuggest]:\n        'Prefix the selector with \"select\": `{{ name }}`.',\n    },\n  },\n  defaultOptions: [],\n  create: (context) => {\n    function reportIfInvalid(name: string, node: TSESTree.Identifier) {\n      // Name starts with select and\n      // the first character after select is an uppercase ASCII letter, _, or $\n      const isValid =\n        name.startsWith('select') &&\n        name.length > 'select'.length &&\n        /^[A-Z_$]/.test(name.slice('select'.length));\n\n      if (!isValid) {\n        const suggestedName = getSuggestedName(name);\n        context.report({\n          node,\n          loc: {\n            start: node.loc.start,\n            end: {\n              line: node.loc.start.line,\n              column: node.loc.start.column + name.length,\n            },\n          },\n          messageId: prefixSelectorsWithSelect,\n          suggest: [\n            {\n              messageId: prefixSelectorsWithSelectSuggest,\n              data: { name: suggestedName },\n              fix: (fixer) => {\n                const parent = node.parent;\n                const sourceCode =\n                  context.sourceCode ?? context.getSourceCode();\n\n                // Handle destructuring: { selectAll: allItems }\n                if (\n                  parent &&\n                  parent.type === 'Property' &&\n                  parent.value === node &&\n                  parent.parent &&\n                  parent.parent.type === 'ObjectPattern'\n                ) {\n                  return fixer.replaceText(node, suggestedName);\n                }\n\n                // Handle simple variable declarator: const allItems = ...\n                if (\n                  parent &&\n                  parent.type === 'VariableDeclarator' &&\n                  parent.id.type === 'Identifier'\n                ) {\n                  const typeAnnotation = parent.id.typeAnnotation\n                    ? sourceCode.getText(parent.id.typeAnnotation)\n                    : '';\n                  return fixer.replaceText(\n                    parent.id,\n                    `${suggestedName}${typeAnnotation}`\n                  );\n                }\n\n                // Fallback: just replace the identifier\n                return fixer.replaceText(node, suggestedName);\n              },\n            },\n          ],\n        });\n      }\n    }\n\n    function isSelectorFactoryCall(node: TSESTree.CallExpression): boolean {\n      const callee = node.callee;\n      return (\n        callee.type === 'Identifier' &&\n        [\n          'createSelector',\n          'createFeatureSelector',\n          'createSelectorFactory',\n        ].includes(callee.name)\n      );\n    }\n\n    function checkFunctionBody(\n      name: string,\n      node: TSESTree.Identifier,\n      body: TSESTree.BlockStatement | TSESTree.Expression\n    ) {\n      if (body.type === 'CallExpression' && isSelectorFactoryCall(body)) {\n        reportIfInvalid(name, node);\n      }\n\n      if (body.type === 'BlockStatement') {\n        for (const stmt of body.body) {\n          if (\n            stmt.type === 'ReturnStatement' &&\n            stmt.argument &&\n            stmt.argument.type === 'CallExpression' &&\n            isSelectorFactoryCall(stmt.argument)\n          ) {\n            reportIfInvalid(name, node);\n          }\n        }\n      }\n    }\n\n    return {\n      VariableDeclarator(node: TSESTree.VariableDeclarator) {\n        const { id, init } = node;\n\n        const isSelectorSource =\n          init?.type === 'CallExpression' &&\n          ((init.callee.type === 'Identifier' &&\n            init.callee.name === 'getSelectors') ||\n            (init.callee.type === 'MemberExpression' &&\n              init.callee.property.type === 'Identifier' &&\n              init.callee.property.name === 'getSelectors'));\n\n        if (id.type === 'ObjectPattern' && isSelectorSource) {\n          for (const prop of id.properties) {\n            if (prop.type === 'Property' && prop.value.type === 'Identifier') {\n              reportIfInvalid(prop.value.name, prop.value);\n            }\n          }\n          return;\n        }\n\n        if (id.type === 'Identifier') {\n          const typeName =\n            node.id.typeAnnotation?.typeAnnotation.type === 'TSTypeReference' &&\n            node.id.typeAnnotation.typeAnnotation.typeName.type === 'Identifier'\n              ? node.id.typeAnnotation.typeAnnotation.typeName.name\n              : null;\n\n          const hasSelectorType =\n            typeName !== null &&\n            [\n              'MemoizedSelector',\n              'MemoizedSelectorWithProps',\n              'Selector',\n              'SelectorWithProps',\n            ].includes(typeName);\n\n          const isSelectorCall =\n            init?.type === 'CallExpression' && isSelectorFactoryCall(init);\n\n          const isArrowFunction =\n            init?.type === 'ArrowFunctionExpression' &&\n            init.body &&\n            (init.body.type === 'CallExpression' ||\n              init.body.type === 'BlockStatement');\n\n          const isFunctionExpression =\n            init?.type === 'FunctionExpression' &&\n            init.body &&\n            init.body.type === 'BlockStatement';\n\n          if (hasSelectorType || isSelectorCall) {\n            reportIfInvalid(id.name, id);\n          } else if (isArrowFunction || isFunctionExpression) {\n            checkFunctionBody(id.name, id, init.body);\n          }\n        }\n      },\n    };\n  },\n});\n\nfunction getSuggestedName(name: string): string {\n  const selectWord = 'select';\n\n  if (name.startsWith(selectWord)) {\n    const rest = name.slice(selectWord.length);\n    if (rest.length === 0) {\n      return 'selectSelect';\n    }\n    if (/^[A-Z_]+$/.test(rest)) {\n      return `${selectWord}${rest}`;\n    }\n    return `${selectWord}${capitalize(rest)}`;\n  }\n\n  if (/^get([^a-z].+)/.test(name)) {\n    const rest = name.slice(3);\n    return `${selectWord}${capitalize(rest)}`;\n  }\n\n  if (/^[A-Z_]+$/.test(name)) {\n    return `${selectWord}${name}`;\n  }\n\n  return `${selectWord}${capitalize(name)}`;\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/select-style.ts",
    "content": "import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport {\n  asPattern,\n  getImportAddFix,\n  getImportRemoveFix,\n  getNearestUpperNodeFrom,\n  getNgRxStores,\n  isCallExpression,\n  isClassDeclaration,\n  isMemberExpression,\n  NGRX_MODULE_PATHS,\n  pipeableSelect,\n  selectExpression,\n} from '../../utils';\n\nexport const selectMethod = 'selectMethod';\nexport const selectOperator = 'selectOperator';\n\nexport const enum SelectStyle {\n  Method = 'method',\n  Operator = 'operator',\n}\n\ntype MessageIds = `${SelectStyle}`;\ntype Options = readonly [MessageIds];\ntype MemberExpressionWithProperty = Omit<\n  TSESTree.MemberExpression,\n  'property'\n> & {\n  property: TSESTree.Identifier;\n};\ntype CallExpression = Omit<TSESTree.CallExpression, 'parent'> & {\n  callee: MemberExpressionWithProperty;\n  parent: TSESTree.CallExpression & {\n    callee: Omit<TSESTree.MemberExpression, 'object'> & {\n      object: MemberExpressionWithProperty;\n    };\n  };\n};\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'Selector can be used either with `select` as a pipeable operator or as a method.',\n      ngrxModule: 'store',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'string',\n        enum: [SelectStyle.Method, SelectStyle.Operator],\n      },\n    ],\n    messages: {\n      [SelectStyle.Method]:\n        'Selector should be used with select method: `this.store.select(selector)`.',\n      [SelectStyle.Operator]:\n        'Selector should be used with the pipeable operator: `this.store.pipe(select(selector))`.',\n    },\n  },\n  defaultOptions: [SelectStyle.Method],\n  create: (context, [mode]) => {\n    const { identifiers = [], sourceCode } = getNgRxStores(context);\n    const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;\n\n    if (!storeNames) {\n      return {};\n    }\n\n    if (mode === SelectStyle.Operator) {\n      return {\n        [selectExpression(storeNames)](node: CallExpression) {\n          context.report({\n            node: node.callee.property,\n            messageId: SelectStyle.Operator,\n            fix: (fixer) => getMethodToOperatorFixes(node, fixer),\n          });\n        },\n      };\n    }\n\n    return {\n      [`Program:has(${pipeableSelect(\n        storeNames\n      )}) ImportDeclaration[source.value='${\n        NGRX_MODULE_PATHS.store\n      }'] > ImportSpecifier[imported.name='select']`](\n        node: TSESTree.ImportSpecifier & {\n          parent: TSESTree.ImportDeclaration;\n        }\n      ) {\n        context.report({\n          node,\n          messageId: SelectStyle.Method,\n          fix: (fixer) =>\n            getImportRemoveFix(sourceCode, [node.parent], 'select', fixer),\n        });\n\n        const [{ references }] = sourceCode.getDeclaredVariables(node);\n\n        for (const { identifier } of references) {\n          context.report({\n            node: identifier,\n            messageId: SelectStyle.Method,\n            fix: (fixer) =>\n              getOperatorToMethodFixes(identifier, sourceCode, fixer),\n          });\n        }\n      },\n    };\n  },\n});\n\nfunction getMethodToOperatorFixes(\n  node: CallExpression,\n  fixer: TSESLint.RuleFixer\n): readonly TSESLint.RuleFix[] {\n  const classDeclaration = getNearestUpperNodeFrom(node, isClassDeclaration);\n\n  if (!classDeclaration) {\n    return [];\n  }\n\n  return [\n    fixer.insertTextBefore(node.callee.property, 'pipe('),\n    fixer.insertTextAfter(node, ')'),\n  ].concat(\n    getImportAddFix({\n      fixer,\n      importName: 'select',\n      moduleName: NGRX_MODULE_PATHS.store,\n      node: classDeclaration,\n    })\n  );\n}\n\nfunction getOperatorToMethodFixes(\n  identifier: TSESTree.Node,\n  sourceCode: Readonly<TSESLint.SourceCode>,\n  fixer: TSESLint.RuleFixer\n): readonly TSESLint.RuleFix[] {\n  const select = identifier.parent;\n  const storePipe = select?.parent;\n\n  if (\n    !storePipe ||\n    !isCallExpression(storePipe) ||\n    !isMemberExpression(storePipe.callee)\n  ) {\n    return [];\n  }\n\n  const pipeContainsOnlySelect = storePipe.arguments.length === 1;\n\n  if (!pipeContainsOnlySelect) {\n    const selectContent = sourceCode.getText(select);\n    const nextTokenAfterSelect = sourceCode.getTokenAfter(select);\n    const store = storePipe.callee.object;\n    return [\n      fixer.remove(select),\n      ...(nextTokenAfterSelect ? [fixer.remove(nextTokenAfterSelect)] : []),\n      fixer.insertTextAfter(store, `.${selectContent}`),\n    ];\n  }\n\n  const { property } = storePipe.callee;\n  const nextTokenAfterPipe = sourceCode.getTokenAfter(property);\n  const [pipeInitialRange, pipeEndRange] = property.range;\n  const pipeRange: TSESTree.Range = [\n    pipeInitialRange,\n    nextTokenAfterPipe?.range[1] ?? pipeEndRange,\n  ];\n  const [, selectEndRange] = identifier.range;\n  return [\n    fixer.removeRange(pipeRange),\n    fixer.insertTextAfterRange([selectEndRange, selectEndRange + 1], '('),\n  ];\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/rules/store/use-consistent-global-store-name.ts",
    "content": "import * as path from 'path';\nimport { createRule } from '../../rule-creator';\nimport { getNgRxStores } from '../../utils';\n\nexport const useConsistentGlobalStoreName = 'useConsistentGlobalStoreName';\nexport const useConsistentGlobalStoreNameSuggest =\n  'useConsistentGlobalStoreNameSuggest';\n\ntype MessageIds =\n  | typeof useConsistentGlobalStoreName\n  | typeof useConsistentGlobalStoreNameSuggest;\ntype Options = readonly [string];\n\nexport default createRule<Options, MessageIds>({\n  name: path.parse(__filename).name,\n  meta: {\n    type: 'suggestion',\n    hasSuggestions: true,\n    docs: {\n      description: 'Use a consistent name for the global store.',\n      ngrxModule: 'store',\n    },\n    schema: [\n      {\n        type: 'string',\n      },\n    ],\n    messages: {\n      [useConsistentGlobalStoreName]:\n        'Global store should be named as `{{ storeName }}`.',\n      [useConsistentGlobalStoreNameSuggest]: 'Rename it to `{{ storeName }}`.',\n    },\n  },\n  defaultOptions: ['store'],\n  create: (context, [storeName]) => {\n    return {\n      Program() {\n        const { identifiers = [] } = getNgRxStores(context);\n\n        for (const { loc, name, range, typeAnnotation } of identifiers) {\n          if (name === storeName) {\n            return;\n          }\n\n          const data = { storeName };\n          context.report({\n            loc: {\n              ...loc,\n              end: {\n                ...loc.start,\n                column: loc.start.column + name.length,\n              },\n            },\n            messageId: useConsistentGlobalStoreName,\n            data,\n            suggest: [\n              {\n                messageId: useConsistentGlobalStoreNameSuggest,\n                data,\n                fix: (fixer) =>\n                  fixer.replaceTextRange(\n                    [range[0], typeAnnotation?.range[0] ?? range[1]],\n                    storeName\n                  ),\n              },\n            ],\n          });\n        }\n      },\n    };\n  },\n});\n"
  },
  {
    "path": "modules/eslint-plugin/src/utils/helper-functions/folder.ts",
    "content": "import * as fs from 'fs';\nimport * as path from 'path';\n\nexport function* traverseFolder(\n  folder: string,\n  extensions: string[]\n): Generator<{ folder: string; file: string; path: string }> {\n  const folders = fs.readdirSync(folder, { withFileTypes: true });\n  for (const folderEntry of folders) {\n    const entryPath = path.resolve(folder, folderEntry.name);\n    if (folderEntry.isDirectory()) {\n      yield* traverseFolder(entryPath, extensions);\n    } else if (extensions.includes(path.extname(entryPath))) {\n      yield {\n        folder,\n        file: path.parse(folderEntry.name).name,\n        path: entryPath,\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/utils/helper-functions/guards.ts",
    "content": "import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\nimport type * as ts from 'typescript';\n\nconst isNodeOfType =\n  <NodeType extends AST_NODE_TYPES>(nodeType: NodeType) =>\n  (node: TSESTree.Node): node is TSESTree.Node & { type: NodeType } =>\n    node.type === nodeType;\n\nexport const isArrowFunctionExpression = isNodeOfType(\n  AST_NODE_TYPES.ArrowFunctionExpression\n);\nexport const isReturnStatement = isNodeOfType(AST_NODE_TYPES.ReturnStatement);\nexport const isMethodDefinition = isNodeOfType(AST_NODE_TYPES.MethodDefinition);\nexport const isCallExpression = isNodeOfType(AST_NODE_TYPES.CallExpression);\nexport const isClassDeclaration = isNodeOfType(AST_NODE_TYPES.ClassDeclaration);\nexport const isPropertyDefinition = isNodeOfType(\n  AST_NODE_TYPES.PropertyDefinition\n);\nexport const isFunctionExpression = isNodeOfType(\n  AST_NODE_TYPES.FunctionExpression\n);\nexport const isFunctionDeclaration = isNodeOfType(\n  AST_NODE_TYPES.FunctionDeclaration\n);\nexport const isIdentifier = isNodeOfType(AST_NODE_TYPES.Identifier);\nexport const isImportDeclaration = isNodeOfType(\n  AST_NODE_TYPES.ImportDeclaration\n);\nexport const isImportDefaultSpecifier = isNodeOfType(\n  AST_NODE_TYPES.ImportDefaultSpecifier\n);\nexport const isImportNamespaceSpecifier = isNodeOfType(\n  AST_NODE_TYPES.ImportNamespaceSpecifier\n);\nexport const isImportSpecifier = isNodeOfType(AST_NODE_TYPES.ImportSpecifier);\nexport const isLiteral = isNodeOfType(AST_NODE_TYPES.Literal);\nexport const isTemplateElement = isNodeOfType(AST_NODE_TYPES.TemplateElement);\nexport const isTemplateLiteral = isNodeOfType(AST_NODE_TYPES.TemplateLiteral);\nexport const isMemberExpression = isNodeOfType(AST_NODE_TYPES.MemberExpression);\nexport const isProgram = isNodeOfType(AST_NODE_TYPES.Program);\nexport const isThisExpression = isNodeOfType(AST_NODE_TYPES.ThisExpression);\nexport const isTSParameterProperty = isNodeOfType(\n  AST_NODE_TYPES.TSParameterProperty\n);\nexport const isTSTypeAnnotation = isNodeOfType(AST_NODE_TYPES.TSTypeAnnotation);\nexport const isTSTypeReference = isNodeOfType(AST_NODE_TYPES.TSTypeReference);\nexport const isTSInstantiationExpression = isNodeOfType(\n  AST_NODE_TYPES.TSInstantiationExpression\n);\nexport const isProperty = isNodeOfType(AST_NODE_TYPES.Property);\nexport const isArrayExpression = isNodeOfType(AST_NODE_TYPES.ArrayExpression);\nexport const isBlockStatement = isNodeOfType(AST_NODE_TYPES.BlockStatement);\nexport function isIdentifierOrMemberExpression(\n  node: TSESTree.Node\n): node is TSESTree.Identifier | TSESTree.MemberExpression {\n  return isIdentifier(node) || isMemberExpression(node);\n}\n\nexport function isTypeReference(type: ts.Type): type is ts.TypeReference {\n  return type.hasOwnProperty('target');\n}\n\nfunction equalTo(one: RegExp | string, other: string) {\n  return typeof one === 'string' ? one === other : one.test(other);\n}\n\nexport function isCallExpressionWith(\n  node: TSESTree.CallExpression,\n  objectName: RegExp | string,\n  propertyName: string\n) {\n  return (\n    isMemberExpression(node.callee) &&\n    !node.callee.computed &&\n    node.callee.property.name === propertyName &&\n    ((isIdentifier(node.callee.object) &&\n      equalTo(objectName, node.callee.object.name)) ||\n      (isMemberExpression(node.callee.object) &&\n        isThisExpression(node.callee.object.object) &&\n        isIdentifier(node.callee.object.property) &&\n        equalTo(objectName, node.callee.object.property.name)))\n  );\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/utils/helper-functions/index.ts",
    "content": "export * from './folder';\nexport * from './guards';\nexport * from './ngrx-modules';\nexport * from './utils';\n"
  },
  {
    "path": "modules/eslint-plugin/src/utils/helper-functions/ngrx-modules.ts",
    "content": "export const NGRX_MODULE_PATHS = {\n  ['component-store']: '@ngrx/component-store',\n  effects: '@ngrx/effects',\n  store: '@ngrx/store',\n  operators: '@ngrx/operators',\n  signals: '@ngrx/signals',\n} as const;\n\nexport type NGRX_MODULE = keyof typeof NGRX_MODULE_PATHS;\n"
  },
  {
    "path": "modules/eslint-plugin/src/utils/helper-functions/rules.ts",
    "content": "import { traverseFolder } from './folder';\nimport { NGRX_MODULE } from './ngrx-modules';\nimport * as path from 'path';\nimport { NgRxRule } from '../../rule-creator';\n\nconst interopRequireDefault = (obj: any): { default: unknown } =>\n  obj && obj.__esModule ? obj : { default: obj };\n\nconst importDefault = (moduleName: string) =>\n  interopRequireDefault(require(moduleName)).default;\n\nconst rulesDir = path.join(__dirname, '../../rules');\nconst configsDir = path.join(__dirname, '../../configs');\nconst excludedFiles = ['index'];\n\nexport const rulesForGenerate = Array.from(traverseFolder(rulesDir, ['.ts']))\n  .filter((rule) => !excludedFiles.includes(rule.file))\n  .reduce<Record<string, NgRxRule>>((allRules, rule) => {\n    const ruleModule = importDefault(rule.path) as NgRxRule;\n    if (!ruleModule.meta.docs) {\n      throw new Error(`Rule ${rule.file} is missing meta.docs information`);\n    }\n    ruleModule.meta.docs.ngrxModule = path.basename(\n      path.dirname(rule.path)\n    ) as NGRX_MODULE;\n    return {\n      ...allRules,\n      [rule.file]: ruleModule,\n    };\n  }, {});\n\nexport const configsForGenerate = Array.from(\n  traverseFolder(configsDir, ['.json'])\n).map((config) => config.file);\n"
  },
  {
    "path": "modules/eslint-plugin/src/utils/helper-functions/utils.ts",
    "content": "import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { ASTUtils } from '@typescript-eslint/utils';\nimport {\n  isCallExpression,\n  isIdentifier,\n  isIdentifierOrMemberExpression,\n  isImportDeclaration,\n  isImportDefaultSpecifier,\n  isImportNamespaceSpecifier,\n  isImportSpecifier,\n  isLiteral,\n  isMethodDefinition,\n  isProgram,\n  isProperty,\n  isPropertyDefinition,\n  isTSTypeAnnotation,\n  isTSTypeReference,\n  isTemplateElement,\n  isTemplateLiteral,\n  isTSInstantiationExpression,\n} from './guards';\nimport { NGRX_MODULE_PATHS } from './ngrx-modules';\n\ntype ConstructorFunctionExpression = TSESTree.FunctionExpression & {\n  parent: TSESTree.MethodDefinition & { kind: 'constructor' };\n};\ntype InjectedParameter = TSESTree.Identifier & {\n  typeAnnotation: TSESTree.TSTypeAnnotation;\n  parent:\n    | ConstructorFunctionExpression\n    | (TSESTree.TSParameterProperty & {\n        parent: ConstructorFunctionExpression;\n      })\n    | TSESTree.PropertyDefinition;\n};\ntype InjectedParameterWithSourceCode = Readonly<{\n  identifiers?: readonly InjectedParameter[];\n  sourceCode: Readonly<TSESLint.SourceCode>;\n}>;\n\nexport function getNearestUpperNodeFrom<T extends TSESTree.Node>(\n  { parent }: TSESTree.Node,\n  predicate: (parent: TSESTree.Node) => parent is T\n): T | undefined {\n  while (parent && !isProgram(parent)) {\n    if (predicate(parent)) {\n      return parent;\n    }\n\n    parent = parent.parent;\n  }\n\n  return undefined;\n}\n\nexport function getImportDeclarationSpecifier(\n  importDeclarations: readonly TSESTree.ImportDeclaration[],\n  importName: string\n) {\n  for (const importDeclaration of importDeclarations) {\n    const importSpecifier = importDeclaration.specifiers.find(\n      (importClause): importClause is TSESTree.ImportSpecifier => {\n        return (\n          isImportSpecifier(importClause) &&\n          isIdentifier(importClause.imported) &&\n          importClause.imported.name === importName\n        );\n      }\n    );\n\n    if (importSpecifier) {\n      return { importDeclaration, importSpecifier } as const;\n    }\n  }\n\n  return undefined;\n}\n\nexport function getImportDeclarations(\n  node: TSESTree.Node,\n  moduleName: string\n): readonly TSESTree.ImportDeclaration[] | undefined {\n  let parentNode: TSESTree.Node | undefined = node;\n\n  while (parentNode && !isProgram(parentNode)) {\n    parentNode = parentNode.parent;\n  }\n\n  return parentNode?.body.filter((node): node is TSESTree.ImportDeclaration => {\n    return isImportDeclaration(node) && node.source.value === moduleName;\n  });\n}\n\nfunction getCorrespondentImportClause(\n  importDeclarations: readonly TSESTree.ImportDeclaration[],\n  compatibleWithTypeOnlyImport = false\n) {\n  let importClause: TSESTree.ImportClause | undefined;\n\n  for (const { importKind, specifiers } of importDeclarations) {\n    const lastImportSpecifier = getLast(specifiers);\n\n    if (\n      (!compatibleWithTypeOnlyImport && importKind === 'type') ||\n      isImportNamespaceSpecifier(lastImportSpecifier)\n    ) {\n      continue;\n    }\n\n    importClause = lastImportSpecifier;\n  }\n\n  return importClause;\n}\n\nexport function getImportAddFix({\n  compatibleWithTypeOnlyImport = false,\n  fixer,\n  importName,\n  moduleName,\n  node,\n}: {\n  compatibleWithTypeOnlyImport?: boolean;\n  fixer: TSESLint.RuleFixer;\n  importName: string;\n  moduleName: string;\n  node: TSESTree.Node;\n}): TSESLint.RuleFix | TSESLint.RuleFix[] {\n  const fullImport = `import { ${importName} } from '${moduleName}';`;\n  const importDeclarations = getImportDeclarations(node, moduleName);\n\n  if (!importDeclarations?.length) {\n    return fixer.insertTextAfterRange([0, 0], fullImport);\n  }\n\n  const importDeclarationSpecifier = getImportDeclarationSpecifier(\n    importDeclarations,\n    importName\n  );\n\n  if (importDeclarationSpecifier) {\n    return [];\n  }\n\n  const importClause = getCorrespondentImportClause(\n    importDeclarations,\n    compatibleWithTypeOnlyImport\n  );\n\n  if (!importClause) {\n    return fixer.insertTextAfterRange([0, 0], fullImport);\n  }\n\n  const replacementText = isImportDefaultSpecifier(importClause)\n    ? `, { ${importName} }`\n    : `, ${importName}`;\n  return fixer.insertTextAfter(importClause, replacementText);\n}\n\nexport function getImportRemoveFix(\n  sourceCode: Readonly<TSESLint.SourceCode>,\n  importDeclarations: readonly TSESTree.ImportDeclaration[],\n  importedName: string,\n  fixer: TSESLint.RuleFixer\n): TSESLint.RuleFix | TSESLint.RuleFix[] {\n  const { importDeclaration, importSpecifier } =\n    getImportDeclarationSpecifier(importDeclarations, importedName) ?? {};\n\n  if (!importDeclaration || !importSpecifier) {\n    return [];\n  }\n\n  const isFirstImportSpecifier =\n    importDeclaration.specifiers[0] === importSpecifier;\n  const isLastImportSpecifier =\n    getLast(importDeclaration.specifiers) === importSpecifier;\n  const isSingleImportSpecifier =\n    isFirstImportSpecifier && isLastImportSpecifier;\n\n  if (isSingleImportSpecifier) {\n    return fixer.remove(importDeclaration);\n  }\n\n  const tokenAfterImportSpecifier = sourceCode.getTokenAfter(importSpecifier);\n\n  if (isFirstImportSpecifier && tokenAfterImportSpecifier) {\n    return fixer.removeRange([\n      importSpecifier.range[0],\n      tokenAfterImportSpecifier.range[1],\n    ]);\n  }\n\n  const tokenBeforeImportSpecifier = sourceCode.getTokenBefore(importSpecifier);\n\n  if (!tokenBeforeImportSpecifier) {\n    return [];\n  }\n\n  return fixer.removeRange([\n    tokenBeforeImportSpecifier.range[0],\n    importSpecifier.range[1],\n  ]);\n}\n\nexport function getNodeToCommaRemoveFix(\n  sourceCode: Readonly<TSESLint.SourceCode>,\n  fixer: TSESLint.RuleFixer,\n  node: TSESTree.Node\n) {\n  const nextToken = sourceCode.getTokenAfter(node);\n  const isNextTokenComma = nextToken && ASTUtils.isCommaToken(nextToken);\n  return [\n    fixer.remove(node),\n    ...(isNextTokenComma ? [fixer.remove(nextToken)] : []),\n  ] as const;\n}\n\nexport function getInterfaceName(\n  interfaceMember: TSESTree.Identifier | TSESTree.MemberExpression\n): string | undefined {\n  if (isIdentifier(interfaceMember)) {\n    return interfaceMember.name;\n  }\n\n  return isIdentifier(interfaceMember.property)\n    ? interfaceMember.property.name\n    : undefined;\n}\n\nexport function getInterfaces({\n  implements: classImplements,\n}: TSESTree.ClassDeclaration): readonly (\n  | TSESTree.Identifier\n  | TSESTree.MemberExpression\n)[] {\n  return (classImplements ?? [])\n    .map(({ expression }) => expression)\n    .filter(isIdentifierOrMemberExpression);\n}\n\nexport function getInterface(\n  node: TSESTree.ClassDeclaration,\n  interfaceName: string\n): TSESTree.Identifier | TSESTree.MemberExpression | undefined {\n  return getInterfaces(node).find(\n    (interfaceMember) => getInterfaceName(interfaceMember) === interfaceName\n  );\n}\n\nexport function getImplementsSchemaFixer(\n  { id, implements: classImplements }: TSESTree.ClassDeclaration,\n  interfaceName: string\n) {\n  const [implementsNodeReplace, implementsTextReplace] =\n    classImplements && classImplements.length\n      ? [getLast(classImplements), `, ${interfaceName}`]\n      : [id as TSESTree.Identifier, ` implements ${interfaceName}`];\n\n  return { implementsNodeReplace, implementsTextReplace } as const;\n}\n\nexport function getLast<T extends readonly unknown[]>(items: T): T[number] {\n  return items.slice(-1)[0];\n}\n\nexport function getDecoratorName({\n  expression,\n}: TSESTree.Decorator): string | undefined {\n  if (isIdentifier(expression)) {\n    return expression.name;\n  }\n\n  return isCallExpression(expression) && isIdentifier(expression.callee)\n    ? expression.callee.name\n    : undefined;\n}\n\nexport function getRawText(node: TSESTree.Node): string | null {\n  if (isIdentifier(node)) {\n    return node.name;\n  }\n\n  if (\n    isPropertyDefinition(node) ||\n    isMethodDefinition(node) ||\n    isProperty(node)\n  ) {\n    return getRawText(node.key);\n  }\n\n  if (isLiteral(node)) {\n    return node.raw;\n  }\n\n  if (isTemplateElement(node)) {\n    return `\\`${node.value.raw}\\``;\n  }\n\n  if (isTemplateLiteral(node)) {\n    return `\\`${node.quasis[0].value.raw}\\``;\n  }\n\n  return null;\n}\n\nexport function capitalize<T extends string>(text: T): Capitalize<T> {\n  return `${text[0].toUpperCase()}${text.slice(1)}` as Capitalize<T>;\n}\n\nfunction getInjectedParametersWithSourceCode(\n  context: TSESLint.RuleContext<string, readonly unknown[]>,\n  moduleName: string,\n  importName: string\n): InjectedParameterWithSourceCode {\n  const sourceCode = context.sourceCode;\n  const importDeclarations =\n    getImportDeclarations(sourceCode.ast, moduleName) ?? [];\n  const { importSpecifier } =\n    getImportDeclarationSpecifier(importDeclarations, importName) ?? {};\n\n  const injectImportDeclarations =\n    getImportDeclarations(sourceCode.ast, '@angular/core') ?? [];\n\n  const { importSpecifier: injectImportSpecifier } =\n    getImportDeclarationSpecifier(injectImportDeclarations, 'inject') ?? {};\n\n  if (!importSpecifier) {\n    return { sourceCode };\n  }\n\n  const variables = sourceCode.getDeclaredVariables(importSpecifier);\n  const typedVariable = variables.find(({ name }) => name === importName);\n  const identifiers = typedVariable?.references?.reduce<\n    readonly InjectedParameter[]\n  >((identifiers, { identifier: { parent } }) => {\n    if (!parent) {\n      return identifiers;\n    }\n\n    if (\n      isTSTypeReference(parent) &&\n      parent.parent &&\n      isTSTypeAnnotation(parent.parent) &&\n      parent.parent.parent &&\n      isIdentifier(parent.parent.parent)\n    ) {\n      return identifiers.concat(parent.parent.parent as InjectedParameter);\n    }\n\n    const parentToCheck = isTSInstantiationExpression(parent)\n      ? parent.parent\n      : parent;\n\n    if (\n      parentToCheck &&\n      isCallExpression(parentToCheck) &&\n      isIdentifier(parentToCheck.callee) &&\n      parentToCheck.callee.name == 'inject' &&\n      parentToCheck.parent &&\n      isPropertyDefinition(parentToCheck.parent) &&\n      isIdentifier(parentToCheck.parent.key) &&\n      injectImportSpecifier\n    ) {\n      return identifiers.concat(parentToCheck.parent.key as InjectedParameter);\n    }\n\n    return identifiers;\n  }, []);\n  return { identifiers, sourceCode };\n}\n\nexport function getNgRxEffectActions(\n  context: TSESLint.RuleContext<string, readonly unknown[]>\n): InjectedParameterWithSourceCode {\n  return getInjectedParametersWithSourceCode(\n    context,\n    NGRX_MODULE_PATHS.effects,\n    'Actions'\n  );\n}\n\nexport function getNgRxComponentStores(\n  context: TSESLint.RuleContext<string, readonly unknown[]>\n): InjectedParameterWithSourceCode {\n  return getInjectedParametersWithSourceCode(\n    context,\n    NGRX_MODULE_PATHS['component-store'],\n    'ComponentStore'\n  );\n}\n\nexport function getNgRxStores(\n  context: TSESLint.RuleContext<string, readonly unknown[]>\n): InjectedParameterWithSourceCode {\n  return getInjectedParametersWithSourceCode(\n    context,\n    NGRX_MODULE_PATHS.store,\n    'Store'\n  );\n}\n\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping\nexport function escapeText(text: string): string {\n  return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport function asPattern(identifiers: readonly InjectedParameter[]): RegExp {\n  const escapedNames = identifiers.map(({ name }) => escapeText(name));\n  return new RegExp(`^(${escapedNames.join('|')})$`);\n}\n\nexport function getNgrxComponentStoreNames(\n  context: TSESLint.RuleContext<string, readonly unknown[]>\n): RegExp | null {\n  const { identifiers = [] } = getNgRxComponentStores(context);\n  return identifiers.length > 0 ? asPattern(identifiers) : null;\n}\n"
  },
  {
    "path": "modules/eslint-plugin/src/utils/index.ts",
    "content": "export * from './helper-functions';\nexport * from './selectors';\n"
  },
  {
    "path": "modules/eslint-plugin/src/utils/selectors/index.ts",
    "content": "export const effectCreator = `PropertyDefinition[value.callee.name='createEffect']`;\nexport const createEffectExpression = `CallExpression[callee.name='createEffect']`;\n\nexport const effectDecorator = `Decorator[expression.callee.name='Effect']`;\nexport const propertyDefinitionWithEffectDecorator =\n  `ClassDeclaration > ClassBody > PropertyDefinition > ${effectDecorator}` as const;\n\nexport const actionCreator = `CallExpression[callee.name='createAction']`;\nexport const actionCreatorWithLiteral =\n  `${actionCreator}[arguments.0.type='Literal'][arguments.0.raw=/^'/]` as const;\nexport const actionCreatorProps =\n  `${actionCreator} > CallExpression[callee.name='props']` as const;\nexport const actionCreatorPropsComputed =\n  `${actionCreatorProps} > TSTypeParameterInstantiation > :matches(TSTypeReference[typeName.name!='Readonly'], [type=/^TS(.*)(Keyword|Type)$/])` as const;\n\nexport const constructorDefinition = `MethodDefinition[kind='constructor']`;\n\nexport function metadataProperty(key: RegExp): string;\nexport function metadataProperty<TKey extends string>(\n  key: TKey\n): `Property:matches([key.name=${TKey}][computed=false], [key.value=${TKey}], [key.quasis.0.value.raw=${TKey}])`;\nexport function metadataProperty(key: RegExp | string): string {\n  return `Property:matches([key.name=${key}][computed=false], [key.value=${key}], [key.quasis.0.value.raw=${key}])`;\n}\n\nexport const ngModuleDecorator = `ClassDeclaration > Decorator > CallExpression[callee.name='NgModule']`;\n\nexport const ngModuleImports =\n  `${ngModuleDecorator} ObjectExpression ${metadataProperty(\n    'imports'\n  )} > ArrayExpression` as const;\n\nexport const ngModuleProviders =\n  `${ngModuleDecorator} ObjectExpression ${metadataProperty(\n    'providers'\n  )} > ArrayExpression` as const;\n\nexport const effectsInNgModuleImports =\n  `${ngModuleImports} CallExpression[callee.object.name='EffectsModule'][callee.property.name=/^for(Root|Feature)$/] ArrayExpression > Identifier` as const;\n\nexport const effectsInNgModuleProviders =\n  `${ngModuleProviders} Identifier` as const;\n\nexport const namedExpression = (name: RegExp | string) =>\n  `:matches(${constructorDefinition} CallExpression[callee.object.name=${name}], CallExpression[callee.object.object.type='ThisExpression'][callee.object.property.name=${name}])` as const;\n\nexport const namedCallableExpression = (name: RegExp | string) =>\n  `:matches(${namedExpression(\n    name\n  )}, ${constructorDefinition} CallExpression[callee.object.callee.object.name=${name}], CallExpression[callee.object.callee.object.object.type='ThisExpression'][callee.object.callee.object.property.name=${name}])` as const;\n\nexport const pipeExpression = (name: RegExp | string) =>\n  `${namedExpression(name)}[callee.property.name='pipe']` as const;\n\nexport const pipeableSelect = (name: RegExp | string) =>\n  `${pipeExpression(name)} CallExpression[callee.name='select']` as const;\n\nexport const selectExpression = (name: RegExp | string) =>\n  `${namedExpression(name)}[callee.property.name='select']` as const;\n\nexport const dispatchExpression = (name: RegExp | string) =>\n  `${namedExpression(name)}[callee.property.name='dispatch']` as const;\n\nexport const dispatchInEffects = (name: RegExp | string) =>\n  `${createEffectExpression} ${dispatchExpression(\n    name\n  )} > MemberExpression:has(Identifier[name=${name}])` as const;\n\nexport const createReducer = `CallExpression[callee.name='createReducer']`;\n\nexport const onFunctionWithoutType =\n  `${createReducer} CallExpression[callee.name='on'] > ArrowFunctionExpression:not([returnType.typeAnnotation])` as const;\n\nexport const storeActionReducerMap =\n  `${ngModuleImports} CallExpression[callee.object.name='StoreModule'][callee.property.name=/^for(Root|Feature)$/] > ObjectExpression:first-child` as const;\n\nexport const actionReducerMap = `VariableDeclarator[id.typeAnnotation.typeAnnotation.typeName.name='ActionReducerMap'] > ObjectExpression`;\n\nconst mapLikeOperators = '/^(concat|exhaust|flat|merge|switch)Map$/';\nconst mapLikeToOperators = '/^(concat|merge|switch)MapTo$/';\nexport const mapLikeOperatorCallExpressions =\n  `:matches(CallExpression[callee.name=${mapLikeToOperators}], CallExpression[callee.name=${mapLikeOperators}] > :matches(ReturnStatement,ArrowFunctionExpression,FunctionExpression))` as const;\n"
  },
  {
    "path": "modules/eslint-plugin/test-setup.ts",
    "content": "import * as vitest from 'vitest';\nimport { RuleTester } from '@typescript-eslint/rule-tester';\n\nRuleTester.afterAll = vitest.afterAll;\n"
  },
  {
    "path": "modules/eslint-plugin/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"nodenext\",\n    \"module\": \"NodeNext\",\n    \"downlevelIteration\": true,\n    \"esModuleInterop\": true,\n    \"isolatedModules\": true,\n    \"outDir\": \"../../dist/modules/eslint-plugin\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\"],\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"declaration\": true,\n    \"target\": \"es2022\",\n    \"resolveJsonModule\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"]\n}\n"
  },
  {
    "path": "modules/eslint-plugin/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"downlevelIteration\": true,\n    \"esModuleInterop\": true,\n    \"outDir\": \"../../dist/modules/eslint-plugin\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "modules/eslint-plugin/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"node\", \"vitest\", \"vitest/globals\"],\n    \"target\": \"es2016\",\n    \"esModuleInterop\": true,\n    \"resolveJsonModule\": true\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/eslint-plugin/v9/index.ts",
    "content": "import { TSESLint } from '@typescript-eslint/utils';\nimport { parser } from 'typescript-eslint';\nimport { rules } from '../src/rules';\nimport {\n  name as packageName,\n  version as packageVersion,\n} from '../package.json';\nimport all from '../src/configs/all';\nimport allTypeChecked from '../src/configs/all-type-checked';\nimport store from '../src/configs/store';\nimport effects from '../src/configs/effects';\nimport effectsTypeChecked from '../src/configs/effects-type-checked';\nimport componentStore from '../src/configs/component-store';\nimport operators from '../src/configs/operators';\nimport signals from '../src/configs/signals';\nimport signalsTypeChecked from '../src/configs/signals-type-checked';\n\nconst meta = { name: packageName, version: packageVersion };\n\nconst tsPlugin: TSESLint.FlatConfig.Plugin = {\n  rules,\n};\n\nconst configs = {\n  all: all(tsPlugin, parser),\n  allTypeChecked: allTypeChecked(tsPlugin, parser),\n  store: store(tsPlugin, parser),\n  effects: effects(tsPlugin, parser),\n  effectsTypeChecked: effectsTypeChecked(tsPlugin, parser),\n  componentStore: componentStore(tsPlugin, parser),\n  operators: operators(tsPlugin, parser),\n  signals: signals(tsPlugin, parser),\n  signalsTypeChecked: signalsTypeChecked(tsPlugin, parser),\n};\n\n/*\nAs the angular-eslint plugin we do both a default and named exports to allow people to use this package from\nboth CJS and ESM in very natural ways.\n*/\nexport default {\n  meta,\n  configs,\n};\nexport { meta, configs };\n"
  },
  {
    "path": "modules/eslint-plugin/v9/tsconfig.build.json",
    "content": "{\n  \"extends\": \"../tsconfig.build\",\n  \"compilerOptions\": {},\n  \"files\": [\"index.ts\"],\n  \"angularCompilerOptions\": {}\n}\n"
  },
  {
    "path": "modules/eslint-plugin/v9/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.spec\",\n  \"files\": [\"index.ts\"]\n}\n"
  },
  {
    "path": "modules/eslint-plugin/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [\n      angular(),\n      nxViteTsPaths(),\n    ],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default'],\n      testTimeout: 8_000,\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "modules/license-banner.txt",
    "content": "/**\n * @license NgRx\n * (c) 2015-2020 Brandon Roberts, Mike Ryan, Rob Wormald, Victor Savkin\n * License: MIT\n */"
  },
  {
    "path": "modules/operators/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/operators/README.md",
    "content": "# @ngrx/operators\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n"
  },
  {
    "path": "modules/operators/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/operators/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n  {\n    ignores: [\n      'schematics-core',\n      '**/vite.config.*.timestamp*',\n      '**/vitest.config.*.timestamp*',\n    ],\n  },\n];\n"
  },
  {
    "path": "modules/operators/index.ts",
    "content": "/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\nexport * from './src/index';\n"
  },
  {
    "path": "modules/operators/migrations/20_0_0-rc_0-tap-response/index.spec.ts",
    "content": "import * as path from 'path';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('migrate tapResponse', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/operators/migrations/migration.json'\n  );\n  const schematicRunner = new SchematicTestRunner('schematics', collectionPath);\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  const verifySchematic = async (input: string, output: string) => {\n    appTree.create('main.ts', input);\n\n    const tree = await schematicRunner.runSchematic(\n      '20.0.0-rc_0-tap-response',\n      {},\n      appTree\n    );\n\n    const actual = tree.readContent('main.ts');\n\n    const normalize = (s: string) => s.replace(/\\s+/g, '').replace(/;/g, '');\n    expect(normalize(actual)).toBe(normalize(output));\n  };\n\n  it('migrates basic tapResponse signature', async () => {\n    const input = `import { tapResponse } from '@ngrx/operators';\ntapResponse(() => {}, () => {});\n`;\n\n    const output = `import { tapResponse } from '@ngrx/operators';\ntapResponse({\n    next: () => { },\n    error: () => { }\n});\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('migrates tapResponse with complete callback', async () => {\n    const input = `\n    import { tapResponse } from '@ngrx/operators';\n    tapResponse(\n      () => next,\n      () => error,\n      () => complete\n    );\n  `;\n\n    const output = `\n    import { tapResponse } from '@ngrx/operators';\n    tapResponse({\n      next: () => next,\n      error: () => error,\n      complete: () => complete\n    });\n  `;\n\n    await verifySchematic(input, output);\n  });\n\n  it('migrates aliased tapResponse calls', async () => {\n    const input = `\n    import { tapResponse } from '@ngrx/operators';\n    const myTapResponse = tapResponse;\n    myTapResponse(\n      () => next,\n      () => error\n    );\n  `;\n\n    const output = `\n    import { tapResponse } from '@ngrx/operators';\n    const myTapResponse = tapResponse;\n    myTapResponse({\n      next: () => next,\n      error: () => error\n    });\n  `;\n\n    await verifySchematic(input, output);\n  });\n\n  it('migrates namespaced tapResponse calls', async () => {\n    const input = `import * as operators from '@ngrx/operators';\noperators.tapResponse(() => next, () => error, () => complete);\n`;\n\n    const output = `import * as operators from '@ngrx/operators';\noperators.tapResponse({\n    next: () => next,\n    error: () => error,\n    complete: () => complete\n});\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('skips tapResponse if not imported from @ngrx/operators', async () => {\n    const input = `import { tapResponse } from '@ngrx/component';\ntapResponse(() => {}, () => {});\n`;\n\n    await verifySchematic(input, input);\n  });\n\n  it('skips correct tapResponse signature', async () => {\n    const input = `import { tapResponse } from '@ngrx/operators';\ntapResponse({\n    next: () => { },\n    error: () => { }\n});\n`;\n\n    await verifySchematic(input, input);\n  });\n\n  it('migrates tapResponse inside a full component-like body', async () => {\n    const input = `import { tapResponse } from '@ngrx/operators';\nfunction handle() {\n  return tapResponse(() => next(), () => error(), () => complete());\n}\n`;\n\n    const output = `import { tapResponse } from '@ngrx/operators';\nfunction handle() {\n  return tapResponse({\n    next: () => next(),\n    error: () => error(),\n    complete: () => complete()\n  });\n}\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('migrates tapResponse(onNext, onError) to object form', async () => {\n    const input = `\n    import { tapResponse } from '@ngrx/operators';\n    const obs = tapResponse(\n      (value) => console.log(value),\n      (error) => console.error(error)\n    );\n  `;\n\n    const output = `\n    import { tapResponse } from '@ngrx/operators';\n    const obs = tapResponse({\n      next: (value) => console.log(value),\n      error: (error) => console.error(error)\n    });\n  `;\n    await verifySchematic(input, output);\n  });\n\n  it('skips migrating tapResponse imported from another module', async () => {\n    const input = `\n    import { tapResponse } from 'some-other-lib';\n    const obs = tapResponse(\n      (value) => console.log(value),\n      (error) => console.error(error)\n    );\n  `;\n\n    await verifySchematic(input, input);\n  });\n});\n"
  },
  {
    "path": "modules/operators/migrations/20_0_0-rc_0-tap-response/index.ts",
    "content": "import { Rule, Tree, SchematicContext } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  createReplaceChange,\n  commitChanges,\n  Change,\n} from '../../schematics-core/index';\nimport { visitCallExpression } from '../../schematics-core/utility/visitors';\nimport * as ts from 'typescript';\n\nexport default function migrateTapResponse(): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile: ts.SourceFile) => {\n      const changes: Change[] = [];\n      const printer = ts.createPrinter();\n\n      const tapResponseIdentifiers = new Set<string>();\n      const namespaceImportsFromOperators = new Set<string>();\n      const aliasedTapResponseVariables = new Set<string>();\n      const importOriginMap = new Map<string, string>();\n\n      // Collect import origins and aliases\n      ts.forEachChild(sourceFile, (node: ts.Node) => {\n        if (\n          ts.isImportDeclaration(node) &&\n          ts.isStringLiteral(node.moduleSpecifier) &&\n          node.importClause?.namedBindings\n        ) {\n          const moduleName = node.moduleSpecifier.text;\n          const bindings = node.importClause.namedBindings;\n\n          if (ts.isNamedImports(bindings)) {\n            for (const element of bindings.elements) {\n              const importedName = element.name.text;\n              importOriginMap.set(importedName, moduleName);\n              if (moduleName === '@ngrx/operators') {\n                tapResponseIdentifiers.add(importedName);\n              }\n            }\n          } else if (ts.isNamespaceImport(bindings)) {\n            if (moduleName === '@ngrx/operators') {\n              namespaceImportsFromOperators.add(bindings.name.text);\n            }\n          }\n        }\n\n        // Track variables assigned to known tapResponse identifiers from @ngrx/operators\n        if (ts.isVariableStatement(node)) {\n          for (const decl of node.declarationList.declarations) {\n            if (\n              ts.isIdentifier(decl.name) &&\n              decl.initializer &&\n              ts.isIdentifier(decl.initializer)\n            ) {\n              const original = decl.initializer.text;\n              if (\n                tapResponseIdentifiers.has(original) &&\n                importOriginMap.get(original) === '@ngrx/operators'\n              ) {\n                aliasedTapResponseVariables.add(decl.name.text);\n              }\n            }\n          }\n        }\n      });\n\n      // Combine aliases into the main set\n      for (const alias of aliasedTapResponseVariables) {\n        tapResponseIdentifiers.add(alias);\n      }\n\n      visitCallExpression(sourceFile, (node: ts.CallExpression) => {\n        const { expression, arguments: args } = node;\n\n        let isTapResponseCall = false;\n\n        if (ts.isIdentifier(expression)) {\n          if (tapResponseIdentifiers.has(expression.text)) {\n            isTapResponseCall = true;\n          }\n        } else if (ts.isPropertyAccessExpression(expression)) {\n          const namespace = expression.expression.getText();\n          const fnName = expression.name.text;\n          if (\n            fnName === 'tapResponse' &&\n            namespaceImportsFromOperators.has(namespace)\n          ) {\n            isTapResponseCall = true;\n          }\n        }\n\n        if (\n          isTapResponseCall &&\n          (args.length === 2 || args.length === 3) &&\n          args.every(\n            (arg) => ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)\n          )\n        ) {\n          const props: ts.PropertyAssignment[] = [\n            ts.factory.createPropertyAssignment('next', args[0]),\n            ts.factory.createPropertyAssignment('error', args[1]),\n          ];\n\n          if (args[2]) {\n            props.push(\n              ts.factory.createPropertyAssignment('complete', args[2])\n            );\n          }\n\n          const newCall = ts.factory.updateCallExpression(\n            node,\n            expression,\n            node.typeArguments,\n            [ts.factory.createObjectLiteralExpression(props, true)]\n          );\n\n          const newText = printer.printNode(\n            ts.EmitHint.Expression,\n            newCall,\n            sourceFile\n          );\n\n          changes.push(\n            createReplaceChange(sourceFile, node, node.getText(), newText)\n          );\n        }\n      });\n\n      if (changes.length) {\n        commitChanges(tree, sourceFile.fileName, changes);\n        context.logger.info(\n          `[ngrx/operators] Migrated deprecated tapResponse in ${sourceFile.fileName}`\n        );\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "modules/operators/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"20.0.0-rc_0-tap-response\": {\n      \"description\": \"Replace deprecated tapResponse signature\",\n      \"version\": \"20.0.0-rc.0\",\n      \"factory\": \"./20_0_0-rc_0-tap-response/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/operators/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/operators\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/operators/package.json",
    "content": "{\n  \"name\": \"@ngrx/operators\",\n  \"version\": \"21.0.1\",\n  \"description\": \"Shared RxJS Operators for NgRx libraries\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"Angular\",\n    \"NgRx\",\n    \"RxJS\",\n    \"Operators\",\n    \"State Management\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"rxjs\": \"^6.5.3 || ^7.4.0\"\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"sideEffects\": false,\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"dependencies\": {\n    \"tslib\": \"^2.3.0\"\n  }\n}\n"
  },
  {
    "path": "modules/operators/project.json",
    "content": "{\n  \"name\": \"operators\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/operators/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/operators/tsconfig.build.json\",\n        \"project\": \"modules/operators/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package operators\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/operators/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/operators\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/operators\"\n          },\n          {\n            \"command\": \"ncp dist/modules/operators node_modules/@ngrx/operators\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/operators\"\n          }\n        ]\n      },\n      \"outputs\": [\n        \"{workspaceRoot}/dist/modules/operators\",\n        \"{workspaceRoot}/node_modules/@ngrx/operators\"\n      ]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/operators/*/**/*.ts\",\n          \"modules/operators/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/operators\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/operators/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Add @ngrx/operators to your application\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/operators/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as SchemaOptions } from './schema';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('Operators ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/operators',\n    path.join(\n      process.cwd(),\n      'dist/modules/operators/schematics/collection.json'\n    )\n  );\n  const defaultOptions: SchemaOptions = {\n    skipPackageJson: false,\n  };\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/operators']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/operators']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "modules/operators/schematics/ng-add/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  chain,\n  noop,\n} from '@angular-devkit/schematics';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\nimport {\n  addPackageToPackageJson,\n  platformVersion,\n} from '../../schematics-core';\nimport { Schema as SchemaOptions } from './schema';\n\nfunction addModuleToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/operators',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nexport default function (options: SchemaOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    return chain([\n      options && options.skipPackageJson ? noop() : addModuleToPackageJson(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/operators/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxOperators\",\n  \"title\": \"NgRx Operators Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/operators as dependency to package.json (e.g., --skipPackageJson).\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/operators/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  skipPackageJson?: boolean;\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/operators/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/operators/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/operators/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/operators/spec/concat_latest_from.spec.ts",
    "content": "import { Observable, of } from 'rxjs';\nimport { skipWhile } from 'rxjs/operators';\nimport { hot } from 'jasmine-marbles';\nimport { concatLatestFrom } from '../src/concat_latest_from';\n\ndescribe('concatLatestFrom', () => {\n  describe('no triggering value appears in source', () => {\n    it('should not evaluate the array', () => {\n      let evaluated = false;\n      const toBeLazilyEvaluated = () => {\n        evaluated = true;\n        return of(4);\n      };\n      const input$: Observable<number> = hot('-a-b-', { a: 1, b: 2 });\n      const numbers$: Observable<[number, number]> = input$.pipe(\n        skipWhile((value) => value < 3),\n        concatLatestFrom(() => [toBeLazilyEvaluated()])\n      );\n      expect(numbers$).toBeObservable(hot('----'));\n      expect(evaluated).toBe(false);\n    });\n    it('should not evaluate the observable', () => {\n      let evaluated = false;\n      const toBeLazilyEvaluated = () => {\n        evaluated = true;\n        return of(4);\n      };\n      const input$: Observable<number> = hot('-a-b-', { a: 1, b: 2 });\n      const numbers$: Observable<[number, number]> = input$.pipe(\n        skipWhile((value) => value < 3),\n        concatLatestFrom(() => toBeLazilyEvaluated())\n      );\n      expect(numbers$).toBeObservable(hot('----'));\n      expect(evaluated).toBe(false);\n    });\n  });\n  describe('a triggering value appears in source', () => {\n    it('should evaluate the array of observables', () => {\n      let evaluated = false;\n      const toBeLazilyEvaluated = () => {\n        evaluated = true;\n        return of(4);\n      };\n      const input$: Observable<number> = hot('-a-b-c-', { a: 1, b: 2, c: 3 });\n      const numbers$: Observable<[number, number]> = input$.pipe(\n        skipWhile((value) => value < 3),\n        concatLatestFrom(() => [toBeLazilyEvaluated()])\n      );\n      expect(numbers$).toBeObservable(hot('-----d', { d: [3, 4] }));\n      expect(evaluated).toBe(true);\n    });\n    it('should evaluate the observable', () => {\n      let evaluated = false;\n      const toBeLazilyEvaluated = () => {\n        evaluated = true;\n        return of(4);\n      };\n      const input$: Observable<number> = hot('-a-b-c-', { a: 1, b: 2, c: 3 });\n      const numbers$: Observable<[number, number]> = input$.pipe(\n        skipWhile((value) => value < 3),\n        concatLatestFrom(() => toBeLazilyEvaluated())\n      );\n      expect(numbers$).toBeObservable(hot('-----d', { d: [3, 4] }));\n      expect(evaluated).toBe(true);\n    });\n  });\n  describe('multiple triggering values appear in source', () => {\n    it('evaluates the array of observables', () => {\n      const input$: Observable<number> = hot('-a-b-c-', { a: 1, b: 2, c: 3 });\n      const numbers$: Observable<[number, string]> = input$.pipe(\n        concatLatestFrom(() => [of('eval')])\n      );\n      expect(numbers$).toBeObservable(\n        hot('-a-b-c', { a: [1, 'eval'], b: [2, 'eval'], c: [3, 'eval'] })\n      );\n    });\n    it('uses incoming value', () => {\n      const input$: Observable<number> = hot('-a-b-c-', { a: 1, b: 2, c: 3 });\n      const numbers$: Observable<[number, string]> = input$.pipe(\n        concatLatestFrom((num) => [of(num + ' eval')])\n      );\n      expect(numbers$).toBeObservable(\n        hot('-a-b-c', { a: [1, '1 eval'], b: [2, '2 eval'], c: [3, '3 eval'] })\n      );\n    });\n  });\n  describe('evaluates multiple observables', () => {\n    it('gets values from both observable in specific order', () => {\n      const input$: Observable<number> = hot('-a-b-c-', { a: 1, b: 2, c: 3 });\n      const numbers$: Observable<[number, string, string]> = input$.pipe(\n        skipWhile((value) => value < 3),\n        concatLatestFrom(() => [of('one'), of('two')])\n      );\n      expect(numbers$).toBeObservable(hot('-----d', { d: [3, 'one', 'two'] }));\n    });\n    it('can use the value passed through source observable', () => {\n      const input$: Observable<number> = hot('-a-b-c-d', {\n        a: 1,\n        b: 2,\n        c: 3,\n        d: 4,\n      });\n      const numbers$: Observable<[number, string, string]> = input$.pipe(\n        skipWhile((value) => value < 3),\n        concatLatestFrom((num) => [of(num + ' one'), of(num + ' two')])\n      );\n      expect(numbers$).toBeObservable(\n        hot('-----c-d', { c: [3, '3 one', '3 two'], d: [4, '4 one', '4 two'] })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "modules/operators/spec/map-response.spec.ts",
    "content": "import { noop, Observable, of, throwError } from 'rxjs';\nimport { mapResponse } from '..';\nimport { concatMap, finalize } from 'rxjs/operators';\nimport { vi } from 'vitest';\n\ndescribe('mapResponse', () => {\n  it('should map the emitted value using the next callback', () => {\n    const results: number[] = [];\n\n    of(1, 2, 3)\n      .pipe(\n        mapResponse({\n          next: (value) => value + 1,\n          error: noop,\n        })\n      )\n      .subscribe((result) => {\n        results.push(result as number);\n      });\n\n    expect(results).toEqual([2, 3, 4]);\n  });\n\n  it('should map the thrown error using the error callback', () =>\n    new Promise((done) => {\n      throwError(() => 'error')\n        .pipe(\n          mapResponse({\n            next: noop,\n            error: (error) => `mapped ${error}`,\n          })\n        )\n        .subscribe((result) => {\n          expect(result).toBe('mapped error');\n          done(void 0);\n        });\n    }));\n\n  it('should map the error thrown in next callback using error callback', () =>\n    new Promise((done) => {\n      function producesError() {\n        throw 'error';\n      }\n\n      of(1)\n        .pipe(\n          mapResponse({\n            next: producesError,\n            error: (error) => `mapped ${error}`,\n          })\n        )\n        .subscribe((result) => {\n          expect(result).toBe('mapped error');\n          done(void 0);\n        });\n    }));\n\n  it('should not unsubscribe from outer observable on inner observable error', () => {\n    const innerCompleteCallback = vi.fn<() => void>();\n    const outerCompleteCallback = vi.fn<() => void>();\n\n    new Observable((subscriber) => subscriber.next(1))\n      .pipe(\n        concatMap(() =>\n          throwError(() => 'error').pipe(\n            mapResponse({\n              next: noop,\n              error: noop,\n            }),\n            finalize(innerCompleteCallback)\n          )\n        ),\n        finalize(outerCompleteCallback)\n      )\n      .subscribe();\n\n    expect(innerCompleteCallback).toHaveBeenCalled();\n    expect(outerCompleteCallback).not.toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "modules/operators/spec/tap-response.spec.ts",
    "content": "import { EMPTY, noop, Observable, of, throwError } from 'rxjs';\nimport { tapResponse } from '..';\nimport { concatMap, finalize } from 'rxjs/operators';\nimport { vi } from 'vitest';\n\ndescribe('tapResponse', () => {\n  it('should invoke next callback on next', () => {\n    const nextCallback = vi.fn<(value: [number]) => void>();\n\n    of(1, 2, 3).pipe(tapResponse(nextCallback, noop)).subscribe();\n\n    expect(nextCallback.mock.calls).toEqual([[1], [2], [3]]);\n  });\n\n  it('should invoke error callback on error', () => {\n    const errorCallback = vi.fn<() => [{ message: string }]>();\n    const error = { message: 'error' };\n\n    throwError(() => error)\n      .pipe(tapResponse(noop, errorCallback))\n      .subscribe();\n\n    expect(errorCallback).toHaveBeenCalledWith(error);\n  });\n\n  it('should invoke error callback on the exception thrown in next', () => {\n    const errorCallback = vi.fn<() => [{ message: string }]>();\n    const error = { message: 'error' };\n\n    function producesError() {\n      throw error;\n    }\n\n    of(1).pipe(tapResponse(producesError, errorCallback)).subscribe();\n\n    expect(errorCallback).toHaveBeenCalledWith(error);\n  });\n\n  it('should invoke complete callback on complete', () => {\n    const completeCallback = vi.fn<() => void>();\n\n    EMPTY.pipe(tapResponse(noop, noop, completeCallback)).subscribe();\n\n    expect(completeCallback).toHaveBeenCalledWith();\n  });\n\n  it('should invoke finalize callback after next and complete', () => {\n    const executionOrder: number[] = [];\n\n    of('ngrx')\n      .pipe(\n        tapResponse({\n          next: () => executionOrder.push(1),\n          error: () => executionOrder.push(-1),\n          complete: () => executionOrder.push(2),\n          finalize: () => executionOrder.push(3),\n        })\n      )\n      .subscribe();\n\n    expect(executionOrder).toEqual([1, 2, 3]);\n  });\n\n  it('should invoke finalize callback after error', () => {\n    const executionOrder: number[] = [];\n\n    throwError(() => 'error!')\n      .pipe(\n        tapResponse({\n          next: () => executionOrder.push(-1),\n          error: () => executionOrder.push(1),\n          complete: () => executionOrder.push(-1),\n          finalize: () => executionOrder.push(2),\n        })\n      )\n      .subscribe();\n\n    expect(executionOrder).toEqual([1, 2]);\n  });\n\n  it('should invoke finalize callback after error when exception is thrown in next', () => {\n    const executionOrder: number[] = [];\n\n    of('ngrx')\n      .pipe(\n        tapResponse({\n          next: () => {\n            throw new Error('error!');\n          },\n          error: () => executionOrder.push(1),\n          complete: () => executionOrder.push(-1),\n          finalize: () => executionOrder.push(2),\n        })\n      )\n      .subscribe();\n\n    expect(executionOrder).toEqual([1, 2]);\n  });\n\n  it('should not unsubscribe from outer observable on inner observable error', () => {\n    const innerCompleteCallback = vi.fn<() => void>();\n    const outerCompleteCallback = vi.fn<() => void>();\n\n    new Observable((subscriber) => subscriber.next(1))\n      .pipe(\n        concatMap(() =>\n          throwError(() => 'error').pipe(\n            tapResponse(noop, noop),\n            finalize(innerCompleteCallback)\n          )\n        ),\n        finalize(outerCompleteCallback)\n      )\n      .subscribe();\n\n    expect(innerCompleteCallback).toHaveBeenCalled();\n    expect(outerCompleteCallback).not.toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "modules/operators/spec/types/tap-response.types.spec.ts",
    "content": "import { Expect, expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('tapResponse types', () => {\n  const snippetFactory = (code: string): string => `\n    import { tapResponse } from '@ngrx/operators';\n    import { noop, of } from 'rxjs';\n\n    ${code}\n  `;\n\n  function testWith(expectSnippet: (code: string) => Expect): void {\n    it('should infer next type', () => {\n      expectSnippet(`\n        of(1).pipe(\n          tapResponse((next) => {\n            const num = next;\n          }, noop)\n        );\n      `).toInfer('num', 'number');\n    });\n\n    it('should accept error type', () => {\n      expectSnippet(`\n        of(true).pipe(\n          tapResponse(noop, (error: { message: string }) => {\n            const err = error;\n          })\n        );\n      `).toInfer('err', '{ message: string; }');\n    });\n\n    it('should use unknown as default error type', () => {\n      expectSnippet(`\n        of(true).pipe(\n          tapResponse(noop, (error) => {\n            const err = error;\n          })\n        );\n      `).toInfer('err', 'unknown');\n    });\n  }\n\n  describe('strict mode', () => {\n    const expectSnippet = expecter(snippetFactory, {\n      ...compilerOptions(),\n      strict: true,\n    });\n\n    testWith(expectSnippet);\n  });\n\n  describe('non-strict mode', () => {\n    const expectSnippet = expecter(snippetFactory, {\n      ...compilerOptions(),\n      strict: false,\n    });\n\n    testWith(expectSnippet);\n  });\n\n  describe('non-strict mode with strict generic checks', () => {\n    const expectSnippet = expecter(snippetFactory, {\n      ...compilerOptions(),\n      strict: false,\n      noStrictGenericChecks: false,\n    });\n\n    testWith(expectSnippet);\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/operators/spec/types/utils.ts",
    "content": "export const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  target: 'ES2022',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  paths: {\n    '@ngrx/operators': ['./modules/operators'],\n  },\n});\n"
  },
  {
    "path": "modules/operators/src/concat_latest_from.ts",
    "content": "import {\n  Observable,\n  ObservableInput,\n  of,\n  ObservedValueOf,\n  OperatorFunction,\n} from 'rxjs';\nimport { concatMap, withLatestFrom } from 'rxjs/operators';\n\n// The array overload is needed first because we want to maintain the proper order in the resulting tuple\nexport function concatLatestFrom<T extends Observable<unknown>[], V>(\n  observablesFactory: (value: V) => [...T]\n): OperatorFunction<V, [V, ...{ [i in keyof T]: ObservedValueOf<T[i]> }]>;\nexport function concatLatestFrom<T extends Observable<unknown>, V>(\n  observableFactory: (value: V) => T\n): OperatorFunction<V, [V, ObservedValueOf<T>]>;\n/**\n * `concatLatestFrom` combines the source value\n * and the last available value from a lazily evaluated Observable\n * in a new array\n *\n * @usageNotes\n *\n * Select the active customer from the NgRx Store\n *\n * ```ts\n * import { concatLatestFrom } from '@ngrx/operators';\n * import * as fromCustomers from '../customers';\n *\n * this.actions$.pipe(\n *  concatLatestFrom(() => this.store.select(fromCustomers.selectActiveCustomer))\n * )\n * ```\n *\n * Select a customer from the NgRx Store by its id that is available on the action\n *\n * ```ts\n * import { concatLatestFrom } from '@ngrx/operators';\n * import * fromCustomers from '../customers';\n *\n * this.actions$.pipe(\n *  concatLatestFrom((action) => this.store.select(fromCustomers.selectCustomer(action.customerId)))\n * )\n * ```\n */\nexport function concatLatestFrom<\n  T extends ObservableInput<unknown>[] | ObservableInput<unknown>,\n  V,\n  R = [\n    V,\n    ...(T extends ObservableInput<unknown>[]\n      ? { [i in keyof T]: ObservedValueOf<T[i]> }\n      : [ObservedValueOf<T>]),\n  ],\n>(observablesFactory: (value: V) => T): OperatorFunction<V, R> {\n  return concatMap((value) => {\n    const observables = observablesFactory(value);\n    const observablesAsArray = Array.isArray(observables)\n      ? observables\n      : [observables];\n    return of(value).pipe(\n      withLatestFrom(...observablesAsArray)\n    ) as unknown as Observable<R>;\n  });\n}\n"
  },
  {
    "path": "modules/operators/src/index.ts",
    "content": "export { concatLatestFrom } from './concat_latest_from';\nexport { mapResponse } from './map-response';\nexport { tapResponse } from './tap-response';\n"
  },
  {
    "path": "modules/operators/src/map-response.ts",
    "content": "import { Observable, of } from 'rxjs';\nimport { catchError, map } from 'rxjs/operators';\n\ntype MapResponseObserver<T, E, R1, R2> = {\n  next: (value: T) => R1;\n  error: (error: E) => R2;\n};\n\n/**\n * `mapResponse` is a map operator with included error handling.\n * It is similar to `tapResponse`, but allows to map the response as well.\n *\n * The main use case is for NgRx Effects which requires an action to be dispatched.\n *\n * @usageNotes\n * ```ts\n * export const loadAllUsers = createEffect((\n *   actions$ = inject(Actions),\n *   usersService = inject(UsersService)\n * ) => {\n *   return actions$.pipe(\n *     ofType(UsersPageActions.opened),\n *     exhaustMap(() => {\n *       return usersService.getAll().pipe(\n *         mapResponse({\n *           next: (users) => UsersApiActions.usersLoadedSuccess({ users }),\n *           error: (error) => UsersApiActions.usersLoadedFailure({ error }),\n *         })\n *       );\n *     })\n *   );\n * });\n * ```\n */\nexport function mapResponse<T, E, R1, R2>(\n  observer: MapResponseObserver<T, E, R1, R2>\n): (source$: Observable<T>) => Observable<R1 | R2> {\n  return (source$) =>\n    source$.pipe(\n      map((value) => observer.next(value)),\n      catchError((error) => of(observer.error(error)))\n    );\n}\n"
  },
  {
    "path": "modules/operators/src/tap-response.ts",
    "content": "import { EMPTY, Observable } from 'rxjs';\nimport { catchError, finalize, tap } from 'rxjs/operators';\n\ntype TapResponseObserver<T, E> = {\n  next: (value: T) => void;\n  error: (error: E) => void;\n  complete?: () => void;\n  finalize?: () => void;\n};\n\nexport function tapResponse<T, E = unknown>(\n  observer: TapResponseObserver<T, E>\n): (source$: Observable<T>) => Observable<T>;\n/**\n * @deprecated Instead of passing a sequence of callbacks, use an observer\n * object. For more info see: https://github.com/ngrx/platform/issues/4840\n */\nexport function tapResponse<T, E = unknown>(\n  next: (value: T) => void,\n  error: (error: E) => void,\n  complete?: () => void\n): (source$: Observable<T>) => Observable<T>;\n/**\n * Handles the response in ComponentStore effects in a safe way, without\n * additional boilerplate. It enforces that the error case is handled and\n * that the effect would still be running should an error occur.\n *\n * Takes optional callbacks for `complete` and `finalize`.\n *\n * @usageNotes\n *\n * ```ts\n * readonly loadUsers = rxMethod<void>(\n *   pipe(\n *     tap(() => this.isLoading.set(true)),\n *     exhaustMap(() =>\n *       this.usersService.getAll().pipe(\n *         tapResponse({\n *           next: (users) => this.users.set(users),\n *           error: (error: HttpErrorResponse) => this.logError(error.message),\n *           finalize: () => this.isLoading.set(false),\n *         })\n *       )\n *     )\n *   )\n * );\n * ```\n */\nexport function tapResponse<T, E>(\n  observerOrNext: TapResponseObserver<T, E> | ((value: T) => void),\n  error?: (error: E) => void,\n  complete?: () => void\n): (source$: Observable<T>) => Observable<T> {\n  const observer: TapResponseObserver<T, E> =\n    typeof observerOrNext === 'function'\n      ? {\n          next: observerOrNext,\n          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n          error: error!,\n          complete,\n        }\n      : observerOrNext;\n\n  return (source) =>\n    source.pipe(\n      tap({ next: observer.next, complete: observer.complete }),\n      catchError((error) => {\n        observer.error(error);\n        return EMPTY;\n      }),\n      observer.finalize ? finalize(observer.finalize) : (source$) => source$\n    );\n}\n"
  },
  {
    "path": "modules/operators/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/operators/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../../\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"downlevelIteration\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"outDir\": \"../../dist/modules/operators\",\n    \"paths\": {},\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"files\": [\"index.ts\"],\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/operators\"\n  }\n}\n"
  },
  {
    "path": "modules/operators/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/operators\",\n    \"paths\": {\n      \"@ngrx/operators/schematics-core\": [\"./schematics-core\"],\n      \"@ngrx/operators\": [\"./src\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/operators/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"node\", \"vitest/globals\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/operators/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [\n      angular(),\n      nxViteTsPaths(),\n    ],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default']\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "modules/router-store/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/router-store/README.md",
    "content": "# @ngrx/router-store\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/router-store/data-persistence/index.ts",
    "content": "export {\n  fetch,\n  navigation,\n  optimisticUpdate,\n  pessimisticUpdate,\n} from './src/operators';\n"
  },
  {
    "path": "modules/router-store/data-persistence/ng-package.json",
    "content": "{}\n"
  },
  {
    "path": "modules/router-store/data-persistence/src/operators.ts",
    "content": "import type { Type } from '@angular/core';\nimport type {\n  ActivatedRouteSnapshot,\n  RouterStateSnapshot,\n} from '@angular/router';\nimport type { RouterNavigationAction } from '@ngrx/router-store';\nimport { ROUTER_NAVIGATION } from '@ngrx/router-store';\nimport type { Action } from '@ngrx/store';\nimport type { Observable } from 'rxjs';\nimport { isObservable, of } from 'rxjs';\nimport {\n  catchError,\n  concatMap,\n  filter,\n  groupBy,\n  map,\n  mergeMap,\n  switchMap,\n} from 'rxjs/operators';\n\nexport interface PessimisticUpdateOpts<T extends Array<unknown>, A> {\n  run(a: A, ...slices: [...T]): Observable<Action> | Action | void;\n  onError(a: A, e: any): Observable<any> | any;\n}\n\nexport interface OptimisticUpdateOpts<T extends Array<unknown>, A> {\n  run(a: A, ...slices: [...T]): Observable<Action> | Action | void;\n  undoAction(a: A, e: any): Observable<Action> | Action;\n}\n\nexport interface FetchOpts<T extends Array<unknown>, A> {\n  id?(a: A, ...slices: [...T]): any;\n  run(a: A, ...slices: [...T]): Observable<Action> | Action | void;\n  onError?(a: A, e: any): Observable<any> | any;\n}\n\nexport interface HandleNavigationOpts<T extends Array<unknown>> {\n  run(\n    a: ActivatedRouteSnapshot,\n    ...slices: [...T]\n  ): Observable<Action> | Action | void;\n  onError?(a: ActivatedRouteSnapshot, e: any): Observable<any> | any;\n}\n\nexport type ActionOrActionWithStates<T extends Array<unknown>, A> =\n  | A\n  | [A, ...T];\nexport type ActionOrActionWithState<T, A> = ActionOrActionWithStates<[T], A>;\nexport type ActionStatesStream<T extends Array<unknown>, A> = Observable<\n  ActionOrActionWithStates<T, A>\n>;\nexport type ActionStateStream<T, A> = Observable<\n  ActionOrActionWithStates<[T], A>\n>;\n\n/**\n * @description\n * Handles pessimistic updates (updating the server first).\n *\n * Updating the server, when implemented naively, suffers from race conditions and poor error handling.\n *\n * `pessimisticUpdate` addresses these problems. It runs all fetches in order, which removes race conditions\n * and forces the developer to handle errors.\n *\n * @usageNotes\n *\n * ```typescript\n * @Injectable()\n * class TodoEffects {\n *   updateTodo$ = createEffect(() =>\n *     this.actions$.pipe(\n *       ofType('UPDATE_TODO'),\n *       pessimisticUpdate({\n *         // provides an action\n *         run: (action: UpdateTodo) => {\n *           // update the backend first, and then dispatch an action that will\n *           // update the client side\n *           return this.backend.updateTodo(action.todo.id, action.todo).pipe(\n *             map((updated) => ({\n *               type: 'UPDATE_TODO_SUCCESS',\n *               todo: updated,\n *             }))\n *           );\n *         },\n *         onError: (action: UpdateTodo, error: any) => {\n *           // we don't need to undo the changes on the client side.\n *           // we can dispatch an error, or simply log the error here and return `null`\n *           return null;\n *         },\n *       })\n *     )\n *   );\n *\n *   constructor(private actions$: Actions, private backend: Backend) {}\n * }\n * ```\n *\n * Note that if you don't return a new action from the run callback, you must set the dispatch property\n * of the effect to false, like this:\n *\n * ```typescript\n * class TodoEffects {\n *   updateTodo$ = createEffect(() =>\n *     this.actions$.pipe(\n *       //...\n *     ), { dispatch: false }\n *   );\n * }\n * ```\n *\n * @param opts\n */\nexport function pessimisticUpdate<T extends Array<unknown>, A extends Action>(\n  opts: PessimisticUpdateOpts<T, A>\n) {\n  return (source: ActionStatesStream<T, A>): Observable<Action> => {\n    return source.pipe(\n      mapActionAndState(),\n      concatMap(runWithErrorHandling(opts.run, opts.onError))\n    );\n  };\n}\n\n/**\n *\n * @description\n *\n * Handles optimistic updates (updating the client first).\n *\n * It runs all fetches in order, which removes race conditions and forces the developer to handle errors.\n *\n * When using `optimisticUpdate`, in case of a failure, the developer has already updated the state locally,\n * so the developer must provide an undo action.\n *\n * The error handling must be done in the callback, or by means of the undo action.\n *\n * @usageNotes\n *\n * ```typescript\n * @Injectable()\n * class TodoEffects {\n *   updateTodo$ = createEffect(() =>\n *     this.actions$.pipe(\n *       ofType('UPDATE_TODO'),\n *       optimisticUpdate({\n *         // provides an action\n *         run: (action: UpdateTodo) => {\n *           return this.backend.updateTodo(action.todo.id, action.todo).pipe(\n *             mapTo({\n *               type: 'UPDATE_TODO_SUCCESS',\n *             })\n *           );\n *         },\n *         undoAction: (action: UpdateTodo, error: any) => {\n *           // dispatch an undo action to undo the changes in the client state\n *           return {\n *             type: 'UNDO_TODO_UPDATE',\n *             todo: action.todo,\n *           };\n *         },\n *       })\n *     )\n *   );\n *\n *   constructor(private actions$: Actions, private backend: Backend) {}\n * }\n * ```\n *\n * Note that if you don't return a new action from the run callback, you must set the dispatch property\n * of the effect to false, like this:\n *\n * ```typescript\n * class TodoEffects {\n *   updateTodo$ = createEffect(() =>\n *     this.actions$.pipe(\n *       //...\n *     ), { dispatch: false }\n *   );\n * }\n * ```\n *\n * @param opts\n */\nexport function optimisticUpdate<T extends Array<unknown>, A extends Action>(\n  opts: OptimisticUpdateOpts<T, A>\n) {\n  return (source: ActionStatesStream<T, A>): Observable<Action> => {\n    return source.pipe(\n      mapActionAndState(),\n      concatMap(runWithErrorHandling(opts.run, opts.undoAction))\n    );\n  };\n}\n\n/**\n *\n * @description\n *\n * Handles data fetching.\n *\n * Data fetching implemented naively suffers from race conditions and poor error handling.\n *\n * `fetch` addresses these problems. It runs all fetches in order, which removes race conditions\n * and forces the developer to handle errors.\n *\n * @usageNotes\n *\n * ```typescript\n * @Injectable()\n * class TodoEffects {\n *   loadTodos$ = createEffect(() =>\n *     this.actions$.pipe(\n *       ofType('GET_TODOS'),\n *       fetch({\n *         // provides an action\n *         run: (a: GetTodos) => {\n *           return this.backend.getAll().pipe(\n *             map((response) => ({\n *               type: 'TODOS',\n *               todos: response.todos,\n *             }))\n *           );\n *         },\n *         onError: (action: GetTodos, error: any) => {\n *           // dispatch an undo action to undo the changes in the client state\n *           return null;\n *         },\n *       })\n *     )\n *   );\n *\n *   constructor(private actions$: Actions, private backend: Backend) {}\n * }\n * ```\n *\n * This is correct, but because it set the concurrency to 1, it may not be performant.\n *\n * To fix that, you can provide the `id` function, like this:\n *\n * ```typescript\n * @Injectable()\n * class TodoEffects {\n *   loadTodo$ = createEffect(() =>\n *     this.actions$.pipe(\n *       ofType('GET_TODO'),\n *       fetch({\n *         id: (todo: GetTodo) => {\n *           return todo.id;\n *         },\n *         // provides an action\n *         run: (todo: GetTodo) => {\n *           return this.backend.getTodo(todo.id).map((response) => ({\n *             type: 'LOAD_TODO_SUCCESS',\n *             todo: response.todo,\n *           }));\n *         },\n *         onError: (action: GetTodo, error: any) => {\n *           // dispatch an undo action to undo the changes in the client state\n *           return null;\n *         },\n *       })\n *     )\n *   );\n *\n *   constructor(private actions$: Actions, private backend: Backend) {}\n * }\n * ```\n *\n * With this setup, the requests for Todo 1 will run concurrently with the requests for Todo 2.\n *\n * In addition, if there are multiple requests for Todo 1 scheduled, it will only run the last one.\n *\n * @param opts\n */\nexport function fetch<T extends Array<unknown>, A extends Action>(\n  opts: FetchOpts<T, A>\n) {\n  return (source: ActionStatesStream<T, A>): Observable<Action> => {\n    if (opts.id) {\n      const groupedFetches = source.pipe(\n        mapActionAndState(),\n        groupBy(([action, ...store]) => {\n          return opts.id(action, ...store);\n        })\n      );\n\n      return groupedFetches.pipe(\n        mergeMap((pairs) =>\n          pairs.pipe(switchMap(runWithErrorHandling(opts.run, opts.onError)))\n        )\n      );\n    }\n\n    return source.pipe(\n      mapActionAndState(),\n      concatMap(runWithErrorHandling(opts.run, opts.onError))\n    );\n  };\n}\n\n/**\n * @description\n *\n * Handles data fetching as part of router navigation.\n *\n * Data fetching implemented naively suffers from race conditions and poor error handling.\n *\n * `navigation` addresses these problems.\n *\n * It checks if an activated router state contains the passed in component type, and, if it does, runs the `run`\n * callback. It provides the activated snapshot associated with the component and the current state. And it only runs\n * the last request.\n *\n * @usageNotes\n *\n * ```typescript\n * @Injectable()\n * class TodoEffects {\n *   loadTodo$ = createEffect(() =>\n *     this.actions$.pipe(\n *       // listens for the routerNavigation action from @ngrx/router-store\n *       navigation(TodoComponent, {\n *         run: (activatedRouteSnapshot: ActivatedRouteSnapshot) => {\n *           return this.backend\n *             .fetchTodo(activatedRouteSnapshot.params['id'])\n *             .pipe(\n *               map((todo) => ({\n *                 type: 'LOAD_TODO_SUCCESS',\n *                 todo: todo,\n *               }))\n *             );\n *         },\n *         onError: (\n *           activatedRouteSnapshot: ActivatedRouteSnapshot,\n *           error: any\n *         ) => {\n *           // we can log and error here and return null\n *           // we can also navigate back\n *           return null;\n *         },\n *       })\n *     )\n *   );\n *\n *   constructor(private actions$: Actions, private backend: Backend) {}\n * }\n * ```\n *\n * @param component\n * @param opts\n */\nexport function navigation<T extends Array<unknown>, A extends Action>(\n  component: Type<any>,\n  opts: HandleNavigationOpts<T>\n) {\n  return (source: ActionStatesStream<T, A>) => {\n    const nav = source.pipe(\n      mapActionAndState(),\n      filter(([action]) => isStateSnapshot(action)),\n      map(([action, ...slices]) => {\n        if (!isStateSnapshot(action)) {\n          // Because of the above filter we'll never get here,\n          // but this properly type narrows `action`\n          // @ts-ignore\n          return;\n        }\n\n        return [\n          findSnapshot(component, action.payload.routerState.root),\n          ...slices,\n        ] as [ActivatedRouteSnapshot, ...T];\n      }),\n      filter(([snapshot]) => !!snapshot)\n    );\n\n    return nav.pipe(switchMap(runWithErrorHandling(opts.run, opts.onError)));\n  };\n}\n\nfunction isStateSnapshot(\n  action: any\n): action is RouterNavigationAction<RouterStateSnapshot> {\n  return action.type === ROUTER_NAVIGATION;\n}\n\nfunction runWithErrorHandling<T extends Array<unknown>, A, R>(\n  run: (a: A, ...slices: [...T]) => Observable<R> | R | void,\n  onError: any\n) {\n  return ([action, ...slices]: [A, ...T]): Observable<R> => {\n    try {\n      const r = wrapIntoObservable(run(action, ...slices));\n      return r.pipe(catchError((e) => wrapIntoObservable(onError(action, e))));\n    } catch (e) {\n      return wrapIntoObservable(onError(action, e));\n    }\n  };\n}\n\n/**\n * @whatItDoes maps Observable<Action | [Action, State]> to\n * Observable<[Action, State]>\n */\nfunction mapActionAndState<T extends Array<unknown>, A>() {\n  return (source: Observable<ActionOrActionWithStates<T, A>>) => {\n    return source.pipe(\n      map((value) => normalizeActionAndState(value) as [A, ...T])\n    );\n  };\n}\n\n/**\n * @whatItDoes Normalizes either a bare action or an array of action and slices\n * into an array of action and slices (or undefined)\n */\nfunction normalizeActionAndState<T extends Array<unknown>, A>(\n  args: ActionOrActionWithStates<T, A>\n): [A, ...T] {\n  let action: A, slices: T;\n\n  if (args instanceof Array) {\n    [action, ...slices] = args;\n  } else {\n    slices = [] as T;\n    action = args;\n  }\n\n  return [action, ...slices];\n}\n\nfunction findSnapshot(\n  component: Type<any>,\n  s: ActivatedRouteSnapshot\n): ActivatedRouteSnapshot {\n  if (s.routeConfig && s.routeConfig.component === component) {\n    return s;\n  }\n  for (const c of s.children) {\n    const ss = findSnapshot(component, c);\n    if (ss) {\n      return ss;\n    }\n  }\n  return null;\n}\n\nfunction wrapIntoObservable<O>(obj: Observable<O> | O | void): Observable<O> {\n  if (isObservable(obj)) {\n    return obj;\n  } else if (!obj) {\n    return of();\n  } else {\n    return of(obj as O);\n  }\n}\n"
  },
  {
    "path": "modules/router-store/data-persistence/src/public_api.ts",
    "content": "export * from './operators';\n"
  },
  {
    "path": "modules/router-store/data-persistence/tsconfig.build.json",
    "content": "{\n  \"extends\": \"../tsconfig.build\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@ngrx/store\": [\"../../dist/packages/store\"],\n      \"@ngrx/effects\": [\"../../dist/packages/effects\"]\n    }\n  },\n  \"files\": [\"index.ts\"],\n  \"angularCompilerOptions\": {}\n}\n"
  },
  {
    "path": "modules/router-store/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: [\n      '**/dist',\n      '**/router-store/data-persistence/index.ts',\n      '**/router-store/data-persistence/src/operators.ts',\n      '**/router-store/data-persistence/src/public_api.ts',\n      '**/schematics-core/**/*.ts'\n    ],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/router-store/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/router-store/index.ts",
    "content": "/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\nexport * from './public_api';\n"
  },
  {
    "path": "modules/router-store/migrations/14_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createPackageJson } from '@ngrx/schematics-core/testing/create-package';\nimport { waitForAsync } from '@angular/core/testing';\n\ndescribe('Router Store Migration 14_0_0', () => {\n  let appTree: UnitTestTree;\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/router-store/migrations/migration.json'\n  );\n  const pkgName = 'router-store';\n\n  beforeEach(() => {\n    appTree = new UnitTestTree(Tree.empty());\n    appTree.create(\n      '/tsconfig.json',\n      `\n        {\n          \"include\": [**./*.ts\"]\n        }\n       `\n    );\n    createPackageJson('', pkgName, appTree);\n  });\n\n  describe('Rename serializers', () => {\n    it(`should rename the DefaultRouterStateSerializer to FullRouterStateSerializer`, waitForAsync(async () => {\n      const input = `\n      import { DefaultRouterStateSerializer } from '@ngrx/router-store';\n\n      const fullSerializer: DefaultRouterStateSerializer;\n\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          StoreRouterConnectingModule.forRoot({ serializer: DefaultRouterStateSerializer, key: 'router' }),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { FullRouterStateSerializer } from '@ngrx/router-store';\n\n      const fullSerializer: FullRouterStateSerializer;\n\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          StoreRouterConnectingModule.forRoot({ serializer: FullRouterStateSerializer, key: 'router' }),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-04`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }));\n  });\n});\n"
  },
  {
    "path": "modules/router-store/migrations/14_0_0/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  commitChanges,\n  createReplaceChange,\n  ReplaceChange,\n} from '../../schematics-core';\n\nconst renames = {\n  DefaultRouterStateSerializer: 'FullRouterStateSerializer',\n};\n\nfunction renameSerializers() {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const routerStoreImports = sourceFile.statements\n        .filter(ts.isImportDeclaration)\n        .filter(({ moduleSpecifier }) =>\n          moduleSpecifier.getText(sourceFile).includes('@ngrx/router-store')\n        );\n\n      if (routerStoreImports.length === 0) {\n        return;\n      }\n\n      const changes = [\n        ...findSerializerImportDeclarations(sourceFile, routerStoreImports),\n        ...findSerializerReplacements(sourceFile),\n      ];\n\n      commitChanges(tree, sourceFile.fileName, changes);\n    });\n  };\n}\n\nfunction findSerializerImportDeclarations(\n  sourceFile: ts.SourceFile,\n  imports: ts.ImportDeclaration[]\n) {\n  const changes = imports\n    .map((p) => (p?.importClause?.namedBindings as ts.NamedImports)?.elements)\n    .reduce(\n      (imports, curr) => imports.concat(curr ?? []),\n      [] as ts.ImportSpecifier[]\n    )\n    .map((specifier) => {\n      if (!ts.isImportSpecifier(specifier)) {\n        return { hit: false };\n      }\n\n      const serializerImports = Object.keys(renames);\n      if (serializerImports.includes(specifier.name.text)) {\n        return { hit: true, specifier, text: specifier.name.text };\n      }\n\n      // if import is renamed\n      if (\n        specifier.propertyName &&\n        serializerImports.includes(specifier.propertyName.text)\n      ) {\n        return { hit: true, specifier, text: specifier.propertyName.text };\n      }\n\n      return { hit: false };\n    })\n    .filter(({ hit }) => hit)\n    .map(({ specifier, text }) =>\n      !!specifier && !!text\n        ? createReplaceChange(\n            sourceFile,\n            specifier,\n            text,\n            (renames as any)[text]\n          )\n        : undefined\n    )\n    .filter((change) => !!change) as Array<ReplaceChange>;\n\n  return changes;\n}\n\nfunction findSerializerReplacements(sourceFile: ts.SourceFile) {\n  const renameKeys = Object.keys(renames);\n  const changes: ReplaceChange[] = [];\n  ts.forEachChild(sourceFile, (node) => find(node, changes));\n  return changes;\n\n  function find(node: ts.Node, changes: ReplaceChange[]) {\n    let change = undefined;\n\n    if (\n      ts.isPropertyAssignment(node) &&\n      renameKeys.includes(node.initializer.getText(sourceFile))\n    ) {\n      change = {\n        node: node.initializer,\n        text: node.initializer.getText(sourceFile),\n      };\n    }\n\n    if (\n      ts.isPropertyAccessExpression(node) &&\n      renameKeys.includes(node.expression.getText(sourceFile))\n    ) {\n      change = {\n        node: node.expression,\n        text: node.expression.getText(sourceFile),\n      };\n    }\n\n    if (\n      ts.isVariableDeclaration(node) &&\n      node.type &&\n      renameKeys.includes(node.type.getText(sourceFile))\n    ) {\n      change = {\n        node: node.type,\n        text: node.type.getText(sourceFile),\n      };\n    }\n\n    if (change) {\n      changes.push(\n        createReplaceChange(\n          sourceFile,\n          change.node,\n          change.text,\n          (renames as any)[change.text]\n        )\n      );\n    }\n\n    ts.forEachChild(node, (childNode) => find(childNode, changes));\n  }\n}\n\nexport default function (): Rule {\n  return chain([renameSerializers()]);\n}\n"
  },
  {
    "path": "modules/router-store/migrations/15_2_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createPackageJson } from '@ngrx/schematics-core/testing/create-package';\nimport { waitForAsync } from '@angular/core/testing';\n\ndescribe('Router Store Migration 15_2_0', () => {\n  let appTree: UnitTestTree;\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/router-store/migrations/migration.json'\n  );\n  const pkgName = 'router-store';\n\n  beforeEach(() => {\n    appTree = new UnitTestTree(Tree.empty());\n    appTree.create(\n      '/tsconfig.json',\n      `\n        {\n          \"include\": [**./*.ts\"]\n        }\n       `\n    );\n    createPackageJson('', pkgName, appTree);\n  });\n\n  describe('Rename selector', () => {\n    it(`renames getSelectors to getRouterSelectors as named imports`, waitForAsync(async () => {\n      const input = `\n        import { getSelectors } from '@ngrx/router-store';\n        export const {\n          selectCurrentRoute,\n          selectQueryParams,\n          selectQueryParam,\n          selectRouteParams,\n          selectRouteParam,\n          selectRouteData,\n          selectUrl,\n          selectTitle,\n        } = getSelectors(selectRouter);\n      `;\n      const expected = `\n        import { getRouterSelectors } from '@ngrx/router-store';\n        export const {\n          selectCurrentRoute,\n          selectQueryParams,\n          selectQueryParam,\n          selectRouteParams,\n          selectRouteParam,\n          selectRouteData,\n          selectUrl,\n          selectTitle,\n        } = getSelectors(selectRouter);\n      `;\n\n      appTree.create('./selector.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-05`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('selector.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`renames getSelectors to getRouterSelectors as namespace import`, waitForAsync(async () => {\n      const input = `\n        import * as routerStore from '@ngrx/router-store';\n        export const selectors = routerStore.getSelectors(selectRouter);\n      `;\n      const expected = `\n        import * as routerStore from '@ngrx/router-store';\n        export const selectors = routerStore.getRouterSelectors(selectRouter);\n      `;\n\n      appTree.create('./selector.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-05`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('selector.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`renames getSelectors to getRouterSelectors as namespace import with deconstruct`, waitForAsync(async () => {\n      const input = `\n        import * as routerStore from '@ngrx/router-store';\n        export const {\n          selectCurrentRoute,\n          selectQueryParams,\n          selectQueryParam,\n          selectRouteParams,\n          selectRouteParam,\n          selectRouteData,\n          selectUrl,\n          selectTitle,\n        } = routerStore.getSelectors(selectRouter);\n      `;\n      const expected = `\n        import * as routerStore from '@ngrx/router-store';\n        export const {\n          selectCurrentRoute,\n          selectQueryParams,\n          selectQueryParam,\n          selectRouteParams,\n          selectRouteParam,\n          selectRouteData,\n          selectUrl,\n          selectTitle,\n        } = routerStore.getRouterSelectors(selectRouter);\n      `;\n\n      appTree.create('./selector.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-05`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('selector.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`does not rename getSelectors if not imported from router-store`, waitForAsync(async () => {\n      const input = `\n        import { getSelectors } from '@ngrx/something';\n        export const { selectCurrentRoute } = getSelectors(selectRouter);\n      `;\n\n      appTree.create('./selector.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-05`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('selector.ts');\n\n      expect(file).toBe(input);\n    }));\n    it(`does not rename other methods on namespace import`, waitForAsync(async () => {\n      const input = `\n        import * as routerStore from '@ngrx/router-store';\n        const root = routerStore.forRoot();\n      `;\n\n      appTree.create('./selector.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-05`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('selector.ts');\n\n      expect(file).toBe(input);\n    }));\n  });\n});\n"
  },
  {
    "path": "modules/router-store/migrations/15_2_0/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  commitChanges,\n  createReplaceChange,\n  ReplaceChange,\n} from '../../schematics-core';\n\nconst renames: { [key: string]: string } = {\n  getSelectors: 'getRouterSelectors',\n};\n\nfunction renameSelector() {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const routerStoreImports = sourceFile.statements\n        .filter((p): p is ts.ImportDeclaration => ts.isImportDeclaration(p))\n        .filter(({ moduleSpecifier }) =>\n          moduleSpecifier.getText(sourceFile).includes('@ngrx/router-store')\n        );\n      const changes: ReplaceChange[] = [\n        ...replaceNamedImports(routerStoreImports, sourceFile),\n        ...replaceNamespaceImports(routerStoreImports, sourceFile),\n      ];\n\n      if (changes.length) {\n        commitChanges(tree, sourceFile.fileName, changes);\n      }\n    });\n  };\n}\n\nfunction replaceNamedImports(\n  routerStoreImports: ts.ImportDeclaration[],\n  sourceFile: ts.SourceFile\n): ReplaceChange[] {\n  const changes: ReplaceChange[] = [];\n\n  const namedImports = routerStoreImports\n    .flatMap((p) =>\n      !!p.importClause && ts.isImportClause(p.importClause)\n        ? p.importClause.namedBindings\n        : []\n    )\n    .flatMap((p) => (!!p && ts.isNamedImports(p) ? p.elements : []));\n\n  for (const namedImport of namedImports) {\n    tryToAddReplacement(namedImport.name, sourceFile, changes);\n  }\n  return changes;\n}\n\nfunction replaceNamespaceImports(\n  routerStoreImports: ts.ImportDeclaration[],\n  sourceFile: ts.SourceFile\n): ReplaceChange[] {\n  const changes: ReplaceChange[] = [];\n\n  const namespaceImports = routerStoreImports\n    .map((p) =>\n      !!p.importClause &&\n      ts.isImportClause(p.importClause) &&\n      !!p.importClause.namedBindings &&\n      ts.isNamespaceImport(p.importClause.namedBindings)\n        ? p.importClause.namedBindings.name.getText(sourceFile)\n        : null\n    )\n    .filter((p): p is string => !!p);\n\n  if (namespaceImports.length === 0) {\n    return changes;\n  }\n\n  for (const statement of sourceFile.statements) {\n    statement.forEachChild((child) => {\n      if (ts.isVariableDeclarationList(child)) {\n        const [declaration] = child.declarations;\n        if (\n          ts.isVariableDeclaration(declaration) &&\n          declaration.initializer &&\n          ts.isCallExpression(declaration.initializer) &&\n          declaration.initializer.expression &&\n          ts.isPropertyAccessExpression(declaration.initializer.expression) &&\n          ts.isIdentifier(declaration.initializer.expression.expression) &&\n          ts.isIdentifier(declaration.initializer.expression.name)\n        ) {\n          if (\n            namespaceImports.includes(\n              declaration.initializer.expression.expression.getText(sourceFile)\n            )\n          ) {\n            tryToAddReplacement(\n              declaration.initializer.expression.name,\n              sourceFile,\n              changes\n            );\n          }\n        }\n      }\n    });\n  }\n\n  return changes;\n}\n\nfunction tryToAddReplacement(\n  oldName: ts.Identifier,\n  sourceFile: ts.SourceFile,\n  changes: ReplaceChange[]\n) {\n  const oldNameText = oldName.getText(sourceFile);\n  const newName = renames[oldNameText];\n  if (newName) {\n    const change = createReplaceChange(\n      sourceFile,\n      oldName,\n      oldNameText,\n      newName\n    );\n    changes.push(change);\n  }\n}\n\nexport default function (): Rule {\n  return chain([renameSelector()]);\n}\n"
  },
  {
    "path": "modules/router-store/migrations/6_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport {\n  createPackageJson,\n  packagePath,\n} from '@ngrx/schematics-core/testing/create-package';\nimport {\n  upgradeVersion,\n  versionPrefixes,\n} from '@ngrx/schematics-core/testing/update';\n\nconst collectionPath = path.join(\n  process.cwd(),\n  'dist/modules/router-store/migrations/migration.json'\n);\n\ndescribe('Router Store Migration 6_0_0', () => {\n  let appTree;\n  const pkgName = 'router-store';\n\n  versionPrefixes.forEach((prefix) => {\n    it(`should install version ${prefix}6.0.0`, async () => {\n      appTree = new UnitTestTree(Tree.empty());\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n      const tree = createPackageJson(prefix, pkgName, appTree);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-01`,\n        {},\n        tree\n      );\n      const pkg = JSON.parse(newTree.readContent(packagePath));\n      expect(pkg.dependencies[`@ngrx/${pkgName}`]).toBe(\n        `${prefix}${upgradeVersion}`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "modules/router-store/migrations/6_0_0/index.ts",
    "content": "import { Rule } from '@angular-devkit/schematics';\nimport { updatePackage } from '../../schematics-core';\n\nexport default function (): Rule {\n  return updatePackage('router-store');\n}\n"
  },
  {
    "path": "modules/router-store/migrations/8_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createPackageJson } from '@ngrx/schematics-core/testing/create-package';\n\ndescribe('Router Store Migration 8_0_0', () => {\n  let appTree: UnitTestTree;\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/router-store/migrations/migration.json'\n  );\n  const pkgName = 'router-store';\n\n  beforeEach(() => {\n    appTree = new UnitTestTree(Tree.empty());\n    appTree.create(\n      '/tsconfig.json',\n      `\n        {\n          \"include\": [**./*.ts\"]\n        }\n       `\n    );\n    createPackageJson('', pkgName, appTree);\n  });\n\n  it(`should import StoreRouterConnectingModule as StoreRouterConnectingModule.forRoot()`, async () => {\n    const contents = `\n      import { StoreRouterConnectingModule } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          /**\n           * @ngrx/router-store keeps router state up-to-date in the store.\n           */\n          StoreRouterConnectingModule,\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n    const expected = `\n      import { StoreRouterConnectingModule } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          /**\n           * @ngrx/router-store keeps router state up-to-date in the store.\n           */\n          StoreRouterConnectingModule.forRoot(),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n    appTree.create('./app.module.ts', contents);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const newTree = await runner.runSchematic(\n      `ngrx-${pkgName}-migration-02`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('app.module.ts');\n\n    expect(file).toBe(expected);\n  });\n\n  it(`should not replace StoreRouterConnectingModule.forRoot()`, async () => {\n    const contents = `\n      import { StoreRouterConnectingModule } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          /**\n           * @ngrx/router-store keeps router state up-to-date in the store.\n           */\n          StoreRouterConnectingModule.forRoot(),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n    const expected = `\n      import { StoreRouterConnectingModule } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          /**\n           * @ngrx/router-store keeps router state up-to-date in the store.\n           */\n          StoreRouterConnectingModule.forRoot(),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n    appTree.create('./app.module.ts', contents);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const newTree = await runner.runSchematic(\n      `ngrx-${pkgName}-migration-02`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('app.module.ts');\n\n    expect(file).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "modules/router-store/migrations/8_0_0/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  ReplaceChange,\n  createReplaceChange,\n  visitTSSourceFiles,\n  commitChanges,\n} from '../../schematics-core';\n\nfunction updateRouterStoreImport(): Rule {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const changes: ReplaceChange[] = [];\n      ts.forEachChild(sourceFile, function findDecorator(node) {\n        if (!ts.isDecorator(node)) {\n          ts.forEachChild(node, findDecorator);\n          return;\n        }\n\n        ts.forEachChild(node, function findImports(node) {\n          if (\n            ts.isPropertyAssignment(node) &&\n            ts.isArrayLiteralExpression(node.initializer) &&\n            ts.isIdentifier(node.name) &&\n            node.name.text === 'imports'\n          ) {\n            node.initializer.elements\n              .filter(ts.isIdentifier)\n              .filter(\n                (element) => element.text === 'StoreRouterConnectingModule'\n              )\n              .forEach((element) => {\n                changes.push(\n                  createReplaceChange(\n                    sourceFile,\n                    element,\n                    'StoreRouterConnectingModule',\n                    'StoreRouterConnectingModule.forRoot()'\n                  )\n                );\n              });\n          }\n\n          ts.forEachChild(node, findImports);\n        });\n      });\n\n      commitChanges(tree, sourceFile.fileName, changes);\n    });\n  };\n}\n\nexport default function (): Rule {\n  return chain([updateRouterStoreImport()]);\n}\n"
  },
  {
    "path": "modules/router-store/migrations/9_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createPackageJson } from '@ngrx/schematics-core/testing/create-package';\n\ndescribe('Router Store Migration 9_0_0', () => {\n  let appTree: UnitTestTree;\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/router-store/migrations/migration.json'\n  );\n  const pkgName = 'router-store';\n\n  beforeEach(() => {\n    appTree = new UnitTestTree(Tree.empty());\n    appTree.create(\n      '/tsconfig.json',\n      `\n        {\n          \"include\": [**./*.ts\"]\n        }\n       `\n    );\n    createPackageJson('', pkgName, appTree);\n  });\n\n  describe('Adds the default serializer when none is set', () => {\n    it(`should use the default serializer if none was present (empty)`, () => {\n      const input = `\n      import { StoreRouterConnectingModule } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          StoreRouterConnectingModule.forRoot(),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { StoreRouterConnectingModule, DefaultRouterStateSerializer } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          StoreRouterConnectingModule.forRoot({ serializer: DefaultRouterStateSerializer }),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n      test(input, expected);\n    });\n\n    it(`should use the default serializer if none was present (with props)`, () => {\n      const input = `\n      import { StoreRouterConnectingModule } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          StoreRouterConnectingModule.forRoot({ key: 'router' }),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { StoreRouterConnectingModule, DefaultRouterStateSerializer } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          StoreRouterConnectingModule.forRoot({ serializer: DefaultRouterStateSerializer, key: 'router' }),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n      test(input, expected);\n    });\n\n    it(`should not run the migration if there was a serializer set`, () => {\n      const input = `\n      import { StoreRouterConnectingModule } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          StoreRouterConnectingModule.forRoot({ serializer: CustomSerializer }),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = input;\n\n      test(input, expected);\n    });\n\n    it(`should not run the migration if there was a routerState set`, () => {\n      const input = `\n      import { StoreRouterConnectingModule, RouterState } from '@ngrx/router-store';\n      @NgModule({\n        imports: [\n          AuthModule,\n          AppRoutingModule,\n          StoreRouterConnectingModule.forRoot({ routerState: RouterState.Minimal }),\n          CoreModule,\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = input;\n\n      test(input, expected);\n    });\n\n    async function test(input: string, expected: string) {\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-03`,\n        {},\n        appTree\n      );\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }\n  });\n});\n"
  },
  {
    "path": "modules/router-store/migrations/9_0_0/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Rule,\n  chain,\n  Tree,\n  SchematicContext,\n} from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  commitChanges,\n  InsertChange,\n  visitNgModuleImports,\n  insertImport,\n  Change,\n  containsProperty,\n} from '../../schematics-core';\n\nfunction addDefaultSerializer(): Rule {\n  const SERIALIZER_PROPERTY = 'serializer: DefaultRouterStateSerializer';\n  return (tree: Tree, ctx: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const changes: Change[] = [];\n\n      visitNgModuleImports(sourceFile, (importsNode, elementsNode) => {\n        elementsNode\n          .filter(\n            (element) =>\n              ts.isCallExpression(element) &&\n              ts.isPropertyAccessExpression(element.expression) &&\n              ts.isIdentifier(element.expression.expression) &&\n              element.expression.expression.text ===\n                'StoreRouterConnectingModule'\n          )\n          .forEach((element) => {\n            const callExpression = element as ts.CallExpression;\n            const callArgument = callExpression.arguments[0];\n\n            // StoreRouterConnectingModule.forRoot() without arguments\n            if (callArgument === undefined) {\n              changes.push(\n                new InsertChange(\n                  sourceFile.fileName,\n                  callExpression.getEnd() - 1,\n                  `{ ${SERIALIZER_PROPERTY} }`\n                )\n              );\n            } else if (ts.isObjectLiteralExpression(callArgument)) {\n              // StoreRouterConnectingModule.forRoot({ key: 'router' }) with arguments\n              const serializerSet = containsProperty(\n                callArgument,\n                'serializer'\n              );\n              const routerStateSet = containsProperty(\n                callArgument,\n                'routerState'\n              );\n\n              if (serializerSet || routerStateSet) {\n                return;\n              }\n\n              changes.push(\n                new InsertChange(\n                  sourceFile.fileName,\n                  callArgument.getStart() + 1,\n                  ` ${SERIALIZER_PROPERTY},`\n                )\n              );\n            }\n          });\n      });\n\n      if (changes.length) {\n        changes.push(\n          insertImport(\n            sourceFile,\n            sourceFile.fileName,\n            'DefaultRouterStateSerializer',\n            '@ngrx/router-store'\n          )\n        );\n      }\n\n      commitChanges(tree, sourceFile.fileName, changes);\n\n      if (changes.length) {\n        ctx.logger.info(\n          `[@ngrx/router-store] Updated StoreRouterConnectingModule's configuration, see the migration guide (https://ngrx.io/guide/migration/v9#ngrxrouter-store) for more info`\n        );\n      }\n    });\n  };\n}\n\nexport default function (): Rule {\n  return chain([addDefaultSerializer()]);\n}\n"
  },
  {
    "path": "modules/router-store/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"ngrx-router-store-migration-01\": {\n      \"description\": \"The road to v6\",\n      \"version\": \"5.2\",\n      \"factory\": \"./6_0_0/index\"\n    },\n    \"ngrx-router-store-migration-02\": {\n      \"description\": \"The road to v8\",\n      \"version\": \"8-beta\",\n      \"factory\": \"./8_0_0/index\"\n    },\n    \"ngrx-router-store-migration-03\": {\n      \"description\": \"The road to v9\",\n      \"version\": \"9-beta\",\n      \"factory\": \"./9_0_0/index\"\n    },\n    \"ngrx-router-store-migration-04\": {\n      \"description\": \"The road to v14\",\n      \"version\": \"14-beta\",\n      \"factory\": \"./14_0_0/index\"\n    },\n    \"ngrx-router-store-migration-05\": {\n      \"description\": \"The road to v15.2.0\",\n      \"version\": \"15.2.0\",\n      \"factory\": \"./15_2_0/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/router-store/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/router-store\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/router-store/package.json",
    "content": "{\n  \"name\": \"@ngrx/router-store\",\n  \"version\": \"21.0.1\",\n  \"description\": \"Bindings to connect @angular/router to @ngrx/store\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"RxJS\",\n    \"Angular\",\n    \"Redux\",\n    \"Schematics\",\n    \"Angular CLI\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"@angular/common\": \"^21.0.0\",\n    \"@angular/core\": \"^21.0.0\",\n    \"@angular/router\": \"^21.0.0\",\n    \"@ngrx/store\": \"21.0.1\",\n    \"rxjs\": \"^6.5.3 || ^7.5.0\"\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"sideEffects\": false,\n  \"dependencies\": {\n    \"tslib\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "modules/router-store/project.json",
    "content": "{\n  \"name\": \"router-store\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/router-store/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/router-store/tsconfig.build.json\",\n        \"project\": \"modules/router-store/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package router-store\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/router-store/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/router-store\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/router-store\"\n          },\n          {\n            \"command\": \"ncp dist/modules/router-store node_modules/@ngrx/router-store\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/router-store\"\n          }\n        ]\n      },\n      \"outputs\": [\"{workspaceRoot}/dist/modules/router-store\"]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/router-store/*/**/*.ts\",\n          \"modules/router-store/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/router-store\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/router-store/public_api.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/router-store/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Register @ngrx/router-store within your application\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/router-store/schematics/ng-add/__snapshots__/index.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Router Store ng-add Schematic > Router Store ng-add Schematic for standalone application > provides initial setup 1`] = `\n\"import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { provideRouterStore } from '@ngrx/router-store';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideBrowserGlobalErrorListeners(),\n    provideRouterStore()\n]\n};\n\"\n`;\n"
  },
  {
    "path": "modules/router-store/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as RouterStoreOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Router Store ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/router-store',\n    path.join(\n      process.cwd(),\n      'dist/modules/router-store/schematics/collection.json'\n    )\n  );\n  const defaultOptions: RouterStoreOptions = {\n    skipPackageJson: false,\n    module: 'app-module',\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/router-store']).toBeUndefined();\n  });\n\n  it('should be provided by default', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(\n      /import { StoreRouterConnectingModule } from '@ngrx\\/router-store';/\n    );\n    expect(content).toMatch(/StoreRouterConnectingModule.forRoot\\(\\)/);\n  });\n\n  it('should import into a specified module', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(\n      /import { StoreRouterConnectingModule } from '@ngrx\\/router-store';/\n    );\n  });\n\n  it('should fail if specified module does not exist', async () => {\n    const options = { ...defaultOptions, module: '/src/app/app-moduleXXX.ts' };\n    let thrownError: Error | null = null;\n    try {\n      await schematicRunner.runSchematic('ng-add', options, appTree);\n    } catch (err: any) {\n      thrownError = err;\n    }\n    expect(thrownError).toBeDefined();\n  });\n\n  describe('Router Store ng-add Schematic for standalone application', () => {\n    const projectPath = getTestProjectPath(undefined, {\n      name: 'bar-standalone',\n    });\n\n    const standaloneDefaultOptions = {\n      ...defaultOptions,\n      project: 'bar-standalone',\n    };\n\n    it('provides initial setup', async () => {\n      const options = { ...standaloneDefaultOptions };\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n\n      const content = tree.readContent(`${projectPath}/src/app/app.config.ts`);\n\n      expect(content).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/router-store/schematics/ng-add/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  Tree,\n  branchAndMerge,\n  chain,\n  noop,\n} from '@angular-devkit/schematics';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\nimport * as ts from 'typescript';\nimport {\n  InsertChange,\n  addImportToModule,\n  addPackageToPackageJson,\n  findModuleFromOptions,\n  getProjectPath,\n  insertImport,\n  parseName,\n  platformVersion,\n} from '../../schematics-core';\nimport { Schema as RouterStoreOptions } from './schema';\nimport { getProjectMainFile } from '../../schematics-core/utility/project';\nimport { isStandaloneApp } from '@schematics/angular/utility/ng-ast-utils';\nimport {\n  addFunctionalProvidersToStandaloneBootstrap,\n  callsProvidersFunction,\n} from '../../schematics-core/utility/standalone';\n\nfunction addImportToNgModule(options: RouterStoreOptions): Rule {\n  return (host: Tree) => {\n    const modulePath = options.module;\n\n    if (!modulePath) {\n      return host;\n    }\n\n    if (!host.exists(modulePath)) {\n      throw new Error('Specified module does not exist');\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const [routerStoreNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreRouterConnectingModule.forRoot()`,\n      `@ngrx/router-store`\n    );\n\n    const changes = [\n      insertImport(\n        source,\n        modulePath,\n        'StoreRouterConnectingModule',\n        '@ngrx/router-store'\n      ),\n      routerStoreNgModuleImport,\n    ];\n    const recorder = host.beginUpdate(modulePath);\n\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nfunction addNgRxRouterStoreToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/router-store',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nfunction addStandaloneConfig(options: RouterStoreOptions): Rule {\n  return (host: Tree) => {\n    const mainFile = getProjectMainFile(host, options);\n\n    if (host.exists(mainFile)) {\n      const providerFn = 'provideRouterStore';\n\n      if (callsProvidersFunction(host, mainFile, providerFn)) {\n        // exit because the store config is already provided\n        return host;\n      }\n\n      const providerOptions: ts.Identifier[] = [];\n\n      const patchedConfigFile = addFunctionalProvidersToStandaloneBootstrap(\n        host,\n        mainFile,\n        providerFn,\n        '@ngrx/router-store',\n        providerOptions\n      );\n\n      const recorder = host.beginUpdate(patchedConfigFile);\n\n      host.commitUpdate(recorder);\n\n      return host;\n    }\n\n    throw new SchematicsException(\n      `Main file not found for a project ${options.project}`\n    );\n  };\n}\n\nexport default function (options: RouterStoreOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    const mainFile = getProjectMainFile(host, options);\n    const isStandalone = isStandaloneApp(host, mainFile);\n\n    options.path = getProjectPath(host, options);\n\n    if (options.module && !isStandalone) {\n      options.module = findModuleFromOptions(host, {\n        name: '',\n        module: options.module,\n        path: options.path,\n      });\n    }\n\n    const parsedPath = parseName(options.path, '');\n    options.path = parsedPath.path;\n\n    const configOrModuleUpdate = isStandalone\n      ? addStandaloneConfig(options)\n      : addImportToNgModule(options);\n\n    return chain([\n      branchAndMerge(chain([configOrModuleUpdate])),\n      options && options.skipPackageJson\n        ? noop()\n        : addNgRxRouterStoreToPackageJson(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/router-store/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxRouterStore\",\n  \"title\": \"NgRx Router Store Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/router-store as dependency to package.json (e.g., --skipPackageJson).\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the router store.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"default\": \"app\",\n      \"description\": \"Allows specification of the declaring module.\",\n      \"alias\": \"m\",\n      \"subtype\": \"filepath\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/router-store/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  skipPackageJson?: boolean;\n  path?: string;\n  project?: string;\n  module?: string;\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/router-store/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/router-store/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/router-store/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/router-store/spec/integration.spec.ts",
    "content": "import { Injectable, ErrorHandler } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  NavigationEnd,\n  Router,\n  RouterStateSnapshot,\n  NavigationCancel,\n  NavigationError,\n  ActivatedRouteSnapshot,\n} from '@angular/router';\nimport { Store, ScannedActionsSubject } from '@ngrx/store';\nimport { filter, first, map, take } from 'rxjs/operators';\n\nimport {\n  NavigationActionTiming,\n  ROUTER_CANCEL,\n  ROUTER_ERROR,\n  ROUTER_NAVIGATED,\n  ROUTER_NAVIGATION,\n  ROUTER_REQUEST,\n  routerNavigationAction,\n  RouterAction,\n  routerReducer,\n  RouterReducerState,\n  RouterStateSerializer,\n  StateKeyOrSelector,\n} from '../src';\nimport { createTestModule } from './utils';\n\ndescribe('integration spec', () => {\n  it('should work', () =>\n    new Promise<void>((done) => {\n      const reducer = (state = '', action: RouterAction<any>) => {\n        if (action.type === ROUTER_NAVIGATION) {\n          return action.payload.routerState.url.toString();\n        } else {\n          return state;\n        }\n      };\n\n      createTestModule({ reducers: { reducer } });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore();\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'store', state: '' }, // init event. has nothing to do with the router\n            { type: 'store', state: '' }, // ROUTER_REQUEST event in the store\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/' },\n            { type: 'store', state: '/' }, // ROUTER_NAVIGATION event in the store\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/' },\n            /* new Router Lifecycle in Angular 4.3 */\n            { type: 'router', event: 'GuardsCheckStart', url: '/' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/' },\n            { type: 'router', event: 'ResolveStart', url: '/' },\n            { type: 'router', event: 'ResolveEnd', url: '/' },\n            { type: 'store', state: '/' }, // ROUTER_NAVIGATED event in the store\n            { type: 'action', action: ROUTER_NAVIGATED },\n            { type: 'router', event: 'NavigationEnd', url: '/' },\n          ]);\n        })\n        .then(() => {\n          log.splice(0);\n          return router.navigateByUrl('next');\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'store', state: '/' }, // ROUTER_REQUEST event in the store\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            { type: 'store', state: '/next' },\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n\n            /* new Router Lifecycle in Angular 4.3 */\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n            { type: 'router', event: 'ResolveStart', url: '/next' },\n            { type: 'router', event: 'ResolveEnd', url: '/next' },\n            { type: 'store', state: '/next' }, // ROUTER_NAVIGATED event in the store\n            { type: 'action', action: ROUTER_NAVIGATED },\n            { type: 'router', event: 'NavigationEnd', url: '/next' },\n          ]);\n\n          done();\n        });\n    }));\n\n  it('should have the routerState in the payload', () =>\n    new Promise<void>((done) => {\n      const actionLog: RouterAction<any>[] = [];\n      const reducer = (state = '', action: RouterAction<any>) => {\n        switch (action.type) {\n          case ROUTER_CANCEL:\n          case ROUTER_ERROR:\n          case ROUTER_NAVIGATED:\n          case ROUTER_NAVIGATION:\n          case ROUTER_REQUEST:\n            actionLog.push(action);\n            return state;\n          default:\n            return state;\n        }\n      };\n\n      createTestModule({\n        reducers: { reducer },\n        canActivate: (\n          route: ActivatedRouteSnapshot,\n          state: RouterStateSnapshot\n        ) => state.url !== 'next',\n      });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore();\n\n      const hasRouterState = (action: RouterAction<any>) =>\n        !!action.payload.routerState;\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          expect(actionLog.filter(hasRouterState).length).toBe(\n            actionLog.length\n          );\n        })\n        .then(() => {\n          actionLog.splice(0);\n          return router.navigateByUrl('next');\n        })\n        .then(() => {\n          expect(actionLog.filter(hasRouterState).length).toBe(\n            actionLog.length\n          );\n          done();\n        });\n    }));\n\n  test.skip('should support preventing navigation', () =>\n    new Promise<void>((done) => {\n      const reducer = (state = '', action: RouterAction<any>) => {\n        if (\n          action.type === ROUTER_NAVIGATION &&\n          action.payload.routerState.url.toString() === '/next'\n        ) {\n          throw new Error('You shall not pass!');\n        } else {\n          return state;\n        }\n      };\n\n      createTestModule({ reducers: { reducer } });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore();\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          log.splice(0);\n          return router.navigateByUrl('next');\n        })\n        .catch((e) => {\n          expect(e.message).toEqual('You shall not pass!');\n          expect(log).toEqual([\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n            { type: 'router', event: 'NavigationError', url: '/next' },\n          ]);\n\n          done();\n        });\n    }));\n\n  it('should ignore routing actions for the URL that is currently open', async () => {\n    createTestModule({\n      reducers: { router: routerReducer },\n    });\n\n    const router = TestBed.inject(Router);\n    const store = TestBed.inject(Store);\n    const navigateByUrlSpy = vi.spyOn(router, 'navigateByUrl');\n\n    await router.navigateByUrl('/');\n\n    const SAME_URL_WITHOUT_SLASH = '';\n\n    store.dispatch(\n      routerNavigationAction({\n        payload: {\n          routerState: { url: SAME_URL_WITHOUT_SLASH, root: {} as any },\n          event: { id: 123 } as any,\n        },\n      })\n    );\n\n    //                         Navigates only ONCE 👇\n    expect(navigateByUrlSpy.mock.calls.length).toBe(1);\n  });\n\n  it('should support rolling back if navigation gets canceled (navigation initialized through router)', () =>\n    new Promise<void>((done) => {\n      const reducer = (state = '', action: RouterAction<any>): any => {\n        if (action.type === ROUTER_NAVIGATION) {\n          return {\n            url: action.payload.routerState.url.toString(),\n            lastAction: ROUTER_NAVIGATION,\n          };\n        } else if (action.type === ROUTER_CANCEL) {\n          return {\n            url: action.payload.routerState.url.toString(),\n            storeState: action.payload.storeState.reducer,\n            lastAction: ROUTER_CANCEL,\n          };\n        } else {\n          return state;\n        }\n      };\n\n      createTestModule({\n        reducers: { reducer, routerReducer },\n        canActivate: () => false,\n      });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore();\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          log.splice(0);\n          return router.navigateByUrl('next');\n        })\n        .then((r) => {\n          expect(r).toEqual(false);\n\n          expect(log).toEqual([\n            {\n              type: 'store',\n              state: { url: '/', lastAction: ROUTER_NAVIGATION },\n            }, // ROUTER_REQUEST event in the store\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            {\n              type: 'store',\n              state: { url: '/next', lastAction: ROUTER_NAVIGATION },\n            },\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n\n            /* new Router Lifecycle in Angular 4.3 - m */\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n            // { type: 'router', event: 'ResolveStart', url: '/next' },\n            // { type: 'router', event: 'ResolveEnd', url: '/next' },\n            {\n              type: 'store',\n              state: {\n                url: '/',\n                lastAction: ROUTER_CANCEL,\n                storeState: { url: '/', lastAction: ROUTER_NAVIGATION },\n              },\n            },\n            { type: 'action', action: ROUTER_CANCEL },\n            { type: 'router', event: 'NavigationCancel', url: '/next' },\n          ]);\n\n          done();\n        });\n    }));\n\n  it('should support rolling back if navigation gets canceled (navigation initialized through store)', () =>\n    new Promise<void>((done) => {\n      const CHANGE_ROUTE = 'CHANGE_ROUTE';\n      const reducer = (\n        state: RouterReducerState,\n        action: any\n      ): RouterReducerState => {\n        if (action.type === CHANGE_ROUTE) {\n          return {\n            state: { url: '/next', root: <any>{} },\n            navigationId: 123,\n          };\n        } else {\n          const nextState = routerReducer(state, action);\n          if (nextState && nextState.state) {\n            return {\n              ...nextState,\n              state: {\n                ...nextState.state,\n                root: {} as any,\n              },\n            };\n          }\n          return nextState;\n        }\n      };\n\n      createTestModule({\n        reducers: { reducer },\n        canActivate: () => false,\n        config: { stateKey: 'reducer' },\n      });\n\n      const router = TestBed.inject(Router);\n      const store = TestBed.inject(Store);\n      const log = logOfRouterAndActionsAndStore();\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          log.splice(0);\n          store.dispatch({ type: CHANGE_ROUTE });\n          return waitForNavigation(router, NavigationCancel);\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            {\n              type: 'store',\n              state: { state: { url: '/next', root: {} }, navigationId: 123 },\n            },\n            { type: 'action', action: CHANGE_ROUTE },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n            {\n              type: 'store',\n              state: { state: { url: '/', root: {} }, navigationId: 2 },\n            },\n            { type: 'action', action: ROUTER_CANCEL },\n            { type: 'router', event: 'NavigationCancel', url: '/next' },\n          ]);\n\n          done();\n        });\n    }));\n\n  it('should support rolling back if navigation errors (navigation initialized through router)', () =>\n    new Promise<void>((done) => {\n      const reducer = (state = '', action: RouterAction<any>): any => {\n        if (action.type === ROUTER_NAVIGATION) {\n          return {\n            url: action.payload.routerState.url.toString(),\n            lastAction: ROUTER_NAVIGATION,\n          };\n        } else if (action.type === ROUTER_ERROR) {\n          return {\n            url: action.payload.routerState.url.toString(),\n            storeState: action.payload.storeState.reducer,\n            lastAction: ROUTER_ERROR,\n          };\n        } else {\n          return state;\n        }\n      };\n\n      createTestModule({\n        reducers: { reducer, routerReducer },\n        canActivate: () => {\n          throw new Error('BOOM!');\n        },\n      });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore();\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          log.splice(0);\n          return router.navigateByUrl('next');\n        })\n        .catch((e) => {\n          expect(e.message).toEqual('BOOM!');\n\n          expect(log).toEqual([\n            {\n              type: 'store',\n              state: { url: '/', lastAction: ROUTER_NAVIGATION },\n            }, // ROUTER_REQUEST event in the store\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            {\n              type: 'store',\n              state: { url: '/next', lastAction: ROUTER_NAVIGATION },\n            },\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n\n            /* new Router Lifecycle in Angular 4.3 */\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n\n            {\n              type: 'store',\n              state: {\n                url: '/',\n                lastAction: ROUTER_ERROR,\n                storeState: { url: '/', lastAction: ROUTER_NAVIGATION },\n              },\n            },\n            { type: 'action', action: ROUTER_ERROR },\n            { type: 'router', event: 'NavigationError', url: '/next' },\n          ]);\n\n          done();\n        });\n    }));\n\n  it('should support rolling back if navigation errors and hand error to error handler (navigation initialized through store)', () =>\n    new Promise<void>((done) => {\n      const CHANGE_ROUTE = 'CHANGE_ROUTE';\n      const reducer = (\n        state: RouterReducerState,\n        action: any\n      ): RouterReducerState => {\n        if (action.type === CHANGE_ROUTE) {\n          return {\n            state: { url: '/next', root: <any>{} },\n            navigationId: 123,\n          };\n        } else {\n          const nextState = routerReducer(state, action);\n          if (nextState && nextState.state) {\n            return {\n              ...nextState,\n              state: {\n                ...nextState.state,\n                root: {} as any,\n              },\n            };\n          }\n          return nextState;\n        }\n      };\n\n      const routerError = new Error('BOOM!');\n      class SilentErrorHandler implements ErrorHandler {\n        handleError(error: any) {\n          expect(error).toBe(routerError);\n        }\n      }\n\n      createTestModule({\n        reducers: { reducer },\n        canActivate: () => {\n          throw routerError;\n        },\n        providers: [{ provide: ErrorHandler, useClass: SilentErrorHandler }],\n        config: { stateKey: 'reducer' },\n      });\n\n      const router = TestBed.inject(Router);\n      const store = TestBed.inject(Store);\n      const log = logOfRouterAndActionsAndStore();\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          log.splice(0);\n          store.dispatch({ type: CHANGE_ROUTE });\n          return waitForNavigation(router, NavigationError);\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            {\n              type: 'store',\n              state: { state: { url: '/next', root: {} }, navigationId: 123 },\n            },\n            { type: 'action', action: CHANGE_ROUTE },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            {\n              type: 'store',\n              state: { state: { url: '/', root: {} }, navigationId: 2 },\n            },\n            { type: 'action', action: ROUTER_ERROR },\n            { type: 'router', event: 'NavigationError', url: '/next' },\n          ]);\n\n          done();\n        });\n    }));\n\n  it('should call navigateByUrl when resetting state of the routerReducer', () =>\n    new Promise<void>((done) => {\n      const reducer = (state: any, action: RouterAction<any>) => {\n        const r = routerReducer(state, action);\n        return r && r.state\n          ? { url: r.state.url, navigationId: r.navigationId }\n          : null;\n      };\n\n      createTestModule({ reducers: { router: routerReducer, reducer } });\n\n      const router = TestBed.inject(Router);\n      const store = TestBed.inject(Store);\n      const log = logOfRouterAndActionsAndStore();\n\n      const routerReducerStates: any[] = [];\n      store.subscribe((state: any) => {\n        if (state.router) {\n          routerReducerStates.push(state.router);\n        }\n      });\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          log.splice(0);\n          return router.navigateByUrl('next');\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'store', state: null }, // ROUTER_REQUEST event in the store\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            { type: 'store', state: { url: '/next', navigationId: 2 } },\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n\n            /* new Router Lifecycle in Angular 4.3 */\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n            { type: 'router', event: 'ResolveStart', url: '/next' },\n            { type: 'router', event: 'ResolveEnd', url: '/next' },\n            { type: 'store', state: null }, // ROUTER_NAVIGATED event in the store\n            { type: 'action', action: ROUTER_NAVIGATED },\n            { type: 'router', event: 'NavigationEnd', url: '/next' },\n          ]);\n          log.splice(0);\n\n          store.dispatch({\n            type: ROUTER_NAVIGATION,\n            payload: {\n              routerState: routerReducerStates[0].state,\n              event: { id: routerReducerStates[0].navigationId },\n            },\n          });\n          return waitForNavigation(router);\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'router', event: 'NavigationStart', url: '/' },\n            { type: 'store', state: { url: '/', navigationId: 1 } }, // restored\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/' },\n\n            /* new Router Lifecycle in Angular 4.3 */\n            { type: 'router', event: 'GuardsCheckStart', url: '/' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/' },\n            { type: 'router', event: 'ResolveStart', url: '/' },\n            { type: 'router', event: 'ResolveEnd', url: '/' },\n\n            { type: 'router', event: 'NavigationEnd', url: '/' },\n          ]);\n          log.splice(0);\n        })\n        .then(() => {\n          store.dispatch({\n            type: ROUTER_NAVIGATION,\n            payload: {\n              routerState: routerReducerStates[3].state,\n              event: { id: routerReducerStates[3].navigationId },\n            },\n          });\n          return waitForNavigation(router);\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            { type: 'store', state: { url: '/next', navigationId: 2 } }, // restored\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n\n            /* new Router Lifecycle in Angular 4.3 */\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n            { type: 'router', event: 'ResolveStart', url: '/next' },\n            { type: 'router', event: 'ResolveEnd', url: '/next' },\n\n            { type: 'router', event: 'NavigationEnd', url: '/next' },\n          ]);\n          done();\n        });\n    }));\n\n  it('should support cancellation of initial navigation using canLoad guard', () =>\n    new Promise<void>((done) => {\n      const reducer = (state: any, action: RouterAction<any>) => {\n        const r = routerReducer(state, action);\n        return r && r.state\n          ? { url: r.state.url, navigationId: r.navigationId }\n          : null;\n      };\n\n      createTestModule({\n        reducers: { routerReducer, reducer },\n        canLoad: () => false,\n      });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore();\n\n      router.navigateByUrl('/load').then((r: boolean) => {\n        expect(r).toBe(false);\n\n        expect(log).toEqual([\n          { type: 'store', state: null }, // initial state\n          { type: 'store', state: null }, // ROUTER_REQUEST event in the store\n          { type: 'action', action: ROUTER_REQUEST },\n          { type: 'router', event: 'NavigationStart', url: '/load' },\n          { type: 'store', state: { url: '', navigationId: 1 } },\n          { type: 'action', action: ROUTER_CANCEL },\n          { type: 'router', event: 'NavigationCancel', url: '/load' },\n        ]);\n        done();\n      });\n    }));\n\n  it('should support cancellation of initial navigation when canLoad guard rejects', () =>\n    new Promise<void>((done) => {\n      const reducer = (state: any, action: RouterAction<any>) => {\n        const r = routerReducer(state, action);\n        return r && r.state\n          ? { url: r.state.url, navigationId: r.navigationId }\n          : null;\n      };\n\n      createTestModule({\n        reducers: { routerReducer, reducer },\n        canLoad: () => Promise.reject('boom'),\n      });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore();\n\n      router\n        .navigateByUrl('/load')\n        .then(() => {\n          fail(`Shouldn't be called`);\n        })\n        .catch((err) => {\n          expect(err).toBe('boom');\n\n          expect(log).toEqual([\n            { type: 'store', state: null }, // initial state\n            { type: 'store', state: null }, // ROUTER_REQEST event in the store\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/load' },\n            { type: 'store', state: { url: '', navigationId: 1 } },\n            { type: 'action', action: ROUTER_ERROR },\n            { type: 'router', event: 'NavigationError', url: '/load' },\n          ]);\n\n          done();\n        });\n    }));\n\n  function shouldSupportCustomSerializer(\n    serializerThroughConfig: boolean,\n    done: Function\n  ) {\n    interface SerializedState {\n      url: string;\n      params: any;\n    }\n\n    const reducer = (\n      state: any,\n      action: RouterAction<any, SerializedState>\n    ) => {\n      const r = routerReducer<SerializedState>(state, action);\n      return r && r.state\n        ? {\n            url: r.state.url,\n            navigationId: r.navigationId,\n            params: r.state.params,\n          }\n        : null;\n    };\n\n    @Injectable()\n    class CustomSerializer implements RouterStateSerializer<SerializedState> {\n      constructor(_store: Store<any>) {\n        // Requiring store to test Serializer with injected arguments works.\n      }\n      serialize(routerState: RouterStateSnapshot): SerializedState {\n        const url = `${routerState.url}-custom`;\n        const params = { test: 1 };\n\n        return { url, params };\n      }\n    }\n\n    if (serializerThroughConfig) {\n      createTestModule({\n        reducers: { routerReducer, reducer },\n        config: { serializer: CustomSerializer },\n      });\n    } else {\n      const providers = [\n        { provide: RouterStateSerializer, useClass: CustomSerializer },\n      ];\n      createTestModule({ reducers: { routerReducer, reducer }, providers });\n    }\n\n    const router = TestBed.inject(Router);\n    const log = logOfRouterAndActionsAndStore();\n\n    router\n      .navigateByUrl('/')\n      .then(() => {\n        log.splice(0);\n        return router.navigateByUrl('next');\n      })\n      .then(() => {\n        expect(log).toEqual([\n          { type: 'store', state: null }, // ROUTER_REQUEST event in the store\n          { type: 'action', action: ROUTER_REQUEST },\n          { type: 'router', event: 'NavigationStart', url: '/next' },\n          {\n            type: 'store',\n            state: {\n              url: '/next-custom',\n              navigationId: 2,\n              params: { test: 1 },\n            },\n          },\n          { type: 'action', action: ROUTER_NAVIGATION },\n          { type: 'router', event: 'RoutesRecognized', url: '/next' },\n          /* new Router Lifecycle in Angular 4.3 */\n          { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n          { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n          { type: 'router', event: 'ResolveStart', url: '/next' },\n          { type: 'router', event: 'ResolveEnd', url: '/next' },\n          { type: 'store', state: null }, // ROUTER_NAVIGATED event in the store\n          { type: 'action', action: ROUTER_NAVIGATED },\n          { type: 'router', event: 'NavigationEnd', url: '/next' },\n        ]);\n        log.splice(0);\n        done();\n      });\n  }\n\n  it('should support a custom RouterStateSnapshot serializer via provider', () =>\n    new Promise<void>((done) => {\n      shouldSupportCustomSerializer(false, done);\n    }));\n\n  it('should support a custom RouterStateSnapshot serializer via config', () =>\n    new Promise<void>((done) => {\n      shouldSupportCustomSerializer(true, done);\n    }));\n\n  it('should support event during an async canActivate guard', () =>\n    new Promise<void>((done) => {\n      createTestModule({\n        reducers: { routerReducer },\n        canActivate: () => {\n          store.dispatch({ type: 'USER_EVENT' });\n          return store.pipe(\n            take(1),\n            map(() => true)\n          );\n        },\n      });\n\n      const router = TestBed.inject(Router);\n      const store = TestBed.inject(Store);\n      const log = logOfRouterAndActionsAndStore();\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          log.splice(0);\n          return router.navigateByUrl('next');\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'store', state: undefined }, // after ROUTER_REQUEST\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            { type: 'store', state: undefined }, // after ROUTER_NAVIGATION\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n            /* new Router Lifecycle in Angular 4.3 */\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            { type: 'store', state: undefined }, // after USER_EVENT\n            { type: 'action', action: 'USER_EVENT' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n            { type: 'router', event: 'ResolveStart', url: '/next' },\n            { type: 'router', event: 'ResolveEnd', url: '/next' },\n            { type: 'store', state: undefined }, // after ROUTER_NAVIGATED\n            { type: 'action', action: ROUTER_NAVIGATED },\n            { type: 'router', event: 'NavigationEnd', url: '/next' },\n          ]);\n\n          done();\n        });\n    }));\n\n  it('should work when defining state key', () =>\n    new Promise<void>((done) => {\n      const reducer = (state = '', action: RouterAction<any>) => {\n        if (action.type === ROUTER_NAVIGATION) {\n          return action.payload.routerState.url.toString();\n        } else {\n          return state;\n        }\n      };\n\n      createTestModule({\n        reducers: { 'router-reducer': reducer },\n        config: { stateKey: 'router-reducer' },\n      });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore({ stateKey: 'router-reducer' });\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'store', state: '' }, // init event. has nothing to do with the router\n            { type: 'store', state: '' }, // ROUTER_REQUEST event in the store\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/' },\n            { type: 'store', state: '/' }, // ROUTER_NAVIGATION event in the store\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/' },\n            { type: 'router', event: 'GuardsCheckStart', url: '/' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/' },\n            { type: 'router', event: 'ResolveStart', url: '/' },\n            { type: 'router', event: 'ResolveEnd', url: '/' },\n            { type: 'store', state: '/' }, // ROUTER_NAVIGATED event in the store\n            { type: 'action', action: ROUTER_NAVIGATED },\n            { type: 'router', event: 'NavigationEnd', url: '/' },\n          ]);\n        })\n        .then(() => {\n          log.splice(0);\n          return router.navigateByUrl('next');\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'store', state: '/' },\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            { type: 'store', state: '/next' },\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n            { type: 'router', event: 'ResolveStart', url: '/next' },\n            { type: 'router', event: 'ResolveEnd', url: '/next' },\n            { type: 'store', state: '/next' },\n            { type: 'action', action: ROUTER_NAVIGATED },\n            { type: 'router', event: 'NavigationEnd', url: '/next' },\n          ]);\n\n          done();\n        });\n    }));\n\n  it('should work when defining state selector', () =>\n    new Promise<void>((done) => {\n      const reducer = (state = '', action: RouterAction<any>) => {\n        if (action.type === ROUTER_NAVIGATION) {\n          return action.payload.routerState.url.toString();\n        } else {\n          return state;\n        }\n      };\n\n      createTestModule({\n        reducers: { routerReducer: reducer },\n        config: { stateKey: (state: any) => state.routerReducer },\n      });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore({\n        stateKey: (state: any) => state.routerReducer,\n      });\n\n      router\n        .navigateByUrl('/')\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'store', state: '' }, // init event. has nothing to do with the router\n            { type: 'store', state: '' }, // ROUTER_REQUEST event in the store\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/' },\n            { type: 'store', state: '/' }, // ROUTER_NAVIGATION event in the store\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/' },\n            { type: 'router', event: 'GuardsCheckStart', url: '/' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/' },\n            { type: 'router', event: 'ResolveStart', url: '/' },\n            { type: 'router', event: 'ResolveEnd', url: '/' },\n            { type: 'store', state: '/' }, // ROUTER_NAVIGATED event in the store\n            { type: 'action', action: ROUTER_NAVIGATED },\n            { type: 'router', event: 'NavigationEnd', url: '/' },\n          ]);\n        })\n        .then(() => {\n          log.splice(0);\n          return router.navigateByUrl('next');\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'store', state: '/' },\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            { type: 'store', state: '/next' },\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n            { type: 'router', event: 'ResolveStart', url: '/next' },\n            { type: 'router', event: 'ResolveEnd', url: '/next' },\n            { type: 'store', state: '/next' },\n            { type: 'action', action: ROUTER_NAVIGATED },\n            { type: 'router', event: 'NavigationEnd', url: '/next' },\n          ]);\n\n          done();\n        });\n    }));\n\n  it('should continue to react to navigation after state initiates router change', () =>\n    new Promise<void>((done) => {\n      const reducer = (state: any = { state: { url: '/' } }, action: any) => {\n        if (action.type === ROUTER_NAVIGATION) {\n          return { state: { url: action.payload.routerState.url.toString() } };\n        } else {\n          return state;\n        }\n      };\n\n      createTestModule({\n        reducers: { reducer },\n        config: { stateKey: 'reducer' },\n      });\n\n      const router = TestBed.inject(Router);\n      const store = TestBed.inject(Store);\n      const log = logOfRouterAndActionsAndStore();\n\n      store.dispatch({\n        type: ROUTER_NAVIGATION,\n        payload: { routerState: { url: '/next' } },\n      });\n      waitForNavigation(router)\n        .then(() => {\n          router.navigate(['/']);\n          return waitForNavigation(router);\n        })\n        .then(() => {\n          expect(log).toEqual([\n            { type: 'store', state: { state: { url: '/' } } },\n            { type: 'router', event: 'NavigationStart', url: '/next' },\n            { type: 'store', state: { state: { url: '/next' } } },\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/next' },\n            { type: 'router', event: 'GuardsCheckStart', url: '/next' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/next' },\n            { type: 'router', event: 'ResolveStart', url: '/next' },\n            { type: 'router', event: 'ResolveEnd', url: '/next' },\n            { type: 'router', event: 'NavigationEnd', url: '/next' },\n            { type: 'store', state: { state: { url: '/next' } } },\n            { type: 'action', action: ROUTER_REQUEST },\n            { type: 'router', event: 'NavigationStart', url: '/' },\n            { type: 'store', state: { state: { url: '/' } } },\n            { type: 'action', action: ROUTER_NAVIGATION },\n            { type: 'router', event: 'RoutesRecognized', url: '/' },\n            { type: 'router', event: 'GuardsCheckStart', url: '/' },\n            { type: 'router', event: 'GuardsCheckEnd', url: '/' },\n            { type: 'router', event: 'ResolveStart', url: '/' },\n            { type: 'router', event: 'ResolveEnd', url: '/' },\n            { type: 'store', state: { state: { url: '/' } } },\n            { type: 'action', action: ROUTER_NAVIGATED },\n            { type: 'router', event: 'NavigationEnd', url: '/' },\n          ]);\n          done();\n        });\n    }));\n\n  it('should dispatch ROUTER_NAVIGATION later when config options set to true', () =>\n    new Promise<void>((done) => {\n      const reducer = (state = '', action: RouterAction<any>) => {\n        if (action.type === ROUTER_NAVIGATION) {\n          return action.payload.routerState.url.toString();\n        } else {\n          return state;\n        }\n      };\n\n      createTestModule({\n        reducers: { reducer },\n        config: {\n          navigationActionTiming: NavigationActionTiming.PostActivation,\n        },\n      });\n\n      const router = TestBed.inject(Router);\n      const log = logOfRouterAndActionsAndStore();\n\n      router.navigateByUrl('/').then(() => {\n        expect(log).toEqual([\n          { type: 'store', state: '' }, // init event. has nothing to do with the router\n          { type: 'store', state: '' }, // ROUTER_REQUEST event in the store\n          { type: 'action', action: ROUTER_REQUEST },\n          { type: 'router', event: 'NavigationStart', url: '/' },\n          { type: 'router', event: 'RoutesRecognized', url: '/' },\n          /* new Router Lifecycle in Angular 4.3 */\n          { type: 'router', event: 'GuardsCheckStart', url: '/' },\n          { type: 'router', event: 'GuardsCheckEnd', url: '/' },\n          { type: 'router', event: 'ResolveStart', url: '/' },\n          { type: 'router', event: 'ResolveEnd', url: '/' },\n          { type: 'store', state: '/' }, // ROUTER_NAVIGATION event in the store\n          { type: 'action', action: ROUTER_NAVIGATION },\n          { type: 'store', state: '/' }, // ROUTER_NAVIGATED event in the store\n          { type: 'action', action: ROUTER_NAVIGATED },\n          { type: 'router', event: 'NavigationEnd', url: '/' },\n        ]);\n        done();\n      });\n    }));\n});\n\nfunction waitForNavigation(router: Router, event: any = NavigationEnd) {\n  return router.events\n    .pipe(\n      filter((e) => e instanceof event),\n      first()\n    )\n    .toPromise();\n}\n\n/**\n * Logs the events of router, store and actions$.\n * Note: Because of the synchronous nature of many of those events, it may sometimes\n * appear that the order is \"mixed\" up even if its correct.\n * Example: router event is fired -> store is updated -> store log appears before router log\n * Also, actions$ always fires the next action AFTER the store is updated\n */\nfunction logOfRouterAndActionsAndStore(\n  options: { stateKey: StateKeyOrSelector } = {\n    stateKey: 'reducer',\n  }\n): any[] {\n  const router = TestBed.inject(Router);\n  const store = TestBed.inject(Store);\n  // Not using effects' Actions to avoid @ngrx/effects dependency\n  const actions$ = TestBed.inject(ScannedActionsSubject);\n  const log: any[] = [];\n  router.events.subscribe((e) => {\n    if (e.hasOwnProperty('url')) {\n      log.push({\n        type: 'router',\n        event: e.constructor.name,\n        url: (<any>e).url.toString(),\n      });\n    }\n  });\n  actions$.subscribe((action) =>\n    log.push({ type: 'action', action: action.type })\n  );\n  store.subscribe((store) => {\n    if (typeof options.stateKey === 'function') {\n      log.push({ type: 'store', state: options.stateKey(store) });\n    } else {\n      log.push({ type: 'store', state: store[options.stateKey] });\n    }\n  });\n  return log;\n}\n"
  },
  {
    "path": "modules/router-store/spec/router_selectors.spec.ts",
    "content": "import {\n  getRouterSelectors,\n  RouterReducerState,\n  DEFAULT_ROUTER_FEATURENAME,\n  createRouterSelector,\n} from '..';\nimport { RouterStateSelectors } from '../src/models';\n\nconst mockData = {\n  state: {\n    root: {\n      params: {},\n      paramMap: {\n        params: {},\n      },\n      data: {},\n      url: [],\n      outlet: 'primary',\n      routeConfig: null,\n      queryParams: {\n        ref: 'ngrx.io',\n      },\n      queryParamMap: {\n        params: {\n          ref: 'ngrx.io',\n        },\n      },\n      fragment: 'test-fragment',\n      firstChild: {\n        params: {},\n        paramMap: {\n          params: {},\n        },\n        data: {},\n        url: [\n          {\n            path: 'login',\n            parameters: {},\n          },\n        ],\n        outlet: 'primary',\n        routeConfig: {\n          path: 'login',\n        },\n        queryParams: {\n          ref: 'ngrx.io',\n        },\n        queryParamMap: {\n          params: {\n            ref: 'ngrx.io',\n          },\n        },\n        firstChild: {\n          params: {\n            id: 'etyDDwAAQBAJ',\n          },\n          paramMap: {\n            params: {\n              id: 'etyDDwAAQBAJ',\n            },\n          },\n          data: {\n            testData: 'test-data',\n          },\n          url: [\n            {\n              path: 'etyDDwAAQBAJ',\n              parameters: {},\n            },\n          ],\n          outlet: 'primary',\n          routeConfig: {\n            path: ':id',\n          },\n          queryParams: {\n            ref: 'ngrx.io',\n          },\n          queryParamMap: {\n            params: {\n              ref: 'ngrx.io',\n            },\n          },\n          fragment: 'test-fragment',\n          children: [],\n        },\n        fragment: 'test-fragment',\n        children: [],\n      },\n      children: [\n        {\n          params: {},\n          paramMap: {\n            params: {},\n          },\n          data: {},\n          url: [\n            {\n              path: 'login',\n              parameters: {},\n            },\n          ],\n          outlet: 'primary',\n          routeConfig: {\n            path: 'login',\n          },\n          queryParams: {\n            ref: 'ngrx.io',\n          },\n          queryParamMap: {\n            params: {\n              ref: 'ngrx.io',\n            },\n          },\n          fragment: 'test-fragment',\n          children: [],\n        },\n      ],\n    },\n    url: '/login',\n  },\n  navigationId: 1,\n};\n\ndescribe('Router State Selectors', () => {\n  describe('Composed Selectors', () => {\n    interface State {\n      router: RouterReducerState<any>;\n    }\n\n    let selectors: RouterStateSelectors<State>;\n    let state: State;\n\n    beforeEach(() => {\n      state = {\n        router: mockData,\n      };\n\n      selectors = getRouterSelectors();\n    });\n\n    it('should create selectCurrentRoute selector for selecting the current route', () => {\n      const result = selectors.selectCurrentRoute(state);\n\n      expect(result).toEqual(state.router.state.root.firstChild.firstChild);\n    });\n\n    it('should be able to overwrite default router feature state name', () => {\n      const stateOverwrite = {\n        anotherRouterKey: mockData,\n      };\n      const selectorOverwrite = getRouterSelectors(\n        (state: typeof stateOverwrite) => state.anotherRouterKey\n      );\n\n      const result = selectorOverwrite.selectCurrentRoute(stateOverwrite);\n      expect(result).toEqual(\n        stateOverwrite.anotherRouterKey.state.root.firstChild.firstChild\n      );\n    });\n\n    it('should be able to use DEFAULT_ROUTER_FEATURENAME and createRouterSelector to select router feature state', () => {\n      const stateOverwrite = {\n        [DEFAULT_ROUTER_FEATURENAME]: mockData,\n      };\n      const selectorOverwrite = getRouterSelectors(createRouterSelector());\n\n      const result = selectorOverwrite.selectCurrentRoute(stateOverwrite);\n      expect(result).toEqual(\n        stateOverwrite[DEFAULT_ROUTER_FEATURENAME].state.root.firstChild\n          .firstChild\n      );\n    });\n\n    it('should return undefined from selectCurrentRoute if routerState does not exist', () => {\n      interface State {\n        router: any;\n      }\n      const state: State = {\n        router: undefined,\n      };\n      selectors = getRouterSelectors((state: State) => state.router);\n\n      const result = selectors.selectCurrentRoute(state);\n\n      expect(result).toEqual(undefined);\n    });\n\n    it('should create a selector for selecting the fragment', () => {\n      const result = selectors.selectFragment(state);\n\n      expect(result).toEqual(state.router.state.root.fragment);\n    });\n\n    it('should create a selector for selecting the query params', () => {\n      const result = selectors.selectQueryParams(state);\n\n      expect(result).toEqual(state.router.state.root.queryParams);\n    });\n\n    it('should create a selector for selecting a specific query param', () => {\n      const result = selectors.selectQueryParam('ref')(state);\n\n      expect(result).toEqual(state.router.state.root.queryParams.ref);\n    });\n\n    it('should create a selector for selecting the route params', () => {\n      const result = selectors.selectRouteParams(state);\n\n      expect(result).toEqual(\n        state.router.state.root.firstChild.firstChild.params\n      );\n    });\n\n    it('should create a selector for selecting a specific route param', () => {\n      const result = selectors.selectRouteParam('id')(state);\n\n      expect(result).toEqual(\n        state.router.state.root.firstChild.firstChild.params.id\n      );\n    });\n\n    it('should create a selector for selecting the route data', () => {\n      const result = selectors.selectRouteData(state);\n\n      expect(result).toEqual(\n        state.router.state.root.firstChild.firstChild.data\n      );\n    });\n\n    it('should create a selector for selecting a specific route data param', () => {\n      const result = selectors.selectRouteDataParam('testData')(state);\n\n      expect(result).toEqual(\n        state.router.state.root.firstChild.firstChild.data.testData\n      );\n    });\n\n    it('should create a selector for selecting the url', () => {\n      const result = selectors.selectUrl(state);\n\n      expect(result).toEqual(state.router.state.url);\n    });\n\n    describe('selectTitle', () => {\n      it('should return undefined when route is not defined', () => {\n        const title = selectors.selectTitle({\n          router: { state: { root: null }, navigationId: 1 },\n        });\n\n        expect(title).toBe(undefined);\n      });\n\n      it('should return undefined when route config is not defined', () => {\n        const title = selectors.selectTitle({\n          router: {\n            state: { root: { routeConfig: null } },\n            navigationId: 1,\n          },\n        });\n\n        expect(title).toBe(undefined);\n      });\n\n      it('should return undefined when title is not defined', () => {\n        const title = selectors.selectTitle({\n          router: {\n            state: { root: { routeConfig: {} } },\n            navigationId: 1,\n          },\n        });\n\n        expect(title).toBe(undefined);\n      });\n\n      it('should return static title', () => {\n        const staticTitle = 'Static Title';\n        const title = selectors.selectTitle({\n          router: {\n            state: { root: { routeConfig: { title: staticTitle } } },\n            navigationId: 1,\n          },\n        });\n\n        expect(title).toBe(staticTitle);\n      });\n\n      it('should return resolved title', () => {\n        const resolvedTitle = 'Resolved Title';\n        const title = selectors.selectTitle({\n          router: {\n            state: {\n              root: {\n                routeConfig: { title: class TitleResolver {} },\n                title: resolvedTitle,\n              },\n            },\n            navigationId: 1,\n          },\n        });\n\n        expect(title).toBe(resolvedTitle);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/router-store/spec/router_store_module.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { NavigationEnd, Router, RouterEvent } from '@angular/router';\nimport {\n  FullRouterStateSerializer,\n  MinimalRouterStateSerializer,\n  RouterAction,\n  routerReducer,\n  RouterReducerState,\n  RouterState,\n  RouterStateSerializer,\n} from '..';\nimport { ActionsSubject, select, Store } from '@ngrx/store';\nimport { filter, withLatestFrom } from 'rxjs/operators';\n\nimport { createTestModule } from './utils';\nimport { StoreRouterConnectingService } from '../src/store_router_connecting.service';\n\ndescribe('Router Store Module', () => {\n  describe('with defining state key', () => {\n    const customStateKey = 'router-reducer';\n    let storeRouterConnectingService: StoreRouterConnectingService;\n    let store: Store<State>;\n    let router: Router;\n\n    interface State {\n      [customStateKey]: RouterReducerState;\n    }\n\n    beforeEach(() => {\n      createTestModule({\n        reducers: {\n          [customStateKey]: routerReducer,\n        },\n        config: {\n          stateKey: customStateKey,\n        },\n      });\n\n      store = TestBed.inject(Store);\n      router = TestBed.inject(Router);\n      storeRouterConnectingService = TestBed.inject(\n        StoreRouterConnectingService\n      );\n    });\n\n    it('should have custom state key as own property', () => {\n      expect((<any>storeRouterConnectingService).stateKey).toBe(customStateKey);\n    });\n\n    it('should call navigateIfNeeded with args selected by custom state key', () =>\n      new Promise<void>((done) => {\n        let logs: any[] = [];\n        store\n          .pipe(select(customStateKey), withLatestFrom(store))\n          .subscribe(([routerStoreState, storeState]) => {\n            logs.push([routerStoreState, storeState]);\n          });\n\n        vi.spyOn(storeRouterConnectingService, 'navigateIfNeeded' as never);\n        logs = [];\n\n        // this dispatches `@ngrx/router-store/navigation` action\n        // and store emits its payload.\n        router.navigateByUrl('/').then(() => {\n          const actual = (<any>storeRouterConnectingService).navigateIfNeeded\n            .mock.calls;\n\n          expect(actual.length).toBe(1);\n          expect(actual[0]).toEqual(logs[0]);\n          done();\n        });\n      }));\n  });\n\n  describe('with defining state selector', () => {\n    const customStateKey = 'routerReducer';\n    const customStateSelector = (state: State) => state.routerReducer;\n\n    let storeRouterConnectingService: StoreRouterConnectingService;\n    let store: Store<State>;\n    let router: Router;\n\n    interface State {\n      [customStateKey]: RouterReducerState;\n    }\n\n    beforeEach(() => {\n      createTestModule({\n        reducers: {\n          [customStateKey]: routerReducer,\n        },\n        config: {\n          stateKey: customStateSelector,\n        },\n      });\n\n      store = TestBed.inject(Store);\n      router = TestBed.inject(Router);\n      storeRouterConnectingService = TestBed.inject(\n        StoreRouterConnectingService\n      );\n    });\n\n    it('should have same state selector as own property', () => {\n      expect((<any>storeRouterConnectingService).stateKey).toBe(\n        customStateSelector\n      );\n    });\n\n    it('should call navigateIfNeeded with args selected by custom state selector', () =>\n      new Promise<void>((done) => {\n        let logs: any[] = [];\n        store\n          .pipe(select(customStateSelector), withLatestFrom(store))\n          .subscribe(([routerStoreState, storeState]) => {\n            logs.push([routerStoreState, storeState]);\n          });\n\n        vi.spyOn(storeRouterConnectingService, 'navigateIfNeeded' as never);\n        logs = [];\n\n        // this dispatches `@ngrx/router-store/navigation` action\n        // and store emits its payload.\n        router.navigateByUrl('/').then(() => {\n          const actual = (<any>storeRouterConnectingService).navigateIfNeeded\n            .mock.calls;\n\n          expect(actual.length).toBe(1);\n          expect(actual[0]).toEqual(logs[0]);\n          done();\n        });\n      }));\n  });\n\n  describe('routerState', () => {\n    function setup(routerState?: RouterState, serializer?: any) {\n      createTestModule({\n        reducers: {},\n        config: {\n          routerState,\n          serializer,\n        },\n      });\n\n      return {\n        actions: TestBed.inject(ActionsSubject),\n        router: TestBed.inject(Router),\n        serializer: TestBed.inject(RouterStateSerializer),\n      };\n    }\n\n    const onlyRouterActions = (a: any): a is RouterAction<any, any> =>\n      a.payload && a.payload.event;\n\n    describe('Full', () => {\n      it('should dispatch the full event', () =>\n        new Promise<void>((done) => {\n          const { actions, router } = setup(RouterState.Full);\n          actions.pipe(filter(onlyRouterActions)).subscribe(({ payload }) => {\n            expect(payload.event instanceof RouterEvent).toBe(true);\n            done();\n          });\n\n          router.navigateByUrl('/');\n        }));\n\n      it('should use the default router serializer by default', () => {\n        const { serializer } = setup();\n        expect(serializer).toEqual(new MinimalRouterStateSerializer());\n      });\n\n      it('should use the default router serializer if minimal state option is passed in', () => {\n        const { serializer } = setup(RouterState.Minimal);\n        expect(serializer).toEqual(new MinimalRouterStateSerializer());\n      });\n\n      it('should use the full router serializer if full state option is passed in', () => {\n        const { serializer } = setup(RouterState.Full);\n        expect(serializer).toEqual(new FullRouterStateSerializer());\n      });\n\n      it('should use the provided serializer if one is provided', () => {\n        const { serializer } = setup(\n          RouterState.Full,\n          FullRouterStateSerializer\n        );\n        expect(serializer).toEqual(new FullRouterStateSerializer());\n      });\n    });\n\n    describe('Minimal', () => {\n      it('should dispatch the navigation id with url', () =>\n        new Promise<void>((done) => {\n          const { actions, router } = setup(RouterState.Minimal);\n          actions\n            .pipe(filter(onlyRouterActions))\n            .subscribe(({ payload }: any) => {\n              expect(payload.event instanceof RouterEvent).toBe(false);\n              expect(payload.event).toEqual({ id: 1, url: '/' });\n              done();\n            });\n\n          router.navigateByUrl('/');\n        }));\n\n      it('should dispatch the navigation with urlAfterRedirects', () =>\n        new Promise<void>((done) => {\n          const { actions, router } = setup(RouterState.Minimal);\n          actions\n            .pipe(\n              filter(onlyRouterActions),\n              // wait until NavigationEnd router event\n              filter(\n                ({ payload }) =>\n                  !!(payload.event as NavigationEnd).urlAfterRedirects\n              )\n            )\n            .subscribe(({ payload }: any) => {\n              expect(payload.event instanceof RouterEvent).toBe(false);\n              expect(payload.event).toEqual({\n                id: 1,\n                url: '/redirect',\n                urlAfterRedirects: '/next',\n              });\n              done();\n            });\n\n          router.navigateByUrl('/redirect');\n        }));\n\n      it('should use the minimal router serializer', () => {\n        const { serializer } = setup(RouterState.Minimal);\n        expect(serializer).toEqual(new FullRouterStateSerializer());\n      });\n\n      it('should use the provided serializer if one is provided', () => {\n        const { serializer } = setup(\n          RouterState.Minimal,\n          MinimalRouterStateSerializer\n        );\n        expect(serializer).toEqual(new MinimalRouterStateSerializer());\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/router-store/spec/serializers.spec.ts",
    "content": "import { RouterStateSnapshot } from '@angular/router';\nimport { FullRouterStateSerializer, MinimalRouterStateSerializer } from '..';\n\ndescribe('full serializer', () => {\n  it('should serialize all properties', () => {\n    const serializer = new FullRouterStateSerializer();\n    const snapshot = createRouteSnapshot();\n    const routerState = {\n      url: 'url',\n      root: snapshot,\n    } as RouterStateSnapshot;\n\n    const actual = serializer.serialize(routerState);\n    const expected = {\n      url: 'url',\n      root: createExpectedSnapshot(),\n    };\n    expect(actual).toEqual(expected);\n  });\n\n  it('should serialize with an empty routeConfig', () => {\n    const serializer = new FullRouterStateSerializer();\n    const snapshot = { ...createRouteSnapshot(), routeConfig: null };\n    const routerState = {\n      url: 'url',\n      root: snapshot,\n    } as RouterStateSnapshot;\n\n    const actual = serializer.serialize(routerState);\n    const expected = {\n      url: 'url',\n      root: {\n        ...createExpectedSnapshot(),\n        routeConfig: null,\n        component: undefined,\n      },\n    };\n    expect(actual).toEqual(expected);\n  });\n\n  it('should serialize children', () => {\n    const serializer = new FullRouterStateSerializer();\n    const snapshot = {\n      ...createRouteSnapshot(),\n      children: [createRouteSnapshot('child')],\n    };\n    const routerState = {\n      url: 'url',\n      root: snapshot,\n    } as RouterStateSnapshot;\n\n    const actual = serializer.serialize(routerState);\n\n    const expected = {\n      url: 'url',\n      root: {\n        ...createExpectedSnapshot(),\n        firstChild: createExpectedSnapshot('child'),\n        children: [createExpectedSnapshot('child')],\n      },\n    };\n\n    expect(actual).toEqual(expected);\n  });\n\n  function createExpectedSnapshot(prefix = 'root') {\n    return {\n      ...createRouteSnapshot(prefix),\n      component: `${prefix}-route.routeConfig.component`,\n      root: undefined,\n      parent: undefined,\n      firstChild: undefined,\n      pathFromRoot: undefined,\n    };\n  }\n});\n\ndescribe('minimal serializer', () => {\n  it('should serialize only the minimal properties', () => {\n    const serializer = new MinimalRouterStateSerializer();\n    const snapshot = createRouteSnapshot();\n    const routerState = {\n      url: 'url',\n      root: snapshot,\n    } as RouterStateSnapshot;\n\n    const actual = serializer.serialize(routerState);\n    const expected = {\n      url: 'url',\n      root: createExpectedSnapshot(),\n    };\n    expect(actual).toEqual(expected);\n  });\n\n  it('should serialize with an empty routeConfig', () => {\n    const serializer = new MinimalRouterStateSerializer();\n    const snapshot = { ...createRouteSnapshot(), routeConfig: null };\n    const routerState = {\n      url: 'url',\n      root: snapshot,\n    } as RouterStateSnapshot;\n\n    const actual = serializer.serialize(routerState);\n    const expected = {\n      url: 'url',\n      root: {\n        ...createExpectedSnapshot(),\n        routeConfig: null,\n      },\n    };\n    expect(actual).toEqual(expected);\n  });\n\n  it('should serialize children', () => {\n    const serializer = new MinimalRouterStateSerializer();\n    const snapshot = {\n      ...createRouteSnapshot(),\n      children: [createRouteSnapshot('child')],\n    };\n    const routerState = {\n      url: 'url',\n      root: snapshot,\n    } as RouterStateSnapshot;\n\n    const actual = serializer.serialize(routerState);\n\n    const expected = {\n      url: 'url',\n      root: {\n        ...createExpectedSnapshot(),\n        firstChild: createExpectedSnapshot('child'),\n        children: [createExpectedSnapshot('child')],\n      },\n    };\n\n    expect(actual).toEqual(expected);\n  });\n\n  function createExpectedSnapshot(prefix = 'root') {\n    const snapshot = {\n      ...createRouteSnapshot(prefix),\n      routeConfig: {\n        // config doesn't have a component because it isn't serializable\n        path: `${prefix}-route.routeConfig.path`,\n        pathMatch: `${prefix}-route.routeConfig.pathMatch`,\n        redirectTo: `${prefix}-route.routeConfig.redirectTo`,\n        outlet: `${prefix}-route.routeConfig.outlet`,\n        title: `${prefix}-route.routeConfig.title`,\n      },\n      firstChild: undefined,\n    };\n\n    // properties that aren't serializable\n    delete snapshot.paramMap;\n    delete snapshot.queryParamMap;\n    delete snapshot.component;\n\n    // properties that do not exist on the minimal serializer\n    delete snapshot.root;\n    delete snapshot.parent;\n    delete snapshot.pathFromRoot;\n\n    return snapshot;\n  }\n});\n\nfunction createRouteSnapshot(prefix = 'root'): any {\n  return {\n    params: `${prefix}-route.params`,\n    paramMap: `${prefix}-route.paramMap`,\n    data: `${prefix}-route.data`,\n    url: `${prefix}-route.url`,\n    outlet: `${prefix}-route.outlet`,\n    routeConfig: {\n      component: `${prefix}-route.routeConfig.component`,\n      path: `${prefix}-route.routeConfig.path`,\n      pathMatch: `${prefix}-route.routeConfig.pathMatch`,\n      redirectTo: `${prefix}-route.routeConfig.redirectTo`,\n      outlet: `${prefix}-route.routeConfig.outlet`,\n      title: `${prefix}-route.routeConfig.title`,\n    },\n    queryParams: `${prefix}-route.queryParams`,\n    queryParamMap: `${prefix}-route.queryParamMap`,\n    fragment: `${prefix}-route.fragment`,\n    root: `${prefix}-route.root`,\n    parent: `${prefix}-route.parent`,\n    pathFromRoot: `${prefix}-route.params`,\n    firstChild: null,\n    children: [],\n  };\n}\n"
  },
  {
    "path": "modules/router-store/spec/types/router_selectors.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('getRouterSelectors', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import { getRouterSelectors, RouterReducerState } from '@ngrx/router-store';\n      import { createSelector, createFeatureSelector } from '@ngrx/store';\n\n      export interface State {\n        router: RouterReducerState<any>;\n      }\n\n      export const selectRouter = createFeatureSelector<\n        State,\n        RouterReducerState<any>\n      >('router');\n\n      export const {\n        selectCurrentRoute,\n        selectQueryParams,\n        selectQueryParam,\n        selectRouteParams,\n        selectRouteParam,\n        selectRouteData,\n        selectUrl,\n        selectTitle,\n      } = getRouterSelectors(selectRouter);\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  it('selectCurrentRoute should return any', () => {\n    expectSnippet(`\n      export const selector = createSelector(\n        selectCurrentRoute,\n        route => route\n      );\n    `).toInfer('selector', 'MemoizedSelector<State, any, (s1: any) => any>');\n  });\n\n  it('selectQueryParams should return Params', () => {\n    expectSnippet(`\n      export const selector = createSelector(\n        selectQueryParams,\n        params => params\n      );\n    `).toInfer(\n      'selector',\n      'MemoizedSelector<State, Params, (s1: Params) => Params>'\n    );\n  });\n\n  it('selectQueryParam should return string or string[]', () => {\n    expectSnippet(`\n      export const selectIdFromRoute = selectQueryParam('id')\n      export const selector = createSelector(\n        selectIdFromRoute,\n        id => id\n      );\n    `).toInfer(\n      'selector',\n      'MemoizedSelector<State, string | string[], (s1: string | string[]) => string | string[]>'\n    );\n  });\n\n  it('selectRouteParams should return Params', () => {\n    expectSnippet(`\n      export const selector = createSelector(\n        selectRouteParams,\n        params => params\n      );\n    `).toInfer(\n      'selector',\n      'MemoizedSelector<State, Params, (s1: Params) => Params>'\n    );\n  });\n\n  it('selectRouteParam should return string', () => {\n    expectSnippet(`\n      export const selectIdFromRoute = selectRouteParam('id')\n      export const selector = createSelector(\n        selectIdFromRoute,\n        id => id\n      );\n    `).toInfer(\n      'selector',\n      'MemoizedSelector<State, string, (s1: string) => string>'\n    );\n  });\n\n  it('selectRouteData should return Data', () => {\n    expectSnippet(`\n      export const selector = createSelector(\n        selectRouteData,\n        data => data\n      );\n    `).toInfer('selector', 'MemoizedSelector<State, Data, (s1: Data) => Data>');\n  });\n\n  it('selectUrl should return string', () => {\n    expectSnippet(`\n      export const selector = createSelector(\n        selectUrl,\n        url => url\n      );\n    `).toInfer(\n      'selector',\n      'MemoizedSelector<State, string, (s1: string) => string>'\n    );\n  });\n\n  it('selectTitle should return string', () => {\n    expectSnippet(`\n      export const selector = createSelector(\n        selectTitle,\n        url => url\n      );\n    `).toInfer(\n      'selector',\n      'MemoizedSelector<State, string, (s1: string) => string>'\n    );\n  });\n}, 8_000);\n\ndescribe('RouterStateSelectors', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import { Selector } from '@ngrx/store';\n      import { RouterStateSelectors } from './modules/router-store/src/models';\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  it('is compatible with a dictionary of selectors', () => {\n    expectSnippet(`\n      type SelectorsDictionary = Record<\n        string,\n        | Selector<Record<string, any>, unknown>\n        | ((...args: any[]) => Selector<Record<string, any>, unknown>)\n      >;\n      type ExtendsSelectorsDictionary<T> = T extends SelectorsDictionary\n        ? true\n        : false;\n\n      let result: ExtendsSelectorsDictionary<\n        RouterStateSelectors<Record<string, any>>\n      >;\n    `).toInfer('result', 'true');\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/router-store/spec/types/utils.ts",
    "content": "export const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  target: 'ES2022',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  paths: {\n    '@ngrx/store': ['./modules/store'],\n    '@ngrx/router-store': ['./modules/router-store'],\n  },\n});\n"
  },
  {
    "path": "modules/router-store/spec/utils.ts",
    "content": "import { Component, Provider, Type, ɵConsole as Console } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { RouterTestingModule } from '@angular/router/testing';\nimport { StoreModule } from '@ngrx/store';\n\nimport { StoreRouterConfig, StoreRouterConnectingModule } from '../src';\nimport { RouterOutlet } from '@angular/router';\nimport { vi } from 'vitest';\n\nexport function createTestModule(\n  opts: {\n    reducers?: any;\n    canActivate?: Function;\n    canLoad?: Function;\n    providers?: Provider[];\n    config?: StoreRouterConfig;\n  } = {}\n) {\n  @Component({\n    selector: 'test-app',\n    template: '<router-outlet></router-outlet>',\n    imports: [RouterOutlet],\n  })\n  class AppComponent {}\n\n  @Component({\n    selector: 'page-cmp',\n    template: 'page-cmp',\n  })\n  class SimpleComponent {}\n\n  TestBed.configureTestingModule({\n    imports: [\n      StoreModule.forRoot(opts.reducers),\n      RouterTestingModule.withRoutes([\n        { path: '', component: SimpleComponent },\n        {\n          path: 'next',\n          component: SimpleComponent,\n          canActivate: ['CanActivateNext'],\n        },\n        {\n          path: 'load',\n          loadChildren: () => Promise.resolve({} as Type<any>),\n          canLoad: ['CanLoadNext'],\n        },\n        {\n          path: 'redirect',\n          pathMatch: 'full',\n          redirectTo: 'next',\n        },\n      ]),\n      StoreRouterConnectingModule.forRoot(opts.config),\n    ],\n    providers: [\n      {\n        provide: 'CanActivateNext',\n        useValue: opts.canActivate || (() => true),\n      },\n      {\n        provide: 'CanLoadNext',\n        useValue: opts.canLoad || (() => true),\n      },\n      {\n        provide: Console,\n        useValue: {\n          log: vi.fn(),\n          warn: vi.fn(),\n        },\n      },\n      opts.providers || [],\n    ],\n  });\n\n  TestBed.createComponent(AppComponent);\n}\n"
  },
  {
    "path": "modules/router-store/src/actions.ts",
    "content": "import {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  RoutesRecognized,\n} from '@angular/router';\n\nimport { BaseRouterStoreState } from './serializers/base';\nimport { SerializedRouterStateSnapshot } from './serializers/full_serializer';\nimport { createAction, props } from '@ngrx/store';\n\n/**\n * An action dispatched when a router navigation request is fired.\n */\nexport const ROUTER_REQUEST = '@ngrx/router-store/request';\n\n/**\n * Payload of ROUTER_REQUEST\n */\nexport type RouterRequestPayload<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  routerState: T;\n  event: NavigationStart;\n};\n\n/**\n * An action dispatched when a router navigation request is fired.\n */\nexport type RouterRequestAction<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  type: typeof ROUTER_REQUEST;\n  payload: RouterRequestPayload<T>;\n};\n\nexport const routerRequestAction = createAction(\n  ROUTER_REQUEST,\n  props<{ payload: RouterRequestPayload<SerializedRouterStateSnapshot> }>()\n);\n/**\n * An action dispatched when the router navigates.\n */\nexport const ROUTER_NAVIGATION = '@ngrx/router-store/navigation';\n\n/**\n * Payload of ROUTER_NAVIGATION.\n */\nexport type RouterNavigationPayload<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  routerState: T;\n  event: RoutesRecognized;\n};\n\n/**\n * An action dispatched when the router navigates.\n */\nexport type RouterNavigationAction<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  type: typeof ROUTER_NAVIGATION;\n  payload: RouterNavigationPayload<T>;\n};\n\nexport const routerNavigationAction = createAction(\n  ROUTER_NAVIGATION,\n  props<{ payload: RouterNavigationPayload<SerializedRouterStateSnapshot> }>()\n);\n\n/**\n * An action dispatched when the router cancels navigation.\n */\nexport const ROUTER_CANCEL = '@ngrx/router-store/cancel';\n\n/**\n * Payload of ROUTER_CANCEL.\n */\nexport type RouterCancelPayload<\n  T,\n  V extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  routerState: V;\n  storeState: T;\n  event: NavigationCancel;\n};\n\n/**\n * An action dispatched when the router cancels navigation.\n */\nexport type RouterCancelAction<\n  T,\n  V extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  type: typeof ROUTER_CANCEL;\n  payload: RouterCancelPayload<T, V>;\n};\n\nexport const routerCancelAction = createAction(\n  ROUTER_CANCEL,\n  props<{ payload: RouterCancelPayload<SerializedRouterStateSnapshot> }>()\n);\n\n/**\n * An action dispatched when the router errors.\n */\nexport const ROUTER_ERROR = '@ngrx/router-store/error';\n\n/**\n * Payload of ROUTER_ERROR.\n */\nexport type RouterErrorPayload<\n  T,\n  V extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  routerState: V;\n  storeState: T;\n  event: NavigationError;\n};\n\n/**\n * An action dispatched when the router errors.\n */\nexport type RouterErrorAction<\n  T,\n  V extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  type: typeof ROUTER_ERROR;\n  payload: RouterErrorPayload<T, V>;\n};\n\nexport const routerErrorAction = createAction(\n  ROUTER_ERROR,\n  props<{ payload: RouterErrorPayload<SerializedRouterStateSnapshot> }>()\n);\n\n/**\n * An action dispatched after navigation has ended and new route is active.\n */\nexport const ROUTER_NAVIGATED = '@ngrx/router-store/navigated';\n\n/**\n * Payload of ROUTER_NAVIGATED.\n */\nexport type RouterNavigatedPayload<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  routerState: T;\n  event: NavigationEnd;\n};\n\n/**\n * An action dispatched after navigation has ended and new route is active.\n */\nexport type RouterNavigatedAction<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  type: typeof ROUTER_NAVIGATED;\n  payload: RouterNavigatedPayload<T>;\n};\n\nexport const routerNavigatedAction = createAction(\n  ROUTER_NAVIGATED,\n  props<{ payload: RouterNavigatedPayload<SerializedRouterStateSnapshot> }>()\n);\n\n/**\n * A union type of router actions.\n */\nexport type RouterAction<\n  T,\n  V extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> =\n  | RouterRequestAction<V>\n  | RouterNavigationAction<V>\n  | RouterCancelAction<T, V>\n  | RouterErrorAction<T, V>\n  | RouterNavigatedAction<V>;\n"
  },
  {
    "path": "modules/router-store/src/index.ts",
    "content": "export {\n  ROUTER_ERROR,\n  ROUTER_CANCEL,\n  ROUTER_NAVIGATION,\n  ROUTER_NAVIGATED,\n  ROUTER_REQUEST,\n  RouterAction,\n  RouterCancelAction,\n  RouterCancelPayload,\n  RouterErrorAction,\n  RouterErrorPayload,\n  RouterNavigatedAction,\n  RouterNavigatedPayload,\n  RouterNavigationAction,\n  RouterNavigationPayload,\n  RouterRequestAction,\n  RouterRequestPayload,\n  routerCancelAction,\n  routerErrorAction,\n  routerNavigatedAction,\n  routerNavigationAction,\n  routerRequestAction,\n} from './actions';\nexport { routerReducer, RouterReducerState } from './reducer';\nexport { StoreRouterConnectingModule } from './router_store_module';\nexport {\n  StateKeyOrSelector,\n  StoreRouterConfig,\n  NavigationActionTiming,\n  ROUTER_CONFIG,\n  DEFAULT_ROUTER_FEATURENAME,\n  RouterState,\n} from './router_store_config';\nexport {\n  RouterStateSerializer,\n  BaseRouterStoreState,\n} from './serializers/base';\nexport {\n  FullRouterStateSerializer,\n  SerializedRouterStateSnapshot,\n} from './serializers/full_serializer';\nexport {\n  MinimalActivatedRouteSnapshot,\n  MinimalRouterStateSnapshot,\n  MinimalRouterStateSerializer,\n} from './serializers/minimal_serializer';\nexport { getRouterSelectors, createRouterSelector } from './router_selectors';\nexport { provideRouterStore } from './provide_router_store';\n"
  },
  {
    "path": "modules/router-store/src/models.ts",
    "content": "import { Data, Params } from '@angular/router';\nimport { MemoizedSelector } from '@ngrx/store';\n\nexport type RouterStateSelectors<V> = {\n  selectCurrentRoute: MemoizedSelector<V, any>;\n  selectFragment: MemoizedSelector<V, string | undefined>;\n  selectQueryParams: MemoizedSelector<V, Params>;\n  selectQueryParam: (\n    param: string\n  ) => MemoizedSelector<V, string | string[] | undefined>;\n  selectRouteParams: MemoizedSelector<V, Params>;\n  selectRouteParam: (param: string) => MemoizedSelector<V, string | undefined>;\n  selectRouteData: MemoizedSelector<V, Data>;\n  selectRouteDataParam: (\n    param: string\n  ) => MemoizedSelector<V, string | undefined>;\n  selectUrl: MemoizedSelector<V, string>;\n  selectTitle: MemoizedSelector<V, string | undefined>;\n};\n"
  },
  {
    "path": "modules/router-store/src/provide_router_store.ts",
    "content": "import {\n  EnvironmentProviders,\n  inject,\n  makeEnvironmentProviders,\n  provideEnvironmentInitializer,\n} from '@angular/core';\nimport {\n  _createRouterConfig,\n  _ROUTER_CONFIG,\n  ROUTER_CONFIG,\n  RouterState,\n  StoreRouterConfig,\n} from './router_store_config';\nimport {\n  FullRouterStateSerializer,\n  SerializedRouterStateSnapshot,\n} from './serializers/full_serializer';\nimport { MinimalRouterStateSerializer } from './serializers/minimal_serializer';\nimport {\n  BaseRouterStoreState,\n  RouterStateSerializer,\n} from './serializers/base';\nimport { StoreRouterConnectingService } from './store_router_connecting.service';\n\n/**\n * Connects the Angular Router to the Store.\n *\n * @usageNotes\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n *   providers: [\n *     provideStore({ router: routerReducer }),\n *     provideRouterStore(),\n *   ],\n * });\n * ```\n */\nexport function provideRouterStore<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n>(config: StoreRouterConfig<T> = {}): EnvironmentProviders {\n  return makeEnvironmentProviders([\n    { provide: _ROUTER_CONFIG, useValue: config },\n    {\n      provide: ROUTER_CONFIG,\n      useFactory: _createRouterConfig,\n      deps: [_ROUTER_CONFIG],\n    },\n    {\n      provide: RouterStateSerializer,\n      useClass: config.serializer\n        ? config.serializer\n        : config.routerState === RouterState.Full\n          ? FullRouterStateSerializer\n          : MinimalRouterStateSerializer,\n    },\n    provideEnvironmentInitializer(() => inject(StoreRouterConnectingService)),\n    StoreRouterConnectingService,\n  ]);\n}\n"
  },
  {
    "path": "modules/router-store/src/reducer.ts",
    "content": "import { Action } from '@ngrx/store';\nimport {\n  ROUTER_CANCEL,\n  ROUTER_ERROR,\n  ROUTER_NAVIGATION,\n  RouterAction,\n} from './actions';\nimport { BaseRouterStoreState } from './serializers/base';\nimport { SerializedRouterStateSnapshot } from './serializers/full_serializer';\n\nexport type RouterReducerState<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = {\n  state: T;\n  navigationId: number;\n};\n\nexport function routerReducer<\n  RouterState extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n  Result = RouterReducerState<RouterState>,\n>(state: Result | undefined, action: Action): Result {\n  // Allow compilation with strictFunctionTypes - ref: #1344\n  const routerAction = action as RouterAction<any, RouterState>;\n  switch (routerAction.type) {\n    case ROUTER_NAVIGATION:\n    case ROUTER_ERROR:\n    case ROUTER_CANCEL:\n      return {\n        state: routerAction.payload.routerState,\n        navigationId: routerAction.payload.event.id,\n      } as unknown as Result;\n    default:\n      return state as Result;\n  }\n}\n"
  },
  {
    "path": "modules/router-store/src/router_selectors.ts",
    "content": "import {\n  createFeatureSelector,\n  createSelector,\n  MemoizedSelector,\n} from '@ngrx/store';\nimport { RouterStateSelectors } from './models';\nimport { RouterReducerState } from './reducer';\nimport { DEFAULT_ROUTER_FEATURENAME } from './router_store_config';\n\nexport function createRouterSelector<\n  State extends Record<string, any>,\n>(): MemoizedSelector<State, RouterReducerState> {\n  return createFeatureSelector(DEFAULT_ROUTER_FEATURENAME);\n}\n\nexport function getRouterSelectors<V extends Record<string, any>>(\n  selectState: (state: V) => RouterReducerState<any> = createRouterSelector<V>()\n): RouterStateSelectors<V> {\n  const selectRouterState = createSelector(\n    selectState,\n    (router) => router && router.state\n  );\n  const selectRootRoute = createSelector(\n    selectRouterState,\n    (routerState) => routerState && routerState.root\n  );\n  const selectCurrentRoute = createSelector(selectRootRoute, (rootRoute) => {\n    if (!rootRoute) {\n      return undefined;\n    }\n    let route = rootRoute;\n    while (route.firstChild) {\n      route = route.firstChild;\n    }\n    return route;\n  });\n  const selectFragment = createSelector(\n    selectRootRoute,\n    (route) => route && route.fragment\n  );\n  const selectQueryParams = createSelector(\n    selectRootRoute,\n    (route) => route && route.queryParams\n  );\n  const selectQueryParam = (param: string) =>\n    createSelector(selectQueryParams, (params) => params && params[param]);\n  const selectRouteParams = createSelector(\n    selectCurrentRoute,\n    (route) => route && route.params\n  );\n  const selectRouteParam = (param: string) =>\n    createSelector(selectRouteParams, (params) => params && params[param]);\n  const selectRouteData = createSelector(\n    selectCurrentRoute,\n    (route) => route && route.data\n  );\n  const selectRouteDataParam = (param: string) =>\n    createSelector(selectRouteData, (data) => data && data[param]);\n  const selectUrl = createSelector(\n    selectRouterState,\n    (routerState) => routerState && routerState.url\n  );\n  const selectTitle = createSelector(selectCurrentRoute, (route) => {\n    if (!route?.routeConfig) {\n      return undefined;\n    }\n    return typeof route.routeConfig.title === 'string'\n      ? route.routeConfig.title // static title\n      : route.title; // resolved title\n  });\n\n  return {\n    selectCurrentRoute,\n    selectFragment,\n    selectQueryParams,\n    selectQueryParam,\n    selectRouteParams,\n    selectRouteParam,\n    selectRouteData,\n    selectRouteDataParam,\n    selectUrl,\n    selectTitle,\n  };\n}\n"
  },
  {
    "path": "modules/router-store/src/router_store_config.ts",
    "content": "import { InjectionToken } from '@angular/core';\nimport { Selector } from '@ngrx/store';\nimport { RouterReducerState } from './reducer';\nimport {\n  BaseRouterStoreState,\n  RouterStateSerializer,\n} from './serializers/base';\nimport { SerializedRouterStateSnapshot } from './serializers/full_serializer';\nimport { MinimalRouterStateSerializer } from './serializers/minimal_serializer';\n\nexport type StateKeyOrSelector<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> = string | Selector<any, RouterReducerState<T>>;\n\nexport enum NavigationActionTiming {\n  PreActivation = 1,\n  PostActivation = 2,\n}\nexport const DEFAULT_ROUTER_FEATURENAME = 'router';\n\nexport const _ROUTER_CONFIG = new InjectionToken(\n  '@ngrx/router-store Internal Configuration'\n);\nexport const ROUTER_CONFIG = new InjectionToken(\n  '@ngrx/router-store Configuration'\n);\n\n/**\n * Minimal = Serializes the router event with MinimalRouterStateSerializer\n * Full = Serializes the router event with FullRouterStateSerializer\n */\nexport enum RouterState {\n  Full,\n  Minimal,\n}\n\nexport function _createRouterConfig(\n  config: StoreRouterConfig\n): StoreRouterConfig {\n  return {\n    stateKey: DEFAULT_ROUTER_FEATURENAME,\n    serializer: MinimalRouterStateSerializer,\n    navigationActionTiming: NavigationActionTiming.PreActivation,\n    ...config,\n  };\n}\n\nexport interface StoreRouterConfig<\n  T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n> {\n  stateKey?: StateKeyOrSelector<T>;\n  serializer?: new (...args: any[]) => RouterStateSerializer;\n  /**\n   * By default, ROUTER_NAVIGATION is dispatched before guards and resolvers run.\n   * Therefore, the action could run too soon, for example\n   * there may be a navigation cancel due to a guard saying the navigation is not allowed.\n   * To run ROUTER_NAVIGATION after guards and resolvers,\n   * set this property to NavigationActionTiming.PostActivation.\n   */\n  navigationActionTiming?: NavigationActionTiming;\n  /**\n   * Decides which router serializer should be used, if there is none provided, and the metadata on the dispatched @ngrx/router-store action payload.\n   * Set to `Minimal` to use the `MinimalRouterStateSerializer` and to set a minimal router event with the navigation id and url as payload.\n   * Set to `Full` to use the `FullRouterStateSerializer` and to set the angular router events as payload.\n   */\n  routerState?: RouterState;\n}\n"
  },
  {
    "path": "modules/router-store/src/router_store_module.ts",
    "content": "import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { BaseRouterStoreState } from './serializers/base';\nimport { SerializedRouterStateSnapshot } from './serializers/full_serializer';\nimport { StoreRouterConfig } from './router_store_config';\nimport { provideRouterStore } from './provide_router_store';\n\n/**\n * Connects RouterModule with StoreModule.\n *\n * During the navigation, before any guards or resolvers run, the router will dispatch\n * a ROUTER_NAVIGATION action, which has the following signature:\n *\n * ```\n * export type RouterNavigationPayload = {\n *   routerState: SerializedRouterStateSnapshot,\n *   event: RoutesRecognized\n * }\n * ```\n *\n * Either a reducer or an effect can be invoked in response to this action.\n * If the invoked reducer throws, the navigation will be canceled.\n *\n * If navigation gets canceled because of a guard, a ROUTER_CANCEL action will be\n * dispatched. If navigation results in an error, a ROUTER_ERROR action will be dispatched.\n *\n * Both ROUTER_CANCEL and ROUTER_ERROR contain the store state before the navigation\n * which can be used to restore the consistency of the store.\n *\n * Usage:\n *\n * ```typescript\n * @NgModule({\n *   declarations: [AppCmp, SimpleCmp],\n *   imports: [\n *     BrowserModule,\n *     StoreModule.forRoot(mapOfReducers),\n *     RouterModule.forRoot([\n *       { path: '', component: SimpleCmp },\n *       { path: 'next', component: SimpleCmp }\n *     ]),\n *     StoreRouterConnectingModule.forRoot()\n *   ],\n *   bootstrap: [AppCmp]\n * })\n * export class AppModule {\n * }\n * ```\n */\n@NgModule({})\nexport class StoreRouterConnectingModule {\n  static forRoot<\n    T extends BaseRouterStoreState = SerializedRouterStateSnapshot,\n  >(\n    config: StoreRouterConfig<T> = {}\n  ): ModuleWithProviders<StoreRouterConnectingModule> {\n    return {\n      ngModule: StoreRouterConnectingModule,\n      providers: [provideRouterStore(config)],\n    };\n  }\n}\n"
  },
  {
    "path": "modules/router-store/src/serializers/base.ts",
    "content": "import { RouterStateSnapshot } from '@angular/router';\n\n/**\n * Simple router state.\n * All custom router states / state serializers should have at least\n * the properties of this interface.\n */\nexport interface BaseRouterStoreState {\n  url: string;\n}\n\nexport abstract class RouterStateSerializer<\n  T extends BaseRouterStoreState = BaseRouterStoreState,\n> {\n  abstract serialize(routerState: RouterStateSnapshot): T;\n}\n"
  },
  {
    "path": "modules/router-store/src/serializers/full_serializer.ts",
    "content": "import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';\nimport { BaseRouterStoreState, RouterStateSerializer } from './base';\n\nexport interface SerializedRouterStateSnapshot extends BaseRouterStoreState {\n  root: ActivatedRouteSnapshot;\n  url: string;\n}\n\nexport class FullRouterStateSerializer\n  implements RouterStateSerializer<SerializedRouterStateSnapshot>\n{\n  serialize(routerState: RouterStateSnapshot): SerializedRouterStateSnapshot {\n    return {\n      root: this.serializeRoute(routerState.root),\n      url: routerState.url,\n    };\n  }\n\n  private serializeRoute(\n    route: ActivatedRouteSnapshot\n  ): ActivatedRouteSnapshot {\n    const children = route.children.map((c) => this.serializeRoute(c));\n    return {\n      params: route.params,\n      paramMap: route.paramMap,\n      data: route.data,\n      url: route.url,\n      outlet: route.outlet,\n      title: route.title,\n      routeConfig: route.routeConfig\n        ? {\n            component: route.routeConfig.component,\n            path: route.routeConfig.path,\n            pathMatch: route.routeConfig.pathMatch,\n            redirectTo: route.routeConfig.redirectTo,\n            outlet: route.routeConfig.outlet,\n            title: route.routeConfig.title,\n          }\n        : null,\n      queryParams: route.queryParams,\n      queryParamMap: route.queryParamMap,\n      fragment: route.fragment,\n      component: (route.routeConfig\n        ? route.routeConfig.component\n        : undefined) as any,\n      root: undefined as any,\n      parent: undefined as any,\n      firstChild: children[0],\n      pathFromRoot: undefined as any,\n      children,\n    };\n  }\n}\n"
  },
  {
    "path": "modules/router-store/src/serializers/minimal_serializer.ts",
    "content": "import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';\nimport { BaseRouterStoreState, RouterStateSerializer } from './base';\n\nexport interface MinimalActivatedRouteSnapshot {\n  routeConfig: ActivatedRouteSnapshot['routeConfig'];\n  url: ActivatedRouteSnapshot['url'];\n  params: ActivatedRouteSnapshot['params'];\n  queryParams: ActivatedRouteSnapshot['queryParams'];\n  fragment: ActivatedRouteSnapshot['fragment'];\n  data: ActivatedRouteSnapshot['data'];\n  outlet: ActivatedRouteSnapshot['outlet'];\n  title: ActivatedRouteSnapshot['title'];\n  firstChild?: MinimalActivatedRouteSnapshot;\n  children: MinimalActivatedRouteSnapshot[];\n}\n\nexport interface MinimalRouterStateSnapshot extends BaseRouterStoreState {\n  root: MinimalActivatedRouteSnapshot;\n  url: string;\n}\n\nexport class MinimalRouterStateSerializer\n  implements RouterStateSerializer<MinimalRouterStateSnapshot>\n{\n  serialize(routerState: RouterStateSnapshot): MinimalRouterStateSnapshot {\n    return {\n      root: this.serializeRoute(routerState.root),\n      url: routerState.url,\n    };\n  }\n\n  private serializeRoute(\n    route: ActivatedRouteSnapshot\n  ): MinimalActivatedRouteSnapshot {\n    const children = route.children.map((c) => this.serializeRoute(c));\n    return {\n      params: route.params,\n      data: route.data,\n      url: route.url,\n      outlet: route.outlet,\n      title: route.title,\n      routeConfig: route.routeConfig\n        ? {\n            path: route.routeConfig.path,\n            pathMatch: route.routeConfig.pathMatch,\n            redirectTo: route.routeConfig.redirectTo,\n            outlet: route.routeConfig.outlet,\n            title:\n              typeof route.routeConfig.title === 'string'\n                ? route.routeConfig.title\n                : undefined,\n          }\n        : null,\n      queryParams: route.queryParams,\n      fragment: route.fragment,\n      firstChild: children[0],\n      children,\n    };\n  }\n}\n"
  },
  {
    "path": "modules/router-store/src/store_router_connecting.service.ts",
    "content": "import { ErrorHandler, Inject, Injectable, isDevMode } from '@angular/core';\nimport {\n  Event,\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n  RouterEvent,\n  RoutesRecognized,\n} from '@angular/router';\nimport {\n  ACTIVE_RUNTIME_CHECKS,\n  isNgrxMockEnvironment,\n  RuntimeChecks,\n  select,\n  Store,\n} from '@ngrx/store';\nimport { withLatestFrom } from 'rxjs/operators';\nimport {\n  ROUTER_CANCEL,\n  ROUTER_ERROR,\n  ROUTER_NAVIGATED,\n  ROUTER_NAVIGATION,\n  ROUTER_REQUEST,\n} from './actions';\nimport {\n  NavigationActionTiming,\n  ROUTER_CONFIG,\n  RouterState,\n  StateKeyOrSelector,\n  StoreRouterConfig,\n} from './router_store_config';\nimport {\n  FullRouterStateSerializer,\n  SerializedRouterStateSnapshot,\n} from './serializers/full_serializer';\nimport { RouterReducerState } from './reducer';\nimport { RouterStateSerializer } from './serializers/base';\n\nenum RouterTrigger {\n  NONE = 1,\n  ROUTER = 2,\n  STORE = 3,\n}\n\ninterface StoreRouterActionPayload {\n  event: RouterEvent;\n  routerState?: SerializedRouterStateSnapshot;\n  storeState?: any;\n}\n\n/**\n * Shared router initialization logic used alongside both the StoreRouterConnectingModule and the provideRouterStore\n * function\n */\n@Injectable()\nexport class StoreRouterConnectingService {\n  private lastEvent: Event | null = null;\n  private routerState: SerializedRouterStateSnapshot | null = null;\n  private storeState: any;\n  private trigger = RouterTrigger.NONE;\n  private readonly stateKey: StateKeyOrSelector;\n\n  constructor(\n    private store: Store<any>,\n    private router: Router,\n    private serializer: RouterStateSerializer<SerializedRouterStateSnapshot>,\n    private errorHandler: ErrorHandler,\n    @Inject(ROUTER_CONFIG) private readonly config: StoreRouterConfig,\n    @Inject(ACTIVE_RUNTIME_CHECKS)\n    private readonly activeRuntimeChecks: RuntimeChecks\n  ) {\n    this.stateKey = this.config.stateKey as StateKeyOrSelector;\n\n    if (\n      !isNgrxMockEnvironment() &&\n      isDevMode() &&\n      (activeRuntimeChecks?.strictActionSerializability ||\n        activeRuntimeChecks?.strictStateSerializability) &&\n      this.serializer instanceof FullRouterStateSerializer\n    ) {\n      console.warn(\n        '@ngrx/router-store: The serializability runtime checks cannot be enabled ' +\n          'with the FullRouterStateSerializer. The FullRouterStateSerializer ' +\n          'has an unserializable router state and actions that are not serializable. ' +\n          'To use the serializability runtime checks either use ' +\n          'the MinimalRouterStateSerializer or implement a custom router state serializer.'\n      );\n    }\n\n    this.setUpStoreStateListener();\n    this.setUpRouterEventsListener();\n  }\n\n  private setUpStoreStateListener(): void {\n    this.store\n      .pipe(select(this.stateKey as any), withLatestFrom(this.store))\n      .subscribe(([routerStoreState, storeState]) => {\n        this.navigateIfNeeded(routerStoreState, storeState);\n      });\n  }\n\n  private navigateIfNeeded(\n    routerStoreState: RouterReducerState,\n    storeState: any\n  ): void {\n    if (!routerStoreState || !routerStoreState.state) {\n      return;\n    }\n    if (this.trigger === RouterTrigger.ROUTER) {\n      return;\n    }\n    if (this.lastEvent instanceof NavigationStart) {\n      return;\n    }\n\n    const url = routerStoreState.state.url;\n    if (!isSameUrl(this.router.url, url)) {\n      this.storeState = storeState;\n      this.trigger = RouterTrigger.STORE;\n      this.router.navigateByUrl(url).catch((error) => {\n        this.errorHandler.handleError(error);\n      });\n    }\n  }\n\n  private setUpRouterEventsListener(): void {\n    const dispatchNavLate =\n      this.config.navigationActionTiming ===\n      NavigationActionTiming.PostActivation;\n    let routesRecognized: RoutesRecognized;\n\n    this.router.events\n      .pipe(withLatestFrom(this.store))\n      .subscribe(([event, storeState]) => {\n        this.lastEvent = event;\n\n        if (event instanceof NavigationStart) {\n          this.routerState = this.serializer.serialize(\n            this.router.routerState.snapshot\n          );\n          if (this.trigger !== RouterTrigger.STORE) {\n            this.storeState = storeState;\n            this.dispatchRouterRequest(event);\n          }\n        } else if (event instanceof RoutesRecognized) {\n          routesRecognized = event;\n\n          if (!dispatchNavLate && this.trigger !== RouterTrigger.STORE) {\n            this.dispatchRouterNavigation(event);\n          }\n        } else if (event instanceof NavigationCancel) {\n          this.dispatchRouterCancel(event);\n          this.reset();\n        } else if (event instanceof NavigationError) {\n          this.dispatchRouterError(event);\n          this.reset();\n        } else if (event instanceof NavigationEnd) {\n          if (this.trigger !== RouterTrigger.STORE) {\n            if (dispatchNavLate) {\n              this.dispatchRouterNavigation(routesRecognized);\n            }\n            this.dispatchRouterNavigated(event);\n          }\n          this.reset();\n        }\n      });\n  }\n\n  private dispatchRouterRequest(event: NavigationStart): void {\n    this.dispatchRouterAction(ROUTER_REQUEST, { event });\n  }\n\n  private dispatchRouterNavigation(\n    lastRoutesRecognized: RoutesRecognized\n  ): void {\n    const nextRouterState = this.serializer.serialize(\n      lastRoutesRecognized.state\n    );\n    this.dispatchRouterAction(ROUTER_NAVIGATION, {\n      routerState: nextRouterState,\n      event: new RoutesRecognized(\n        lastRoutesRecognized.id,\n        lastRoutesRecognized.url,\n        lastRoutesRecognized.urlAfterRedirects,\n        nextRouterState\n      ),\n    });\n  }\n\n  private dispatchRouterCancel(event: NavigationCancel): void {\n    this.dispatchRouterAction(ROUTER_CANCEL, {\n      storeState: this.storeState,\n      event,\n    });\n  }\n\n  private dispatchRouterError(event: NavigationError): void {\n    this.dispatchRouterAction(ROUTER_ERROR, {\n      storeState: this.storeState,\n      event: new NavigationError(event.id, event.url, `${event}`),\n    });\n  }\n\n  private dispatchRouterNavigated(event: NavigationEnd): void {\n    const routerState = this.serializer.serialize(\n      this.router.routerState.snapshot\n    );\n    this.dispatchRouterAction(ROUTER_NAVIGATED, { event, routerState });\n  }\n\n  private dispatchRouterAction(\n    type: string,\n    payload: StoreRouterActionPayload\n  ): void {\n    this.trigger = RouterTrigger.ROUTER;\n    try {\n      this.store.dispatch({\n        type,\n        payload: {\n          routerState: this.routerState,\n          ...payload,\n          event:\n            this.config.routerState === RouterState.Full\n              ? payload.event\n              : {\n                  id: payload.event.id,\n                  url: payload.event.url,\n                  // safe, as it will just be `undefined` for non-NavigationEnd router events\n                  urlAfterRedirects: (payload.event as NavigationEnd)\n                    .urlAfterRedirects,\n                },\n        },\n      });\n    } finally {\n      this.trigger = RouterTrigger.NONE;\n    }\n  }\n\n  private reset() {\n    this.trigger = RouterTrigger.NONE;\n    this.storeState = null;\n    this.routerState = null;\n  }\n}\n\n/**\n * Check if the URLs are matching. Accounts for the possibility of trailing \"/\" in url.\n */\nfunction isSameUrl(first: string, second: string): boolean {\n  return stripTrailingSlash(first) === stripTrailingSlash(second);\n}\n\nfunction stripTrailingSlash(text: string): string {\n  if (text?.length > 0 && text[text.length - 1] === '/') {\n    return text.substring(0, text.length - 1);\n  }\n  return text;\n}\n"
  },
  {
    "path": "modules/router-store/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/router-store/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../../\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmitOnError\": false,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/router-store\",\n    \"paths\": {},\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"skipLibCheck\": true\n  },\n  \"files\": [\"public_api.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/router-store\"\n  }\n}\n"
  },
  {
    "path": "modules/router-store/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/router-store\",\n    \"paths\": {\n      \"@ngrx/router-store/schematics-core\": [\"./schematics-core\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/router-store/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"node\", \"vitest\", \"vitest/globals\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/router-store/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [\n      angular(),\n      nxViteTsPaths(),\n    ],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default']\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "modules/router-store/webpack.e2e.config.js",
    "content": "const ngtools = require('@ngtools/webpack');\n\nmodule.exports = {\n  resolve: {\n    extensions: ['.ts', '.js'],\n  },\n  entry: './e2e/main.ts',\n  output: {\n    path: './dist/e2e',\n    filename: 'bundle.js',\n  },\n  plugins: [\n    new ngtools.AotPlugin({\n      tsConfigPath: './tsconfig.e2e.json',\n    }),\n  ],\n  module: {\n    rules: [\n      {\n        test: /\\.ts$/,\n        use: ['@ngtools/webpack'],\n      },\n    ],\n  },\n};\n"
  },
  {
    "path": "modules/schematics/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/schematics/README.md",
    "content": "# @ngrx/schematics\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/schematics/collection.json",
    "content": "{\n  \"extends\": [\"@schematics/angular\"],\n  \"schematics\": {\n    \"action\": {\n      \"aliases\": [\"a\"],\n      \"factory\": \"./src/action\",\n      \"schema\": \"./src/action/schema.json\",\n      \"description\": \"Add store actions\"\n    },\n\n    \"container\": {\n      \"aliases\": [\"co\"],\n      \"factory\": \"./src/container\",\n      \"schema\": \"./src/container/schema.json\",\n      \"description\": \"Add store container component\"\n    },\n\n    \"effect\": {\n      \"aliases\": [\"ef\"],\n      \"factory\": \"./src/effect\",\n      \"schema\": \"./src/effect/schema.json\",\n      \"description\": \"Add side effect class\"\n    },\n\n    \"entity\": {\n      \"aliases\": [\"en\"],\n      \"factory\": \"./src/entity\",\n      \"schema\": \"./src/entity/schema.json\",\n      \"description\": \"Add entity state\"\n    },\n\n    \"feature\": {\n      \"aliases\": [\"f\"],\n      \"factory\": \"./src/feature\",\n      \"schema\": \"./src/feature/schema.json\",\n      \"description\": \"Add feature state\"\n    },\n\n    \"ngrx-push-migration\": {\n      \"aliases\": [\"ngrxpush\"],\n      \"factory\": \"./src/ngrx-push-migration\",\n      \"schema\": \"./src/ngrx-push-migration/schema.json\",\n      \"description\": \"Migration to replace the `async` pipe with `ngrxPush`\"\n    },\n\n    \"reducer\": {\n      \"aliases\": [\"r\"],\n      \"factory\": \"./src/reducer\",\n      \"schema\": \"./src/reducer/schema.json\",\n      \"description\": \"Add state reducer\"\n    },\n\n    \"store\": {\n      \"aliases\": [\"st\"],\n      \"factory\": \"./src/store\",\n      \"schema\": \"./src/store/schema.json\",\n      \"description\": \"Adds initial setup for state management\"\n    },\n    \"selector\": {\n      \"aliases\": [\"se\"],\n      \"factory\": \"./src/selector\",\n      \"schema\": \"./src/selector/schema.json\",\n      \"description\": \"Add selectors\"\n    },\n\n    \"data\": {\n      \"aliases\": [\"dt\"],\n      \"factory\": \"./src/data\",\n      \"schema\": \"./src/data/schema.json\",\n      \"description\": \"Adds a data entity service\"\n    },\n\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./src/ng-add\",\n      \"schema\": \"./src/ng-add/schema.json\",\n      \"description\": \"Installs the NgRx schematics package\"\n    },\n    \"component-store\": {\n      \"aliases\": [\"cs\"],\n      \"factory\": \"./src/component-store\",\n      \"schema\": \"./src/component-store/schema.json\",\n      \"description\": \"Add component store.\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/schematics/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/schematics/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n  {\n    ignores: ['schematics-core'],\n  },\n];\n"
  },
  {
    "path": "modules/schematics/jest.config.ts",
    "content": "/* eslint-disable */\nexport default {\n  displayName: 'Schematics',\n  preset: '../../jest.preset.js',\n  coverageDirectory: '../../coverage/modules/schematics',\n  setupFilesAfterEnv: ['<rootDir>/test-setup.ts'],\n  transform: {\n    '^.+\\\\.(ts|mjs|js|html)$': [\n      'jest-preset-angular',\n      {\n        tsconfig: '<rootDir>/tsconfig.spec.json',\n        stringifyContentPathRegex: '\\\\.(html|svg)$',\n      },\n    ],\n  },\n  transformIgnorePatterns: ['node_modules/(?!.*\\\\.mjs$)'],\n  snapshotSerializers: [\n    'jest-preset-angular/build/serializers/no-ng-attributes',\n    'jest-preset-angular/build/serializers/ng-snapshot',\n    'jest-preset-angular/build/serializers/html-comment',\n  ],\n};\n"
  },
  {
    "path": "modules/schematics/migrations/6_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport {\n  createPackageJson,\n  packagePath,\n} from '@ngrx/schematics-core/testing/create-package';\nimport {\n  upgradeVersion,\n  versionPrefixes,\n} from '@ngrx/schematics-core/testing/update';\n\nconst collectionPath = path.join(__dirname, '../migration.json');\n\ndescribe('Schematics Migration 6_0_0', () => {\n  let appTree;\n  const pkgName = 'schematics';\n\n  versionPrefixes.forEach((prefix) => {\n    it(`should install version ${prefix}6.0.0`, async () => {\n      appTree = new UnitTestTree(Tree.empty());\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n      const tree = createPackageJson(prefix, pkgName, appTree);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-01`,\n        {},\n        tree\n      );\n      const pkg = JSON.parse(newTree.readContent(packagePath));\n      expect(pkg.dependencies[`@ngrx/${pkgName}`]).toBe(\n        `${prefix}${upgradeVersion}`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "modules/schematics/migrations/6_0_0/index.ts",
    "content": "import { Rule } from '@angular-devkit/schematics';\nimport { updatePackage } from '../../schematics-core';\n\nexport default function (): Rule {\n  return updatePackage('schematics');\n}\n"
  },
  {
    "path": "modules/schematics/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"ngrx-schematics-migration-01\": {\n      \"description\": \"The road to v6\",\n      \"version\": \"5.2\",\n      \"factory\": \"./6_0_0/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/schematics/package.json",
    "content": "{\n  \"name\": \"@ngrx/schematics\",\n  \"version\": \"21.0.1\",\n  \"description\": \"NgRx Schematics for Angular\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"RxJS\",\n    \"Angular\",\n    \"Redux\",\n    \"NgRx\",\n    \"Schematics\",\n    \"Angular CLI\"\n  ],\n  \"author\": \"Brandon Roberts <robertsbt@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"schematics\": \"./collection.json\",\n  \"ng-add\": {\n    \"save\": \"devDependencies\"\n  },\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  }\n}\n"
  },
  {
    "path": "modules/schematics/project.json",
    "content": "{\n  \"name\": \"schematics\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"sourceRoot\": \"modules/schematics/src\",\n  \"projectType\": \"library\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/schematics/*/**/*.ts\",\n          \"modules/schematics/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@nx/jest:jest\",\n      \"options\": {\n        \"jestConfig\": \"modules/schematics/jest.config.ts\",\n        \"runInBand\": true,\n        \"passWithNoTests\": false\n      },\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/schematics\"]\n    },\n    \"build-package\": {\n      \"executor\": \"@nx/js:tsc\",\n      \"options\": {\n        \"outputPath\": \"dist/modules/schematics\",\n        \"tsConfig\": \"modules/schematics/tsconfig.build.json\",\n        \"packageJson\": \"modules/schematics/package.json\",\n        \"main\": \"modules/schematics/src/index.ts\",\n        \"updateBuildableProjectDepsInPackageJson\": false,\n        \"sourceMap\": false,\n        \"assets\": [\n          \"collection.json\",\n          {\n            \"input\": \"./modules/schematics/src\",\n            \"glob\": \"**/*.!(ts)\",\n            \"output\": \"./src\"\n          },\n          {\n            \"input\": \"./modules/schematics\",\n            \"glob\": \"collection.json\",\n            \"output\": \".\"\n          }\n        ],\n        \"srcRootForCompilationRoot\": \"modules/schematics\"\n      },\n      \"outputs\": [\"{options.outputPath}\"]\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package schematics\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/schematics/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"cpy modules/schematics/collection.json dist\"\n          },\n          {\n            \"command\": \"cpy modules/schematics/migrations/migration.json dist\"\n          },\n          {\n            \"command\": \"cpy modules/schematics/src/**/files/**/*.* dist/modules/schematics/src\"\n          },\n          {\n            \"command\": \"cpy modules/schematics/src/**/integration-files/**/*.* dist/modules/schematics/src\"\n          },\n          {\n            \"command\": \"cpy modules/schematics/src/**/schema.json dist/modules/schematics/src\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/schematics\"\n          }\n        ]\n      },\n      \"outputs\": [\"{workspaceRoot}/dist/modules/schematics\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/schematics/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/schematics/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/schematics/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/schematics/src/action/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Action Schematic api should create api actions 1`] = `\n\"import { createActionGroup, emptyProps, props } from '@ngrx/store';\n\nexport const FooActions = createActionGroup({\n  source: 'Foo',\n  events: {\n    'Load Foos': emptyProps(),\n    'Load Foos Success': props<{ data: unknown }>(),\n    'Load Foos Failure': props<{ error: unknown }>(),\n  }\n});\n\"\n`;\n\nexports[`Action Schematic should create an action with the defined prefix 1`] = `\n\"import { createActionGroup, emptyProps, props } from '@ngrx/store';\n\nexport const FooActions = createActionGroup({\n  source: 'Foo',\n  events: {\n    'Prefix Foos': emptyProps(),\n    \n    \n  }\n});\n\"\n`;\n\nexports[`Action Schematic should create api actions (load, success, error) when the api flag is set 1`] = `\n\"import { createActionGroup, emptyProps, props } from '@ngrx/store';\n\nexport const FooActions = createActionGroup({\n  source: 'Foo',\n  events: {\n    'Load Foos': emptyProps(),\n    'Load Foos Success': props<{ data: unknown }>(),\n    'Load Foos Failure': props<{ error: unknown }>(),\n  }\n});\n\"\n`;\n\nexports[`Action Schematic should define actions using createActionGroup 1`] = `\n\"import { createActionGroup, emptyProps, props } from '@ngrx/store';\n\nexport const FooActions = createActionGroup({\n  source: 'Foo',\n  events: {\n    'Load Foos': emptyProps(),\n    \n    \n  }\n});\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/action/files/__name@dasherize@if-flat__/__name@dasherize__.actions.ts.template",
    "content": "import { createActionGroup, emptyProps, props } from '@ngrx/store';\n\nexport const <%= classify(name) %>Actions = createActionGroup({\n  source: '<%= classify(name) %>',\n  events: {\n    '<%= classify(prefix) %> <%= classify(name) %>s': emptyProps(),\n    <% if (api) { %>'<%= classify(prefix) %> <%= classify(name) %>s Success': props<{ data: unknown }>(),<% } %>\n    <% if (api) { %>'<%= classify(prefix) %> <%= classify(name) %>s Failure': props<{ error: unknown }>(),<% } %>\n  }\n});\n"
  },
  {
    "path": "modules/schematics/src/action/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as ActionOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n  defaultWorkspaceOptions,\n  defaultAppOptions,\n} from '@ngrx/schematics-core/testing';\nimport { capitalize } from '../../schematics-core/utility/strings';\n\ndescribe('Action Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n  const defaultOptions: ActionOptions = {\n    name: 'foo',\n    prefix: 'load',\n    project: 'bar',\n    group: false,\n    flat: true,\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should create an action to specified project if provided', async () => {\n    const options = {\n      ...defaultOptions,\n      project: 'baz',\n    };\n\n    const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {\n      ...defaultAppOptions,\n      name: 'baz',\n    });\n\n    const tree = await schematicRunner.runSchematic('action', options, appTree);\n    const files = tree.files;\n    expect(\n      files.includes(`${specifiedProjectPath}/src/lib/foo.actions.ts`)\n    ).toBeTruthy();\n  });\n\n  it('should create one file', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'action',\n      defaultOptions,\n      appTree\n    );\n    expect(\n      tree.files.includes(`${projectPath}/src/app/foo.actions.ts`)\n    ).toBeTruthy();\n  });\n\n  it('should not create test files', async () => {\n    const options = {\n      ...defaultOptions,\n    };\n    const tree = await schematicRunner.runSchematic('action', options, appTree);\n    expect(\n      tree.files.includes(`${projectPath}/src/app/foo.actions.spec.ts`)\n    ).toBe(false);\n  });\n\n  it('should define actions using createActionGroup', async () => {\n    const options = {\n      ...defaultOptions,\n    };\n\n    const tree = await schematicRunner.runSchematic('action', options, appTree);\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.actions.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should create api actions (load, success, error) when the api flag is set', async () => {\n    const options = {\n      ...defaultOptions,\n      api: true,\n    };\n\n    const tree = await schematicRunner.runSchematic('action', options, appTree);\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.actions.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should create an action with the defined prefix', async () => {\n    const options = {\n      ...defaultOptions,\n      prefix: 'prefix',\n    };\n\n    const tree = await schematicRunner.runSchematic('action', options, appTree);\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.actions.ts`\n    );\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  describe('api', () => {\n    it('should group within an \"actions\" folder if group is set', async () => {\n      const tree = await schematicRunner.runSchematic(\n        'action',\n        {\n          ...defaultOptions,\n          group: true,\n        },\n        appTree\n      );\n      expect(\n        tree.files.includes(`${projectPath}/src/app/actions/foo.actions.ts`)\n      ).toBeTruthy();\n    });\n\n    it('should create api actions', async () => {\n      const tree = await schematicRunner.runSchematic(\n        'action',\n        {\n          ...defaultOptions,\n          api: true,\n        },\n        appTree\n      );\n      const fileContent = tree.readContent(\n        `${projectPath}/src/app/foo.actions.ts`\n      );\n\n      expect(fileContent).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/action/index.ts",
    "content": "import {\n  Rule,\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  mergeWith,\n  move,\n  url,\n  Tree,\n  SchematicContext,\n} from '@angular-devkit/schematics';\nimport { Schema as ActionOptions } from './schema';\nimport {\n  getProjectPath,\n  stringUtils,\n  parseName,\n  getPrefix,\n} from '../../schematics-core';\n\nexport default function (options: ActionOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    options.path = getProjectPath(host, options);\n\n    options.prefix = getPrefix(options);\n\n    const parsedPath = parseName(options.path, options.name);\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n\n    const templateSource = apply(url('./files'), [\n      applyTemplates({\n        ...stringUtils,\n        'if-flat': (s: string) =>\n          stringUtils.group(\n            options.flat ? '' : s,\n            options.group ? 'actions' : ''\n          ),\n        ...options,\n      }),\n      move(parsedPath.path),\n    ]);\n\n    return chain([branchAndMerge(chain([mergeWith(templateSource)]))])(\n      host,\n      context\n    );\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/action/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxAction\",\n  \"title\": \"NgRx Action Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the action.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What should be the name of the action?\"\n    },\n    \"prefix\": {\n      \"description\": \"The prefix of the action.\",\n      \"type\": \"string\",\n      \"default\": \"load\",\n      \"x-prompt\": \"What should be the prefix of the action?\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the component.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"group\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Group actions file within 'actions' folder\",\n      \"aliases\": [\"g\"]\n    },\n    \"api\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Specifies if api success and failure actions should be generated.\",\n      \"aliases\": [\"a\"],\n      \"x-prompt\": \"Should we generate success and failure actions?\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/action/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The name of the component.\n   */\n  name: string;\n\n  /**\n   * The prefix for the actions.\n   */\n  prefix: string;\n\n  /**\n   * The path to create the component.\n   */\n  path?: string;\n\n  /**\n   * The name of the project.\n   */\n  project?: string;\n\n  /**\n   * Flag to indicate if a dir is created.\n   */\n\n  flat?: boolean;\n\n  /**\n   * Group actions file within 'actions' folder\n   */\n  group?: boolean;\n\n  /**\n   * Specifies if api success and failure actions\n   * should be generated.\n   */\n  api?: boolean;\n}\n"
  },
  {
    "path": "modules/schematics/src/cli.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('CLI Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../collection.json')\n  );\n\n  const defaultOptions = {\n    name: 'foo',\n    project: 'bar',\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should create a class by the angular/cli', async () => {\n    const options = { ...defaultOptions };\n    const tree = await schematicRunner.runSchematic('class', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/foo.ts`);\n\n    expect(content).toMatch(/export class Foo/);\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/component-store/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`component-store should import into a specified module when the module provided 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { FooStore } from './foo/foo.store';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners(),\n    FooStore\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`component-store should inject the component store correctly into the spec 1`] = `\n\"import { FooStore } from './foo.store';\n\ndescribe('FooStore', () => {\n  const componentStore = new FooStore();\n\n  it('should be created', () => {\n    expect(componentStore).toBeTruthy();\n  });\n});\n\"\n`;\n\nexports[`component-store should not be provided into the module by default 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`component-store should register the component store in the provided component 1`] = `\n\"import { Component, signal } from '@angular/core';\nimport { FooStore } from './foo/foo.store';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.html',\n  standalone: false,\n  styleUrl: './app.css',\n  providers: [FooStore]\n})\nexport class App {\n  protected readonly title = signal('bar');\n}\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/component-store/files/__name@dasherize@if-flat__/__name@dasherize__.store.spec.ts.template",
    "content": "import { <%= classify(name) %>Store } from './<%= dasherize(name) %>.store';\n\ndescribe('<%= classify(name) %>Store', () => {\n  const componentStore = new <%= classify(name) %>Store();\n\n  it('should be created', () => {\n    expect(componentStore).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/component-store/files/__name@dasherize@if-flat__/__name@dasherize__.store.ts.template",
    "content": "import { Injectable } from '@angular/core';\nimport { ComponentStore } from '@ngrx/component-store';\n\nexport interface <%= classify(name) %>State {};\n\nconst initialState: <%= classify(name) %>State = {};\n\n@Injectable()\nexport class <%= classify(name) %>Store extends ComponentStore<<%= classify(name) %>State> {\n  constructor() {\n    super(initialState);\n  }\n}\n"
  },
  {
    "path": "modules/schematics/src/component-store/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as ComponentStoreOptions } from '../component-store/schema';\nimport {\n  createWorkspace,\n  defaultAppOptions,\n  defaultWorkspaceOptions,\n  getTestProjectPath,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('component-store', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n\n  const defaultOptions: ComponentStoreOptions = {\n    name: 'foo',\n    project: 'bar',\n    module: undefined,\n    flat: false,\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should create an component store to specified project if provided', async () => {\n    const options = {\n      ...defaultOptions,\n      project: 'baz',\n    };\n\n    const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {\n      ...defaultAppOptions,\n      name: 'baz',\n    });\n\n    const tree = await schematicRunner.runSchematic('cs', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo/foo.store.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo/foo.store.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create an component store with a spec file', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic(\n      'component-store',\n      options,\n      appTree\n    );\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.store.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.store.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should not be provided into the module by default', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic(\n      'component-store',\n      options,\n      appTree\n    );\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).not.toMatch(/FooStore/i);\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should import into a specified module when the module provided', async () => {\n    const options = { ...defaultOptions, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic(\n      'component-store',\n      options,\n      appTree\n    );\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n\n    expect(content).toMatch(/FooStore/i);\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should fail if specified module does not exist', async () => {\n    const options = {\n      ...defaultOptions,\n      module: `${projectPath}/src/app/app-moduleXXX.ts`,\n    };\n    let thrownError: Error | null = null;\n    try {\n      await schematicRunner.runSchematic('component-store', options, appTree);\n    } catch (err: any) {\n      thrownError = err;\n    }\n    expect(thrownError).toBeDefined();\n  });\n\n  it('should respect the skipTests flag', async () => {\n    const options = { ...defaultOptions, skipTests: true };\n\n    const tree = await schematicRunner.runSchematic(\n      'component-store',\n      options,\n      appTree\n    );\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.store.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.store.spec.ts`)\n    ).toEqual(-1);\n  });\n\n  it('should register the component store in the provided component', async () => {\n    const options = { ...defaultOptions, component: 'app.ts' };\n\n    const tree = await schematicRunner.runSchematic(\n      'component-store',\n      options,\n      appTree\n    );\n    const content = tree.readContent(`${projectPath}/src/app/app.ts`);\n\n    expect(content).toMatch(/FooStore/i);\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should fail if specified component does not exist', async () => {\n    const options = {\n      ...defaultOptions,\n      component: `${projectPath}/src/app/appnotexist.ts`,\n    };\n\n    await expect(\n      schematicRunner.runSchematic('component-store', options, appTree)\n    ).rejects.toThrowError();\n  });\n\n  it('should inject the component store correctly into the spec', async () => {\n    const options = { ...defaultOptions };\n    const tree = await schematicRunner.runSchematic(\n      'component-store',\n      options,\n      appTree\n    );\n    const content = tree.readContent(\n      `${projectPath}/src/app/foo/foo.store.spec.ts`\n    );\n\n    expect(content).toMatch(/FooStore/i);\n    expect(content).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/component-store/index.ts",
    "content": "import {\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  filter,\n  mergeWith,\n  move,\n  noop,\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  Tree,\n  url,\n} from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { Schema as ComponentStoreOptions } from './schema';\nimport {\n  addProviderToComponent,\n  addProviderToModule,\n  buildRelativePath,\n  findComponentFromOptions,\n  findModuleFromOptions,\n  getProjectPath,\n  InsertChange,\n  parseName,\n  stringUtils,\n  insertImport,\n} from '../../schematics-core';\n\ninterface AddProviderContext {\n  componentStoreRelativePath: string;\n  componentStoreName: string;\n}\n\nfunction createProvidingContext(\n  options: Pick<ComponentStoreOptions, 'name' | 'flat' | 'path'>,\n  providingPath: string\n): AddProviderContext {\n  const componentStoreName = `${stringUtils.classify(`${options.name}Store`)}`;\n  const componentStorePath =\n    `/${options.path}/` +\n    (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n    stringUtils.dasherize(options.name) +\n    '.store';\n  const componentStoreRelativePath = buildRelativePath(\n    providingPath,\n    componentStorePath\n  );\n\n  return {\n    componentStoreRelativePath,\n    componentStoreName,\n  };\n}\n\n/**\n * Add component store to NgModule\n */\nexport function addComponentStoreProviderToNgModule(\n  options: ComponentStoreOptions\n): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const context = createProvidingContext(options, options.module);\n\n    const componentStore = insertImport(\n      source,\n      modulePath,\n      context.componentStoreName,\n      context.componentStoreRelativePath\n    );\n    const [storeNgModuleProvider] = addProviderToModule(\n      source,\n      modulePath,\n      context.componentStoreName,\n      context.componentStoreRelativePath\n    );\n    const changes = [componentStore, storeNgModuleProvider];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Add component store to Component\n */\nexport function addComponentStoreProviderToComponent(\n  options: ComponentStoreOptions\n): Rule {\n  return (host: Tree) => {\n    if (!options.component) {\n      return host;\n    }\n\n    const componentPath = options.component;\n    if (!host.exists(options.component)) {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n\n    const text = host.read(componentPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${componentPath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      componentPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const context = createProvidingContext(options, options.component);\n\n    const componentStore = insertImport(\n      source,\n      componentPath,\n      context.componentStoreName,\n      context.componentStoreRelativePath\n    );\n    const [storeNgModuleProvider] = addProviderToComponent(\n      source,\n      componentPath,\n      context.componentStoreName,\n      context.componentStoreRelativePath\n    );\n    const changes = [componentStore, storeNgModuleProvider];\n    const recorder = host.beginUpdate(componentPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport default function (options: ComponentStoreOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    options.path = getProjectPath(host, options);\n\n    if (options.module) {\n      options.module = findModuleFromOptions(host, options);\n    }\n\n    if (options.component) {\n      options.component = findComponentFromOptions(host, options);\n    }\n\n    const parsedPath = parseName(options.path, options.name);\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n\n    const templateSource = apply(url('./files'), [\n      options.skipTests\n        ? filter((path) => !path.endsWith('.spec.ts.template'))\n        : noop(),\n      applyTemplates({\n        ...stringUtils,\n        'if-flat': (s: string) => (options.flat ? '' : s),\n        ...options,\n      }),\n      move(parsedPath.path),\n    ]);\n\n    return chain([\n      branchAndMerge(\n        chain([\n          addComponentStoreProviderToNgModule(options),\n          addComponentStoreProviderToComponent(options),\n          mergeWith(templateSource),\n        ])\n      ),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/component-store/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxComponentStore\",\n  \"title\": \"NgRx Component Store Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the component store.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What should be the name of the component store?\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the component store.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"When true, does not create test files.\"\n    },\n    \"component\": {\n      \"type\": \"string\",\n      \"default\": \"\",\n      \"description\": \"Allows specification of the declaring component.\",\n      \"alias\": \"m\",\n      \"subtype\": \"filepath\",\n      \"x-prompt\": \"To which component (path) should the component store be provided in?\"\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"default\": \"\",\n      \"description\": \"Allows specification of the declaring module.\",\n      \"alias\": \"m\",\n      \"subtype\": \"filepath\",\n      \"x-prompt\": \"To which module (path) should the component store be provided in?\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/component-store/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The name of the component store.\n   */\n  name: string;\n\n  /**\n   * The path to create the component store.\n   */\n  path?: string;\n\n  /**\n   * The name of the project.\n   */\n  project?: string;\n\n  /**\n   * Flag to indicate if a dir is created.\n   */\n  flat?: boolean;\n\n  /**\n   * When true, does not create test files.\n   */\n  skipTests?: boolean;\n\n  /**\n   * Allows specification of the declaring component.\n   */\n  component?: string;\n\n  /**\n   * Allows specification of the declaring module.\n   */\n  module?: string;\n}\n"
  },
  {
    "path": "modules/schematics/src/container/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Container Schematic display-block should be disabled by default 1`] = `\"\"`;\n\nexports[`Container Schematic display-block should create add style if true 1`] = `\n\":host {\n  display: block;\n}\n\"\n`;\n\nexports[`Container Schematic should import Store into the component 1`] = `\n\"import { Component } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport * as fromStore from '../reducers';\n\n@Component({\n  selector: 'app-foo',\n  imports: [],\n  templateUrl: './foo.html',\n  styleUrl: './foo.css',\n})\nexport class Foo {\n  constructor(private store: Store) {}\n}\n\"\n`;\n\nexports[`Container Schematic should respect the state option if not provided 1`] = `\n\"import { Component } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\n@Component({\n  selector: 'app-foo',\n  imports: [],\n  templateUrl: './foo.html',\n  styleUrl: './foo.css',\n})\nexport class Foo {\n  constructor(private store: Store) {}\n}\n\"\n`;\n\nexports[`Container Schematic should update the component spec 1`] = `\n\"import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { FooComponent } from './foo.component';\nimport { provideMockStore, MockStore } from '@ngrx/store/testing';\n\ndescribe('FooComponent', () => {\n  let component: FooComponent;\n  let fixture: ComponentFixture<FooComponent>;\n  let store: MockStore;\n\n  beforeEach(async() => {\n    TestBed.configureTestingModule({\n      providers: [ provideMockStore() ],\n      declarations: [ FooComponent ]\n    });\n\n    await TestBed.compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(FooComponent);\n    component = fixture.componentInstance;\n    store = TestBed.inject(Store);\n\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n\"\n`;\n\nexports[`Container Schematic should use StoreModule if integration test 1`] = `\n\"import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { FooComponent } from './foo.component';\nimport { Store, StoreModule } from '@ngrx/store';\n\ndescribe('FooComponent', () => {\n  let component: FooComponent;\n  let fixture: ComponentFixture<FooComponent>;\n  let store: Store;\n\n  beforeEach(async() => {\n    TestBed.configureTestingModule({\n      imports: [ StoreModule.forRoot({}) ],\n      declarations: [ FooComponent ]\n    });\n\n    await TestBed.compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(FooComponent);\n    component = fixture.componentInstance;\n    store = TestBed.inject(Store);\n\n    spyOn(store, 'dispatch').and.callThrough();\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n\"\n`;\n\nexports[`Container Schematic standalone should be standalone by default 1`] = `\n\"import { Component } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\n@Component({\n  selector: 'app-foo',\n  imports: [],\n  templateUrl: './foo.html',\n  styleUrl: './foo.css',\n})\nexport class Foo {\n  constructor(private store: Store) {}\n}\n\"\n`;\n\nexports[`Container Schematic standalone should create a non-standalone component if false 1`] = `\n\"import { Component } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\n@Component({\n  selector: 'app-foo',\n  standalone: false,\n  templateUrl: './foo.html',\n  styleUrl: './foo.css',\n})\nexport class Foo {\n  constructor(private store: Store) {}\n}\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/container/files/__name@dasherize@if-flat__/__name@dasherize__-component.spec.ts.template",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';\nimport { provideMockStore, MockStore } from '@ngrx/store/testing';\n\ndescribe('<%= classify(name) %>Component', () => {\n  let component: <%= classify(name) %>Component;\n  let fixture: ComponentFixture<<%= classify(name) %>Component>;\n  let store: MockStore;\n\n  beforeEach(async() => {\n    TestBed.configureTestingModule({\n      providers: [ provideMockStore() ],\n      declarations: [ <%= classify(name) %>Component ]\n    });\n\n    await TestBed.compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(<%= classify(name) %>Component);\n    component = fixture.componentInstance;\n    store = TestBed.inject(Store);\n\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/container/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as ContainerOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Container Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n\n  const defaultOptions: ContainerOptions = {\n    name: 'foo',\n    project: 'bar',\n    inlineStyle: false,\n    inlineTemplate: false,\n    changeDetection: 'Default',\n    style: 'css',\n    module: undefined,\n    export: false,\n    prefix: 'app',\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should respect the state option if not provided', async () => {\n    const options = { ...defaultOptions, state: undefined };\n    const tree = await schematicRunner.runSchematic(\n      'container',\n      options,\n      appTree\n    );\n    const content = tree.readContent(`${projectPath}/src/app/foo/foo.ts`);\n    expect(content).not.toMatch(/fromStore/);\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should remove .ts from the state path if provided', async () => {\n    const options = { ...defaultOptions, state: 'reducers/foo.ts' };\n    appTree.create(`${projectPath}/src/app/reducers/foo.ts`, '');\n    const tree = await schematicRunner.runSchematic(\n      'container',\n      options,\n      appTree\n    );\n    expect(tree.exists(`${projectPath}/src/app/foo/foo.ts`)).toBeTruthy();\n  });\n\n  it('should remove index.ts from the state path if provided', async () => {\n    const options = { ...defaultOptions, state: 'reducers/index.ts' };\n    appTree.create(`${projectPath}/src/app/reducers/index.ts`, '');\n    const tree = await schematicRunner.runSchematic(\n      'container',\n      options,\n      appTree\n    );\n    expect(tree.exists(`${projectPath}/src/app/foo/foo.ts`)).toBeTruthy();\n  });\n\n  it('should import Store into the component', async () => {\n    const options = { ...defaultOptions, state: 'reducers' };\n    appTree.create(`${projectPath}/src/app/reducers`, '');\n    const tree = await schematicRunner.runSchematic(\n      'container',\n      options,\n      appTree\n    );\n    const content = tree.readContent(`${projectPath}/src/app/foo/foo.ts`);\n    expect(content).toMatch(/Store/);\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should update the component spec', async () => {\n    const options = { ...defaultOptions, testDepth: 'unit' };\n    const tree = await schematicRunner.runSchematic(\n      'container',\n      options,\n      appTree\n    );\n    const content = tree.readContent(\n      `${projectPath}/src/app/foo/foo-component.spec.ts`\n    );\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should use StoreModule if integration test', async () => {\n    const options = { ...defaultOptions };\n    const tree = await schematicRunner.runSchematic(\n      'container',\n      options,\n      appTree\n    );\n    const content = tree.readContent(\n      `${projectPath}/src/app/foo/foo-component.spec.ts`\n    );\n    expect(content).toMatchSnapshot();\n  });\n\n  describe('standalone', () => {\n    it('should be standalone by default', async () => {\n      const options = { ...defaultOptions };\n      const tree = await schematicRunner.runSchematic(\n        'container',\n        options,\n        appTree\n      );\n      const content = tree.readContent(`${projectPath}/src/app/foo/foo.ts`);\n      expect(content).not.toMatch(/standalone/);\n      expect(content).toMatchSnapshot();\n    });\n\n    it('should create a non-standalone component if false', async () => {\n      const options = { ...defaultOptions, standalone: false };\n      const tree = await schematicRunner.runSchematic(\n        'container',\n        options,\n        appTree\n      );\n      const content = tree.readContent(`${projectPath}/src/app/foo/foo.ts`);\n      expect(content).toMatch(/standalone: false/);\n      expect(content).toMatchSnapshot();\n    });\n  });\n\n  describe('display-block', () => {\n    it('should be disabled by default', async () => {\n      const options = { ...defaultOptions };\n      const tree = await schematicRunner.runSchematic(\n        'container',\n        options,\n        appTree\n      );\n      const content = tree.readContent(`${projectPath}/src/app/foo/foo.css`);\n      expect(content).not.toMatch(/display: block/);\n      expect(content).toMatchSnapshot();\n    });\n\n    it('should create add style if true', async () => {\n      const options = { ...defaultOptions, displayBlock: true };\n      const tree = await schematicRunner.runSchematic(\n        'container',\n        options,\n        appTree\n      );\n      const content = tree.readContent(`${projectPath}/src/app/foo/foo.css`);\n      expect(content).toMatch(/display: block/);\n      expect(content).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/container/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  Tree,\n  chain,\n  externalSchematic,\n  apply,\n  applyTemplates,\n  url,\n  noop,\n  filter,\n  move,\n  mergeWith,\n} from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport {\n  stringUtils,\n  buildRelativePath,\n  insertImport,\n  NoopChange,\n  ReplaceChange,\n  InsertChange,\n  getProjectPath,\n  omit,\n  parseName,\n} from '../../schematics-core';\nimport { Schema as ContainerOptions } from './schema';\n\nfunction addStateToComponent(options: Partial<ContainerOptions>) {\n  return (host: Tree) => {\n    if (!options.state && !options.stateInterface) {\n      return host;\n    }\n\n    const statePath = `/${options.path}/${options.state}`;\n\n    if (options.state) {\n      if (!host.exists(statePath)) {\n        throw new Error(`The Specified state path ${statePath} does not exist`);\n      }\n    }\n\n    let componentPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      stringUtils.dasherize(options.name) +\n      '.component.ts';\n\n    if (!host.exists(componentPath)) {\n      componentPath =\n        `/${options.path}/` +\n        (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n        stringUtils.dasherize(options.name) +\n        '.ts';\n      if (!host.exists(componentPath)) {\n        throw new SchematicsException(`File ${componentPath} does not exist.`);\n      }\n    }\n\n    const text = host.read(componentPath);\n    if (text === null) {\n      throw new SchematicsException(\n        `File content ${componentPath} does not exist.`\n      );\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      componentPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const stateImportPath = buildRelativePath(componentPath, statePath);\n    const storeImport = insertImport(\n      source,\n      componentPath,\n      'Store',\n      '@ngrx/store'\n    );\n    const stateImport = options.state\n      ? insertImport(\n          source,\n          componentPath,\n          `* as fromStore`,\n          stateImportPath,\n          true\n        )\n      : new NoopChange();\n\n    const componentClass = source.statements.find(\n      (stm) => stm.kind === ts.SyntaxKind.ClassDeclaration\n    ) as ts.ClassDeclaration;\n    const constructorUpdate = new ReplaceChange(\n      componentPath,\n      componentClass.members.pos,\n      '\\n',\n      `\\n  constructor(private store: Store) {}`\n    );\n\n    const changes = [storeImport, stateImport, constructorUpdate];\n    const recorder = host.beginUpdate(componentPath);\n\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      } else if (change instanceof ReplaceChange) {\n        recorder.remove(change.pos, change.oldText.length);\n        recorder.insertLeft(change.order, change.newText);\n      }\n    }\n\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport default function (options: ContainerOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    options.path = getProjectPath(host, options);\n\n    const parsedPath = parseName(options.path, options.name);\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n\n    const opts = ['state', 'stateInterface', 'testDepth'].reduce(\n      (current: Partial<ContainerOptions>, key) => {\n        return omit(current, key as any);\n      },\n      options\n    );\n\n    const templateSource = apply(\n      url(options.testDepth === 'unit' ? './files' : './integration-files'),\n      [\n        options.skipTests\n          ? filter((path) => !path.endsWith('.spec.ts.template'))\n          : noop(),\n        applyTemplates({\n          'if-flat': (s: string) => (options.flat ? '' : s),\n          ...stringUtils,\n          ...(options as object),\n        } as any),\n        move(parsedPath.path),\n      ]\n    );\n\n    // Remove all undefined values to use the schematic defaults (in angular.json or the Angular schema)\n    (Object.keys(opts) as (keyof ContainerOptions)[]).forEach((key) =>\n      opts[key] === undefined ? delete opts[key] : {}\n    );\n\n    return chain([\n      externalSchematic('@schematics/angular', 'component', {\n        ...opts,\n        skipTests: true,\n      }),\n      addStateToComponent(options),\n      mergeWith(templateSource),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/container/integration-files/__name@dasherize@if-flat__/__name@dasherize__-component.spec.ts.template",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';\nimport { Store, StoreModule } from '@ngrx/store';\n\ndescribe('<%= classify(name) %>Component', () => {\n  let component: <%= classify(name) %>Component;\n  let fixture: ComponentFixture<<%= classify(name) %>Component>;\n  let store: Store;\n\n  beforeEach(async() => {\n    TestBed.configureTestingModule({\n      imports: [ StoreModule.forRoot({}) ],\n      declarations: [ <%= classify(name) %>Component ]\n    });\n\n    await TestBed.compileComponents();\n  });\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(<%= classify(name) %>Component);\n    component = fixture.componentInstance;\n    store = TestBed.inject(Store);\n\n    spyOn(store, 'dispatch').and.callThrough();\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/container/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxContainer\",\n  \"title\": \"NgRx Container Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the component.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the component.\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What should be the name of the container component?\"\n    },\n    \"inlineStyle\": {\n      \"description\": \"Specifies if the style will be in the ts file.\",\n      \"type\": \"boolean\",\n      \"alias\": \"s\"\n    },\n    \"inlineTemplate\": {\n      \"description\": \"Specifies if the template will be in the ts file.\",\n      \"type\": \"boolean\",\n      \"alias\": \"t\"\n    },\n    \"viewEncapsulation\": {\n      \"description\": \"Specifies the view encapsulation strategy.\",\n      \"enum\": [\"Emulated\", \"Native\", \"None\"],\n      \"type\": \"string\",\n      \"alias\": \"v\"\n    },\n    \"changeDetection\": {\n      \"description\": \"Specifies the change detection strategy.\",\n      \"enum\": [\"Default\", \"OnPush\"],\n      \"type\": \"string\",\n      \"alias\": \"c\"\n    },\n    \"prefix\": {\n      \"type\": \"string\",\n      \"format\": \"html-selector\",\n      \"description\": \"The prefix to apply to generated selectors.\",\n      \"alias\": \"p\"\n    },\n    \"style\": {\n      \"description\": \"The file extension or preprocessor to use for style files.\",\n      \"type\": \"string\"\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"description\": \"When true, does not create test files.\"\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"skipImport\": {\n      \"type\": \"boolean\",\n      \"description\": \"Flag to skip the module import.\"\n    },\n    \"selector\": {\n      \"type\": \"string\",\n      \"format\": \"html-selector\",\n      \"description\": \"The selector to use for the component.\"\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"description\": \"Allows specification of the declaring module.\",\n      \"alias\": \"m\"\n    },\n    \"export\": {\n      \"type\": \"boolean\",\n      \"description\": \"Specifies if declaring module exports the component.\"\n    },\n    \"state\": {\n      \"type\": \"string\",\n      \"description\": \"Specifies the path to the state exports.\"\n    },\n    \"stateInterface\": {\n      \"type\": \"string\",\n      \"default\": \"State\",\n      \"description\": \"Specifies the interface for the state.\"\n    },\n    \"testDepth\": {\n      \"description\": \"Specifies whether to create a unit test or an integration test.\",\n      \"enum\": [\"unit\", \"integration\"],\n      \"type\": \"string\",\n      \"default\": \"integration\"\n    },\n    \"standalone\": {\n      \"description\": \"Whether the generated component is standalone.\",\n      \"type\": \"boolean\",\n      \"default\": true\n    },\n    \"displayBlock\": {\n      \"description\": \"Specifies if the style will contain :host { display: block; }.\",\n      \"type\": \"boolean\",\n      \"alias\": \"b\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/container/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The path to create the component.\n   */\n  path?: string;\n  /**\n   * The name of the project.\n   */\n  project?: string;\n  /**\n   * The name of the component.\n   */\n  name: string;\n  /**\n   * Specifies if the style will be in the ts file.\n   */\n  inlineStyle?: boolean;\n  /**\n   * Specifies if the template will be in the ts file.\n   */\n  inlineTemplate?: boolean;\n  /**\n   * Specifies the view encapsulation strategy.\n   */\n  viewEncapsulation?: 'Emulated' | 'Native' | 'None';\n  /**\n   * Specifies the change detection strategy.\n   */\n  changeDetection?: 'Default' | 'OnPush';\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * The file extension or preprocessor to use for style files.\n   */\n  style?: string;\n  /**\n   * When true, does not create test files.\n   */\n  skipTests?: boolean;\n  /**\n   * Flag to indicate if a dir is created.\n   */\n  flat?: boolean;\n  /**\n   * Flag to skip the module import.\n   */\n  skipImport?: boolean;\n  /**\n   * The selector to use for the component.\n   */\n  selector?: string;\n  /**\n   * Allows specification of the declaring module.\n   */\n  module?: string;\n  /**\n   * Specifies if declaring module exports the component.\n   */\n  export?: boolean;\n  /**\n   * Specifies the path to the state exports\n   */\n  state?: string;\n\n  /**\n   * Specifies the interface for the state\n   */\n  stateInterface?: string;\n\n  /**\n   * Specifies whether to create a unit test or an integration test.\n   */\n  testDepth?: string;\n\n  /**\n   * Whether the generated component is standalone\n   */\n  standalone?: boolean;\n\n  /**\n   * Specifies if the style will contain :host { display: block; }.\n   */\n  displayBlock?: boolean;\n}\n"
  },
  {
    "path": "modules/schematics/src/data/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Data Schematic should create a model interface 1`] = `\n\"export interface Foo {\n  id?: unknown;\n}\n\"\n`;\n\nexports[`Data Schematic should create a service class 1`] = `\n\"import { Injectable } from '@angular/core';\nimport {\n  EntityCollectionServiceBase,\n  EntityCollectionServiceElementsFactory\n} from '@ngrx/data';\nimport { Foo } from './foo';\n\n@Injectable({ providedIn: 'root' })\nexport class FooService extends EntityCollectionServiceBase<Foo> {\n  constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {\n    super('Foo', serviceElementsFactory);\n  }\n}\n\"\n`;\n\nexports[`Data Schematic should create a spec class 1`] = `\n\"import { TestBed } from '@angular/core/testing';\nimport {\n  EntityCollectionServiceElementsFactory\n} from '@ngrx/data';\nimport { FooService } from './foo.service';\n\ndescribe('FooService', () => {\n  let service: FooService;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      providers: [\n        EntityCollectionServiceElementsFactory,\n        FooService\n      ]\n    });\n\n    await TestBed.compileComponents();\n  });\n\n  beforeEach(() => {\n    service = TestBed.inject(FooService);\n  });\n\n  it('should create an instance', () => {\n    expect(service).toBeTruthy();\n  });\n});\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/data/files/__name@dasherize@if-flat__/__name@dasherize__.service.spec.ts.template",
    "content": "import { TestBed } from '@angular/core/testing';\nimport {\n  EntityCollectionServiceElementsFactory\n} from '@ngrx/data';\nimport { <%= classify(name) %>Service } from './<%= dasherize(name) %>.service';\n\ndescribe('<%= classify(name) %>Service', () => {\n  let service: <%= classify(name) %>Service;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      providers: [\n        EntityCollectionServiceElementsFactory,\n        <%= classify(name) %>Service\n      ]\n    });\n\n    await TestBed.compileComponents();\n  });\n\n  beforeEach(() => {\n    service = TestBed.inject(<%= classify(name) %>Service);\n  });\n\n  it('should create an instance', () => {\n    expect(service).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/data/files/__name@dasherize@if-flat__/__name@dasherize__.service.ts.template",
    "content": "import { Injectable } from '@angular/core';\nimport {\n  EntityCollectionServiceBase,\n  EntityCollectionServiceElementsFactory\n} from '@ngrx/data';\nimport { <%= classify(name) %> } from './<%= dasherize(name) %>';\n\n@Injectable({ providedIn: 'root' })\nexport class <%= classify(name) %>Service extends EntityCollectionServiceBase<<%= classify(name) %>> {\n  constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {\n    super('<%= classify(name) %>', serviceElementsFactory);\n  }\n}\n"
  },
  {
    "path": "modules/schematics/src/data/files/__name@dasherize@if-flat__/__name@dasherize__.ts.template",
    "content": "export interface <%= classify(name) %> {\n  id?: unknown;\n}\n"
  },
  {
    "path": "modules/schematics/src/data/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as DataOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n  defaultWorkspaceOptions,\n  defaultAppOptions,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Data Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n  const defaultOptions: DataOptions = {\n    name: 'foo',\n    project: 'bar',\n    group: false,\n    flat: true,\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should create the data service to a specified project if provided', async () => {\n    const options = { ...defaultOptions, project: 'baz' };\n\n    const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {\n      ...defaultAppOptions,\n      name: 'baz',\n    });\n\n    const tree = await schematicRunner.runSchematic('data', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo.service.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create the service and model files', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'data',\n      defaultOptions,\n      appTree\n    );\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.service.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create two files if skipTests is false(as it is by default)', async () => {\n    const options = {\n      ...defaultOptions,\n    };\n    const tree = await schematicRunner.runSchematic('data', options, appTree);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.service.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.service.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create a service class', async () => {\n    const options = { ...defaultOptions };\n    const tree = await schematicRunner.runSchematic('data', options, appTree);\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.service.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should create a model interface', async () => {\n    const options = { ...defaultOptions };\n    const tree = await schematicRunner.runSchematic('data', options, appTree);\n    const fileContent = tree.readContent(`${projectPath}/src/app/foo.ts`);\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should create a spec class', async () => {\n    const options = { ...defaultOptions };\n    const tree = await schematicRunner.runSchematic('data', options, appTree);\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.service.spec.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/data/index.ts",
    "content": "import {\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  filter,\n  mergeWith,\n  move,\n  noop,\n  Rule,\n  SchematicContext,\n  Tree,\n  url,\n} from '@angular-devkit/schematics';\nimport { getProjectPath, parseName, stringUtils } from '../../schematics-core';\nimport { Schema as DataOptions } from './schema';\n\nexport default function (options: DataOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    options.path = getProjectPath(host, options);\n\n    const parsedPath = parseName(options.path, options.name);\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n\n    const templateSource = apply(url('./files'), [\n      options.skipTests\n        ? filter((path) => !path.endsWith('.spec.ts.template'))\n        : noop(),\n      applyTemplates({\n        ...stringUtils,\n        'if-flat': (s: string) =>\n          stringUtils.group(options.flat ? '' : s, options.group ? 'data' : ''),\n        ...options,\n      }),\n      move(parsedPath.path),\n    ]);\n\n    return chain([branchAndMerge(chain([mergeWith(templateSource)]))])(\n      host,\n      context\n    );\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/data/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxData\",\n  \"title\": \"NgRx Data Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the data entity.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      }\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the service.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"description\": \"When true, does not create test files.\",\n      \"default\": false\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"group\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Group the services within relative subfolders\",\n      \"aliases\": [\"g\"]\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/data/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The name of the component.\n   */\n  name: string;\n\n  /**\n   * The path to create the component.\n   */\n  path?: string;\n\n  /**\n   * The name of the project.\n   */\n  project?: string;\n\n  /**\n   * When true, does not create test files.\n   */\n  skipTests?: boolean;\n\n  /**\n   * Flag to indicate if a dir is created.\n   */\n\n  flat?: boolean;\n\n  /**\n   * Group entity metadata files within 'data' folder\n   */\n  group?: boolean;\n}\n"
  },
  {
    "path": "modules/schematics/src/effect/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Effect Schematic feature effects should add an effect to the existing registered feature effects 1`] = `\n\"\n    import { BrowserModule } from '@angular/platform-browser';\n    import { NgModule } from '@angular/core';\n    import { AppComponent } from './app.component';\n    import { EffectsModule } from '@ngrx/effects';\nimport { FooEffects } from './foo/foo.effects';\n\n    @NgModule({\n      declarations: [\n        AppComponent\n      ],\n      imports: [\n        BrowserModule,\n        EffectsModule.forRoot([RootEffects])\n    EffectsModule.forFeature([UserEffects, FooEffects])\n      ],\n      providers: [],\n      bootstrap: [AppComponent]\n    })\n    export class AppModule { }\n  \"\n`;\n\nexports[`Effect Schematic feature effects should create an effect that describes a source of actions within a feature 1`] = `\n\"import { Injectable } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\n\nimport { concatMap } from 'rxjs/operators';\nimport { Observable, EMPTY } from 'rxjs';\nimport { FooActions } from './foo.actions';\n\n@Injectable()\nexport class FooEffects {\n\n\n  loadFoos$ = createEffect(() => {\n    return this.actions$.pipe(\n\n      ofType(FooActions.loadFoos),\n      /** An EMPTY observable only emits completion. Replace with your own observable API request */\n      concatMap(() => EMPTY as Observable<{ type: string }>)\n    );\n  });\n\n  constructor(private actions$: Actions) {}\n}\n\"\n`;\n\nexports[`Effect Schematic feature effects should not add an effect to registered effects defined with a variable 1`] = `\n\"\n    import { BrowserModule } from '@angular/platform-browser';\n    import { NgModule } from '@angular/core';\n    import { AppComponent } from './app.component';\n    import { EffectsModule } from '@ngrx/effects';\nimport { FooEffects } from './foo/foo.effects';\n\n    @NgModule({\n      declarations: [\n        AppComponent\n      ],\n      imports: [\n        BrowserModule,\n        EffectsModule.forRoot(effects),\n        EffectsModule.forFeature([FooEffects])\n      ],\n      providers: [],\n      bootstrap: [AppComponent]\n    })\n    export class AppModule { }\n  \"\n`;\n\nexports[`Effect Schematic feature effects should still register the feature effect module with an effect with the minimal flag 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { EffectsModule } from '@ngrx/effects';\nimport { FooEffects } from './foo/foo.effects';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    EffectsModule.forFeature([FooEffects])\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Effect Schematic root effects should add an effect to the empty array of registered effects 1`] = `\n\"\n    import { BrowserModule } from '@angular/platform-browser';\n    import { NgModule } from '@angular/core';\n    import { AppComponent } from './app.component';\n    import { EffectsModule } from '@ngrx/effects';\nimport { FooEffects } from './foo/foo.effects';\n\n    @NgModule({\n      declarations: [\n        AppComponent\n      ],\n      imports: [\n        BrowserModule,\n        EffectsModule.forRoot([FooEffects])\n      ],\n      providers: [],\n      bootstrap: [AppComponent]\n    })\n    export class AppModule { }\n  \"\n`;\n\nexports[`Effect Schematic root effects should add an effect to the existing registered root effects 1`] = `\n\"\n    import { BrowserModule } from '@angular/platform-browser';\n    import { NgModule } from '@angular/core';\n    import { AppComponent } from './app.component';\n    import { EffectsModule } from '@ngrx/effects';\nimport { FooEffects } from './foo/foo.effects';\n\n    @NgModule({\n      declarations: [\n        AppComponent\n      ],\n      imports: [\n        BrowserModule,\n        EffectsModule.forRoot([UserEffects, FooEffects])\n      ],\n      providers: [],\n      bootstrap: [AppComponent]\n    })\n    export class AppModule { }\n  \"\n`;\n\nexports[`Effect Schematic root effects should create an effect that does not define a source of actions within the root 1`] = `\n\"import { Injectable } from '@angular/core';\nimport { Actions, createEffect } from '@ngrx/effects';\n\n\n\n@Injectable()\nexport class FooEffects {\n\n\n  constructor(private actions$: Actions) {}\n}\n\"\n`;\n\nexports[`Effect Schematic root effects should register the root effect in the provided module 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { EffectsModule } from '@ngrx/effects';\nimport { FooEffects } from './foo/foo.effects';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    EffectsModule.forRoot([FooEffects])\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Effect Schematic root effects should register the root effect module without effect with the minimal flag 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { EffectsModule } from '@ngrx/effects';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    EffectsModule.forRoot([])\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Effect Schematic should add prefix to the effect 1`] = `\n\"import { Injectable } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { catchError, map, concatMap } from 'rxjs/operators';\nimport { Observable, EMPTY, of } from 'rxjs';\nimport { FooActions } from './foo.actions';\n\n\n@Injectable()\nexport class FooEffects {\n\n  customFoos$ = createEffect(() => {\n    return this.actions$.pipe(\n\n      ofType(FooActions.customFoos),\n      concatMap(() =>\n        /** An EMPTY observable only emits completion. Replace with your own observable API request */\n        EMPTY.pipe(\n          map(data => FooActions.customFoosSuccess({ data })),\n          catchError(error => of(FooActions.customFoosFailure({ error }))))\n      )\n    );\n  });\n\n\n  constructor(private actions$: Actions) {}\n}\n\"\n`;\n\nexports[`Effect Schematic should create an api effect that describes a source of actions within a feature 1`] = `\n\"import { Injectable } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { catchError, map, concatMap } from 'rxjs/operators';\nimport { Observable, EMPTY, of } from 'rxjs';\nimport { FooActions } from './foo.actions';\n\n\n@Injectable()\nexport class FooEffects {\n\n  loadFoos$ = createEffect(() => {\n    return this.actions$.pipe(\n\n      ofType(FooActions.loadFoos),\n      concatMap(() =>\n        /** An EMPTY observable only emits completion. Replace with your own observable API request */\n        EMPTY.pipe(\n          map(data => FooActions.loadFoosSuccess({ data })),\n          catchError(error => of(FooActions.loadFoosFailure({ error }))))\n      )\n    );\n  });\n\n\n  constructor(private actions$: Actions) {}\n}\n\"\n`;\n\nexports[`Effect Schematic should group and nest the effect within a feature 1`] = `\n\"import { Injectable } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\n\nimport { concatMap } from 'rxjs/operators';\nimport { Observable, EMPTY } from 'rxjs';\nimport { FooActions } from '../../actions/foo/foo.actions';\n\n@Injectable()\nexport class FooEffects {\n\n\n  loadFoos$ = createEffect(() => {\n    return this.actions$.pipe(\n\n      ofType(FooActions.loadFoos),\n      /** An EMPTY observable only emits completion. Replace with your own observable API request */\n      concatMap(() => EMPTY as Observable<{ type: string }>)\n    );\n  });\n\n  constructor(private actions$: Actions) {}\n}\n\"\n`;\n\nexports[`Effect Schematic should import into a specified module when provided 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { EffectsModule } from '@ngrx/effects';\nimport { FooEffects } from './foo/foo.effects';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    EffectsModule.forFeature([FooEffects])\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Effect Schematic should inject the effect service correctly within the spec 1`] = `\n\"import { TestBed } from '@angular/core/testing';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport { Observable } from 'rxjs';\n\nimport { FooEffects } from './foo.effects';\n\ndescribe('FooEffects', () => {\n  let actions$: Observable<any>;\n  let effects: FooEffects;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        FooEffects,\n        provideMockActions(() => actions$)\n      ]\n    });\n\n    effects = TestBed.inject(FooEffects);\n  });\n\n  it('should be created', () => {\n    expect(effects).toBeTruthy();\n  });\n});\n\"\n`;\n\nexports[`Effect Schematic should not be provided by default 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.spec.ts.template",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport { Observable } from 'rxjs';\n\nimport { <%= classify(name) %>Effects } from './<%= dasherize(name) %>.effects';\n\ndescribe('<%= classify(name) %>Effects', () => {\n  let actions$: Observable<any>;\n  let effects: <%= classify(name) %>Effects;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        <%= classify(name) %>Effects,\n        provideMockActions(() => actions$)\n      ]\n    });\n\n    effects = TestBed.inject(<%= classify(name) %>Effects);\n  });\n\n  it('should be created', () => {\n    expect(effects).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/effect/files/__name@dasherize@if-flat__/__name@dasherize__.effects.ts.template",
    "content": "import { Injectable } from '@angular/core';\nimport { Actions, <%= effectMethod %><% if (feature) { %>, ofType<% } %> } from '@ngrx/effects';\n<% if (feature && api) { %>import { catchError, map, concatMap } from 'rxjs/operators';\nimport { Observable, EMPTY, of } from 'rxjs';\nimport { <%= classify(name) %>Actions } from '<%= featurePath(group, flat, \"actions\", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %>\n<% if (feature && !api) { %>import { concatMap } from 'rxjs/operators';\nimport { Observable, EMPTY } from 'rxjs';\nimport { <%= classify(name) %>Actions } from '<%= featurePath(group, flat, \"actions\", dasherize(name)) %><%= dasherize(name) %>.actions';<% } %>\n\n@Injectable()\nexport class <%= classify(name) %>Effects {\n<% if (feature && api) { %>\n  <%= effectStart %>\n      ofType(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>s),\n      concatMap(() =>\n        /** An EMPTY observable only emits completion. Replace with your own observable API request */\n        EMPTY.pipe(\n          map(data => <%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sSuccess({ data })),\n          catchError(error => of(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sFailure({ error }))))\n      )\n  <%= effectEnd %>\n<% } %>\n<% if (feature && !api) { %>\n  <%= effectStart %>\n      ofType(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>s),\n      /** An EMPTY observable only emits completion. Replace with your own observable API request */\n      concatMap(() => EMPTY as Observable<{ type: string }>)\n  <%= effectEnd %>\n<% } %>\n  constructor(private actions$: Actions) {}\n}\n"
  },
  {
    "path": "modules/schematics/src/effect/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as EffectOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n  createAppModuleWithEffects,\n  defaultWorkspaceOptions,\n  defaultAppOptions,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Effect Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n\n  const defaultOptions: EffectOptions = {\n    name: 'foo',\n    project: 'bar',\n    module: undefined,\n    flat: false,\n    feature: false,\n    root: false,\n    group: false,\n    prefix: 'load',\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should create an effect to specified project if provided', async () => {\n    const options = {\n      ...defaultOptions,\n      project: 'baz',\n    };\n\n    const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {\n      ...defaultAppOptions,\n      name: 'baz',\n    });\n\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo/foo.effects.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo/foo.effects.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create an effect with a spec file', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.effects.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.effects.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should not be provided by default', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).not.toMatch(/FooEffects/);\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should import into a specified module when provided', async () => {\n    const options = { ...defaultOptions, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(/FooEffects/);\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should fail if specified module does not exist', async () => {\n    const options = {\n      ...defaultOptions,\n      module: `${projectPath}/src/app/app.moduleXXX.ts`,\n    };\n    await expect(\n      schematicRunner.runSchematic('effects', options, appTree)\n    ).rejects.toThrowError();\n  });\n\n  it('should respect the skipTests flag', async () => {\n    const options = { ...defaultOptions, skipTests: true };\n\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.effects.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/foo.effects.spec.ts`)\n    ).toEqual(-1);\n  });\n\n  it('should group within an \"effects\" folder if group is set', async () => {\n    const options = {\n      ...defaultOptions,\n      flat: true,\n      skipTests: true,\n      group: true,\n    };\n\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/effects/foo.effects.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should group and nest the effect within a feature', async () => {\n    const options = {\n      ...defaultOptions,\n      skipTests: true,\n      group: true,\n      flat: false,\n      feature: true,\n    };\n\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/effects/foo/foo.effects.ts`)\n    ).toBeGreaterThanOrEqual(0);\n\n    const content = tree.readContent(\n      `${projectPath}/src/app/effects/foo/foo.effects.ts`\n    );\n\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should inject the effect service correctly within the spec', async () => {\n    const options = { ...defaultOptions };\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const content = tree.readContent(\n      `${projectPath}/src/app/foo/foo.effects.spec.ts`\n    );\n\n    expect(content).toMatch(/effects = TestBed\\.inject\\(FooEffects\\);/);\n    expect(content).toMatchSnapshot();\n  });\n\n  describe('root effects', () => {\n    it('should register the root effect in the provided module', async () => {\n      const options = {\n        ...defaultOptions,\n        root: true,\n        module: 'app-module.ts',\n      };\n\n      const tree = await schematicRunner.runSchematic(\n        'effect',\n        options,\n        appTree\n      );\n      const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n\n      expect(content).toMatch(/EffectsModule\\.forRoot\\(\\[FooEffects\\]\\)/);\n      expect(content).toMatchSnapshot();\n    });\n\n    it('should register the root effect module without effect with the minimal flag', async () => {\n      const options = {\n        ...defaultOptions,\n        root: true,\n        name: undefined,\n        module: 'app-module.ts',\n        minimal: true,\n      };\n\n      const tree = await schematicRunner.runSchematic(\n        'effect',\n        options,\n        appTree\n      );\n      const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n\n      expect(content).toMatch(/EffectsModule\\.forRoot\\(\\[\\]\\)/);\n      expect(content).not.toMatch(/FooEffects/);\n      expect(content).toMatchSnapshot();\n    });\n\n    it('should add an effect to the empty array of registered effects', async () => {\n      const storeModule = `${projectPath}/src/app/store.module.ts`;\n      const options = {\n        ...defaultOptions,\n        root: true,\n        module: 'store.module.ts',\n      };\n      appTree = createAppModuleWithEffects(\n        appTree,\n        storeModule,\n        'EffectsModule.forRoot([])'\n      );\n\n      const tree = await schematicRunner.runSchematic(\n        'effect',\n        options,\n        appTree\n      );\n      const content = tree.readContent(storeModule);\n\n      expect(content).toMatch(/EffectsModule\\.forRoot\\(\\[FooEffects\\]\\)/);\n      expect(content).toMatchSnapshot();\n    });\n\n    it('should add an effect to the existing registered root effects', async () => {\n      const storeModule = `${projectPath}/src/app/store.module.ts`;\n      const options = {\n        ...defaultOptions,\n        root: true,\n        module: 'store.module.ts',\n      };\n      appTree = createAppModuleWithEffects(\n        appTree,\n        storeModule,\n        'EffectsModule.forRoot([UserEffects])'\n      );\n\n      const tree = await schematicRunner.runSchematic(\n        'effect',\n        options,\n        appTree\n      );\n      const content = tree.readContent(storeModule);\n\n      expect(content).toMatch(\n        /EffectsModule\\.forRoot\\(\\[UserEffects, FooEffects\\]\\)/\n      );\n      expect(content).toMatchSnapshot();\n    });\n\n    it('should create an effect that does not define a source of actions within the root', async () => {\n      const options = { ...defaultOptions, root: true };\n\n      const tree = await schematicRunner.runSchematic(\n        'effect',\n        options,\n        appTree\n      );\n      const content = tree.readContent(\n        `${projectPath}/src/app/foo/foo.effects.ts`\n      );\n      expect(content).toMatchSnapshot();\n    });\n  });\n\n  describe('feature effects', () => {\n    it('should still register the feature effect module with an effect with the minimal flag', async () => {\n      const options = {\n        ...defaultOptions,\n        root: false,\n        module: 'app-module.ts',\n        minimal: true,\n      };\n\n      const tree = await schematicRunner.runSchematic(\n        'effect',\n        options,\n        appTree\n      );\n      const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n\n      expect(content).toMatch(/EffectsModule\\.forFeature\\(\\[FooEffects\\]\\)/);\n      expect(\n        tree.files.indexOf(`${projectPath}/src/app/foo/foo.effects.ts`)\n      ).toBeGreaterThanOrEqual(0);\n\n      expect(content).toMatchSnapshot();\n    });\n\n    it('should add an effect to the existing registered feature effects', async () => {\n      const storeModule = `${projectPath}/src/app/store.module.ts`;\n      const options = { ...defaultOptions, module: 'store.module.ts' };\n      appTree = createAppModuleWithEffects(\n        appTree,\n        storeModule,\n        `EffectsModule.forRoot([RootEffects])\\n    EffectsModule.forFeature([UserEffects])`\n      );\n\n      const tree = await schematicRunner.runSchematic(\n        'effect',\n        options,\n        appTree\n      );\n      const content = tree.readContent(storeModule);\n\n      expect(content).toMatch(\n        /EffectsModule\\.forFeature\\(\\[UserEffects, FooEffects\\]\\)/\n      );\n      expect(content).toMatchSnapshot();\n    });\n\n    it('should not add an effect to registered effects defined with a variable', async () => {\n      const storeModule = `${projectPath}/src/app/store.module.ts`;\n      const options = { ...defaultOptions, module: 'store.module.ts' };\n      appTree = createAppModuleWithEffects(\n        appTree,\n        storeModule,\n        'EffectsModule.forRoot(effects)'\n      );\n\n      const tree = await schematicRunner.runSchematic(\n        'effect',\n        options,\n        appTree\n      );\n      const content = tree.readContent(storeModule);\n\n      expect(content).not.toMatch(/EffectsModule\\.forRoot\\(\\[FooEffects\\]\\)/);\n      expect(content).toMatchSnapshot();\n    });\n\n    it('should create an effect that describes a source of actions within a feature', async () => {\n      const options = { ...defaultOptions, feature: true };\n\n      const tree = await schematicRunner.runSchematic(\n        'effect',\n        options,\n        appTree\n      );\n      const content = tree.readContent(\n        `${projectPath}/src/app/foo/foo.effects.ts`\n      );\n      expect(content).toMatchSnapshot();\n    });\n  });\n\n  it('should create an api effect that describes a source of actions within a feature', async () => {\n    const options = { ...defaultOptions, feature: true, api: true };\n\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const content = tree.readContent(\n      `${projectPath}/src/app/foo/foo.effects.ts`\n    );\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should add prefix to the effect', async () => {\n    const options = {\n      ...defaultOptions,\n      api: true,\n      feature: true,\n      prefix: 'custom',\n    };\n\n    const tree = await schematicRunner.runSchematic('effect', options, appTree);\n    const content = tree.readContent(\n      `${projectPath}/src/app/foo/foo.effects.ts`\n    );\n    expect(content).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/effect/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  Tree,\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  filter,\n  mergeWith,\n  move,\n  noop,\n  url,\n} from '@angular-devkit/schematics';\nimport {\n  InsertChange,\n  addImportToModule,\n  buildRelativePath,\n  findModuleFromOptions,\n  getProjectPath,\n  insertImport,\n  parseName,\n  stringUtils,\n  getPrefix,\n} from '../../schematics-core';\nimport { Schema as EffectOptions } from './schema';\n\nfunction addImportToNgModule(options: EffectOptions): Rule {\n  return (host: Tree) => {\n    const modulePath = options.module;\n\n    if (!modulePath) {\n      return host;\n    }\n\n    if (!host.exists(modulePath)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const effectsName = `${stringUtils.classify(`${options.name}Effects`)}`;\n\n    const effectsModuleImport = insertImport(\n      source,\n      modulePath,\n      'EffectsModule',\n      '@ngrx/effects'\n    );\n\n    const effectsPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'effects/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.effects';\n    const relativePath = buildRelativePath(modulePath, effectsPath);\n    const effectsImport = insertImport(\n      source,\n      modulePath,\n      effectsName,\n      relativePath\n    );\n\n    const effectsSetup =\n      options.root && options.minimal ? `[]` : `[${effectsName}]`;\n    const [effectsNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `EffectsModule.for${options.root ? 'Root' : 'Feature'}(${effectsSetup})`,\n      relativePath\n    );\n\n    let changes = [effectsModuleImport, effectsNgModuleImport];\n\n    if (!options.root || (options.root && !options.minimal)) {\n      changes = changes.concat([effectsImport]);\n    }\n\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nfunction getEffectStart(name: string, effectPrefix: string): string {\n  const effectName = stringUtils.classify(name);\n  const effectMethodPrefix = stringUtils.camelize(effectPrefix);\n\n  return (\n    `${effectMethodPrefix}${effectName}s$ = createEffect(() => {` +\n    '\\n    return this.actions$.pipe(\\n'\n  );\n}\n\nexport default function (options: EffectOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    options.path = getProjectPath(host, options);\n    const parsedPath = parseName(options.path, options.name || '');\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n    options.prefix = getPrefix(options);\n\n    if (options.module) {\n      options.module = findModuleFromOptions(host, options);\n    }\n\n    const templateSource = apply(url('./files'), [\n      options.skipTests\n        ? filter((path) => !path.endsWith('.spec.ts.template'))\n        : noop(),\n      options.root && options.minimal ? filter((_) => false) : noop(),\n      applyTemplates({\n        ...stringUtils,\n        'if-flat': (s: string) =>\n          stringUtils.group(\n            options.flat ? '' : s,\n            options.group ? 'effects' : ''\n          ),\n        effectMethod: 'createEffect',\n        effectStart: getEffectStart(options.name, options.prefix),\n        effectEnd: '  );\\n' + '  });',\n        ...(options as object),\n      } as any),\n      move(parsedPath.path),\n    ]);\n\n    return chain([\n      branchAndMerge(\n        chain([addImportToNgModule(options), mergeWith(templateSource)])\n      ),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/effect/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxEffect\",\n  \"title\": \"NgRx Effect Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the effect.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What should be the name of the Effect?\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the component.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"When true, does not create test files.\"\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"default\": \"\",\n      \"description\": \"Allows specification of the declaring module.\",\n      \"alias\": \"m\",\n      \"subtype\": \"filepath\",\n      \"x-prompt\": \"To which module (path) should the effect be registered in?\"\n    },\n    \"root\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Flag to indicate whether the effects are registered as root.\"\n    },\n    \"feature\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Flag to indicate if part of a feature schematic.\"\n    },\n    \"group\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Group effects file within 'effects' folder\",\n      \"aliases\": [\"g\"]\n    },\n    \"api\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Specifies if effect has api success and failure actions wired up\",\n      \"aliases\": [\"a\"],\n      \"x-prompt\": \"Should we wire up success and failure actions?\"\n    },\n    \"minimal\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Setup root effects module without registering initial effects.\"\n    },\n    \"prefix\": {\n      \"description\": \"The prefix of the effect.\",\n      \"type\": \"string\",\n      \"default\": \"load\",\n      \"x-prompt\": \"What should be the prefix of the effect?\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/effect/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The name of the component.\n   */\n  name: string;\n\n  /**\n   * The path to create the effect.\n   */\n  path?: string;\n\n  /**\n   * The name of the project.\n   */\n  project?: string;\n\n  /**\n   * Flag to indicate if a dir is created.\n   */\n  flat?: boolean;\n\n  /**\n   * When true, does not create test files.\n   */\n  skipTests?: boolean;\n\n  /**\n   * Allows specification of the declaring module.\n   */\n  module?: string;\n\n  /**\n   * Specifies if this is a root-level effect\n   */\n  root?: boolean;\n\n  /**\n   * Specifies if this is grouped within a feature\n   */\n  feature?: boolean;\n\n  /**\n   * Specifies if this is grouped within an 'effects' folder\n   */\n  group?: boolean;\n\n  /**\n   * Specifies if effect has api success and failure actions wired up\n   */\n  api?: boolean;\n\n  /**\n   * Setup root effects module without registering initial effects.\n   */\n  minimal?: boolean;\n\n  /**\n   * The prefix for the effects.\n   */\n  prefix?: string;\n}\n"
  },
  {
    "path": "modules/schematics/src/entity/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Entity Schematic should create 3 files 1`] = `\n\"import { createActionGroup, emptyProps, props } from '@ngrx/store';\nimport { Update } from '@ngrx/entity';\n\nimport { Foo } from './foo.model';\n\nexport const FooActions = createActionGroup({\n  source: 'Foo/API',\n  events: {\n    'Load Foos': props<{ foos: Foo[] }>(),\n    'Add Foo': props<{ foo: Foo }>(),\n    'Upsert Foo': props<{ foo: Foo }>(),\n    'Add Foos': props<{ foos: Foo[] }>(),\n    'Upsert Foos': props<{ foos: Foo[] }>(),\n    'Update Foo': props<{ foo: Update<Foo> }>(),\n    'Update Foos': props<{ foos: Update<Foo>[] }>(),\n    'Delete Foo': props<{ id: string }>(),\n    'Delete Foos': props<{ ids: string[] }>(),\n    'Clear Foos': emptyProps(),\n  }\n});\n\"\n`;\n\nexports[`Entity Schematic should create 3 files 2`] = `\n\"export interface Foo {\n  id: string;\n}\n\"\n`;\n\nexports[`Entity Schematic should create 3 files 3`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';\nimport { Foo } from './foo.model';\nimport { FooActions } from './foo.actions';\n\nexport const foosFeatureKey = 'foos';\n\nexport interface State extends EntityState<Foo> {\n  // additional entities state properties\n}\n\nexport const adapter: EntityAdapter<Foo> = createEntityAdapter<Foo>();\n\nexport const initialState: State = adapter.getInitialState({\n  // additional entity state properties\n});\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.addFoo,\n    (state, action) => adapter.addOne(action.foo, state)\n  ),\n  on(FooActions.upsertFoo,\n    (state, action) => adapter.upsertOne(action.foo, state)\n  ),\n  on(FooActions.addFoos,\n    (state, action) => adapter.addMany(action.foos, state)\n  ),\n  on(FooActions.upsertFoos,\n    (state, action) => adapter.upsertMany(action.foos, state)\n  ),\n  on(FooActions.updateFoo,\n    (state, action) => adapter.updateOne(action.foo, state)\n  ),\n  on(FooActions.updateFoos,\n    (state, action) => adapter.updateMany(action.foos, state)\n  ),\n  on(FooActions.deleteFoo,\n    (state, action) => adapter.removeOne(action.id, state)\n  ),\n  on(FooActions.deleteFoos,\n    (state, action) => adapter.removeMany(action.ids, state)\n  ),\n  on(FooActions.loadFoos,\n    (state, action) => adapter.setAll(action.foos, state)\n  ),\n  on(FooActions.clearFoos,\n    state => adapter.removeAll(state)\n  ),\n);\n\nexport const foosFeature = createFeature({\n  name: foosFeatureKey,\n  reducer,\n  extraSelectors: ({ selectFoosState }) => ({\n    ...adapter.getSelectors(selectFoosState)\n  }),\n});\n\nexport const {\n  selectIds,\n  selectEntities,\n  selectAll,\n  selectTotal,\n} = foosFeature;\n\"\n`;\n\nexports[`Entity Schematic should create 3 files of an entity to specified project if provided 1`] = `\"\"`;\n\nexports[`Entity Schematic should create 3 files of an entity to specified project if provided 2`] = `\"\"`;\n\nexports[`Entity Schematic should create 3 files of an entity to specified project if provided 3`] = `\"\"`;\n\nexports[`Entity Schematic should create all files of an entity within grouped and nested folders 1`] = `\n\"import { createActionGroup, emptyProps, props } from '@ngrx/store';\nimport { Update } from '@ngrx/entity';\n\nimport { Foo } from '../../models/foo/foo.model';\n\nexport const FooActions = createActionGroup({\n  source: 'Foo/API',\n  events: {\n    'Load Foos': props<{ foos: Foo[] }>(),\n    'Add Foo': props<{ foo: Foo }>(),\n    'Upsert Foo': props<{ foo: Foo }>(),\n    'Add Foos': props<{ foos: Foo[] }>(),\n    'Upsert Foos': props<{ foos: Foo[] }>(),\n    'Update Foo': props<{ foo: Update<Foo> }>(),\n    'Update Foos': props<{ foos: Update<Foo>[] }>(),\n    'Delete Foo': props<{ id: string }>(),\n    'Delete Foos': props<{ ids: string[] }>(),\n    'Clear Foos': emptyProps(),\n  }\n});\n\"\n`;\n\nexports[`Entity Schematic should create all files of an entity within grouped and nested folders 2`] = `\n\"export interface Foo {\n  id: string;\n}\n\"\n`;\n\nexports[`Entity Schematic should create all files of an entity within grouped and nested folders 3`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';\nimport { Foo } from '../../models/foo/foo.model';\nimport { FooActions } from '../../actions/foo/foo.actions';\n\nexport const foosFeatureKey = 'foos';\n\nexport interface State extends EntityState<Foo> {\n  // additional entities state properties\n}\n\nexport const adapter: EntityAdapter<Foo> = createEntityAdapter<Foo>();\n\nexport const initialState: State = adapter.getInitialState({\n  // additional entity state properties\n});\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.addFoo,\n    (state, action) => adapter.addOne(action.foo, state)\n  ),\n  on(FooActions.upsertFoo,\n    (state, action) => adapter.upsertOne(action.foo, state)\n  ),\n  on(FooActions.addFoos,\n    (state, action) => adapter.addMany(action.foos, state)\n  ),\n  on(FooActions.upsertFoos,\n    (state, action) => adapter.upsertMany(action.foos, state)\n  ),\n  on(FooActions.updateFoo,\n    (state, action) => adapter.updateOne(action.foo, state)\n  ),\n  on(FooActions.updateFoos,\n    (state, action) => adapter.updateMany(action.foos, state)\n  ),\n  on(FooActions.deleteFoo,\n    (state, action) => adapter.removeOne(action.id, state)\n  ),\n  on(FooActions.deleteFoos,\n    (state, action) => adapter.removeMany(action.ids, state)\n  ),\n  on(FooActions.loadFoos,\n    (state, action) => adapter.setAll(action.foos, state)\n  ),\n  on(FooActions.clearFoos,\n    state => adapter.removeAll(state)\n  ),\n);\n\nexport const foosFeature = createFeature({\n  name: foosFeatureKey,\n  reducer,\n  extraSelectors: ({ selectFoosState }) => ({\n    ...adapter.getSelectors(selectFoosState)\n  }),\n});\n\nexport const {\n  selectIds,\n  selectEntities,\n  selectAll,\n  selectTotal,\n} = foosFeature;\n\"\n`;\n\nexports[`Entity Schematic should create all files of an entity within grouped and nested folders 4`] = `\n\"import { reducer, initialState } from '../../reducers/foo/foo.reducer';\n\ndescribe('Foo Reducer', () => {\n  describe('unknown action', () => {\n    it('should return the previous state', () => {\n      const action = {} as any;\n\n      const result = reducer(initialState, action);\n\n      expect(result).toBe(initialState);\n    });\n  });\n});\n\"\n`;\n\nexports[`Entity Schematic should create all files of an entity within grouped folders if group is set 1`] = `\n\"import { createActionGroup, emptyProps, props } from '@ngrx/store';\nimport { Update } from '@ngrx/entity';\n\nimport { Foo } from '../models/foo.model';\n\nexport const FooActions = createActionGroup({\n  source: 'Foo/API',\n  events: {\n    'Load Foos': props<{ foos: Foo[] }>(),\n    'Add Foo': props<{ foo: Foo }>(),\n    'Upsert Foo': props<{ foo: Foo }>(),\n    'Add Foos': props<{ foos: Foo[] }>(),\n    'Upsert Foos': props<{ foos: Foo[] }>(),\n    'Update Foo': props<{ foo: Update<Foo> }>(),\n    'Update Foos': props<{ foos: Update<Foo>[] }>(),\n    'Delete Foo': props<{ id: string }>(),\n    'Delete Foos': props<{ ids: string[] }>(),\n    'Clear Foos': emptyProps(),\n  }\n});\n\"\n`;\n\nexports[`Entity Schematic should create all files of an entity within grouped folders if group is set 2`] = `\n\"export interface Foo {\n  id: string;\n}\n\"\n`;\n\nexports[`Entity Schematic should create all files of an entity within grouped folders if group is set 3`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';\nimport { Foo } from '../models/foo.model';\nimport { FooActions } from '../actions/foo.actions';\n\nexport const foosFeatureKey = 'foos';\n\nexport interface State extends EntityState<Foo> {\n  // additional entities state properties\n}\n\nexport const adapter: EntityAdapter<Foo> = createEntityAdapter<Foo>();\n\nexport const initialState: State = adapter.getInitialState({\n  // additional entity state properties\n});\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.addFoo,\n    (state, action) => adapter.addOne(action.foo, state)\n  ),\n  on(FooActions.upsertFoo,\n    (state, action) => adapter.upsertOne(action.foo, state)\n  ),\n  on(FooActions.addFoos,\n    (state, action) => adapter.addMany(action.foos, state)\n  ),\n  on(FooActions.upsertFoos,\n    (state, action) => adapter.upsertMany(action.foos, state)\n  ),\n  on(FooActions.updateFoo,\n    (state, action) => adapter.updateOne(action.foo, state)\n  ),\n  on(FooActions.updateFoos,\n    (state, action) => adapter.updateMany(action.foos, state)\n  ),\n  on(FooActions.deleteFoo,\n    (state, action) => adapter.removeOne(action.id, state)\n  ),\n  on(FooActions.deleteFoos,\n    (state, action) => adapter.removeMany(action.ids, state)\n  ),\n  on(FooActions.loadFoos,\n    (state, action) => adapter.setAll(action.foos, state)\n  ),\n  on(FooActions.clearFoos,\n    state => adapter.removeAll(state)\n  ),\n);\n\nexport const foosFeature = createFeature({\n  name: foosFeatureKey,\n  reducer,\n  extraSelectors: ({ selectFoosState }) => ({\n    ...adapter.getSelectors(selectFoosState)\n  }),\n});\n\nexport const {\n  selectIds,\n  selectEntities,\n  selectAll,\n  selectTotal,\n} = foosFeature;\n\"\n`;\n\nexports[`Entity Schematic should create all files of an entity within grouped folders if group is set 4`] = `\n\"import { reducer, initialState } from '../reducers/foo.reducer';\n\ndescribe('Foo Reducer', () => {\n  describe('unknown action', () => {\n    it('should return the previous state', () => {\n      const action = {} as any;\n\n      const result = reducer(initialState, action);\n\n      expect(result).toBe(initialState);\n    });\n  });\n});\n\"\n`;\n\nexports[`Entity Schematic should import into a specified module 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { StoreModule } from '@ngrx/store';\nimport * as fromFoo from './foo.reducer';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    StoreModule.forFeature(fromFoo.foosFeatureKey, fromFoo.reducer)\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Entity Schematic should update the state to plural 1`] = `\n\"import { createActionGroup, emptyProps, props } from '@ngrx/store';\nimport { Update } from '@ngrx/entity';\n\nimport { User } from './user.model';\n\nexport const UserActions = createActionGroup({\n  source: 'User/API',\n  events: {\n    'Load Users': props<{ users: User[] }>(),\n    'Add User': props<{ user: User }>(),\n    'Upsert User': props<{ user: User }>(),\n    'Add Users': props<{ users: User[] }>(),\n    'Upsert Users': props<{ users: User[] }>(),\n    'Update User': props<{ user: Update<User> }>(),\n    'Update Users': props<{ users: Update<User>[] }>(),\n    'Delete User': props<{ id: string }>(),\n    'Delete Users': props<{ ids: string[] }>(),\n    'Clear Users': emptyProps(),\n  }\n});\n\"\n`;\n\nexports[`Entity Schematic should update the state to plural 2`] = `\n\"export interface User {\n  id: string;\n}\n\"\n`;\n\nexports[`Entity Schematic should update the state to plural 3`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';\nimport { User } from './user.model';\nimport { UserActions } from './user.actions';\n\nexport const usersFeatureKey = 'users';\n\nexport interface State extends EntityState<User> {\n  // additional entities state properties\n}\n\nexport const adapter: EntityAdapter<User> = createEntityAdapter<User>();\n\nexport const initialState: State = adapter.getInitialState({\n  // additional entity state properties\n});\n\nexport const reducer = createReducer(\n  initialState,\n  on(UserActions.addUser,\n    (state, action) => adapter.addOne(action.user, state)\n  ),\n  on(UserActions.upsertUser,\n    (state, action) => adapter.upsertOne(action.user, state)\n  ),\n  on(UserActions.addUsers,\n    (state, action) => adapter.addMany(action.users, state)\n  ),\n  on(UserActions.upsertUsers,\n    (state, action) => adapter.upsertMany(action.users, state)\n  ),\n  on(UserActions.updateUser,\n    (state, action) => adapter.updateOne(action.user, state)\n  ),\n  on(UserActions.updateUsers,\n    (state, action) => adapter.updateMany(action.users, state)\n  ),\n  on(UserActions.deleteUser,\n    (state, action) => adapter.removeOne(action.id, state)\n  ),\n  on(UserActions.deleteUsers,\n    (state, action) => adapter.removeMany(action.ids, state)\n  ),\n  on(UserActions.loadUsers,\n    (state, action) => adapter.setAll(action.users, state)\n  ),\n  on(UserActions.clearUsers,\n    state => adapter.removeAll(state)\n  ),\n);\n\nexport const usersFeature = createFeature({\n  name: usersFeatureKey,\n  reducer,\n  extraSelectors: ({ selectUsersState }) => ({\n    ...adapter.getSelectors(selectUsersState)\n  }),\n});\n\nexport const {\n  selectIds,\n  selectEntities,\n  selectAll,\n  selectTotal,\n} = usersFeature;\n\"\n`;\n\nexports[`Entity Schematic should update the state to plural 4`] = `\n\"import { reducer, initialState } from './user.reducer';\n\ndescribe('User Reducer', () => {\n  describe('unknown action', () => {\n    it('should return the previous state', () => {\n      const action = {} as any;\n\n      const result = reducer(initialState, action);\n\n      expect(result).toBe(initialState);\n    });\n  });\n});\n\"\n`;\n\nexports[`Entity Schematic should update the state to plural 5`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\nimport * as fromUser from '../user.reducer';\n\nexport const userFeatureKey = 'user';\n\nexport interface State {\n\n  [fromUser.usersFeatureKey]: fromUser.State;\n}\n\nexport const reducers: ActionReducerMap<State> = {\n\n  [fromUser.usersFeatureKey]: fromUser.reducer,\n};\n\n\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/entity/files/__name@dasherize@if-flat__/__name@dasherize@group-actions__.actions.ts.template",
    "content": "import { createActionGroup, emptyProps, props } from '@ngrx/store';\nimport { Update } from '@ngrx/entity';\n\nimport { <%= classify(name) %> } from '<%= featurePath(group, flat, \"models\", dasherize(name)) %><%= dasherize(name) %>.model';\n\nexport const <%= classify(name) %>Actions = createActionGroup({\n  source: '<%= classify(name) %>/API',\n  events: {\n    'Load <%= classify(name) %>s': props<{ <%= camelize(name) %>s: <%= classify(name) %>[] }>(),\n    'Add <%= classify(name) %>': props<{ <%= camelize(name) %>: <%= classify(name) %> }>(),\n    'Upsert <%= classify(name) %>': props<{ <%= camelize(name) %>: <%= classify(name) %> }>(),\n    'Add <%= classify(name) %>s': props<{ <%= camelize(name) %>s: <%= classify(name) %>[] }>(),\n    'Upsert <%= classify(name) %>s': props<{ <%= camelize(name) %>s: <%= classify(name) %>[] }>(),\n    'Update <%= classify(name) %>': props<{ <%= camelize(name) %>: Update<<%= classify(name) %>> }>(),\n    'Update <%= classify(name) %>s': props<{ <%= camelize(name) %>s: Update<<%= classify(name) %>>[] }>(),\n    'Delete <%= classify(name) %>': props<{ id: string }>(),\n    'Delete <%= classify(name) %>s': props<{ ids: string[] }>(),\n    'Clear <%= classify(name) %>s': emptyProps(),\n  }\n});\n"
  },
  {
    "path": "modules/schematics/src/entity/files/__name@dasherize@if-flat__/__name@dasherize@group-models__.model.ts.template",
    "content": "export interface <%= classify(name) %> {\n  id: string;\n}\n"
  },
  {
    "path": "modules/schematics/src/entity/files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.spec.ts.template",
    "content": "import { reducer, initialState } from '<%= featurePath(group, flat, \"reducers\", dasherize(name)) %><%= dasherize(name) %>.reducer';\n\ndescribe('<%= classify(name) %> Reducer', () => {\n  describe('unknown action', () => {\n    it('should return the previous state', () => {\n      const action = {} as any;\n\n      const result = reducer(initialState, action);\n\n      expect(result).toBe(initialState);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/entity/files/__name@dasherize@if-flat__/__name@dasherize@group-reducers__.reducer.ts.template",
    "content": "import { createFeature, createReducer, on } from '@ngrx/store';\nimport { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';\nimport { <%= classify(name) %> } from '<%= featurePath(group, flat, \"models\", dasherize(name)) %><%= dasherize(name) %>.model';\nimport { <%= classify(name) %>Actions } from '<%= featurePath(group, flat, \"actions\", dasherize(name)) %><%= dasherize(name) %>.actions';\n\nexport const <%= pluralize(name) %>FeatureKey = '<%= pluralize(name) %>';\n\nexport interface State extends EntityState<<%= classify(name) %>> {\n  // additional entities state properties\n}\n\nexport const adapter: EntityAdapter<<%= classify(name) %>> = createEntityAdapter<<%= classify(name) %>>();\n\nexport const initialState: State = adapter.getInitialState({\n  // additional entity state properties\n});\n\nexport const reducer = createReducer(\n  initialState,\n  on(<%= classify(name) %>Actions.add<%= classify(name) %>,\n    (state, action) => adapter.addOne(action.<%= camelize(name) %>, state)\n  ),\n  on(<%= classify(name) %>Actions.upsert<%= classify(name) %>,\n    (state, action) => adapter.upsertOne(action.<%= camelize(name) %>, state)\n  ),\n  on(<%= classify(name) %>Actions.add<%= classify(name) %>s,\n    (state, action) => adapter.addMany(action.<%= camelize(name) %>s, state)\n  ),\n  on(<%= classify(name) %>Actions.upsert<%= classify(name) %>s,\n    (state, action) => adapter.upsertMany(action.<%= camelize(name) %>s, state)\n  ),\n  on(<%= classify(name) %>Actions.update<%= classify(name) %>,\n    (state, action) => adapter.updateOne(action.<%= camelize(name) %>, state)\n  ),\n  on(<%= classify(name) %>Actions.update<%= classify(name) %>s,\n    (state, action) => adapter.updateMany(action.<%= camelize(name) %>s, state)\n  ),\n  on(<%= classify(name) %>Actions.delete<%= classify(name) %>,\n    (state, action) => adapter.removeOne(action.id, state)\n  ),\n  on(<%= classify(name) %>Actions.delete<%= classify(name) %>s,\n    (state, action) => adapter.removeMany(action.ids, state)\n  ),\n  on(<%= classify(name) %>Actions.load<%= classify(name) %>s,\n    (state, action) => adapter.setAll(action.<%= camelize(name) %>s, state)\n  ),\n  on(<%= classify(name) %>Actions.clear<%= classify(name) %>s,\n    state => adapter.removeAll(state)\n  ),\n);\n\nexport const <%= pluralize(name) %>Feature = createFeature({\n  name: <%= pluralize(name) %>FeatureKey,\n  reducer,\n  extraSelectors: ({ select<%= capitalize(pluralize(name)) %>State }) => ({\n    ...adapter.getSelectors(select<%= capitalize(pluralize(name)) %>State)\n  }),\n});\n\nexport const {\n  selectIds,\n  selectEntities,\n  selectAll,\n  selectTotal,\n} = <%= pluralize(name) %>Feature;\n"
  },
  {
    "path": "modules/schematics/src/entity/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as EntityOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n  defaultWorkspaceOptions,\n  defaultAppOptions,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Entity Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n  const defaultOptions: EntityOptions = {\n    name: 'foo',\n    project: 'bar',\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should create 3 files', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'entity',\n      defaultOptions,\n      appTree\n    );\n\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.actions.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.model.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n\n    expect(\n      tree.readContent(`${projectPath}/src/app/foo.actions.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/foo.model.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/foo.reducer.ts`)\n    ).toMatchSnapshot();\n  });\n\n  it('should create 3 files of an entity to specified project if provided', async () => {\n    const options = {\n      ...defaultOptions,\n      project: 'baz',\n    };\n\n    const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {\n      ...defaultAppOptions,\n      name: 'baz',\n    });\n\n    const tree = await schematicRunner.runSchematic('entity', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo.actions.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo.model.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n\n    expect(\n      tree.readContent(`${projectPath}/src/lib/foo.actions.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/lib/foo.model.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/lib/foo.reducer.ts`)\n    ).toMatchSnapshot();\n  });\n\n  it('should create a folder if flat is false', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'entity',\n      {\n        ...defaultOptions,\n        flat: false,\n      },\n      appTree\n    );\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo/foo.actions.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo/foo.model.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create 4 files if spec is true', async () => {\n    const options = {\n      ...defaultOptions,\n    };\n    const tree = await schematicRunner.runSchematic('entity', options, appTree);\n\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.actions.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.model.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.reducer.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should import into a specified module', async () => {\n    const options = { ...defaultOptions, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic('entity', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n\n    expect(content).toMatchSnapshot();\n  });\n  it('should create all files of an entity within grouped and nested folders', async () => {\n    const options = { ...defaultOptions, flat: false, group: true };\n\n    const tree = await schematicRunner.runSchematic('entity', options, appTree);\n    const files = tree.files;\n\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/actions/foo.actions.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/models/foo.model.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/reducers/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/foo/reducers/foo.reducer.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n\n    expect(\n      tree.readContent(`${projectPath}/src/app/foo/actions/foo.actions.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/foo/models/foo.model.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/foo/reducers/foo.reducer.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(\n        `${projectPath}/src/app/foo/reducers/foo.reducer.spec.ts`\n      )\n    ).toMatchSnapshot();\n  });\n\n  it('should create all files of an entity within grouped folders if group is set', async () => {\n    const options = { ...defaultOptions, group: true };\n\n    const tree = await schematicRunner.runSchematic('entity', options, appTree);\n    const files = tree.files;\n\n    expect(\n      files.indexOf(`${projectPath}/src/app/actions/foo.actions.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/models/foo.model.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/reducers/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/reducers/foo.reducer.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n\n    expect(\n      tree.readContent(`${projectPath}/src/app/actions/foo.actions.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/models/foo.model.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/reducers/foo.reducer.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/reducers/foo.reducer.spec.ts`)\n    ).toMatchSnapshot();\n  });\n\n  it('should update the state to plural', async () => {\n    const options = {\n      ...defaultOptions,\n      name: 'user',\n      reducers: 'reducers/index.ts',\n    };\n\n    const _reducerTree = await schematicRunner.runSchematic(\n      'store',\n      options,\n      appTree\n    );\n    const tree = await schematicRunner.runSchematic('entity', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/user.actions.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/user.model.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/user.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      files.indexOf(`${projectPath}/src/app/user.reducer.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n\n    expect(\n      tree.readContent(`${projectPath}/src/app/user.actions.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/user.model.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/user.reducer.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/user.reducer.spec.ts`)\n    ).toMatchSnapshot();\n    expect(\n      tree.readContent(`${projectPath}/src/app/reducers/index.ts`)\n    ).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/entity/index.ts",
    "content": "import {\n  Rule,\n  SchematicsException,\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  filter,\n  mergeWith,\n  move,\n  noop,\n  template,\n  url,\n  Tree,\n  SchematicContext,\n} from '@angular-devkit/schematics';\nimport {\n  stringUtils,\n  addReducerToState,\n  addReducerImportToNgModule,\n  getProjectPath,\n  findModuleFromOptions,\n  parseName,\n  getProject,\n} from '../../schematics-core';\nimport { Schema as EntityOptions } from './schema';\n\nexport default function (options: EntityOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    const projectConfig = getProject(host, options);\n    options.path = getProjectPath(host, options);\n\n    const parsedPath = parseName(options.path, options.name);\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n\n    if (options.module) {\n      options.module = findModuleFromOptions(host, options);\n    }\n\n    const templateOptions = {\n      ...stringUtils,\n      'if-flat': (s: string) => (options.flat ? '' : s),\n      'group-actions': (name: string) =>\n        stringUtils.group(name, options.group ? 'actions' : ''),\n      'group-models': (name: string) =>\n        stringUtils.group(name, options.group ? 'models' : ''),\n      'group-reducers': (s: string) =>\n        stringUtils.group(s, options.group ? 'reducers' : ''),\n      ...(options as object),\n    };\n\n    const templateSource = apply(url('./files'), [\n      options.skipTests\n        ? filter((path) => !path.endsWith('.spec.ts.template'))\n        : noop(),\n      applyTemplates(templateOptions),\n      move(parsedPath.path),\n    ]);\n\n    return chain([\n      addReducerToState({ ...options, plural: true }),\n      addReducerImportToNgModule({ ...options, plural: true }),\n      branchAndMerge(mergeWith(templateSource)),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/entity/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxEntity\",\n  \"title\": \"NgRx Entity Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the entity.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What should be the name of the entity?\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the component.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"description\": \"When true, does not create test files.\",\n      \"default\": false\n    },\n    \"reducers\": {\n      \"type\": \"string\",\n      \"description\": \"Specifies the reducers file.\",\n      \"aliases\": [\"r\"]\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"description\": \"Specifies the declaring module.\",\n      \"aliases\": [\"m\"]\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"group\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Group actions, reducers and effects within relative subfolders\",\n      \"aliases\": [\"g\"]\n    },\n    \"feature\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Flag to indicate if part of a feature schematic.\",\n      \"visible\": false\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/entity/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The name of the component.\n   */\n\n  name: string;\n  /**\n   * The path to create the effect.\n   */\n\n  path?: string;\n  /**\n   * The name of the project.\n   */\n  project?: string;\n  /**\n   * Flag to indicate if a dir is created.\n   */\n  flat?: boolean;\n  /**\n   * When true, does not create test files.\n   */\n  skipTests?: boolean;\n  /**\n   * Allows specification of the declaring module.\n   */\n\n  module?: string;\n  /**\n   * Allows specification of the declaring reducers.\n   */\n\n  reducers?: string;\n  /**\n   * Specifies if this is grouped within sub folders\n   */\n\n  group?: boolean;\n\n  /**\n   * Specifies if this is grouped within a feature\n   */\n  feature?: boolean;\n}\n"
  },
  {
    "path": "modules/schematics/src/feature/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Feature Schematic should create all files of a feature with an entity 1`] = `\n\"import { createActionGroup, emptyProps, props } from '@ngrx/store';\nimport { Update } from '@ngrx/entity';\n\nimport { Foo } from './foo.model';\n\nexport const FooActions = createActionGroup({\n  source: 'Foo/API',\n  events: {\n    'Load Foos': props<{ foos: Foo[] }>(),\n    'Add Foo': props<{ foo: Foo }>(),\n    'Upsert Foo': props<{ foo: Foo }>(),\n    'Add Foos': props<{ foos: Foo[] }>(),\n    'Upsert Foos': props<{ foos: Foo[] }>(),\n    'Update Foo': props<{ foo: Update<Foo> }>(),\n    'Update Foos': props<{ foos: Update<Foo>[] }>(),\n    'Delete Foo': props<{ id: string }>(),\n    'Delete Foos': props<{ ids: string[] }>(),\n    'Clear Foos': emptyProps(),\n  }\n});\n\"\n`;\n\nexports[`Feature Schematic should create all files of a feature with an entity 2`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';\nimport { Foo } from './foo.model';\nimport { FooActions } from './foo.actions';\n\nexport const foosFeatureKey = 'foos';\n\nexport interface State extends EntityState<Foo> {\n  // additional entities state properties\n}\n\nexport const adapter: EntityAdapter<Foo> = createEntityAdapter<Foo>();\n\nexport const initialState: State = adapter.getInitialState({\n  // additional entity state properties\n});\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.addFoo,\n    (state, action) => adapter.addOne(action.foo, state)\n  ),\n  on(FooActions.upsertFoo,\n    (state, action) => adapter.upsertOne(action.foo, state)\n  ),\n  on(FooActions.addFoos,\n    (state, action) => adapter.addMany(action.foos, state)\n  ),\n  on(FooActions.upsertFoos,\n    (state, action) => adapter.upsertMany(action.foos, state)\n  ),\n  on(FooActions.updateFoo,\n    (state, action) => adapter.updateOne(action.foo, state)\n  ),\n  on(FooActions.updateFoos,\n    (state, action) => adapter.updateMany(action.foos, state)\n  ),\n  on(FooActions.deleteFoo,\n    (state, action) => adapter.removeOne(action.id, state)\n  ),\n  on(FooActions.deleteFoos,\n    (state, action) => adapter.removeMany(action.ids, state)\n  ),\n  on(FooActions.loadFoos,\n    (state, action) => adapter.setAll(action.foos, state)\n  ),\n  on(FooActions.clearFoos,\n    state => adapter.removeAll(state)\n  ),\n);\n\nexport const foosFeature = createFeature({\n  name: foosFeatureKey,\n  reducer,\n  extraSelectors: ({ selectFoosState }) => ({\n    ...adapter.getSelectors(selectFoosState)\n  }),\n});\n\nexport const {\n  selectIds,\n  selectEntities,\n  selectAll,\n  selectTotal,\n} = foosFeature;\n\"\n`;\n\nexports[`Feature Schematic should create all files of a feature with an entity 3`] = `\n\"import { reducer, initialState } from './foo.reducer';\n\ndescribe('Foo Reducer', () => {\n  describe('unknown action', () => {\n    it('should return the previous state', () => {\n      const action = {} as any;\n\n      const result = reducer(initialState, action);\n\n      expect(result).toBe(initialState);\n    });\n  });\n});\n\"\n`;\n\nexports[`Feature Schematic should create all files of a feature with an entity 4`] = `\n\"import { Injectable } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\n\nimport { concatMap } from 'rxjs/operators';\nimport { Observable, EMPTY } from 'rxjs';\nimport { FooActions } from './foo.actions';\n\n@Injectable()\nexport class FooEffects {\n\n\n  loadFoos$ = createEffect(() => {\n    return this.actions$.pipe(\n\n      ofType(FooActions.loadFoos),\n      /** An EMPTY observable only emits completion. Replace with your own observable API request */\n      concatMap(() => EMPTY as Observable<{ type: string }>)\n    );\n  });\n\n  constructor(private actions$: Actions) {}\n}\n\"\n`;\n\nexports[`Feature Schematic should create all files of a feature with an entity 5`] = `\n\"import { TestBed } from '@angular/core/testing';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport { Observable } from 'rxjs';\n\nimport { FooEffects } from './foo.effects';\n\ndescribe('FooEffects', () => {\n  let actions$: Observable<any>;\n  let effects: FooEffects;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        FooEffects,\n        provideMockActions(() => actions$)\n      ]\n    });\n\n    effects = TestBed.inject(FooEffects);\n  });\n\n  it('should be created', () => {\n    expect(effects).toBeTruthy();\n  });\n});\n\"\n`;\n\nexports[`Feature Schematic should create all files of a feature with an entity 6`] = `\n\"export interface Foo {\n  id: string;\n}\n\"\n`;\n\nexports[`Feature Schematic should have all api actions in reducer if api flag enabled 1`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { FooActions } from './foo.actions';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const initialState: State = {\n\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.loadFoos, state => state),\n  on(FooActions.loadFoosSuccess, (state, action) => state),\n  on(FooActions.loadFoosFailure, (state, action) => state),\n);\n\nexport const fooFeature = createFeature({\n  name: fooFeatureKey,\n  reducer,\n});\n\n\"\n`;\n\nexports[`Feature Schematic should have all api actions with prefix in reducer if api flag enabled 1`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { FooActions } from './foo.actions';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const initialState: State = {\n\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.customFoos, state => state),\n  on(FooActions.customFoosSuccess, (state, action) => state),\n  on(FooActions.customFoosFailure, (state, action) => state),\n);\n\nexport const fooFeature = createFeature({\n  name: fooFeatureKey,\n  reducer,\n});\n\n\"\n`;\n\nexports[`Feature Schematic should have all api effect if api flag enabled 1`] = `\n\"import { Injectable } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { catchError, map, concatMap } from 'rxjs/operators';\nimport { Observable, EMPTY, of } from 'rxjs';\nimport { FooActions } from './foo.actions';\n\n\n@Injectable()\nexport class FooEffects {\n\n  loadFoos$ = createEffect(() => {\n    return this.actions$.pipe(\n\n      ofType(FooActions.loadFoos),\n      concatMap(() =>\n        /** An EMPTY observable only emits completion. Replace with your own observable API request */\n        EMPTY.pipe(\n          map(data => FooActions.loadFoosSuccess({ data })),\n          catchError(error => of(FooActions.loadFoosFailure({ error }))))\n      )\n    );\n  });\n\n\n  constructor(private actions$: Actions) {}\n}\n\"\n`;\n\nexports[`Feature Schematic should have all api effect with prefix if api flag enabled 1`] = `\n\"import { Injectable } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { catchError, map, concatMap } from 'rxjs/operators';\nimport { Observable, EMPTY, of } from 'rxjs';\nimport { FooActions } from './foo.actions';\n\n\n@Injectable()\nexport class FooEffects {\n\n  customFoos$ = createEffect(() => {\n    return this.actions$.pipe(\n\n      ofType(FooActions.customFoos),\n      concatMap(() =>\n        /** An EMPTY observable only emits completion. Replace with your own observable API request */\n        EMPTY.pipe(\n          map(data => FooActions.customFoosSuccess({ data })),\n          catchError(error => of(FooActions.customFoosFailure({ error }))))\n      )\n    );\n  });\n\n\n  constructor(private actions$: Actions) {}\n}\n\"\n`;\n\nexports[`Feature Schematic should respect the path provided for the feature name 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/feature/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as FeatureOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n  defaultWorkspaceOptions,\n  defaultAppOptions,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Feature Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n  const defaultOptions: FeatureOptions = {\n    name: 'foo',\n    project: 'bar',\n    module: '',\n    group: false,\n    entity: false,\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should create all files of a feature', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const files = tree.files;\n    expect(\n      files.includes(`${projectPath}/src/app/foo.actions.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.actions.spec.ts`)\n    ).toBeFalsy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.reducer.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.reducer.spec.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.effects.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.effects.spec.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.selectors.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.selectors.spec.ts`)\n    ).toBeTruthy();\n    expect(files.includes(`${projectPath}/src/app/foo.model.ts`)).toBeFalsy();\n  });\n\n  it('should not create test files when skipTests is true', async () => {\n    const options = { ...defaultOptions, skipTests: true };\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const files = tree.files;\n    expect(\n      files.includes(`${projectPath}/src/app/foo.actions.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.actions.spec.ts`)\n    ).toBeFalsy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.reducer.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.reducer.spec.ts`)\n    ).toBeFalsy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.effects.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.effects.spec.ts`)\n    ).toBeFalsy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.selectors.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/foo.selectors.spec.ts`)\n    ).toBeFalsy();\n  });\n\n  it('should create all files of a feature to specified project if provided', async () => {\n    const options = {\n      ...defaultOptions,\n      project: 'baz',\n    };\n\n    const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {\n      ...defaultAppOptions,\n      name: 'baz',\n    });\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const files = tree.files;\n    expect(\n      files.includes(`${specifiedProjectPath}/src/lib/foo.actions.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${specifiedProjectPath}/src/lib/foo.actions.spec.ts`)\n    ).toBeFalsy();\n    expect(\n      files.includes(`${specifiedProjectPath}/src/lib/foo.reducer.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${specifiedProjectPath}/src/lib/foo.reducer.spec.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${specifiedProjectPath}/src/lib/foo.effects.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${specifiedProjectPath}/src/lib/foo.effects.spec.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${specifiedProjectPath}/src/lib/foo.selectors.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${specifiedProjectPath}/src/lib/foo.selectors.spec.ts`)\n    ).toBeTruthy();\n  });\n\n  it('should create all files of a feature within grouped folders if group is set', async () => {\n    const options = { ...defaultOptions, group: true };\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const files = tree.files;\n    expect(\n      files.includes(`${projectPath}/src/app/actions/foo.actions.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/reducers/foo.reducer.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/reducers/foo.reducer.spec.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/effects/foo.effects.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/effects/foo.effects.spec.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/selectors/foo.selectors.ts`)\n    ).toBeTruthy();\n    expect(\n      files.includes(`${projectPath}/src/app/selectors/foo.selectors.spec.ts`)\n    ).toBeTruthy();\n  });\n\n  it('should respect the path provided for the feature name', async () => {\n    const options = {\n      ...defaultOptions,\n      name: 'foo/Foo',\n      group: true,\n      module: '../app',\n    };\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const moduleFileContent = tree.readContent(\n      `${projectPath}/src/app/app-module.ts`\n    );\n\n    expect(moduleFileContent).toMatchSnapshot();\n  });\n\n  it('should have all api effect if api flag enabled', async () => {\n    const options = {\n      ...defaultOptions,\n      api: true,\n    };\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.effects.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should have all api actions in reducer if api flag enabled', async () => {\n    const options = {\n      ...defaultOptions,\n      api: true,\n    };\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.reducer.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should have all api effect with prefix if api flag enabled', async () => {\n    const options = {\n      ...defaultOptions,\n      api: true,\n      prefix: 'custom',\n    };\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.effects.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should have all api actions with prefix in reducer if api flag enabled', async () => {\n    const options = {\n      ...defaultOptions,\n      api: true,\n      prefix: 'custom',\n    };\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.reducer.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should create all files of a feature with an entity', async () => {\n    const options = { ...defaultOptions, entity: true };\n\n    const tree = await schematicRunner.runSchematic(\n      'feature',\n      options,\n      appTree\n    );\n    const paths = [\n      `${projectPath}/src/app/foo.actions.ts`,\n      `${projectPath}/src/app/foo.reducer.ts`,\n      `${projectPath}/src/app/foo.reducer.spec.ts`,\n      `${projectPath}/src/app/foo.effects.ts`,\n      `${projectPath}/src/app/foo.effects.spec.ts`,\n      `${projectPath}/src/app/foo.model.ts`,\n    ];\n\n    paths.forEach((path) => {\n      expect(tree.files.includes(path)).toBeTruthy();\n      expect(tree.readContent(path)).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/feature/index.ts",
    "content": "import {\n  chain,\n  Rule,\n  schematic,\n  SchematicContext,\n  Tree,\n} from '@angular-devkit/schematics';\n\nimport { Schema as FeatureOptions } from './schema';\n\nexport default function (options: FeatureOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    return chain(\n      (options.entity\n        ? [\n            schematic('entity', {\n              name: options.name,\n              path: options.path,\n              project: options.project,\n              flat: options.flat,\n              skipTests: options.skipTests,\n              module: options.module,\n              reducers: options.reducers,\n              group: options.group,\n              feature: true,\n            }),\n          ]\n        : [\n            schematic('action', {\n              flat: options.flat,\n              group: options.group,\n              name: options.name,\n              path: options.path,\n              project: options.project,\n              skipTests: options.skipTests,\n              api: options.api,\n              prefix: options.prefix,\n            }),\n            schematic('reducer', {\n              flat: options.flat,\n              group: options.group,\n              module: options.module,\n              name: options.name,\n              path: options.path,\n              project: options.project,\n              skipTests: options.skipTests,\n              reducers: options.reducers,\n              feature: true,\n              api: options.api,\n              prefix: options.prefix,\n            }),\n            schematic('selector', {\n              flat: options.flat,\n              group: options.group,\n              name: options.name,\n              path: options.path,\n              project: options.project,\n              skipTests: options.skipTests,\n              feature: true,\n            }),\n          ]\n      ).concat([\n        schematic('effect', {\n          flat: options.flat,\n          group: options.group,\n          module: options.module,\n          name: options.name,\n          path: options.path,\n          project: options.project,\n          skipTests: options.skipTests,\n          feature: true,\n          api: options.api,\n          prefix: options.prefix,\n        }),\n      ])\n    )(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/feature/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxFeature\",\n  \"title\": \"NgRx Feature State Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the feature.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"name\": {\n      \"description\": \"The name of the feature.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What should be the name of the feature?\"\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"description\": \"Specifies the declaring module.\",\n      \"aliases\": [\"m\"]\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"When true, does not create test files.\"\n    },\n    \"reducers\": {\n      \"type\": \"string\",\n      \"description\": \"Specifies the reducers file.\",\n      \"aliases\": [\"r\"]\n    },\n    \"group\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Group actions, reducers and effects within relative subfolders\",\n      \"aliases\": [\"g\"]\n    },\n    \"api\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Specifies if api success and failure actions, reducer, and effects should be generated as part of this feature.\",\n      \"aliases\": [\"a\"],\n      \"x-prompt\": \"Should we generate and wire success and failure actions?\"\n    },\n    \"prefix\": {\n      \"description\": \"The prefix of the action, effect and reducer.\",\n      \"type\": \"string\",\n      \"default\": \"load\",\n      \"x-prompt\": \"What should be the prefix of the action, effect and reducer?\"\n    },\n    \"entity\": {\n      \"description\": \"Toggle whether an entity is created as part of the feature\",\n      \"type\": \"boolean\",\n      \"aliases\": [\"e\"],\n      \"x-prompt\": \"Should we use @ngrx/entity to create the reducer?\",\n      \"default\": false\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/feature/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The name of the feature.\n   */\n  name: string;\n\n  /**\n   * The path to create the feature.\n   */\n  path?: string;\n\n  /**\n   * The name of the project.\n   */\n  project?: string;\n\n  /**\n   * Flag to indicate if a dir is created.\n   */\n  flat?: boolean;\n\n  /**\n   * When true, does not create test files.\n   */\n  skipTests?: boolean;\n\n  /**\n   * Allows specification of the declaring module.\n   */\n  module?: string;\n\n  /**\n   * Allows specification of the declaring reducers.\n   */\n  reducers?: string;\n\n  /**\n   * Specifies if this is grouped within sub folders\n   */\n  group?: boolean;\n\n  /**\n   * Specifies if api success and failure actions, reducer, and effects\n   * should be generated as part of this feature.\n   */\n  api?: boolean;\n\n  prefix?: string;\n\n  entity?: boolean;\n}\n"
  },
  {
    "path": "modules/schematics/src/index.ts",
    "content": ""
  },
  {
    "path": "modules/schematics/src/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n\n  const defaultWorkspace = {\n    version: 1,\n    projects: {},\n  };\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should add @ngrx/schematics into schematicCollections ', async () => {\n    appTree.overwrite(\n      '/angular.json',\n      JSON.stringify(\n        {\n          ...defaultWorkspace,\n          cli: { schematicCollections: ['existingCollection'] },\n        },\n        undefined,\n        2\n      )\n    );\n\n    const tree = await schematicRunner.runSchematic('ng-add', {}, appTree);\n    const workspace = JSON.parse(tree.readContent('/angular.json'));\n    expect(workspace.cli.schematicCollections).toEqual([\n      'existingCollection',\n      '@ngrx/schematics',\n    ]);\n  });\n\n  it('should create schematicCollections is not defined', async () => {\n    appTree.overwrite(\n      '/angular.json',\n      JSON.stringify(defaultWorkspace, undefined, 2)\n    );\n\n    const tree = await schematicRunner.runSchematic('ng-add', {}, appTree);\n    const workspace = JSON.parse(tree.readContent('/angular.json'));\n    expect(workspace.cli.schematicCollections).toEqual(['@ngrx/schematics']);\n  });\n\n  it('should create schematicCollections is not defined using the original defaultCollection ', async () => {\n    appTree.overwrite(\n      '/angular.json',\n      JSON.stringify(\n        {\n          ...defaultWorkspace,\n          cli: { defaultCollection: 'existingCollection' },\n        },\n        undefined,\n        2\n      )\n    );\n\n    const tree = await schematicRunner.runSchematic('ng-add', {}, appTree);\n    const workspace = JSON.parse(tree.readContent('/angular.json'));\n    expect(workspace.cli.schematicCollections).toEqual([\n      'existingCollection',\n      '@ngrx/schematics',\n    ]);\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/ng-add/index.ts",
    "content": "import {\n  chain,\n  Rule,\n  SchematicContext,\n  Tree,\n} from '@angular-devkit/schematics';\nimport { getWorkspace, getWorkspacePath } from '../../schematics-core';\n\nfunction updateSchematicCollections(host: Tree) {\n  const workspace = getWorkspace(host);\n  const path = getWorkspacePath(host);\n\n  workspace.cli = workspace.cli || {};\n  workspace.cli.schematicCollections = workspace.cli.schematicCollections || [];\n  if (workspace.cli.defaultCollection) {\n    workspace.cli.schematicCollections.push(workspace.cli.defaultCollection);\n    delete workspace.cli.defaultCollection;\n  }\n  workspace.cli.schematicCollections.push('@ngrx/schematics');\n\n  host.overwrite(path, JSON.stringify(workspace, null, 2));\n}\n\nfunction updateWorkspaceCli() {\n  return (host: Tree) => {\n    updateSchematicCollections(host);\n    return host;\n  };\n}\n\nexport default function (): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    return chain([updateWorkspaceCli()])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxSchematics\",\n  \"title\": \"Scaffolding library for Angular applications using NgRx libraries\",\n  \"type\": \"object\",\n  \"properties\": {},\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/ng-add/schema.ts",
    "content": "// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface Schema {}\n"
  },
  {
    "path": "modules/schematics/src/ngrx-push-migration/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('NgrxPush migration', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  describe('migrateToNgrxPush', () => {\n    const TEMPLATE = `\n    <span>promise|async</span> <!-- this will also get replaced -->\n    <span>One whitespace {{ greeting | async }}</span>\n    <span>No whitespace {{ greeting |async }}</span>\n    <span>Multiple whitespace {{ greeting |      async }}</span>\n  `;\n\n    it('should replace an inline template', async () => {\n      appTree.create(\n        './sut.component.ts',\n        `@Component({\n        selector: 'sut',\n        template: \\`${TEMPLATE}\\`\n      })\n      export class SUTComponent { }`\n      );\n\n      const tree = await schematicRunner.runSchematic(\n        'ngrx-push-migration',\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent('./sut.component.ts');\n      expect(actual).not.toContain('async');\n      expect(actual).toContain('ngrxPush');\n    });\n\n    it('should replace a file template', async () => {\n      appTree.create(\n        './sut.component.ts',\n        `@Component({\n        selector: 'sut',\n        templateUrl: './sut.component.html'\n      })\n      export class SUTComponent { }`\n      );\n      appTree.create('./sut.component.html', TEMPLATE);\n\n      const tree = await schematicRunner.runSchematic(\n        'ngrx-push-migration',\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent('./sut.component.html');\n      expect(actual).not.toContain('async');\n      expect(actual).toContain('ngrxPush');\n    });\n\n    it('should not touch templates that are not referenced', async () => {\n      appTree.create('./sut.component.html', TEMPLATE);\n\n      const tree = await schematicRunner.runSchematic(\n        'ngrx-push-migration',\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent('./sut.component.html');\n      expect(actual).toBe(TEMPLATE);\n    });\n  });\n\n  describe('importPushModule', () => {\n    it('should import PushModule when BrowserModule is imported', async () => {\n      appTree.create(\n        './sut.module.ts',\n        `\n          import { BrowserModule } from '@angular/platform-browser';\n          import { NgModule } from '@angular/core';\n\n          import { AppComponent } from './app.component';\n\n          @NgModule({\n            declarations: [ AppComponent ],\n            imports: [ BrowserModule ],\n            providers: [],\n            bootstrap: [AppComponent]\n          })\n          export class AppModule { }\n      `\n      );\n      const tree = await schematicRunner.runSchematic(\n        'ngrx-push-migration',\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent('./sut.module.ts');\n      expect(actual).toMatch(/imports: \\[ BrowserModule, PushModule \\],/);\n      expect(actual).toMatch(/import { PushModule } from '@ngrx\\/component'/);\n    });\n\n    it('should import PushModule when CommonModule is imported', async () => {\n      appTree.create(\n        './sut.module.ts',\n        `\n          import { CommonModule } from '@angular/common';\n          import { NgModule } from '@angular/core';\n\n          import { AppComponent } from './app.component';\n\n          @NgModule({\n            declarations: [ AppComponent ],\n            imports: [ CommonModule ],\n            providers: [],\n            bootstrap: [AppComponent]\n          })\n          export class AppModule { }\n      `\n      );\n      const tree = await schematicRunner.runSchematic(\n        'ngrx-push-migration',\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent('./sut.module.ts');\n      expect(actual).toMatch(/imports: \\[ CommonModule, PushModule \\],/);\n      expect(actual).toMatch(/import { PushModule } from '@ngrx\\/component'/);\n    });\n\n    it(\"should not import PushModule when it doesn't need to\", async () => {\n      appTree.create(\n        './sut.module.ts',\n        `\n          import { AppComponent } from './app.component';\n\n          @NgModule({\n            declarations: [ AppComponent ],\n            imports: [],\n            providers: [],\n          })\n          export class AppModule { }\n      `\n      );\n      const tree = await schematicRunner.runSchematic(\n        'ngrx-push-migration',\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent('./sut.module.ts');\n      expect(actual).not.toMatch(/imports: \\[ CommonModule, PushModule \\],/);\n      expect(actual).not.toMatch(\n        /import { PushModule } from '@ngrx\\/component'/\n      );\n    });\n  });\n\n  describe('exportPushModule', () => {\n    it('should export PushModule when BrowserModule is exported', async () => {\n      appTree.create(\n        './sut.module.ts',\n        `\n          import { BrowserModule } from '@angular/platform-browser';\n          import { NgModule } from '@angular/core';\n\n          import { AppComponent } from './app.component';\n\n          @NgModule({\n            declarations: [ AppComponent ],\n            exports: [ BrowserModule ],\n            providers: [],\n            bootstrap: [AppComponent]\n          })\n          export class AppModule { }\n      `\n      );\n      const tree = await schematicRunner.runSchematic(\n        'ngrx-push-migration',\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent('./sut.module.ts');\n      expect(actual).toMatch(/exports: \\[ BrowserModule, PushModule \\],/);\n      expect(actual).toMatch(/import { PushModule } from '@ngrx\\/component'/);\n    });\n\n    it('should export PushModule when CommonModule is exported', async () => {\n      appTree.create(\n        './sut.module.ts',\n        `\n          import { CommonModule } from '@angular/common';\n          import { NgModule } from '@angular/core';\n\n          import { AppComponent } from './app.component';\n\n          @NgModule({\n            declarations: [ AppComponent ],\n            exports: [ CommonModule ],\n            providers: [],\n            bootstrap: [AppComponent]\n          })\n          export class AppModule { }\n      `\n      );\n      const tree = await schematicRunner.runSchematic(\n        'ngrx-push-migration',\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent('./sut.module.ts');\n      expect(actual).toMatch(/exports: \\[ CommonModule, PushModule \\],/);\n      expect(actual).toMatch(/import { PushModule } from '@ngrx\\/component'/);\n    });\n\n    it(\"should not export PushModule when it doesn't need to\", async () => {\n      appTree.create(\n        './sut.module.ts',\n        `\n          import { AppComponent } from './app.component';\n\n          @NgModule({\n            declarations: [ AppComponent ],\n            exports: [],\n            providers: [],\n          })\n          export class AppModule { }\n      `\n      );\n      const tree = await schematicRunner.runSchematic(\n        'ngrx-push-migration',\n        {},\n        appTree\n      );\n\n      const actual = tree.readContent('./sut.module.ts');\n      expect(actual).not.toMatch(/exports: \\[ CommonModule, PushModule \\],/);\n      expect(actual).not.toMatch(\n        /import { PushModule } from '@ngrx\\/component'/\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/ngrx-push-migration/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, Rule, chain } from '@angular-devkit/schematics';\nimport {\n  commitChanges,\n  visitTemplates,\n  ReplaceChange,\n  Change,\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  addImportToModule,\n  addExportToModule,\n} from '../../schematics-core';\n\nconst ASYNC_REGEXP = /\\| {0,}async/g;\nconst PUSH_MODULE = 'PushModule';\nconst COMPONENT_MODULE = '@ngrx/component';\n\nconst pushModuleToFind = (node: ts.Node) =>\n  ts.isIdentifier(node) && node.text === PUSH_MODULE;\n\nconst ngModulesToFind = (node: ts.Node) =>\n  ts.isIdentifier(node) &&\n  (node.text === 'CommonModule' || node.text === 'BrowserModule');\n\nexport function migrateToNgrxPush(): Rule {\n  return (host: Tree) =>\n    visitTemplates(host, (template) => {\n      let match: RegExpMatchArray | null;\n      const changes: Change[] = [];\n      while ((match = ASYNC_REGEXP.exec(template.content)) !== null) {\n        const m = match.toString();\n\n        changes.push(\n          new ReplaceChange(\n            template.fileName,\n            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n            template.start + match.index!,\n            m,\n            m.replace('async', 'ngrxPush')\n          )\n        );\n      }\n\n      return commitChanges(host, template.fileName, changes);\n    });\n}\n\nexport function importPushModule(): Rule {\n  return (host: Tree) => {\n    visitTSSourceFiles(host, (sourceFile) => {\n      let hasCommonModuleOrBrowserModule = false;\n      let hasPushModule = false;\n\n      visitNgModuleImports(sourceFile, (_, importNodes) => {\n        hasCommonModuleOrBrowserModule = importNodes.some(ngModulesToFind);\n        hasPushModule = importNodes.some(pushModuleToFind);\n      });\n\n      if (hasCommonModuleOrBrowserModule && !hasPushModule) {\n        const changes: Change[] = addImportToModule(\n          sourceFile,\n          sourceFile.fileName,\n          PUSH_MODULE,\n          COMPONENT_MODULE\n        );\n        commitChanges(host, sourceFile.fileName, changes);\n      }\n    });\n  };\n}\n\nexport function exportPushModule(): Rule {\n  return (host: Tree) => {\n    visitTSSourceFiles(host, (sourceFile) => {\n      let hasCommonModuleOrBrowserModule = false;\n      let hasPushModule = false;\n\n      visitNgModuleExports(sourceFile, (_, exportNodes) => {\n        hasCommonModuleOrBrowserModule = exportNodes.some(ngModulesToFind);\n        hasPushModule = exportNodes.some(pushModuleToFind);\n      });\n\n      if (hasCommonModuleOrBrowserModule && !hasPushModule) {\n        const changes: Change[] = addExportToModule(\n          sourceFile,\n          sourceFile.fileName,\n          PUSH_MODULE,\n          COMPONENT_MODULE\n        );\n        commitChanges(host, sourceFile.fileName, changes);\n      }\n    });\n  };\n}\n\nexport default function (): Rule {\n  return chain([migrateToNgrxPush(), importPushModule(), exportPushModule()]);\n}\n"
  },
  {
    "path": "modules/schematics/src/ngrx-push-migration/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxNgRxPushMigration\",\n  \"title\": \"NgRx Component NgRxPush Migration from async to ngrxPush\",\n  \"type\": \"object\",\n  \"properties\": {},\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/ngrx-push-migration/schema.ts",
    "content": "// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface Schema {}\n"
  },
  {
    "path": "modules/schematics/src/reducer/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Reducer Schematic should create a reducer 1`] = `\n\"import { createReducer, on } from '@ngrx/store';\nimport { FooActions } from './foo.actions';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const initialState: State = {\n\n};\n\nexport const reducer = createReducer(\n  initialState,\n);\n\n\"\n`;\n\nexports[`Reducer Schematic should create a reducer with prefix in an api feature 1`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { FooActions } from './foo.actions';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const initialState: State = {\n\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.customFoos, state => state),\n  on(FooActions.customFoosSuccess, (state, action) => state),\n  on(FooActions.customFoosFailure, (state, action) => state),\n);\n\nexport const fooFeature = createFeature({\n  name: fooFeatureKey,\n  reducer,\n});\n\n\"\n`;\n\nexports[`Reducer Schematic should create a reducers barrel file 1`] = `\n\"\n    import { isDevMode } from '@angular/core';\n    import {\n      ActionReducer,\n      ActionReducerMap,\n      createFeatureSelector,\n      createSelector,\n      MetaReducer\n    } from '@ngrx/store';\nimport * as fromFoo from '../foo.reducer';\n\n    export interface State {\n\n      [fromFoo.fooFeatureKey]: fromFoo.State;\n}\n\n    export const reducers: ActionReducerMap<State> = {\n\n      [fromFoo.fooFeatureKey]: fromFoo.reducer,\n};\n\n\n    export const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n  \"\n`;\n\nexports[`Reducer Schematic should create and export a reducer in a feature 1`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { FooActions } from './foo.actions';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const initialState: State = {\n\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.loadFoos, state => state),\n\n);\n\nexport const fooFeature = createFeature({\n  name: fooFeatureKey,\n  reducer,\n});\n\n\"\n`;\n\nexports[`Reducer Schematic should create and export a reducer in an api feature 1`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { FooActions } from './foo.actions';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const initialState: State = {\n\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.loadFoos, state => state),\n  on(FooActions.loadFoosSuccess, (state, action) => state),\n  on(FooActions.loadFoosFailure, (state, action) => state),\n);\n\nexport const fooFeature = createFeature({\n  name: fooFeatureKey,\n  reducer,\n});\n\n\"\n`;\n\nexports[`Reducer Schematic should group and nest the reducer within a feature 1`] = `\n\"import { createFeature, createReducer, on } from '@ngrx/store';\nimport { FooActions } from '../../actions/foo/foo.actions';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const initialState: State = {\n\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(FooActions.loadFoos, state => state),\n\n);\n\nexport const fooFeature = createFeature({\n  name: fooFeatureKey,\n  reducer,\n});\n\n\"\n`;\n\nexports[`Reducer Schematic should group within a \"reducers\" folder if group is set 1`] = `\n\"import { createReducer, on } from '@ngrx/store';\nimport { FooActions } from '../actions/foo.actions';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const initialState: State = {\n\n};\n\nexport const reducer = createReducer(\n  initialState,\n);\n\n\"\n`;\n\nexports[`Reducer Schematic should import into a specified module 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { StoreModule } from '@ngrx/store';\nimport * as fromFoo from './foo.reducer';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    StoreModule.forFeature(fromFoo.fooFeatureKey, fromFoo.reducer)\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.spec.ts.template",
    "content": "import { reducer, initialState } from './<%= dasherize(name) %>.reducer';\n\ndescribe('<%= classify(name) %> Reducer', () => {\n  describe('an unknown action', () => {\n    it('should return the previous state', () => {\n      const action = {} as any;\n\n      const result = reducer(initialState, action);\n\n      expect(result).toBe(initialState);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/reducer/files/__name@dasherize@if-flat__/__name@dasherize__.reducer.ts.template",
    "content": "import {<% if(feature) { %> createFeature,<% } %> createReducer, on } from '@ngrx/store';\nimport { <%= classify(name) %>Actions } from '<%= featurePath(group, flat, \"actions\", dasherize(name)) %><%= dasherize(name) %>.actions';\n\nexport const <%= camelize(name) %>FeatureKey = '<%= camelize(name) %>';\n\nexport interface State {\n\n}\n\nexport const initialState: State = {\n\n};\n\nexport const reducer = createReducer(\n  initialState,<% if(feature) { %>\n  on(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>s, state => state),\n<% if(api) { %>  on(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sSuccess, (state, action) => state),\n  on(<%= classify(name) %>Actions.<%= prefix %><%= classify(name) %>sFailure, (state, action) => state),<% } %><% } %>\n);\n<% if(feature) { %>\nexport const <%= camelize(name) %>Feature = createFeature({\n  name: <%= camelize(name) %>FeatureKey,\n  reducer,\n});\n<% } %>\n"
  },
  {
    "path": "modules/schematics/src/reducer/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as ReducerOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createReducers,\n  createWorkspace,\n  defaultWorkspaceOptions,\n  defaultAppOptions,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Reducer Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n  const defaultOptions: ReducerOptions = {\n    name: 'foo',\n    project: 'bar',\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = createReducers(await createWorkspace(schematicRunner, appTree));\n  });\n\n  it('should create one file', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      defaultOptions,\n      appTree\n    );\n\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create a reducer to specified project if provided', async () => {\n    const options = {\n      ...defaultOptions,\n      project: 'baz',\n    };\n\n    const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {\n      ...defaultAppOptions,\n      name: 'baz',\n    });\n\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      options,\n      appTree\n    );\n    const files = tree.files;\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create two files if spec is true', async () => {\n    const options = {\n      ...defaultOptions,\n    };\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      options,\n      appTree\n    );\n\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.reducer.spec.ts`)\n    ).toBeGreaterThanOrEqual(0);\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create a reducer', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      defaultOptions,\n      appTree\n    );\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.reducer.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should create and export a reducer in a feature', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      { ...defaultOptions, feature: true },\n      appTree\n    );\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.reducer.ts`\n    );\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should create and export a reducer in an api feature', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      { ...defaultOptions, feature: true, api: true },\n      appTree\n    );\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.reducer.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n\n  it('should import into a specified module', async () => {\n    const options = { ...defaultOptions, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      options,\n      appTree\n    );\n    const appModule = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n\n    expect(appModule).toMatchSnapshot();\n  });\n\n  it('should create a reducers barrel file', async () => {\n    const options = { ...defaultOptions, reducers: `reducers/index.ts` };\n\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      options,\n      appTree\n    );\n    const reducers = tree.readContent(\n      `${projectPath}/src/app/reducers/index.ts`\n    );\n\n    expect(reducers).toMatchSnapshot();\n  });\n\n  it('should group within a \"reducers\" folder if group is set', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      {\n        ...defaultOptions,\n        group: true,\n      },\n      appTree\n    );\n\n    expect(\n      tree.files.indexOf(`${projectPath}/src/app/reducers/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n\n    expect(\n      tree.readContent(`${projectPath}/src/app/reducers/foo.reducer.ts`)\n    ).toMatchSnapshot();\n  });\n\n  it('should group and nest the reducer within a feature', async () => {\n    const options = {\n      ...defaultOptions,\n      skipTests: true,\n      group: true,\n      flat: false,\n      feature: true,\n    };\n\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      options,\n      appTree\n    );\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/reducers/foo/foo.reducer.ts`)\n    ).toBeGreaterThanOrEqual(0);\n\n    const content = tree.readContent(\n      `${projectPath}/src/app/reducers/foo/foo.reducer.ts`\n    );\n\n    expect(content).toMatchSnapshot();\n  });\n\n  it('should create a reducer with prefix in an api feature', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'reducer',\n      { ...defaultOptions, feature: true, api: true, prefix: 'custom' },\n      appTree\n    );\n    const fileContent = tree.readContent(\n      `${projectPath}/src/app/foo.reducer.ts`\n    );\n\n    expect(fileContent).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/reducer/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  Tree,\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  filter,\n  mergeWith,\n  move,\n  noop,\n  template,\n  url,\n} from '@angular-devkit/schematics';\nimport {\n  getProjectPath,\n  findModuleFromOptions,\n  stringUtils,\n  addReducerToState,\n  addReducerImportToNgModule,\n  parseName,\n  getProject,\n  getPrefix,\n} from '../../schematics-core';\nimport { Schema as ReducerOptions } from './schema';\n\nexport default function (options: ReducerOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    const projectConfig = getProject(host, options);\n    options.path = getProjectPath(host, options);\n    options.prefix = getPrefix(options);\n\n    const parsedPath = parseName(options.path, options.name);\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n\n    if (options.module) {\n      options.module = findModuleFromOptions(host, options);\n    }\n\n    const templateOptions = {\n      ...stringUtils,\n      'if-flat': (s: string) =>\n        stringUtils.group(\n          options.flat ? '' : s,\n          options.group ? 'reducers' : ''\n        ),\n      ...(options as object),\n    };\n\n    const templateSource = apply(url('./files'), [\n      options.skipTests\n        ? filter((path) => !path.endsWith('.spec.ts.template'))\n        : noop(),\n      applyTemplates(templateOptions),\n      move(parsedPath.path),\n    ]);\n\n    return chain([\n      branchAndMerge(chain([addReducerToState(options)])),\n      branchAndMerge(\n        chain([addReducerImportToNgModule(options), mergeWith(templateSource)])\n      ),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/reducer/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxReducer\",\n  \"title\": \"NgRx Reducer Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the reducer.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What should be the name of the reducer?\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the component.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"description\": \"When true, does not create test files.\",\n      \"default\": false\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"description\": \"Specifies the declaring module.\",\n      \"aliases\": [\"m\"]\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"feature\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Flag to indicate if part of a feature schematic.\"\n    },\n    \"reducers\": {\n      \"type\": \"string\",\n      \"description\": \"Specifies the reducers file.\",\n      \"aliases\": [\"r\"]\n    },\n    \"group\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Group reducer file within 'reducers' folder\",\n      \"aliases\": [\"g\"]\n    },\n    \"api\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Specifies if api success and failure actions should be added to the reducer\",\n      \"aliases\": [\"a\"],\n      \"x-prompt\": \"Should we add success and failure actions to the reducer?\"\n    }\n  },\n  \"prefix\": {\n    \"description\": \"The prefix of the reducer.\",\n    \"type\": \"string\",\n    \"default\": \"load\",\n    \"x-prompt\": \"What should be the prefix of the reducer?\"\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/reducer/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The name of the component.\n   */\n  name: string;\n\n  /**\n   * The path to create the effect.\n   */\n  path?: string;\n\n  /**\n   * The name of the project.\n   */\n  project?: string;\n\n  /**\n   * Flag to indicate if a dir is created.\n   */\n  flat?: boolean;\n\n  /**\n   * When true, does not create test files.\n   */\n  skipTests?: boolean;\n\n  /**\n   * Allows specification of the declaring module.\n   */\n  module?: string;\n\n  /**\n   * Allows specification of the declaring reducers.\n   */\n  reducers?: string;\n\n  /**\n   * Specifies if this is grouped within sub folders\n   */\n  group?: boolean;\n\n  /**\n   * Specifies if this is grouped within a feature\n   */\n  feature?: boolean;\n\n  /**\n   * Specifies if api success and failure actions\n   * should be added to the reducer.\n   */\n  api?: boolean;\n\n  /**\n   * The prefix for the reducers.\n   */\n  prefix?: string;\n}\n"
  },
  {
    "path": "modules/schematics/src/selector/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Selector Schematic With feature flag should create a selector 1`] = `\n\"import { createFeatureSelector, createSelector } from '@ngrx/store';\nimport * as fromFoo from './foo.reducer';\n\nexport const selectFooState = createFeatureSelector<fromFoo.State>(\n  fromFoo.fooFeatureKey\n);\n\"\n`;\n\nexports[`Selector Schematic With feature flag should create a selector 2`] = `\n\"import * as fromFoo from './foo.reducer';\nimport { selectFooState } from './foo.selectors';\n\ndescribe('Foo Selectors', () => {\n  it('should select the feature state', () => {\n    const result = selectFooState({\n      [fromFoo.fooFeatureKey]: {}\n    });\n\n    expect(result).toEqual({});\n  });\n});\n\"\n`;\n\nexports[`Selector Schematic With feature flag should group and nest the selectors within a feature 1`] = `\n\"import { createFeatureSelector, createSelector } from '@ngrx/store';\nimport * as fromFoo from '../../reducers/foo/foo.reducer';\n\nexport const selectFooState = createFeatureSelector<fromFoo.State>(\n  fromFoo.fooFeatureKey\n);\n\"\n`;\n\nexports[`Selector Schematic With feature flag should group and nest the selectors within a feature 2`] = `\n\"import * as fromFoo from '../../reducers/foo/foo.reducer';\nimport { selectFooState } from './foo.selectors';\n\ndescribe('Foo Selectors', () => {\n  it('should select the feature state', () => {\n    const result = selectFooState({\n      [fromFoo.fooFeatureKey]: {}\n    });\n\n    expect(result).toEqual({});\n  });\n});\n\"\n`;\n\nexports[`Selector Schematic should create selector files 1`] = `\n\"import { createFeatureSelector, createSelector } from '@ngrx/store';\n\n\"\n`;\n\nexports[`Selector Schematic should create selector files 2`] = `\n\"\n\ndescribe('Foo Selectors', () => {\n  it('should select the feature state', () => {\n    \n  });\n});\n\"\n`;\n\nexports[`Selector Schematic should group selectors if group is true 1`] = `\n\"import { createFeatureSelector, createSelector } from '@ngrx/store';\n\n\"\n`;\n\nexports[`Selector Schematic should group selectors if group is true 2`] = `\n\"\n\ndescribe('Foo Selectors', () => {\n  it('should select the feature state', () => {\n    \n  });\n});\n\"\n`;\n\nexports[`Selector Schematic should not flatten selectors if flat is false 1`] = `\n\"import { createFeatureSelector, createSelector } from '@ngrx/store';\n\n\"\n`;\n\nexports[`Selector Schematic should not flatten selectors if flat is false 2`] = `\n\"\n\ndescribe('Foo Selectors', () => {\n  it('should select the feature state', () => {\n    \n  });\n});\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/selector/files/__name@dasherize@if-flat__/__name@dasherize__.selectors.spec.ts.template",
    "content": "<% if(feature) { %>import * as from<%= classify(name) %> from '<%= reducerPath %>';\nimport { select<%= classify(name) %>State } from './<%= dasherize(name) %>.selectors';<% } %>\n\ndescribe('<%= classify(name) %> Selectors', () => {\n  it('should select the feature state', () => {\n    <% if(feature) { %>const result = select<%= classify(name) %>State({\n      [from<%= classify(name) %>.<%= camelize(name) %>FeatureKey]: {}\n    });\n\n    expect(result).toEqual({});<% } %>\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/selector/files/__name@dasherize@if-flat__/__name@dasherize__.selectors.ts.template",
    "content": "import { createFeatureSelector, createSelector } from '@ngrx/store';\n<% if(feature) { %>import * as from<%= classify(name) %> from '<%= reducerPath %>';\n\nexport const select<%= classify(name) %>State = createFeatureSelector<from<%= classify(name) %>.State>(\n  from<%= classify(name) %>.<%= camelize(name) %>FeatureKey\n);<% } %>\n"
  },
  {
    "path": "modules/schematics/src/selector/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as SelectorOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Selector Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n  const defaultOptions: SelectorOptions = {\n    name: 'foo',\n    project: 'bar',\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should create selector files', async () => {\n    const tree = await schematicRunner.runSchematic(\n      'selector',\n      defaultOptions,\n      appTree\n    );\n\n    const selectorPath = `${projectPath}/src/app/foo.selectors.ts`;\n    const specPath = `${projectPath}/src/app/foo.selectors.spec.ts`;\n\n    expect(tree.files.includes(selectorPath)).toBeTruthy();\n    expect(tree.files.includes(specPath)).toBeTruthy();\n\n    expect(tree.readContent(selectorPath)).toMatchSnapshot();\n    expect(tree.readContent(specPath)).toMatchSnapshot();\n  });\n\n  it('should not create a spec file if spec is false', async () => {\n    const options = {\n      ...defaultOptions,\n      skipTests: true,\n    };\n    const tree = await schematicRunner.runSchematic(\n      'selector',\n      options,\n      appTree\n    );\n\n    expect(\n      tree.files.includes(`${projectPath}/src/app/foo.selectors.spec.ts`)\n    ).toBeFalsy();\n  });\n\n  it('should group selectors if group is true', async () => {\n    const options = {\n      ...defaultOptions,\n      group: true,\n    };\n    const tree = await schematicRunner.runSchematic(\n      'selector',\n      options,\n      appTree\n    );\n\n    const selectorPath = `${projectPath}/src/app/selectors/foo.selectors.ts`;\n    const specPath = `${projectPath}/src/app/selectors/foo.selectors.spec.ts`;\n\n    expect(tree.files.includes(selectorPath)).toBeTruthy();\n    expect(tree.files.includes(specPath)).toBeTruthy();\n\n    expect(tree.readContent(selectorPath)).toMatchSnapshot();\n    expect(tree.readContent(specPath)).toMatchSnapshot();\n  });\n\n  it('should not flatten selectors if flat is false', async () => {\n    const options = {\n      ...defaultOptions,\n      flat: false,\n    };\n    const tree = await schematicRunner.runSchematic(\n      'selector',\n      options,\n      appTree\n    );\n\n    const selectorPath = `${projectPath}/src/app/foo/foo.selectors.ts`;\n    const specPath = `${projectPath}/src/app/foo/foo.selectors.spec.ts`;\n\n    expect(tree.files.includes(selectorPath)).toBeTruthy();\n    expect(tree.files.includes(specPath)).toBeTruthy();\n\n    expect(tree.readContent(selectorPath)).toMatchSnapshot();\n    expect(tree.readContent(specPath)).toMatchSnapshot();\n  });\n\n  describe('With feature flag', () => {\n    it('should create a selector', async () => {\n      const options = {\n        ...defaultOptions,\n        feature: true,\n      };\n\n      const tree = await schematicRunner.runSchematic(\n        'selector',\n        options,\n        appTree\n      );\n\n      const selectorPath = `${projectPath}/src/app/foo.selectors.ts`;\n      const specPath = `${projectPath}/src/app/foo.selectors.spec.ts`;\n\n      expect(tree.files.includes(selectorPath)).toBeTruthy();\n      expect(tree.files.includes(specPath)).toBeTruthy();\n\n      expect(tree.readContent(selectorPath)).toMatchSnapshot();\n      expect(tree.readContent(specPath)).toMatchSnapshot();\n    });\n\n    it('should group and nest the selectors within a feature', async () => {\n      const options = {\n        ...defaultOptions,\n        feature: true,\n        group: true,\n        flat: false,\n      };\n\n      const tree = await schematicRunner.runSchematic(\n        'selector',\n        options,\n        appTree\n      );\n      const selectorPath = `${projectPath}/src/app/selectors/foo/foo.selectors.ts`;\n      const specPath = `${projectPath}/src/app/selectors/foo/foo.selectors.spec.ts`;\n\n      expect(tree.files.includes(selectorPath)).toBeTruthy();\n      expect(tree.files.includes(specPath)).toBeTruthy();\n\n      expect(tree.readContent(selectorPath)).toMatchSnapshot();\n      expect(tree.readContent(specPath)).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/selector/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  filter,\n  mergeWith,\n  move,\n  noop,\n  url,\n} from '@angular-devkit/schematics';\nimport { getProjectPath, parseName, stringUtils } from '../../schematics-core';\nimport { Schema as SelectorOptions } from './schema';\n\nexport default function (options: SelectorOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    options.path = getProjectPath(host, options);\n\n    const parsedPath = parseName(options.path, options.name || '');\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n\n    const templateSource = apply(url('./files'), [\n      options.skipTests\n        ? filter((path) => !path.endsWith('.spec.ts.template'))\n        : noop(),\n      applyTemplates({\n        ...stringUtils,\n        'if-flat': (s: string) =>\n          stringUtils.group(\n            options.flat ? '' : s,\n            options.group ? 'selectors' : ''\n          ),\n        reducerPath: `${relativePath(options)}${stringUtils.dasherize(\n          options.name\n        )}.reducer`,\n        ...(options as object),\n      } as any),\n      move(parsedPath.path),\n    ]);\n\n    return chain([branchAndMerge(chain([mergeWith(templateSource)]))])(\n      host,\n      context\n    );\n  };\n}\n\nfunction relativePath(options: SelectorOptions) {\n  if (options.feature) {\n    return stringUtils.featurePath(\n      options.group,\n      options.flat,\n      'reducers',\n      stringUtils.dasherize(options.name)\n    );\n  }\n\n  return '';\n}\n"
  },
  {
    "path": "modules/schematics/src/selector/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxSelector\",\n  \"title\": \"NgRx Selector Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the selector.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      }\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the selectors.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"description\": \"When true, does not create test files.\",\n      \"default\": false\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"group\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Group selector file within 'selectors' folder\",\n      \"aliases\": [\"g\"]\n    },\n    \"feature\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Flag to indicate if part of a feature schematic.\",\n      \"visible\": false\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/selector/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The name of the selector.\n   */\n  name: string;\n\n  /**\n   * The path to create the selector.\n   */\n  path?: string;\n\n  /**\n   * The name of the project.\n   */\n  project?: string;\n\n  /**\n   * Flag to indicate if a dir is created.\n   */\n  flat?: boolean;\n\n  /**\n   * When true, does not create test files.\n   */\n  skipTests?: boolean;\n\n  /**\n   * Specifies if this is grouped within a feature\n   */\n  feature?: boolean;\n\n  /**\n   * Specifies if this is grouped within an 'selectors' folder\n   */\n  group?: boolean;\n}\n"
  },
  {
    "path": "modules/schematics/src/store/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Store Schematic should add a feature key if not root 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should add a feature key if not root 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const reducers: ActionReducerMap<State> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n\"\n`;\n\nexports[`Store Schematic should add the initial config correctly into an empty module 1`] = `\n\"\n        import { NgModule, isDevMode } from '@angular/core';\nimport { StoreModule } from '@ngrx/store';\nimport { reducers, metaReducers } from './reducers';\nimport { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n        @NgModule({\n          declarations: [],\n          imports: [StoreModule.forRoot(reducers, { metaReducers }), isDevMode() ? StoreDevtoolsModule.instrument() : []],\n        })\n        export class EmptyModule {}\n      \"\n`;\n\nexports[`Store Schematic should create the initial store setup 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should create the initial store setup 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\n\nexport interface State {\n\n}\n\nexport const reducers: ActionReducerMap<State> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n\"\n`;\n\nexports[`Store Schematic should import a feature a specified module 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { StoreModule } from '@ngrx/store';\nimport * as fromFoo from './reducers';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    StoreModule.forFeature(fromFoo.fooFeatureKey, fromFoo.reducers, { metaReducers: fromFoo.metaReducers })\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should import a feature a specified module 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const reducers: ActionReducerMap<State> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n\"\n`;\n\nexports[`Store Schematic should import into a specified module 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners, isDevMode } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { StoreModule } from '@ngrx/store';\nimport { reducers, metaReducers } from './reducers';\nimport { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    StoreModule.forRoot(reducers, { metaReducers }),\n    isDevMode() ? StoreDevtoolsModule.instrument() : []\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should import into a specified module 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\n\nexport interface State {\n\n}\n\nexport const reducers: ActionReducerMap<State> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n\"\n`;\n\nexports[`Store Schematic should not add a feature key if root 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should not add a feature key if root 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\n\nexport interface State {\n\n}\n\nexport const reducers: ActionReducerMap<State> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n\"\n`;\n\nexports[`Store Schematic should not skip the initial store setup files if the minimal flag is provided with a feature 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { StoreModule } from '@ngrx/store';\nimport * as fromFoo from './reducers';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    StoreModule.forFeature(fromFoo.fooFeatureKey, fromFoo.reducers, { metaReducers: fromFoo.metaReducers })\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should not skip the initial store setup files if the minimal flag is provided with a feature 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\nexport const fooFeatureKey = 'foo';\n\nexport interface State {\n\n}\n\nexport const reducers: ActionReducerMap<State> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n\"\n`;\n\nexports[`Store Schematic should skip the initial store setup files if the minimal flag is provided 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners, isDevMode } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\nimport { StoreModule } from '@ngrx/store';\nimport { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule,\n    StoreModule.forRoot({}),\n    isDevMode() ? StoreDevtoolsModule.instrument() : []\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should support a custom feature state interface name 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should support a custom feature state interface name 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\nexport const featureFeatureKey = 'feature';\n\nexport interface FeatureState {\n\n}\n\nexport const reducers: ActionReducerMap<FeatureState> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<FeatureState>[] = isDevMode() ? [] : [];\n\"\n`;\n\nexports[`Store Schematic should support a custom root state interface name 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should support a custom root state interface name 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\n\nexport interface AppState {\n\n}\n\nexport const reducers: ActionReducerMap<AppState> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<AppState>[] = isDevMode() ? [] : [];\n\"\n`;\n\nexports[`Store Schematic should support a default feature state interface name 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should support a default feature state interface name 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\nexport const featureFeatureKey = 'feature';\n\nexport interface State {\n\n}\n\nexport const reducers: ActionReducerMap<State> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n\"\n`;\n\nexports[`Store Schematic should support a default root state interface name 1`] = `\n\"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { App } from './app';\n\n@NgModule({\n  declarations: [\n    App\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [\n    provideBrowserGlobalErrorListeners()\n  ],\n  bootstrap: [App]\n})\nexport class AppModule { }\n\"\n`;\n\nexports[`Store Schematic should support a default root state interface name 2`] = `\n\"import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\n\nexport interface State {\n\n}\n\nexport const reducers: ActionReducerMap<State> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n\"\n`;\n"
  },
  {
    "path": "modules/schematics/src/store/files/__statePath__/index.ts.template",
    "content": "<% if (!isLib) { %>import { isDevMode } from '@angular/core';\n<% } %>import {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n<% if (!root) { %>\nexport const <%= camelize(name) %>FeatureKey = '<%= camelize(name) %>';<% } %>\n\nexport interface <%= classify(stateInterface) %> {\n\n}\n\nexport const reducers: ActionReducerMap<<%= classify(stateInterface) %>> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<<%= classify(stateInterface) %>>[] = <% if (!isLib) { %>isDevMode() ? [] : <% } %>[];\n"
  },
  {
    "path": "modules/schematics/src/store/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as StoreOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n  defaultWorkspaceOptions,\n  defaultAppOptions,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Store Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/schematics',\n    path.join(__dirname, '../../collection.json')\n  );\n  const defaultOptions: StoreOptions = {\n    name: 'foo',\n    project: 'bar',\n    module: undefined,\n    flat: false,\n    root: true,\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should create the initial store setup', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should skip the initial store setup files if the minimal flag is provided', async () => {\n    const options = {\n      ...defaultOptions,\n      module: 'app-module.ts',\n      minimal: true,\n    };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBe(-1);\n  });\n\n  it('should not skip the initial store setup files if the minimal flag is provided with a feature', async () => {\n    const options = {\n      ...defaultOptions,\n      root: false,\n      module: 'app-module.ts',\n      minimal: true,\n    };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should create the initial store to specified project if provided', async () => {\n    const options = {\n      ...defaultOptions,\n      project: 'baz',\n    };\n\n    const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {\n      ...defaultAppOptions,\n      name: 'baz',\n    });\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const files = tree.files;\n\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/lib/reducers/index.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should create the initial store to defaultProject if project is not provided', async () => {\n    const options = {\n      ...defaultOptions,\n      project: undefined,\n    };\n\n    const specifiedProjectPath = getTestProjectPath(defaultWorkspaceOptions, {\n      ...defaultAppOptions,\n      name: 'bar',\n    });\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n\n    const files = tree.files;\n\n    expect(\n      files.indexOf(`${specifiedProjectPath}/src/app/reducers/index.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should import into a specified module', async () => {\n    const options = { ...defaultOptions, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should fail if specified module does not exist', async () => {\n    const options = { ...defaultOptions, module: '/src/app/app.moduleXXX.ts' };\n    await expect(\n      schematicRunner.runSchematic('store', options, appTree)\n    ).rejects.toThrow();\n  });\n\n  it('should import a feature a specified module', async () => {\n    const options = { ...defaultOptions, root: false, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should support a default root state interface name', async () => {\n    const options = { ...defaultOptions, name: 'State' };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should support a custom root state interface name', async () => {\n    const options = {\n      ...defaultOptions,\n      name: 'State',\n      stateInterface: 'AppState',\n    };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should support a default feature state interface name', async () => {\n    const options = { ...defaultOptions, root: false, name: 'Feature' };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should support a custom feature state interface name', async () => {\n    const options = {\n      ...defaultOptions,\n      root: false,\n      name: 'Feature',\n      stateInterface: 'FeatureState',\n    };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should fail if a feature state name is not specified', async () => {\n    const options = {\n      ...defaultOptions,\n      name: undefined,\n      root: false,\n    };\n\n    await expect(\n      schematicRunner.runSchematic('store', options, appTree)\n    ).rejects.toThrow();\n  });\n\n  it('should pass if a root state name is not specified', () => {\n    const options = {\n      ...defaultOptions,\n      name: undefined,\n    };\n\n    expect(async () => {\n      await schematicRunner.runSchematic('store', options, appTree);\n    }).not.toThrow();\n  });\n\n  it('should add a feature key if not root', async () => {\n    const options = { ...defaultOptions, root: false };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should not add a feature key if root', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const modulePath = `${projectPath}/src/app/app-module.ts`;\n    const reducersPath = `${projectPath}/src/app/reducers/index.ts`;\n\n    expect(tree.files.indexOf(modulePath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(modulePath)).toMatchSnapshot();\n\n    expect(tree.files.indexOf(reducersPath)).toBeGreaterThanOrEqual(0);\n    expect(tree.readContent(reducersPath)).toMatchSnapshot();\n  });\n\n  it('should add the initial config correctly into an empty module', async () => {\n    const options = {\n      ...defaultOptions,\n      module: 'empty.module.ts',\n    };\n\n    appTree.create(\n      `${projectPath}/src/app/empty.module.ts`,\n      `\n        import { NgModule } from '@angular/core';\n\n        @NgModule({\n          declarations: [],\n          imports: [],\n        })\n        export class EmptyModule {}\n      `\n    );\n\n    const tree = await schematicRunner.runSchematic('store', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/empty.module.ts`);\n\n    expect(content).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "modules/schematics/src/store/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  Tree,\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  mergeWith,\n  url,\n  move,\n  filter,\n  noop,\n} from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport {\n  stringUtils,\n  buildRelativePath,\n  insertImport,\n  Change,\n  InsertChange,\n  getProjectPath,\n  isLib,\n  findModuleFromOptions,\n  addImportToModule,\n  parseName,\n  visitNgModuleImports,\n} from '../../schematics-core';\nimport { Schema as StoreOptions } from './schema';\n\nfunction addImportToNgModule(options: StoreOptions): Rule {\n  return (host: Tree) => {\n    const modulePath = options.module;\n\n    if (!modulePath) {\n      return host;\n    }\n\n    if (!host.exists(modulePath)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const statePath = `${options.path}/${options.statePath}`;\n    const relativePath = buildRelativePath(modulePath, statePath);\n\n    const rootStoreReducers = options.minimal ? `{}` : `reducers`;\n    const rootStoreConfig = options.minimal ? `` : `, { metaReducers }`;\n\n    const storeNgModuleImport = addImportToModule(\n      source,\n      modulePath,\n      options.root\n        ? `StoreModule.forRoot(${rootStoreReducers}${rootStoreConfig})`\n        : `StoreModule.forFeature(from${stringUtils.classify(\n            options.name\n          )}.${stringUtils.camelize(\n            options.name\n          )}FeatureKey, from${stringUtils.classify(\n            options.name\n          )}.reducers, { metaReducers: from${stringUtils.classify(\n            options.name\n          )}.metaReducers })`,\n      relativePath\n    ).shift();\n\n    let commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n      storeNgModuleImport,\n    ];\n\n    if (options.root && !options.minimal) {\n      commonImports = commonImports.concat([\n        insertImport(\n          source,\n          modulePath,\n          'reducers, metaReducers',\n          relativePath\n        ),\n      ]);\n    } else if (!options.root) {\n      commonImports = commonImports.concat([\n        insertImport(\n          source,\n          modulePath,\n          `* as from${stringUtils.classify(options.name)}`,\n          relativePath,\n          true\n        ),\n      ]);\n    }\n\n    let rootImports: (Change | undefined)[] = [];\n\n    if (options.root) {\n      let hasImports = false;\n      visitNgModuleImports(source, (_, importNodes) => {\n        hasImports = importNodes.length > 0;\n      });\n\n      // `addImportToModule` adds a comma to imports when there are already imports present\n      // because at this time the store import hasn't been committed yet, `addImportToModule` wont add a comma\n      // so we have to add it here for empty import arrays\n      const adjectiveComma = hasImports ? '' : ', ';\n\n      const storeDevtoolsNgModuleImport = addImportToModule(\n        source,\n        modulePath,\n        `${adjectiveComma}isDevMode() ? StoreDevtoolsModule.instrument() : []`,\n        relativePath\n      ).shift();\n\n      rootImports = rootImports.concat([\n        insertImport(\n          source,\n          modulePath,\n          'StoreDevtoolsModule',\n          '@ngrx/store-devtools'\n        ),\n        insertImport(source, modulePath, 'isDevMode', '@angular/core'),\n        storeDevtoolsNgModuleImport,\n      ]);\n    }\n\n    const changes = [...commonImports, ...rootImports];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport default function (options: StoreOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    if (!options.name && !options.root) {\n      throw new Error(`Please provide a name for the feature state`);\n    }\n\n    options.path = getProjectPath(host, options);\n\n    const parsedPath = parseName(options.path, options.name || '');\n    options.name = parsedPath.name;\n    options.path = parsedPath.path;\n\n    if (options.module) {\n      options.module = findModuleFromOptions(host, options);\n    }\n\n    if (\n      options.root &&\n      options.stateInterface &&\n      options.stateInterface !== 'State'\n    ) {\n      options.stateInterface = stringUtils.classify(options.stateInterface);\n    }\n\n    const templateSource = apply(url('./files'), [\n      options.root && options.minimal ? filter((_) => false) : noop(),\n      applyTemplates({\n        ...stringUtils,\n        ...(options as object),\n        isLib: isLib(host, options),\n      }),\n      move(parsedPath.path),\n    ]);\n\n    return chain([\n      branchAndMerge(\n        chain([addImportToNgModule(options), mergeWith(templateSource)])\n      ),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/schematics/src/store/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxState\",\n  \"title\": \"NgRx State Management Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the state.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What should be the name of the state?\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the component.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"flat\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Flag to indicate if a dir is created.\"\n    },\n    \"skipTests\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"When true, does not create test files.\"\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"default\": \"\",\n      \"description\": \"Allows specification of the declaring module.\",\n      \"alias\": \"m\",\n      \"subtype\": \"filepath\",\n      \"x-prompt\": \"To which module (path) should the state be registered in?\"\n    },\n    \"statePath\": {\n      \"type\": \"string\",\n      \"default\": \"reducers\"\n    },\n    \"root\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Flag to setup the root state or feature state.\"\n    },\n    \"stateInterface\": {\n      \"type\": \"string\",\n      \"default\": \"State\",\n      \"description\": \"Specifies the interface for the state.\",\n      \"alias\": \"si\"\n    },\n    \"minimal\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Setup root state management without registering initial reducers.\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/schematics/src/store/schema.ts",
    "content": "export interface Schema {\n  /**\n   * The name of the component.\n   */\n\n  name: string;\n  /**\n   * The path to create the effect.\n   */\n\n  path?: string;\n  /**\n   * The name of the project.\n   */\n  project?: string;\n  /**\n   * Flag to indicate if a dir is created.\n   */\n  flat?: boolean;\n  /**\n   * When true, does not create test files.\n   */\n  skipTests?: boolean;\n  /**\n   * Allows specification of the declaring module.\n   */\n  module?: string;\n  /**\n   * Specifies the dir for the state folder\n   */\n\n  statePath?: string;\n  /**\n   * Specifies whether this is the root state or feature state\n   */\n\n  root?: boolean;\n  /**\n   * Specifies the interface for the state\n   */\n  stateInterface?: string;\n  /**\n   * Setup state management without registering initial reducers.\n   */\n  minimal?: boolean;\n}\n"
  },
  {
    "path": "modules/schematics/test-setup.ts",
    "content": "import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';\n\nsetupZoneTestEnv();\nObject.assign(global, { TextDecoder, TextEncoder });\n"
  },
  {
    "path": "modules/schematics/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics\",\n    \"paths\": {\n      \"@ngrx/schematics/schematics-core\": [\"./schematics-core\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/schematics/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics\",\n    \"paths\": {\n      \"@ngrx/schematics/schematics-core\": [\"./schematics-core\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/schematics/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"commonjs\",\n    \"types\": [\"jest\", \"node\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"jest.config.ts\", \"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/schematics-core/project.json",
    "content": "{\n  \"name\": \"schematics-core\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/schematics-core\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/schematics-core/*/**/*.ts\",\n          \"modules/schematics-core/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/schematics-core/testing/create-app-module.ts",
    "content": "import { UnitTestTree } from '@angular-devkit/schematics/testing';\n\nexport function createAppModule(\n  tree: UnitTestTree,\n  path?: string\n): UnitTestTree {\n  tree.create(\n    path || '/src/app/app.module.ts',\n    `\n    import { BrowserModule } from '@angular/platform-browser';\n    import { NgModule } from '@angular/core';\n    import { AppComponent } from './app.component';\n\n    @NgModule({\n    declarations: [\n      AppComponent\n    ],\n    imports: [\n      BrowserModule\n    ],\n    providers: [],\n    bootstrap: [AppComponent]\n    })\n    export class AppModule { }\n  `\n  );\n\n  return tree;\n}\n\nexport function createAppModuleWithEffects(\n  tree: UnitTestTree,\n  path: string,\n  effects?: string\n): UnitTestTree {\n  tree.create(\n    path || '/src/app/app.module.ts',\n    `\n    import { BrowserModule } from '@angular/platform-browser';\n    import { NgModule } from '@angular/core';\n    import { AppComponent } from './app.component';\n    import { EffectsModule } from '@ngrx/effects';\n\n    @NgModule({\n      declarations: [\n        AppComponent\n      ],\n      imports: [\n        BrowserModule,\n        ${effects}\n      ],\n      providers: [],\n      bootstrap: [AppComponent]\n    })\n    export class AppModule { }\n  `\n  );\n\n  return tree;\n}\n"
  },
  {
    "path": "modules/schematics-core/testing/create-package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  UnitTestTree,\n  SchematicTestRunner,\n} from '@angular-devkit/schematics/testing';\n\nexport const packagePath = '/package.json';\n\nexport function createPackageJson(\n  prefix: string,\n  pkg: string,\n  tree: UnitTestTree,\n  version = '5.2.0',\n  packagePath = '/package.json'\n) {\n  tree.create(\n    packagePath,\n    `{\n      \"dependencies\": {\n        \"@ngrx/${pkg}\": \"${prefix}${version}\"\n      }\n    }`\n  );\n\n  return tree;\n}\n"
  },
  {
    "path": "modules/schematics-core/testing/create-reducers.ts",
    "content": "import { UnitTestTree } from '@angular-devkit/schematics/testing';\n\nexport function createReducers(\n  tree: UnitTestTree,\n  path?: string,\n  project = 'bar'\n) {\n  tree.create(\n    path || `/projects/${project}/src/app/reducers/index.ts`,\n    `\n    import { isDevMode } from '@angular/core';\n    import {\n      ActionReducer,\n      ActionReducerMap,\n      createFeatureSelector,\n      createSelector,\n      MetaReducer\n    } from '@ngrx/${'store'}';\n\n    export interface State {\n\n    }\n\n    export const reducers: ActionReducerMap<State> = {\n\n    };\n\n\n    export const metaReducers: MetaReducer<State>[] = isDevMode() ? [] : [];\n  `\n  );\n\n  return tree;\n}\n"
  },
  {
    "path": "modules/schematics-core/testing/create-workspace.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\n\nexport const defaultWorkspaceOptions = {\n  name: 'workspace',\n  newProjectRoot: 'projects',\n  version: '6.0.0',\n};\n\nexport const defaultAppOptions = {\n  name: 'bar',\n  inlineStyle: false,\n  inlineTemplate: false,\n  viewEncapsulation: 'Emulated',\n  routing: false,\n  style: 'css',\n  skipTests: false,\n  standalone: false,\n};\n\nconst defaultLibOptions = {\n  name: 'baz',\n};\n\nexport function getTestProjectPath(\n  workspaceOptions: any = defaultWorkspaceOptions,\n  appOptions: any = defaultAppOptions\n) {\n  return `/${workspaceOptions.newProjectRoot}/${appOptions.name}`;\n}\n\nexport async function createWorkspace(\n  schematicRunner: SchematicTestRunner,\n  appTree: UnitTestTree,\n  workspaceOptions = defaultWorkspaceOptions,\n  appOptions = defaultAppOptions,\n  libOptions = defaultLibOptions\n) {\n  appTree = await schematicRunner.runExternalSchematic(\n    '@schematics/angular',\n    'workspace',\n    workspaceOptions\n  );\n\n  appTree = await schematicRunner.runExternalSchematic(\n    '@schematics/angular',\n    'application',\n    { ...appOptions, standalone: false },\n    appTree\n  );\n\n  appTree = await schematicRunner.runExternalSchematic(\n    '@schematics/angular',\n    'application',\n    { ...appOptions, name: 'bar-standalone', standalone: true },\n    appTree\n  );\n\n  appTree = await schematicRunner.runExternalSchematic(\n    '@schematics/angular',\n    'library',\n    libOptions,\n    appTree\n  );\n\n  return appTree;\n}\n"
  },
  {
    "path": "modules/schematics-core/testing/index.ts",
    "content": "export * from './create-app-module';\nexport * from './create-reducers';\nexport * from './create-workspace';\n"
  },
  {
    "path": "modules/schematics-core/testing/update.ts",
    "content": "export const upgradeVersion = '6.0.0';\nexport const versionPrefixes = ['~', '^', ''];\n"
  },
  {
    "path": "modules/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/signals/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/signals/README.md",
    "content": "# @ngrx/signals\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n"
  },
  {
    "path": "modules/signals/entities/index.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/signals/entities/ng-package.json",
    "content": "{\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/signals/entities/spec/helpers.ts",
    "content": "import { SelectEntityId } from '../src';\nimport { Todo } from './mocks';\n\nexport const selectTodoId: SelectEntityId<Todo> = (todo) => todo._id;\n"
  },
  {
    "path": "modules/signals/entities/spec/mocks.ts",
    "content": "export type User = {\n  id: number;\n  firstName: string;\n  lastName: string;\n  age?: number;\n};\nexport type Todo = { _id: string; text: string; completed: boolean };\n\nexport const user1: User = { id: 1, firstName: 'John', lastName: 'Doe' };\nexport const user2: User = { id: 2, firstName: 'Jane', lastName: 'Smith' };\nexport const user3: User = { id: 3, firstName: 'Joe', lastName: 'Johnson' };\n\nexport const todo1: Todo = { _id: 'x', text: 'Buy milk', completed: true };\nexport const todo2: Todo = { _id: 'y', text: 'Buy eggs', completed: false };\nexport const todo3: Todo = { _id: 'z', text: 'Make bread', completed: true };\n"
  },
  {
    "path": "modules/signals/entities/spec/types/entity-config.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './helpers';\n\ndescribe('entityConfig', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import { type } from '@ngrx/signals';\n        import { entityConfig, SelectEntityId } from '@ngrx/signals/entities';\n\n        type User = { key: number; name: string };\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('fails when empty object is passed', () => {\n    expectSnippet('entityConfig({})').toFail(\n      /Property 'entity' is missing in type '{}'/\n    );\n  });\n\n  it('succeeds when entity is passed', () => {\n    const snippet = `\n      const userConfig = entityConfig({ entity: type<User>() });\n    `;\n\n    const result = expectSnippet(snippet);\n\n    result.toInfer('userConfig', '{ entity: User; }');\n  });\n\n  it('succeeds when entity and collection are passed', () => {\n    const snippet = `\n      const userConfig = entityConfig({\n        entity: type<User>(),\n        collection: 'user',\n      });\n    `;\n\n    const result = expectSnippet(snippet);\n\n    result.toInfer('userConfig', '{ entity: User; collection: \"user\"; }');\n  });\n\n  it('succeeds when entity and selectId are passed', () => {\n    const snippet = `\n      const userConfig1 = entityConfig({\n        entity: type<User>(),\n        selectId: (user) => user.key,\n      });\n\n      const selectId2 = (user: User) => user.key;\n      const userConfig2 = entityConfig({\n        entity: type<User>(),\n        selectId: selectId2,\n      });\n\n      const selectId3: SelectEntityId<User> = (user) => user.key;\n      const userConfig3 = entityConfig({\n        entity: type<User>(),\n        selectId: selectId3,\n      });\n    `;\n\n    const result = expectSnippet(snippet);\n\n    result.toInfer(\n      'userConfig1',\n      '{ entity: User; selectId: SelectEntityId<NoInfer<User>>; }'\n    );\n    result.toInfer(\n      'userConfig2',\n      '{ entity: User; selectId: SelectEntityId<NoInfer<User>>; }'\n    );\n    result.toInfer(\n      'userConfig3',\n      '{ entity: User; selectId: SelectEntityId<NoInfer<User>>; }'\n    );\n  });\n\n  it('fails when entity and wrong selectId are passed', () => {\n    expectSnippet(`\n      const userConfig = entityConfig({\n        entity: type<User>(),\n        selectId: (user) => user.id,\n      });\n    `).toFail(/Property 'id' does not exist on type 'User'/);\n\n    expectSnippet(`\n      const selectId = (entity: { key: number; foo: string }) => entity.key;\n\n      const userConfig = entityConfig({\n        entity: type<User>(),\n        selectId,\n      });\n    `).toFail(/No overload matches this call/);\n\n    expectSnippet(`\n      const selectId: SelectEntityId<{ key: string }> = (entity) => entity.key;\n\n      const userConfig = entityConfig({\n        entity: type<User>(),\n        selectId,\n      });\n    `).toFail(/No overload matches this call/);\n  });\n\n  it('succeeds when entity, collection, and selectId are passed', () => {\n    const snippet = `\n      const userConfig1 = entityConfig({\n        entity: type<User>(),\n        collection: 'user',\n        selectId: (user) => user.key,\n      });\n\n      const selectId2 = (user: { key: number }) => user.key;\n      const userConfig2 = entityConfig({\n        entity: type<User>(),\n        collection: 'user',\n        selectId: selectId2,\n      });\n\n      const selectId3: SelectEntityId<User> = (user) => user.key;\n      const userConfig3 = entityConfig({\n        entity: type<User>(),\n        collection: 'user',\n        selectId: selectId3,\n      });\n    `;\n\n    const result = expectSnippet(snippet);\n\n    result.toInfer(\n      'userConfig1',\n      '{ entity: User; collection: \"user\"; selectId: SelectEntityId<NoInfer<User>>; }'\n    );\n    result.toInfer(\n      'userConfig2',\n      '{ entity: User; collection: \"user\"; selectId: SelectEntityId<NoInfer<User>>; }'\n    );\n    result.toInfer(\n      'userConfig3',\n      '{ entity: User; collection: \"user\"; selectId: SelectEntityId<NoInfer<User>>; }'\n    );\n  });\n\n  it('fails when entity, collection, and wrong selectId are passed', () => {\n    expectSnippet(`\n      const userConfig = entityConfig({\n        entity: type<User>(),\n        collection: 'user',\n        selectId: (user) => user.id,\n      });\n    `).toFail(/Property 'id' does not exist on type 'User'/);\n\n    expectSnippet(`\n      const selectId = (entity: { key: number; foo: string }) => entity.key;\n\n      const userConfig = entityConfig({\n        entity: type<User>(),\n        collection: 'user',\n        selectId,\n      });\n    `).toFail(/No overload matches this call/);\n\n    expectSnippet(`\n      const selectId: SelectEntityId<{ key: string }> = (entity) => entity.key;\n\n      const userConfig = entityConfig({\n        entity: type<User>(),\n        collection: 'user',\n        selectId,\n      });\n    `).toFail(/No overload matches this call/);\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/signals/entities/spec/types/helpers.ts",
    "content": "export const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  target: 'ES2022',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  strict: true,\n  noImplicitAny: true,\n  paths: {\n    '@ngrx/signals': ['./modules/signals'],\n    '@ngrx/signals/entities': ['./modules/signals/entities'],\n  },\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/types/with-entities.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './helpers';\n\ndescribe('withEntities', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import {\n          patchState,\n          signalStoreFeature,\n          type,\n          withMethods,\n        } from '@ngrx/signals';\n        import {\n          addEntity,\n          entityConfig,\n          EntityId,\n          withEntities,\n        } from '@ngrx/signals/entities';\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('succeeds when creating a custom feature with named collection', () => {\n    const snippet = `\n      function withAddEntities<\n        Entity extends { id: EntityId },\n        Collection extends string\n      >(\n        collection: Collection\n      ) {\n        const config = entityConfig({\n          entity: type<Entity>(),\n          collection,\n        });\n\n        return signalStoreFeature(\n          withEntities(config),\n          withMethods((store) => ({\n            addEntity(entity: Entity): void {\n              patchState(store, addEntity(entity, { collection }));\n            },\n          }))\n        );\n      }\n    `;\n\n    expectSnippet(snippet).toSucceed();\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/add-entities.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { addEntities, entityConfig, withEntities } from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId as selectId } from '../helpers';\n\ndescribe('addEntities', () => {\n  it('adds entities if they do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, addEntities([user1]));\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n\n    patchState(store, addEntities([user2, user3]));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([user1, user2, user3]);\n  });\n\n  it('does not add entities if they already exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, addEntities([user1, user2]));\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      addEntities([user2, { ...user2, firstName: 'Jack' }, user1]),\n      addEntities([] as User[])\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([user1, user2]);\n\n    patchState(store, addEntities([user1, user3, user2]));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([user1, user2, user3]);\n  });\n\n  it('adds entities to the specified collection if they do not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2], { collection: 'user' }),\n      addEntities([user3], { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.userIds()).toEqual([1, 2, 3]);\n    expect(store.userEntities()).toEqual([user1, user2, user3]);\n  });\n\n  it('does not add entities to the specified collection if they already exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, { ...user1, lastName: 'Hendrix' }, user3, user1], {\n        collection: 'user',\n      })\n    );\n\n    const userEntityMap = store.userEntityMap();\n    const userIds = store.userIds();\n    const userEntities = store.userEntities();\n\n    patchState(\n      store,\n      addEntities([] as User[], { collection: 'user' }),\n      addEntities([user3, { ...user3, firstName: 'Jimmy' }, user1], {\n        collection: 'user',\n      })\n    );\n\n    expect(store.userEntityMap()).toBe(userEntityMap);\n    expect(store.userIds()).toBe(userIds);\n    expect(store.userEntities()).toBe(userEntities);\n    expect(store.userEntityMap()).toEqual({ 1: user1, 3: user3 });\n    expect(store.userIds()).toEqual([1, 3]);\n    expect(store.userEntities()).toEqual([user1, user3]);\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3], { collection: 'user' })\n    );\n    expect(store.userEntityMap()).toEqual({ 1: user1, 3: user3, 2: user2 });\n    expect(store.userIds()).toEqual([1, 3, 2]);\n    expect(store.userEntities()).toEqual([user1, user3, user2]);\n  });\n\n  it('adds entities with a custom id if they do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, addEntities([todo2, todo3], { selectId }));\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.ids()).toEqual(['y', 'z']);\n    expect(store.entities()).toEqual([todo2, todo3]);\n\n    patchState(\n      store,\n      addEntities([todo1], { selectId }),\n      addEntities([] as Todo[], { selectId })\n    );\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3, x: todo1 });\n    expect(store.ids()).toEqual(['y', 'z', 'x']);\n    expect(store.entities()).toEqual([todo2, todo3, todo1]);\n  });\n\n  it('does not add entities with a custom id if they already exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1], { selectId }),\n      addEntities([todo2, todo1], { selectId }),\n      addEntities([] as Todo[], { selectId })\n    );\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      addEntities([] as Todo[], { selectId }),\n      addEntities([todo2, { ...todo2, text: 'NgRx' }, todo1], { selectId })\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.ids()).toEqual(['x', 'y']);\n    expect(store.entities()).toEqual([todo1, todo2]);\n\n    patchState(store, addEntities([todo1, todo3, todo2], { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2, z: todo3 });\n    expect(store.ids()).toEqual(['x', 'y', 'z']);\n    expect(store.entities()).toEqual([todo1, todo2, todo3]);\n  });\n\n  it('adds entities with a custom id to the specified collection if they do not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo3, todo2], {\n        collection: 'todo',\n        selectId,\n      })\n    );\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2 });\n    expect(store.todoIds()).toEqual(['z', 'y']);\n    expect(store.todoEntities()).toEqual([todo3, todo2]);\n\n    patchState(\n      store,\n      addEntities([todo1], { collection: 'todo', selectId }),\n      addEntities([] as Todo[], { collection: 'todo', selectId })\n    );\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2, x: todo1 });\n    expect(store.todoIds()).toEqual(['z', 'y', 'x']);\n    expect(store.todoEntities()).toEqual([todo3, todo2, todo1]);\n  });\n\n  it('does not add entities with a custom id to the specified collection if they already exist', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo2, { ...todo2, text: 'NgRx' }, todo3, todo2], todoConfig)\n    );\n\n    const todoEntityMap = store.todoEntityMap();\n    const todoIds = store.todoIds();\n    const todoEntities = store.todoEntities();\n\n    patchState(\n      store,\n      addEntities([] as Todo[], todoConfig),\n      addEntities([todo3, todo2, { ...todo3, text: 'NgRx' }], todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toBe(todoEntityMap);\n    expect(store.todoIds()).toBe(todoIds);\n    expect(store.todoEntities()).toBe(todoEntities);\n    expect(store.todoEntityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.todoIds()).toEqual(['y', 'z']);\n    expect(store.todoEntities()).toEqual([todo2, todo3]);\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], todoConfig),\n      addEntities([todo2, todo3, todo1], todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toEqual({ y: todo2, z: todo3, x: todo1 });\n    expect(store.todoIds()).toEqual(['y', 'z', 'x']);\n    expect(store.todoEntities()).toEqual([todo2, todo3, todo1]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/add-entity.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { addEntity, entityConfig, withEntities } from '../../src';\nimport { Todo, todo1, todo2, User, user1, user2 } from '../mocks';\nimport { selectTodoId as selectId } from '../helpers';\n\ndescribe('addEntity', () => {\n  it('adds entity if it does not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, addEntity(user1));\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n\n    patchState(store, addEntity(user2));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([user1, user2]);\n  });\n\n  it('does not add entity if it already exists', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, addEntity(user1));\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(store, addEntity(user1));\n    patchState(store, addEntity({ ...user1, firstName: 'Jack' }));\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n  });\n\n  it('adds entity to the specified collection if it does not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntity(user1, { collection: 'user' }),\n      addEntity(user2, { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.userIds()).toEqual([1, 2]);\n    expect(store.userEntities()).toEqual([user1, user2]);\n  });\n\n  it('does not add entity to the specified collection if it already exists', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntity(user1, { collection: 'user' }),\n      addEntity(user1, { collection: 'user' })\n    );\n\n    const userEntityMap = store.userEntityMap();\n    const userIds = store.userIds();\n    const userEntities = store.userEntities();\n\n    patchState(\n      store,\n      addEntity(user1, { collection: 'user' }),\n      addEntity(user1, { collection: 'user' }),\n      addEntity({ ...user1, firstName: 'Jack' }, { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toBe(userEntityMap);\n    expect(store.userIds()).toBe(userIds);\n    expect(store.userEntities()).toBe(userEntities);\n    expect(store.userEntityMap()).toEqual({ 1: user1 });\n    expect(store.userIds()).toEqual([1]);\n    expect(store.userEntities()).toEqual([user1]);\n  });\n\n  it('adds entity with a custom id if it does not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, addEntity(todo1, { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1 });\n    expect(store.ids()).toEqual(['x']);\n    expect(store.entities()).toEqual([todo1]);\n\n    patchState(store, addEntity(todo2, { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.ids()).toEqual(['x', 'y']);\n    expect(store.entities()).toEqual([todo1, todo2]);\n  });\n\n  it('does not add entity with a custom id if it already exists', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntity(todo1, { selectId }),\n      addEntity(todo2, { selectId })\n    );\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      addEntity(todo1, { selectId }),\n      addEntity({ ...todo1, text: 'NgRx' }, { selectId }),\n      addEntity(todo2, { selectId }),\n      addEntity(todo1, { selectId })\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.ids()).toEqual(['x', 'y']);\n    expect(store.entities()).toEqual([todo1, todo2]);\n  });\n\n  it('adds entity with a custom id to the specified collection if it does not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(store, addEntity(todo1, { collection: 'todo', selectId }));\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1 });\n    expect(store.todoIds()).toEqual(['x']);\n    expect(store.todoEntities()).toEqual([todo1]);\n\n    patchState(store, addEntity(todo2, { collection: 'todo', selectId }));\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.todoIds()).toEqual(['x', 'y']);\n    expect(store.todoEntities()).toEqual([todo1, todo2]);\n  });\n\n  it('does not add entity with a custom id to the specified collection if it already exists', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntity(todo1, todoConfig),\n      addEntity(todo2, todoConfig)\n    );\n\n    const todoEntityMap = store.todoEntityMap();\n    const todoIds = store.todoIds();\n    const todoEntities = store.todoEntities();\n\n    patchState(\n      store,\n      addEntity(todo1, todoConfig),\n      addEntity({ ...todo1, text: 'NgRx' }, todoConfig),\n      addEntity(todo2, todoConfig),\n      addEntity(todo1, todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toBe(todoEntityMap);\n    expect(store.todoIds()).toBe(todoIds);\n    expect(store.todoEntities()).toBe(todoEntities);\n    expect(store.todoEntityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.todoIds()).toEqual(['x', 'y']);\n    expect(store.todoEntities()).toEqual([todo1, todo2]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/prepend-entities.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { prependEntities, entityConfig, withEntities } from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId as selectId } from '../helpers';\n\ndescribe('prependEntities', () => {\n  it('prepends entities if they do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, prependEntities([user1]));\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n\n    patchState(store, prependEntities([user2, user3]));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.ids()).toEqual([2, 3, 1]);\n    expect(store.entities()).toEqual([user2, user3, user1]);\n  });\n\n  it('does not add entities if they already exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, prependEntities([user1, user2]));\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      prependEntities([user2, { ...user2, firstName: 'Jack' }, user1]),\n      prependEntities([] as User[])\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([user1, user2]);\n\n    patchState(store, prependEntities([user1, user3, user2]));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.ids()).toEqual([3, 1, 2]);\n    expect(store.entities()).toEqual([user3, user1, user2]);\n  });\n\n  it('prepends entities to the specified collection if they do not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      prependEntities([user1, user2], { collection: 'user' }),\n      prependEntities([user3], { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.userIds()).toEqual([3, 1, 2]);\n    expect(store.userEntities()).toEqual([user3, user1, user2]);\n  });\n\n  it('does not add entities to the specified collection if they already exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      prependEntities(\n        [user1, { ...user1, lastName: 'Hendrix' }, user3, user1],\n        {\n          collection: 'user',\n        }\n      )\n    );\n\n    const userEntityMap = store.userEntityMap();\n    const userIds = store.userIds();\n    const userEntities = store.userEntities();\n\n    patchState(\n      store,\n      prependEntities([] as User[], { collection: 'user' }),\n      prependEntities([user3, { ...user3, firstName: 'Jimmy' }, user1], {\n        collection: 'user',\n      })\n    );\n\n    expect(store.userEntityMap()).toBe(userEntityMap);\n    expect(store.userIds()).toBe(userIds);\n    expect(store.userEntities()).toBe(userEntities);\n    expect(store.userEntityMap()).toEqual({ 1: user1, 3: user3 });\n    expect(store.userIds()).toEqual([1, 3]);\n    expect(store.userEntities()).toEqual([user1, user3]);\n\n    patchState(\n      store,\n      prependEntities([user1, user2, user3], { collection: 'user' })\n    );\n    expect(store.userEntityMap()).toEqual({ 1: user1, 3: user3, 2: user2 });\n    expect(store.userIds()).toEqual([2, 1, 3]);\n    expect(store.userEntities()).toEqual([user2, user1, user3]);\n  });\n\n  it('prepends entities with a custom id if they do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, prependEntities([todo2, todo3], { selectId }));\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.ids()).toEqual(['y', 'z']);\n    expect(store.entities()).toEqual([todo2, todo3]);\n\n    patchState(\n      store,\n      prependEntities([todo1], { selectId }),\n      prependEntities([] as Todo[], { selectId })\n    );\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3, x: todo1 });\n    expect(store.ids()).toEqual(['x', 'y', 'z']);\n    expect(store.entities()).toEqual([todo1, todo2, todo3]);\n  });\n\n  it('does not add entities with a custom id if they already exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      prependEntities([todo1], { selectId }),\n      prependEntities([todo2, todo1], { selectId }),\n      prependEntities([] as Todo[], { selectId })\n    );\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      prependEntities([] as Todo[], { selectId }),\n      prependEntities([todo2, { ...todo2, text: 'NgRx' }, todo1], { selectId })\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.ids()).toEqual(['y', 'x']);\n    expect(store.entities()).toEqual([todo2, todo1]);\n\n    patchState(store, prependEntities([todo1, todo3, todo2], { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2, z: todo3 });\n    expect(store.ids()).toEqual(['z', 'y', 'x']);\n    expect(store.entities()).toEqual([todo3, todo2, todo1]);\n  });\n\n  it('prepends entities with a custom id to the specified collection if they do not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      prependEntities([todo3, todo2], {\n        collection: 'todo',\n        selectId,\n      })\n    );\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2 });\n    expect(store.todoIds()).toEqual(['z', 'y']);\n    expect(store.todoEntities()).toEqual([todo3, todo2]);\n\n    patchState(\n      store,\n      prependEntities([todo1], { collection: 'todo', selectId }),\n      prependEntities([] as Todo[], { collection: 'todo', selectId })\n    );\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2, x: todo1 });\n    expect(store.todoIds()).toEqual(['x', 'z', 'y']);\n    expect(store.todoEntities()).toEqual([todo1, todo3, todo2]);\n  });\n\n  it('does not add entities with a custom id to the specified collection if they already exist', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      prependEntities(\n        [todo2, { ...todo2, text: 'NgRx' }, todo3, todo2],\n        todoConfig\n      )\n    );\n\n    const todoEntityMap = store.todoEntityMap();\n    const todoIds = store.todoIds();\n    const todoEntities = store.todoEntities();\n\n    patchState(\n      store,\n      prependEntities([] as Todo[], todoConfig),\n      prependEntities([todo3, todo2, { ...todo3, text: 'NgRx' }], todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toBe(todoEntityMap);\n    expect(store.todoIds()).toBe(todoIds);\n    expect(store.todoEntities()).toBe(todoEntities);\n    expect(store.todoEntityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.todoIds()).toEqual(['y', 'z']);\n    expect(store.todoEntities()).toEqual([todo2, todo3]);\n\n    patchState(store, prependEntities([todo1, todo2, todo3], todoConfig));\n\n    expect(store.todoEntityMap()).toEqual({ y: todo2, z: todo3, x: todo1 });\n    expect(store.todoIds()).toEqual(['x', 'y', 'z']);\n    expect(store.todoEntities()).toEqual([todo1, todo2, todo3]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/prepend-entity.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { prependEntity, entityConfig, withEntities } from '../../src';\nimport { Todo, todo1, todo2, User, user1, user2 } from '../mocks';\nimport { selectTodoId as selectId } from '../helpers';\n\ndescribe('prependEntity', () => {\n  it('prepends entity if it does not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, prependEntity(user1));\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n\n    patchState(store, prependEntity(user2));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.ids()).toEqual([2, 1]);\n    expect(store.entities()).toEqual([user2, user1]);\n  });\n\n  it('does not add entity if it already exists', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, prependEntity(user1));\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(store, prependEntity(user1));\n    patchState(store, prependEntity({ ...user1, firstName: 'Jack' }));\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n  });\n\n  it('prepends entity to the specified collection if it does not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      prependEntity(user1, { collection: 'user' }),\n      prependEntity(user2, { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.userIds()).toEqual([2, 1]);\n    expect(store.userEntities()).toEqual([user2, user1]);\n  });\n\n  it('does not add entity to the specified collection if it already exists', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      prependEntity(user1, { collection: 'user' }),\n      prependEntity(user1, { collection: 'user' })\n    );\n\n    const userEntityMap = store.userEntityMap();\n    const userIds = store.userIds();\n    const userEntities = store.userEntities();\n\n    patchState(\n      store,\n      prependEntity(user1, { collection: 'user' }),\n      prependEntity(user1, { collection: 'user' }),\n      prependEntity({ ...user1, firstName: 'Jack' }, { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toBe(userEntityMap);\n    expect(store.userIds()).toBe(userIds);\n    expect(store.userEntities()).toBe(userEntities);\n    expect(store.userEntityMap()).toEqual({ 1: user1 });\n    expect(store.userIds()).toEqual([1]);\n    expect(store.userEntities()).toEqual([user1]);\n  });\n\n  it('prepends entity with a custom id if it does not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, prependEntity(todo1, { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1 });\n    expect(store.ids()).toEqual(['x']);\n    expect(store.entities()).toEqual([todo1]);\n\n    patchState(store, prependEntity(todo2, { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.ids()).toEqual(['y', 'x']);\n    expect(store.entities()).toEqual([todo2, todo1]);\n  });\n\n  it('does not add entity with a custom id if it already exists', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      prependEntity(todo1, { selectId }),\n      prependEntity(todo2, { selectId })\n    );\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      prependEntity(todo1, { selectId }),\n      prependEntity({ ...todo1, text: 'NgRx' }, { selectId }),\n      prependEntity(todo2, { selectId }),\n      prependEntity(todo1, { selectId })\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.ids()).toEqual(['y', 'x']);\n    expect(store.entities()).toEqual([todo2, todo1]);\n  });\n\n  it('prepends entity with a custom id to the specified collection if it does not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(store, prependEntity(todo1, { collection: 'todo', selectId }));\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1 });\n    expect(store.todoIds()).toEqual(['x']);\n    expect(store.todoEntities()).toEqual([todo1]);\n\n    patchState(store, prependEntity(todo2, { collection: 'todo', selectId }));\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.todoIds()).toEqual(['y', 'x']);\n    expect(store.todoEntities()).toEqual([todo2, todo1]);\n  });\n\n  it('does not add entity with a custom id to the specified collection if it already exists', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      prependEntity(todo1, todoConfig),\n      prependEntity(todo2, todoConfig)\n    );\n\n    const todoEntityMap = store.todoEntityMap();\n    const todoIds = store.todoIds();\n    const todoEntities = store.todoEntities();\n\n    patchState(\n      store,\n      prependEntity(todo1, todoConfig),\n      prependEntity({ ...todo1, text: 'NgRx' }, todoConfig),\n      prependEntity(todo2, todoConfig),\n      prependEntity(todo1, todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toBe(todoEntityMap);\n    expect(store.todoIds()).toBe(todoIds);\n    expect(store.todoEntities()).toBe(todoEntities);\n    expect(store.todoEntityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.todoIds()).toEqual(['y', 'x']);\n    expect(store.todoEntities()).toEqual([todo2, todo1]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/remove-all-entities.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { removeAllEntities, setAllEntities, withEntities } from '../../src';\nimport { Todo, todo1, todo2, User, user1, user2 } from '../mocks';\nimport { selectTodoId } from '../helpers';\n\ndescribe('removeAllEntities', () => {\n  it('removes all entities', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, setAllEntities([user1, user2]), removeAllEntities());\n\n    expect(store.entityMap()).toEqual({});\n    expect(store.ids()).toEqual([]);\n    expect(store.entities()).toEqual([]);\n  });\n\n  it('removes all entities from specified entity collection', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      setAllEntities([todo1, todo2], {\n        collection: 'todo',\n        selectId: selectTodoId,\n      })\n    );\n    patchState(store, removeAllEntities({ collection: 'todo' }));\n\n    expect(store.todoEntityMap()).toEqual({});\n    expect(store.todoIds()).toEqual([]);\n    expect(store.todoEntities()).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/remove-entities.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport {\n  addEntities,\n  entityConfig,\n  removeEntities,\n  withEntities,\n} from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId } from '../helpers';\n\ndescribe('removeEntities', () => {\n  it('removes entities by ids', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3]),\n      removeEntities([user1.id, user3.id])\n    );\n\n    expect(store.entityMap()).toEqual({ 2: user2 });\n    expect(store.ids()).toEqual([2]);\n    expect(store.entities()).toEqual([user2]);\n  });\n\n  it('removes entities by predicate', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], { selectId: selectTodoId }),\n      removeEntities((todo) => todo.completed)\n    );\n\n    expect(store.entityMap()).toEqual({ y: todo2 });\n    expect(store.ids()).toEqual(['y']);\n    expect(store.entities()).toEqual([todo2]);\n  });\n\n  it('does not modify entity state if entities do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, addEntities([user1, user2, user3]));\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      removeEntities([20, 30]),\n      removeEntities((user) => user.id > 100)\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([user1, user2, user3]);\n  });\n\n  it('removes entities by ids from specified collection', () => {\n    const userConfig = entityConfig({\n      entity: type<User>(),\n      collection: 'users',\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(userConfig)\n    );\n    const store = new Store();\n\n    patchState(store, addEntities([user1, user2, user3], userConfig));\n    patchState(store, removeEntities([user1.id, user2.id], userConfig));\n\n    expect(store.usersEntityMap()).toEqual({ 3: user3 });\n    expect(store.usersIds()).toEqual([3]);\n    expect(store.usersEntities()).toEqual([user3]);\n  });\n\n  it('removes entities by predicate from specified collection', () => {\n    const userConfig = entityConfig({\n      entity: type<User>(),\n      collection: 'users',\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(userConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3], userConfig),\n      removeEntities((user) => user.lastName.startsWith('J'), userConfig)\n    );\n\n    expect(store.usersEntityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.usersIds()).toEqual([1, 2]);\n    expect(store.usersEntities()).toEqual([user1, user2]);\n  });\n\n  it('does not modify entity state if entities do not exist in specified collection', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId: selectTodoId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(store, addEntities([todo1, todo2, todo3], todoConfig));\n\n    const todoEntityMap = store.todoEntityMap();\n    const todoIds = store.todoIds();\n    const todoEntities = store.todoEntities();\n\n    patchState(\n      store,\n      removeEntities(['a', 'b'], todoConfig),\n      removeEntities((todo) => todo.text === 'NgRx', todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toBe(todoEntityMap);\n    expect(store.todoIds()).toBe(todoIds);\n    expect(store.todoEntities()).toBe(todoEntities);\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1, y: todo2, z: todo3 });\n    expect(store.todoIds()).toEqual(['x', 'y', 'z']);\n    expect(store.todoEntities()).toEqual([todo1, todo2, todo3]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/remove-entity.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport {\n  addEntities,\n  entityConfig,\n  removeEntity,\n  withEntities,\n} from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2 } from '../mocks';\nimport { selectTodoId } from '../helpers';\n\ndescribe('removeEntity', () => {\n  it('removes entity', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, addEntities([user1, user2]), removeEntity(user1.id));\n\n    expect(store.entityMap()).toEqual({ 2: user2 });\n    expect(store.ids()).toEqual([2]);\n    expect(store.entities()).toEqual([user2]);\n  });\n\n  it('does not modify entity state if entity does not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, addEntities([todo2, todo3], { selectId: selectTodoId }));\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(store, removeEntity(todo1._id));\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.ids()).toEqual(['y', 'z']);\n    expect(store.entities()).toEqual([todo2, todo3]);\n  });\n\n  it('removes entity from specified collection', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId: selectTodoId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], todoConfig),\n      removeEntity(todo1._id, todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.todoIds()).toEqual(['y', 'z']);\n    expect(store.todoEntities()).toEqual([todo2, todo3]);\n\n    patchState(store, removeEntity(todo2._id, todoConfig));\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3 });\n    expect(store.todoIds()).toEqual(['z']);\n    expect(store.todoEntities()).toEqual([todo3]);\n  });\n\n  it('does not modify entity state if entity does not exist in specified collection', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(store, addEntities([user1, user2], { collection: 'user' }));\n\n    const userEntityMap = store.userEntityMap();\n    const userIds = store.userIds();\n    const userEntities = store.userEntities();\n\n    patchState(\n      store,\n      removeEntity(200, { collection: 'user' }),\n      removeEntity(300, { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toBe(userEntityMap);\n    expect(store.userIds()).toBe(userIds);\n    expect(store.userEntities()).toBe(userEntities);\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.userIds()).toEqual([1, 2]);\n    expect(store.userEntities()).toEqual([user1, user2]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/set-all-entities.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { entityConfig, setAllEntities, withEntities } from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId as selectId } from '../helpers';\n\ndescribe('setAllEntities', () => {\n  it('replaces entity collection with provided entities', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, setAllEntities([user1, user2]));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([user1, user2]);\n\n    patchState(store, setAllEntities([user3, user2, user1]));\n\n    expect(store.entityMap()).toEqual({ 3: user3, 2: user2, 1: user1 });\n    expect(store.ids()).toEqual([3, 2, 1]);\n    expect(store.entities()).toEqual([user3, user2, user1]);\n\n    patchState(store, setAllEntities([] as User[]));\n\n    expect(store.entityMap()).toEqual({});\n    expect(store.ids()).toEqual([]);\n    expect(store.entities()).toEqual([]);\n  });\n\n  it('replaces specified entity collection with provided entities', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(store, setAllEntities([user1, user2], { collection: 'user' }));\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.userIds()).toEqual([1, 2]);\n    expect(store.userEntities()).toEqual([user1, user2]);\n\n    patchState(\n      store,\n      setAllEntities([user3, user2, user1], { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({ 3: user3, 2: user2, 1: user1 });\n    expect(store.userIds()).toEqual([3, 2, 1]);\n    expect(store.userEntities()).toEqual([user3, user2, user1]);\n\n    patchState(store, setAllEntities([] as User[], { collection: 'user' }));\n\n    expect(store.userEntityMap()).toEqual({});\n    expect(store.userIds()).toEqual([]);\n    expect(store.userEntities()).toEqual([]);\n  });\n\n  it('replaces entity collection with provided entities with a custom id', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, setAllEntities([todo2, todo3], { selectId }));\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.ids()).toEqual(['y', 'z']);\n    expect(store.entities()).toEqual([todo2, todo3]);\n\n    patchState(store, setAllEntities([todo3, todo2, todo1], { selectId }));\n\n    expect(store.entityMap()).toEqual({ z: todo3, y: todo2, x: todo1 });\n    expect(store.ids()).toEqual(['z', 'y', 'x']);\n    expect(store.entities()).toEqual([todo3, todo2, todo1]);\n\n    patchState(store, setAllEntities([] as Todo[], { selectId }));\n\n    expect(store.entityMap()).toEqual({});\n    expect(store.ids()).toEqual([]);\n    expect(store.entities()).toEqual([]);\n  });\n\n  it('replaces specified entity collection with provided entities with a custom id', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(store, setAllEntities([todo1, todo3], todoConfig));\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1, z: todo3 });\n    expect(store.todoIds()).toEqual(['x', 'z']);\n    expect(store.todoEntities()).toEqual([todo1, todo3]);\n\n    patchState(store, setAllEntities([todo3, todo2, todo1], todoConfig));\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2, x: todo1 });\n    expect(store.todoIds()).toEqual(['z', 'y', 'x']);\n    expect(store.todoEntities()).toEqual([todo3, todo2, todo1]);\n\n    patchState(store, setAllEntities([] as Todo[], todoConfig));\n\n    expect(store.todoEntityMap()).toEqual({});\n    expect(store.todoIds()).toEqual([]);\n    expect(store.todoEntities()).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/set-entities.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { entityConfig, setEntities, withEntities } from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId as selectId } from '../helpers';\n\ndescribe('setEntities', () => {\n  it('adds entities if they do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, setEntities([user1]));\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n\n    patchState(store, setEntities([user2, user3]));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([user1, user2, user3]);\n  });\n\n  it('replaces entities if they already exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntities([user1, user2, { ...user1, lastName: 'Hendrix' }])\n    );\n\n    expect(store.entityMap()).toEqual({\n      1: { ...user1, lastName: 'Hendrix' },\n      2: user2,\n    });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([\n      { ...user1, lastName: 'Hendrix' },\n      user2,\n    ]);\n\n    patchState(\n      store,\n      setEntities([user3, user2, { ...user2, firstName: 'Jack' }]),\n      setEntities([] as User[])\n    );\n\n    expect(store.entityMap()).toEqual({\n      1: { ...user1, lastName: 'Hendrix' },\n      2: { ...user2, firstName: 'Jack' },\n      3: user3,\n    });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([\n      { ...user1, lastName: 'Hendrix' },\n      { ...user2, firstName: 'Jack' },\n      user3,\n    ]);\n  });\n\n  it('adds entities to the specified collection if they do not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntities([user1, user2], { collection: 'user' }),\n      setEntities([user3], { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.userIds()).toEqual([1, 2, 3]);\n    expect(store.userEntities()).toEqual([user1, user2, user3]);\n  });\n\n  it('replaces entities to the specified collection if they already exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntities([user1, { ...user1, lastName: 'Hendrix' }, user3, user1], {\n        collection: 'user',\n      })\n    );\n\n    patchState(\n      store,\n      setEntities([] as User[], { collection: 'user' }),\n      setEntities([user3, user2, { ...user3, firstName: 'Jimmy' }], {\n        collection: 'user',\n      })\n    );\n\n    expect(store.userEntityMap()).toEqual({\n      1: user1,\n      3: { ...user3, firstName: 'Jimmy' },\n      2: user2,\n    });\n    expect(store.userIds()).toEqual([1, 3, 2]);\n    expect(store.userEntities()).toEqual([\n      user1,\n      { ...user3, firstName: 'Jimmy' },\n      user2,\n    ]);\n  });\n\n  it('adds entities with a custom id if they do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, setEntities([todo2, todo3], { selectId }));\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.ids()).toEqual(['y', 'z']);\n    expect(store.entities()).toEqual([todo2, todo3]);\n\n    patchState(\n      store,\n      setEntities([todo1], { selectId }),\n      setEntities([] as Todo[], { selectId })\n    );\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3, x: todo1 });\n    expect(store.ids()).toEqual(['y', 'z', 'x']);\n    expect(store.entities()).toEqual([todo2, todo3, todo1]);\n  });\n\n  it('replaces entities with a custom id if they already exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntities([todo1], { selectId }),\n      setEntities([todo2, { ...todo1, text: 'Signals' }], { selectId }),\n      setEntities([] as Todo[], { selectId })\n    );\n\n    patchState(\n      store,\n      setEntities([] as Todo[], { selectId }),\n      setEntities([todo3, todo2, { ...todo2, text: 'NgRx' }, todo1], {\n        selectId,\n      })\n    );\n\n    expect(store.entityMap()).toEqual({\n      x: todo1,\n      y: { ...todo2, text: 'NgRx' },\n      z: todo3,\n    });\n    expect(store.ids()).toEqual(['x', 'y', 'z']);\n    expect(store.entities()).toEqual([\n      todo1,\n      { ...todo2, text: 'NgRx' },\n      todo3,\n    ]);\n  });\n\n  it('adds entities with a custom id to the specified collection if they do not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntities([todo3, todo2], {\n        collection: 'todo',\n        selectId,\n      })\n    );\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2 });\n    expect(store.todoIds()).toEqual(['z', 'y']);\n    expect(store.todoEntities()).toEqual([todo3, todo2]);\n\n    patchState(\n      store,\n      setEntities([todo1], { collection: 'todo', selectId }),\n      setEntities([] as Todo[], { collection: 'todo', selectId })\n    );\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2, x: todo1 });\n    expect(store.todoIds()).toEqual(['z', 'y', 'x']);\n    expect(store.todoEntities()).toEqual([todo3, todo2, todo1]);\n  });\n\n  it('replaces entities with a custom id to the specified collection if they already exist', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntities(\n        [todo2, { ...todo2, text: 'NgRx' }, todo3, todo2],\n        todoConfig\n      ),\n      setEntities([] as Todo[], todoConfig),\n      setEntities([todo3, { ...todo3, text: 'NgRx' }, todo1], todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      y: todo2,\n      z: { ...todo3, text: 'NgRx' },\n      x: todo1,\n    });\n    expect(store.todoIds()).toEqual(['y', 'z', 'x']);\n    expect(store.todoEntities()).toEqual([\n      todo2,\n      { ...todo3, text: 'NgRx' },\n      todo1,\n    ]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/set-entity.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { entityConfig, setEntity, withEntities } from '../../src';\nimport { Todo, todo1, todo2, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId as selectId } from '../helpers';\n\ndescribe('setEntity', () => {\n  it('adds entity if it does not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, setEntity(user1));\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n\n    patchState(store, setEntity(user2));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([user1, user2]);\n  });\n\n  it('replaces entity if it already exists', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, setEntity(user1), setEntity(user2));\n    patchState(store, setEntity({ ...user1, firstName: 'Jack' }));\n\n    expect(store.entityMap()).toEqual({\n      1: { ...user1, firstName: 'Jack' },\n      2: user2,\n    });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([{ ...user1, firstName: 'Jack' }, user2]);\n  });\n\n  it('adds entity to the specified collection if it does not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntity(user1, { collection: 'user' }),\n      setEntity(user2, { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.userIds()).toEqual([1, 2]);\n    expect(store.userEntities()).toEqual([user1, user2]);\n  });\n\n  it('replaces entity to the specified collection if it already exists', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntity(user2, { collection: 'user' }),\n      setEntity(user1, { collection: 'user' }),\n      setEntity({ ...user1, lastName: 'Hendrix' }, { collection: 'user' }),\n      setEntity(user3, { collection: 'user' }),\n      setEntity({ ...user1, firstName: 'Jimmy' }, { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({\n      2: user2,\n      1: { ...user1, firstName: 'Jimmy' },\n      3: user3,\n    });\n    expect(store.userIds()).toEqual([2, 1, 3]);\n    expect(store.userEntities()).toEqual([\n      user2,\n      { ...user1, firstName: 'Jimmy' },\n      user3,\n    ]);\n  });\n\n  it('adds entity with a custom id if it does not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, setEntity(todo1, { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1 });\n    expect(store.ids()).toEqual(['x']);\n    expect(store.entities()).toEqual([todo1]);\n\n    patchState(store, setEntity(todo2, { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.ids()).toEqual(['x', 'y']);\n    expect(store.entities()).toEqual([todo1, todo2]);\n  });\n\n  it('replaces entity with a custom id if it already exists', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntity(todo1, { selectId }),\n      setEntity(todo2, { selectId })\n    );\n    patchState(store, setEntity({ ...todo2, text: 'NgRx' }, { selectId }));\n\n    expect(store.entityMap()).toEqual({\n      x: todo1,\n      y: { ...todo2, text: 'NgRx' },\n    });\n    expect(store.ids()).toEqual(['x', 'y']);\n    expect(store.entities()).toEqual([todo1, { ...todo2, text: 'NgRx' }]);\n  });\n\n  it('adds entity with a custom id to the specified collection if it does not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(store, setEntity(todo1, { collection: 'todo', selectId }));\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1 });\n    expect(store.todoIds()).toEqual(['x']);\n    expect(store.todoEntities()).toEqual([todo1]);\n\n    patchState(store, setEntity(todo2, { collection: 'todo', selectId }));\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.todoIds()).toEqual(['x', 'y']);\n    expect(store.todoEntities()).toEqual([todo1, todo2]);\n  });\n\n  it('replaces entity with a custom id to the specified collection if it already exists', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      setEntity(todo1, todoConfig),\n      setEntity(todo2, todoConfig)\n    );\n    patchState(\n      store,\n      setEntity({ ...todo2, text: 'Signals' }, todoConfig),\n      setEntity(todo1, todoConfig),\n      setEntity({ ...todo1, text: 'NgRx' }, todoConfig),\n      setEntity(todo2, todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      x: { ...todo1, text: 'NgRx' },\n      y: todo2,\n    });\n    expect(store.todoIds()).toEqual(['x', 'y']);\n    expect(store.todoEntities()).toEqual([{ ...todo1, text: 'NgRx' }, todo2]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/update-all-entities.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { addEntities, updateAllEntities, withEntities } from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId } from '../helpers';\n\ndescribe('updateAllEntities', () => {\n  it('updates all entities', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3]),\n      updateAllEntities((user) => ({ firstName: `Jimmy${user.id}` }))\n    );\n\n    expect(store.entityMap()).toEqual({\n      1: { ...user1, firstName: 'Jimmy1' },\n      2: { ...user2, firstName: 'Jimmy2' },\n      3: { ...user3, firstName: 'Jimmy3' },\n    });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([\n      { ...user1, firstName: 'Jimmy1' },\n      { ...user2, firstName: 'Jimmy2' },\n      { ...user3, firstName: 'Jimmy3' },\n    ]);\n\n    patchState(store, updateAllEntities({ lastName: 'Hendrix' }));\n\n    expect(store.entityMap()).toEqual({\n      1: { id: 1, firstName: 'Jimmy1', lastName: 'Hendrix' },\n      2: { id: 2, firstName: 'Jimmy2', lastName: 'Hendrix' },\n      3: { id: 3, firstName: 'Jimmy3', lastName: 'Hendrix' },\n    });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([\n      { id: 1, firstName: 'Jimmy1', lastName: 'Hendrix' },\n      { id: 2, firstName: 'Jimmy2', lastName: 'Hendrix' },\n      { id: 3, firstName: 'Jimmy3', lastName: 'Hendrix' },\n    ]);\n  });\n\n  it('does not modify entity state if entities do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      updateAllEntities({ text: '' }, { selectId: (todo) => todo._id }),\n      updateAllEntities((todo) => ({ completed: !todo.completed }), {\n        selectId: (todo) => todo._id,\n      })\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n\n    expect(store.entityMap()).toEqual({});\n    expect(store.ids()).toEqual([]);\n    expect(store.entities()).toEqual([]);\n  });\n\n  it('updates all entities from specified entity collection', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], {\n        collection: 'todo',\n        selectId: selectTodoId,\n      }),\n      updateAllEntities(\n        { completed: false },\n        {\n          collection: 'todo',\n          selectId: (todo) => todo._id,\n        }\n      )\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      x: { ...todo1, completed: false },\n      y: { ...todo2, completed: false },\n      z: { ...todo3, completed: false },\n    });\n    expect(store.todoIds()).toEqual(['x', 'y', 'z']);\n    expect(store.todoEntities()).toEqual([\n      { ...todo1, completed: false },\n      { ...todo2, completed: false },\n      { ...todo3, completed: false },\n    ]);\n\n    patchState(\n      store,\n      updateAllEntities(({ completed }) => ({ completed: !completed }), {\n        collection: 'todo',\n        selectId: (todo) => todo._id,\n      })\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      x: { ...todo1, completed: true },\n      y: { ...todo2, completed: true },\n      z: { ...todo3, completed: true },\n    });\n    expect(store.todoIds()).toEqual(['x', 'y', 'z']);\n    expect(store.todoEntities()).toEqual([\n      { ...todo1, completed: true },\n      { ...todo2, completed: true },\n      { ...todo3, completed: true },\n    ]);\n  });\n\n  it('does not modify entity state if entities do not exist in specified collection', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    const userEntityMap = store.userEntityMap();\n    const userEntities = store.userEntities();\n    const userIds = store.userIds();\n\n    patchState(\n      store,\n      updateAllEntities({ firstName: 'Jimmy' }, { collection: 'user' }),\n      updateAllEntities((user) => ({ lastName: `Hendrix${user.id}` }), {\n        collection: 'user',\n      })\n    );\n\n    expect(store.userEntityMap()).toBe(userEntityMap);\n    expect(store.userIds()).toBe(userIds);\n    expect(store.userEntities()).toBe(userEntities);\n\n    expect(store.userEntityMap()).toEqual({});\n    expect(store.userIds()).toEqual([]);\n    expect(store.userEntities()).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/update-entities.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport {\n  addEntities,\n  entityConfig,\n  updateEntities,\n  withEntities,\n} from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId } from '../helpers';\n\ndescribe('updateEntities', () => {\n  it('updates entities by ids', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3]),\n      updateEntities({\n        ids: [user1.id, user3.id, 'a'],\n        changes: ({ id }) => ({ firstName: `Jimmy${id}` }),\n      }),\n      updateEntities({\n        ids: [user1.id, user2.id, 'b'],\n        changes: { lastName: 'Hendrix' },\n      })\n    );\n\n    expect(store.entityMap()).toEqual({\n      1: { id: 1, firstName: 'Jimmy1', lastName: 'Hendrix' },\n      2: { ...user2, lastName: 'Hendrix' },\n      3: { ...user3, firstName: 'Jimmy3' },\n    });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([\n      { id: 1, firstName: 'Jimmy1', lastName: 'Hendrix' },\n      { ...user2, lastName: 'Hendrix' },\n      { ...user3, firstName: 'Jimmy3' },\n    ]);\n  });\n\n  it('updates entities by predicate', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], { selectId: selectTodoId })\n    );\n\n    patchState(\n      store,\n      updateEntities(\n        {\n          predicate: (todo) => todo.text.startsWith('Buy'),\n          changes: { completed: false },\n        },\n        { selectId: (todo) => todo._id }\n      ),\n      updateEntities(\n        {\n          predicate: ({ completed }) => !completed,\n          changes: ({ text }) => ({ text: `Don't ${text}` }),\n        },\n        { selectId: (todo) => todo._id }\n      )\n    );\n\n    expect(store.entityMap()).toEqual({\n      x: { _id: 'x', text: `Don't Buy milk`, completed: false },\n      y: { _id: 'y', text: `Don't Buy eggs`, completed: false },\n      z: todo3,\n    });\n    expect(store.ids()).toEqual(['x', 'y', 'z']);\n    expect(store.entities()).toEqual([\n      { _id: 'x', text: `Don't Buy milk`, completed: false },\n      { _id: 'y', text: `Don't Buy eggs`, completed: false },\n      todo3,\n    ]);\n  });\n\n  it('does not modify entity state if entities do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, addEntities([user1, user2, user3]));\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      updateEntities({\n        ids: [20, 30],\n        changes: ({ id }) => ({ firstName: `Jimmy${id}` }),\n      }),\n      updateEntities({\n        predicate: (user) => user.id > 100,\n        changes: { lastName: 'Hendrix' },\n      })\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([user1, user2, user3]);\n  });\n\n  it('updates entities by ids from specified collection', () => {\n    const userConfig = entityConfig({\n      entity: type<User>(),\n      collection: 'users',\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(userConfig)\n    );\n    const store = new Store();\n\n    patchState(store, addEntities([user1, user2, user3], userConfig));\n    patchState(\n      store,\n      updateEntities(\n        {\n          ids: [user1.id, user2.id, 20, 30],\n          changes: { lastName: 'Hendrix' },\n        },\n        userConfig\n      ),\n      updateEntities(\n        {\n          ids: [user1.id, user3.id],\n          changes: ({ id }) => ({ firstName: `Jimmy${id}` }),\n        },\n        userConfig\n      )\n    );\n\n    expect(store.usersEntityMap()).toEqual({\n      1: { id: 1, firstName: 'Jimmy1', lastName: 'Hendrix' },\n      2: { ...user2, lastName: 'Hendrix' },\n      3: { ...user3, firstName: 'Jimmy3' },\n    });\n    expect(store.usersIds()).toEqual([1, 2, 3]);\n    expect(store.usersEntities()).toEqual([\n      { id: 1, firstName: 'Jimmy1', lastName: 'Hendrix' },\n      { ...user2, lastName: 'Hendrix' },\n      { ...user3, firstName: 'Jimmy3' },\n    ]);\n  });\n\n  it('updates entities by predicate from specified collection', () => {\n    const userConfig = entityConfig({\n      entity: type<User>(),\n      collection: 'users',\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(userConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3], userConfig),\n      updateEntities(\n        {\n          predicate: ({ id }) => id < 3,\n          changes: { lastName: 'Hendrix' },\n        },\n        userConfig\n      ),\n      updateEntities(\n        {\n          predicate: ({ id }) => id > 2,\n          changes: ({ id }) => ({ firstName: `Jimmy${id}` }),\n        },\n        userConfig\n      )\n    );\n\n    expect(store.usersEntityMap()).toEqual({\n      1: { ...user1, lastName: 'Hendrix' },\n      2: { ...user2, lastName: 'Hendrix' },\n      3: { ...user3, firstName: 'Jimmy3' },\n    });\n    expect(store.usersIds()).toEqual([1, 2, 3]);\n    expect(store.usersEntities()).toEqual([\n      { ...user1, lastName: 'Hendrix' },\n      { ...user2, lastName: 'Hendrix' },\n      { ...user3, firstName: 'Jimmy3' },\n    ]);\n  });\n\n  it('does not modify entity state if entities do not exist in specified collection', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId: (todo) => todo._id,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(store, addEntities([todo1, todo2, todo3], todoConfig));\n\n    const todoEntityMap = store.todoEntityMap();\n    const todoIds = store.todoIds();\n    const todoEntities = store.todoEntities();\n\n    patchState(\n      store,\n      updateEntities(\n        {\n          ids: ['a', 'b'],\n          changes: { completed: false },\n        },\n        todoConfig\n      ),\n      updateEntities(\n        {\n          predicate: (todo) => todo.text === 'NgRx',\n          changes: ({ text }) => ({ text: `Don't ${text}` }),\n        },\n        todoConfig\n      )\n    );\n\n    expect(store.todoEntityMap()).toBe(todoEntityMap);\n    expect(store.todoIds()).toBe(todoIds);\n    expect(store.todoEntities()).toBe(todoEntities);\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1, y: todo2, z: todo3 });\n    expect(store.todoIds()).toEqual(['x', 'y', 'z']);\n    expect(store.todoEntities()).toEqual([todo1, todo2, todo3]);\n  });\n\n  it('updates entity ids', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3]),\n      updateEntities({\n        ids: [user1.id, user2.id],\n        changes: ({ id }) => ({ id: id + 10, firstName: `Jimmy${id}` }),\n      }),\n      updateEntities({\n        ids: [user3.id],\n        changes: { id: 303, lastName: 'Hendrix' },\n      })\n    );\n\n    expect(store.entityMap()).toEqual({\n      11: { ...user1, id: 11, firstName: 'Jimmy1' },\n      12: { ...user2, id: 12, firstName: 'Jimmy2' },\n      303: { ...user3, id: 303, lastName: 'Hendrix' },\n    });\n    expect(store.ids()).toEqual([11, 12, 303]);\n\n    patchState(\n      store,\n      updateEntities({\n        predicate: ({ id }) => id > 300,\n        changes: ({ id }) => ({ id: id - 300 }),\n      }),\n      updateEntities({\n        predicate: ({ firstName }) => firstName === 'Jimmy1',\n        changes: { id: 1, firstName: 'Jimmy' },\n      })\n    );\n\n    expect(store.entityMap()).toEqual({\n      1: { ...user1, id: 1, firstName: 'Jimmy' },\n      12: { ...user2, id: 12, firstName: 'Jimmy2' },\n      3: { ...user3, id: 3, lastName: 'Hendrix' },\n    });\n    expect(store.ids()).toEqual([1, 12, 3]);\n  });\n\n  it('updates custom entity ids', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], { selectId: (todo) => todo._id }),\n      updateEntities(\n        {\n          ids: [todo1._id, todo2._id],\n          changes: ({ _id }) => ({ _id: _id + 10, text: `Todo ${_id}` }),\n        },\n        { selectId: (todo) => todo._id }\n      ),\n      updateEntities(\n        {\n          ids: [todo3._id],\n          changes: { _id: 'z30' },\n        },\n        { selectId: (todo) => todo._id }\n      )\n    );\n\n    expect(store.entityMap()).toEqual({\n      x10: { ...todo1, _id: 'x10', text: 'Todo x' },\n      y10: { ...todo2, _id: 'y10', text: 'Todo y' },\n      z30: { ...todo3, _id: 'z30' },\n    });\n    expect(store.ids()).toEqual(['x10', 'y10', 'z30']);\n\n    patchState(\n      store,\n      updateEntities(\n        {\n          predicate: ({ text }) => text.startsWith('Todo '),\n          changes: ({ _id }) => ({ _id: `${_id}0` }),\n        },\n        { selectId: (todo) => todo._id }\n      ),\n      updateEntities(\n        {\n          predicate: ({ _id }) => _id === 'z30',\n          changes: { _id: 'z' },\n        },\n        { selectId: (todo) => todo._id }\n      )\n    );\n\n    expect(store.entityMap()).toEqual({\n      x100: { ...todo1, _id: 'x100', text: 'Todo x' },\n      y100: { ...todo2, _id: 'y100', text: 'Todo y' },\n      z: { ...todo3, _id: 'z' },\n    });\n    expect(store.ids()).toEqual(['x100', 'y100', 'z']);\n  });\n\n  it('updates entity ids from specified collection', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3], { collection: 'user' }),\n      updateEntities(\n        {\n          ids: [user1.id, user2.id],\n          changes: ({ id }) => ({ id: id + 100, firstName: `Jimmy${id}` }),\n        },\n        { collection: 'user' }\n      ),\n      updateEntities(\n        {\n          ids: [user3.id],\n          changes: { id: 303, lastName: 'Hendrix' },\n        },\n        { collection: 'user' }\n      )\n    );\n\n    expect(store.userEntityMap()).toEqual({\n      101: { ...user1, id: 101, firstName: 'Jimmy1' },\n      102: { ...user2, id: 102, firstName: 'Jimmy2' },\n      303: { ...user3, id: 303, lastName: 'Hendrix' },\n    });\n    expect(store.userIds()).toEqual([101, 102, 303]);\n\n    patchState(\n      store,\n      updateEntities(\n        {\n          predicate: ({ id }) => id > 300,\n          changes: ({ id }) => ({ id: id - 300 }),\n        },\n        { collection: 'user' }\n      ),\n      updateEntities(\n        {\n          predicate: ({ firstName }) => firstName === 'Jimmy1',\n          changes: { id: 1, firstName: 'Jimmy' },\n        },\n        { collection: 'user' }\n      )\n    );\n\n    expect(store.userEntityMap()).toEqual({\n      1: { ...user1, id: 1, firstName: 'Jimmy' },\n      102: { ...user2, id: 102, firstName: 'Jimmy2' },\n      3: { ...user3, id: 3, lastName: 'Hendrix' },\n    });\n    expect(store.userIds()).toEqual([1, 102, 3]);\n  });\n\n  it('updates custom entity ids from specified collection', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], {\n        collection: 'todo',\n        selectId: (todo) => todo._id,\n      }),\n      updateEntities(\n        {\n          ids: [todo1._id, todo2._id],\n          changes: ({ _id }) => ({ _id: _id + 10, text: `Todo ${_id}` }),\n        },\n        { collection: 'todo', selectId: (todo) => todo._id }\n      ),\n      updateEntities(\n        {\n          ids: [todo3._id],\n          changes: { _id: 'z30' },\n        },\n        { collection: 'todo', selectId: (todo) => todo._id }\n      )\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      x10: { ...todo1, _id: 'x10', text: 'Todo x' },\n      y10: { ...todo2, _id: 'y10', text: 'Todo y' },\n      z30: { ...todo3, _id: 'z30' },\n    });\n    expect(store.todoIds()).toEqual(['x10', 'y10', 'z30']);\n\n    patchState(\n      store,\n      updateEntities(\n        {\n          predicate: ({ text }) => text.startsWith('Todo '),\n          changes: ({ _id }) => ({ _id: `${_id}0` }),\n        },\n        { collection: 'todo', selectId: (todo) => todo._id }\n      ),\n      updateEntities(\n        {\n          predicate: ({ _id }) => _id === 'z30',\n          changes: { _id: 'z' },\n        },\n        { collection: 'todo', selectId: (todo) => todo._id }\n      )\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      x100: { ...todo1, _id: 'x100', text: 'Todo x' },\n      y100: { ...todo2, _id: 'y100', text: 'Todo y' },\n      z: { ...todo3, _id: 'z' },\n    });\n    expect(store.todoIds()).toEqual(['x100', 'y100', 'z']);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/update-entity.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport {\n  addEntities,\n  entityConfig,\n  updateEntity,\n  withEntities,\n} from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId } from '../helpers';\n\ndescribe('updateEntity', () => {\n  it('updates entity', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3]),\n      updateEntity({\n        id: user1.id,\n        changes: ({ id }) => ({ firstName: `Jimmy${id}` }),\n      })\n    );\n\n    expect(store.entityMap()).toEqual({\n      1: { ...user1, firstName: 'Jimmy1' },\n      2: user2,\n      3: user3,\n    });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([\n      { ...user1, firstName: 'Jimmy1' },\n      user2,\n      user3,\n    ]);\n\n    patchState(\n      store,\n      updateEntity({\n        id: user1.id,\n        changes: { lastName: 'Hendrix' },\n      }),\n      updateEntity({\n        id: user3.id,\n        changes: ({ id }) => ({ firstName: `Stevie${id}` }),\n      })\n    );\n\n    expect(store.entityMap()).toEqual({\n      1: { id: 1, firstName: 'Jimmy1', lastName: 'Hendrix' },\n      2: user2,\n      3: { ...user3, firstName: 'Stevie3' },\n    });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([\n      { id: 1, firstName: 'Jimmy1', lastName: 'Hendrix' },\n      user2,\n      { ...user3, firstName: 'Stevie3' },\n    ]);\n  });\n\n  it('does not modify entity state if entity do not exist', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      selectId: selectTodoId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(store, addEntities([todo2, todo3], todoConfig));\n\n    const entityMap = store.entityMap();\n    const ids = store.ids();\n    const entities = store.entities();\n\n    patchState(\n      store,\n      updateEntity(\n        {\n          id: todo1._id,\n          changes: { text: '' },\n        },\n        todoConfig\n      ),\n      updateEntity(\n        {\n          id: 'a',\n          changes: ({ completed }) => ({ completed: !completed }),\n        },\n        todoConfig\n      )\n    );\n\n    expect(store.entityMap()).toBe(entityMap);\n    expect(store.ids()).toBe(ids);\n    expect(store.entities()).toBe(entities);\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.ids()).toEqual(['y', 'z']);\n    expect(store.entities()).toEqual([todo2, todo3]);\n  });\n\n  it('updates entity from specified entity collection', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], {\n        collection: 'todo',\n        selectId: selectTodoId,\n      }),\n      updateEntity(\n        { id: todo1._id, changes: { text: '' } },\n        { collection: 'todo', selectId: selectTodoId }\n      ),\n      updateEntity(\n        {\n          id: todo2._id,\n          changes: ({ completed }) => ({ completed: !completed }),\n        },\n        { collection: 'todo', selectId: (todo) => todo._id }\n      )\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      x: { ...todo1, text: '' },\n      y: { ...todo2, completed: true },\n      z: todo3,\n    });\n    expect(store.todoIds()).toEqual(['x', 'y', 'z']);\n    expect(store.todoEntities()).toEqual([\n      { ...todo1, text: '' },\n      { ...todo2, completed: true },\n      todo3,\n    ]);\n  });\n\n  it('does not modify entity state if entity do not exist in specified collection', () => {\n    const userConfig = entityConfig({\n      entity: type<User>(),\n      collection: 'user',\n      selectId: (user) => user.id,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(userConfig)\n    );\n    const store = new Store();\n\n    patchState(store, addEntities([user1, user2, user3], userConfig));\n\n    const userEntityMap = store.userEntityMap();\n    const userEntities = store.userEntities();\n    const userIds = store.userIds();\n\n    patchState(\n      store,\n      updateEntity({ id: 10, changes: { firstName: '' } }, userConfig),\n      updateEntity(\n        {\n          id: 100,\n          changes: ({ id, lastName }) => ({ lastName: `${lastName}${id}` }),\n        },\n        userConfig\n      )\n    );\n\n    expect(store.userEntityMap()).toBe(userEntityMap);\n    expect(store.userIds()).toBe(userIds);\n    expect(store.userEntities()).toBe(userEntities);\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.userIds()).toEqual([1, 2, 3]);\n    expect(store.userEntities()).toEqual([user1, user2, user3]);\n  });\n\n  it('updates an entity id', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3]),\n      updateEntity({\n        id: user1.id,\n        changes: ({ id }) => ({ id: id + 10 }),\n      })\n    );\n\n    expect(store.entityMap()).toEqual({\n      11: { ...user1, id: 11 },\n      2: user2,\n      3: user3,\n    });\n    expect(store.ids()).toEqual([11, 2, 3]);\n    expect(store.entities()).toEqual([{ ...user1, id: 11 }, user2, user3]);\n\n    patchState(\n      store,\n      updateEntity({\n        id: 11,\n        changes: { id: 101, firstName: 'Jimmy1' },\n      }),\n      updateEntity({\n        id: user3.id,\n        changes: ({ id }) => ({ id: 303, firstName: `Stevie${id}` }),\n      })\n    );\n\n    expect(store.entityMap()).toEqual({\n      101: { ...user1, id: 101, firstName: 'Jimmy1' },\n      2: user2,\n      303: { ...user3, id: 303, firstName: 'Stevie3' },\n    });\n    expect(store.ids()).toEqual([101, 2, 303]);\n  });\n\n  it('updates a custom entity id', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], {\n        selectId: (todo) => todo._id,\n      }),\n      updateEntity(\n        {\n          id: todo2._id,\n          changes: ({ _id, text }) => ({ _id: _id + 200, text: `${text} 200` }),\n        },\n        { selectId: (todo) => todo._id }\n      ),\n      updateEntity(\n        {\n          id: todo3._id,\n          changes: { _id: 'z300', text: 'Todo 300' },\n        },\n        { selectId: (todo) => todo._id }\n      )\n    );\n\n    expect(store.entityMap()).toEqual({\n      x: todo1,\n      y200: { ...todo2, _id: 'y200', text: 'Buy eggs 200' },\n      z300: { ...todo3, _id: 'z300', text: 'Todo 300' },\n    });\n    expect(store.ids()).toEqual(['x', 'y200', 'z300']);\n  });\n\n  it('updates an entity id from specified entity collection', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([user1, user2, user3], { collection: 'user' }),\n      updateEntity(\n        {\n          id: user1.id,\n          changes: ({ id }) => ({ id: id + 100, firstName: 'Jimi' }),\n        },\n        { collection: 'user' }\n      ),\n      updateEntity(\n        {\n          id: user2.id,\n          changes: { id: 202, lastName: 'Hendrix' },\n        },\n        { collection: 'user' }\n      )\n    );\n\n    expect(store.userEntityMap()).toEqual({\n      101: { ...user1, id: 101, firstName: 'Jimi' },\n      202: { ...user2, id: 202, lastName: 'Hendrix' },\n      3: user3,\n    });\n    expect(store.userIds()).toEqual([101, 202, 3]);\n  });\n\n  it('updates a custom entity id from specified entity collection', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      addEntities([todo1, todo2, todo3], {\n        collection: 'todo',\n        selectId: (todo) => todo._id,\n      }),\n      updateEntity(\n        {\n          id: todo2._id,\n          changes: ({ _id }) => ({ _id: `${_id}200`, text: 'Todo 200' }),\n        },\n        { collection: 'todo', selectId: (todo) => todo._id }\n      ),\n      updateEntity(\n        {\n          id: todo3._id,\n          changes: { _id: '303' },\n        },\n        { collection: 'todo', selectId: (todo) => todo._id }\n      )\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      x: todo1,\n      y200: { ...todo2, _id: 'y200', text: 'Todo 200' },\n      303: { ...todo3, _id: '303' },\n    });\n    expect(store.todoIds()).toEqual(['x', 'y200', '303']);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/upsert-entities.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { entityConfig, upsertEntities, withEntities } from '../../src';\nimport { Todo, todo1, todo2, todo3, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId as selectId } from '../helpers';\n\nconst user1WithAge: User = {\n  ...user1,\n  age: 40,\n};\nconst newUser1WithoutAge: User = {\n  ...user2,\n  id: user1WithAge.id,\n};\nconst expectedUser1: User = {\n  ...user2,\n  id: user1WithAge.id,\n  age: user1WithAge.age,\n};\n\ndescribe('upsertEntities', () => {\n  it('adds entities if they do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, upsertEntities([user1]));\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n\n    patchState(store, upsertEntities([user2, user3]));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([user1, user2, user3]);\n  });\n\n  it('updates entities if they already exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntities([user1WithAge, user2, { ...user2, lastName: 'Hendrix' }])\n    );\n\n    expect(store.entityMap()).toEqual({\n      1: user1WithAge,\n      2: { ...user2, lastName: 'Hendrix' },\n    });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([\n      user1WithAge,\n      { ...user2, lastName: 'Hendrix' },\n    ]);\n\n    patchState(\n      store,\n      upsertEntities([user3, newUser1WithoutAge]),\n      upsertEntities([] as User[])\n    );\n\n    expect(store.entityMap()).toEqual({\n      1: expectedUser1,\n      2: { ...user2, lastName: 'Hendrix' },\n      3: user3,\n    });\n    expect(store.ids()).toEqual([1, 2, 3]);\n    expect(store.entities()).toEqual([\n      expectedUser1,\n      { ...user2, lastName: 'Hendrix' },\n      user3,\n    ]);\n  });\n\n  it('adds entities to the specified collection if they do not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntities([user1, user2], { collection: 'user' }),\n      upsertEntities([user3], { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2, 3: user3 });\n    expect(store.userIds()).toEqual([1, 2, 3]);\n    expect(store.userEntities()).toEqual([user1, user2, user3]);\n  });\n\n  it('updates entities to the specified collection if they already exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntities([user1WithAge, user3, newUser1WithoutAge], {\n        collection: 'user',\n      })\n    );\n\n    patchState(\n      store,\n      upsertEntities([] as User[], { collection: 'user' }),\n      upsertEntities([user3, user2, { ...user3, firstName: 'Jimmy' }], {\n        collection: 'user',\n      })\n    );\n\n    expect(store.userEntityMap()).toEqual({\n      1: expectedUser1,\n      3: { ...user3, firstName: 'Jimmy' },\n      2: user2,\n    });\n    expect(store.userIds()).toEqual([1, 3, 2]);\n    expect(store.userEntities()).toEqual([\n      expectedUser1,\n      { ...user3, firstName: 'Jimmy' },\n      user2,\n    ]);\n  });\n\n  it('adds entities with a custom id if they do not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, upsertEntities([todo2, todo3], { selectId }));\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.ids()).toEqual(['y', 'z']);\n    expect(store.entities()).toEqual([todo2, todo3]);\n\n    patchState(\n      store,\n      upsertEntities([todo1], { selectId }),\n      upsertEntities([] as Todo[], { selectId })\n    );\n\n    expect(store.entityMap()).toEqual({ y: todo2, z: todo3, x: todo1 });\n    expect(store.ids()).toEqual(['y', 'z', 'x']);\n    expect(store.entities()).toEqual([todo2, todo3, todo1]);\n  });\n\n  it('updates entities with a custom id if they already exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntities([todo1], { selectId }),\n      upsertEntities([todo2, { ...todo1, text: 'Signals' }], { selectId }),\n      upsertEntities([] as Todo[], { selectId })\n    );\n\n    patchState(\n      store,\n      upsertEntities([] as Todo[], { selectId }),\n      upsertEntities([todo3, todo2, { ...todo2, text: 'NgRx' }, todo1], {\n        selectId,\n      })\n    );\n\n    expect(store.entityMap()).toEqual({\n      x: todo1,\n      y: { ...todo2, text: 'NgRx' },\n      z: todo3,\n    });\n    expect(store.ids()).toEqual(['x', 'y', 'z']);\n    expect(store.entities()).toEqual([\n      todo1,\n      { ...todo2, text: 'NgRx' },\n      todo3,\n    ]);\n  });\n\n  it('adds entities with a custom id to the specified collection if they do not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntities([todo3, todo2], {\n        collection: 'todo',\n        selectId,\n      })\n    );\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2 });\n    expect(store.todoIds()).toEqual(['z', 'y']);\n    expect(store.todoEntities()).toEqual([todo3, todo2]);\n\n    patchState(\n      store,\n      upsertEntities([todo1], { collection: 'todo', selectId }),\n      upsertEntities([] as Todo[], { collection: 'todo', selectId })\n    );\n\n    expect(store.todoEntityMap()).toEqual({ z: todo3, y: todo2, x: todo1 });\n    expect(store.todoIds()).toEqual(['z', 'y', 'x']);\n    expect(store.todoEntities()).toEqual([todo3, todo2, todo1]);\n  });\n\n  it('updates entities with a custom id to the specified collection if they already exist', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntities(\n        [todo2, { ...todo2, text: 'NgRx' }, todo3, todo2],\n        todoConfig\n      ),\n      upsertEntities([] as Todo[], todoConfig),\n      upsertEntities([todo3, { ...todo3, text: 'NgRx' }, todo1], todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      y: todo2,\n      z: { ...todo3, text: 'NgRx' },\n      x: todo1,\n    });\n    expect(store.todoIds()).toEqual(['y', 'z', 'x']);\n    expect(store.todoEntities()).toEqual([\n      todo2,\n      { ...todo3, text: 'NgRx' },\n      todo1,\n    ]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/updaters/upsert-entity.spec.ts",
    "content": "import { patchState, signalStore, type } from '@ngrx/signals';\nimport { entityConfig, upsertEntity, withEntities } from '../../src';\nimport { Todo, todo1, todo2, User, user1, user2, user3 } from '../mocks';\nimport { selectTodoId as selectId } from '../helpers';\n\nconst user2WithAge: User = {\n  ...user2,\n  age: 30,\n};\nconst newUser2WithoutAge: User = {\n  ...user3,\n  id: user2WithAge.id,\n};\nconst expectedUser2: User = {\n  ...user3,\n  id: user2WithAge.id,\n  age: user2WithAge.age,\n};\n\ndescribe('upsertEntity', () => {\n  it('adds entity if it does not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, upsertEntity(user1));\n\n    expect(store.entityMap()).toEqual({ 1: user1 });\n    expect(store.ids()).toEqual([1]);\n    expect(store.entities()).toEqual([user1]);\n\n    patchState(store, upsertEntity(user2));\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([user1, user2]);\n  });\n\n  it('updates entity if it already exists', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<User>());\n    const store = new Store();\n\n    patchState(store, upsertEntity(user1), upsertEntity(user2WithAge));\n    patchState(store, upsertEntity(newUser2WithoutAge));\n\n    expect(store.entityMap()).toEqual({\n      1: user1,\n      2: expectedUser2,\n    });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([user1, expectedUser2]);\n  });\n\n  it('adds entity to the specified collection if it does not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntity(user1, { collection: 'user' }),\n      upsertEntity(user2, { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.userIds()).toEqual([1, 2]);\n    expect(store.userEntities()).toEqual([user1, user2]);\n  });\n\n  it('updates entity to the specified collection if it already exists', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<User>(),\n        collection: 'user',\n      })\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntity(user1, { collection: 'user' }),\n      upsertEntity(user2WithAge, { collection: 'user' }),\n      upsertEntity({ ...user1, firstName: 'Jimmy' }, { collection: 'user' }),\n      upsertEntity(user3, { collection: 'user' }),\n      upsertEntity(newUser2WithoutAge, { collection: 'user' })\n    );\n\n    expect(store.userEntityMap()).toEqual({\n      1: { ...user1, firstName: 'Jimmy' },\n      2: expectedUser2,\n      3: user3,\n    });\n    expect(store.userIds()).toEqual([1, 2, 3]);\n    expect(store.userEntities()).toEqual([\n      { ...user1, firstName: 'Jimmy' },\n      expectedUser2,\n      user3,\n    ]);\n  });\n\n  it('adds entity with a custom id if it does not exist', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(store, upsertEntity(todo1, { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1 });\n    expect(store.ids()).toEqual(['x']);\n    expect(store.entities()).toEqual([todo1]);\n\n    patchState(store, upsertEntity(todo2, { selectId }));\n\n    expect(store.entityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.ids()).toEqual(['x', 'y']);\n    expect(store.entities()).toEqual([todo1, todo2]);\n  });\n\n  it('updates entity with a custom id if it already exists', () => {\n    const Store = signalStore({ protectedState: false }, withEntities<Todo>());\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntity(todo1, { selectId }),\n      upsertEntity(todo2, { selectId })\n    );\n    patchState(store, upsertEntity({ ...todo2, text: 'NgRx' }, { selectId }));\n\n    expect(store.entityMap()).toEqual({\n      x: todo1,\n      y: { ...todo2, text: 'NgRx' },\n    });\n    expect(store.ids()).toEqual(['x', 'y']);\n    expect(store.entities()).toEqual([todo1, { ...todo2, text: 'NgRx' }]);\n  });\n\n  it('adds entity with a custom id to the specified collection if it does not exist', () => {\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities({\n        entity: type<Todo>(),\n        collection: 'todo',\n      })\n    );\n    const store = new Store();\n\n    patchState(store, upsertEntity(todo1, { collection: 'todo', selectId }));\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1 });\n    expect(store.todoIds()).toEqual(['x']);\n    expect(store.todoEntities()).toEqual([todo1]);\n\n    patchState(store, upsertEntity(todo2, { collection: 'todo', selectId }));\n\n    expect(store.todoEntityMap()).toEqual({ x: todo1, y: todo2 });\n    expect(store.todoIds()).toEqual(['x', 'y']);\n    expect(store.todoEntities()).toEqual([todo1, todo2]);\n  });\n\n  it('updates entity with a custom id to the specified collection if it already exists', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId,\n    });\n\n    const Store = signalStore(\n      { protectedState: false },\n      withEntities(todoConfig)\n    );\n    const store = new Store();\n\n    patchState(\n      store,\n      upsertEntity(todo1, todoConfig),\n      upsertEntity(todo2, todoConfig)\n    );\n    patchState(\n      store,\n      upsertEntity({ ...todo2, text: 'Signals' }, todoConfig),\n      upsertEntity(todo1, todoConfig),\n      upsertEntity({ ...todo1, text: 'NgRx' }, todoConfig),\n      upsertEntity(todo2, todoConfig)\n    );\n\n    expect(store.todoEntityMap()).toEqual({\n      x: { ...todo1, text: 'NgRx' },\n      y: todo2,\n    });\n    expect(store.todoIds()).toEqual(['x', 'y']);\n    expect(store.todoEntities()).toEqual([{ ...todo1, text: 'NgRx' }, todo2]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/spec/with-entities.spec.ts",
    "content": "import { isSignal } from '@angular/core';\nimport { patchState, signalStore, type, withMethods } from '@ngrx/signals';\nimport { addEntities, entityConfig, withEntities } from '../src';\nimport { Todo, todo2, todo3, User, user1, user2 } from './mocks';\nimport { selectTodoId } from './helpers';\n\ndescribe('withEntities', () => {\n  it('adds entity feature to the store', () => {\n    const Store = signalStore(\n      withEntities<User>(),\n      withMethods((store) => ({\n        addUsers(): void {\n          patchState(store, addEntities([user1, user2]));\n        },\n      }))\n    );\n    const store = new Store();\n\n    expect(isSignal(store.entityMap)).toBe(true);\n    expect(store.entityMap()).toEqual({});\n\n    expect(isSignal(store.ids)).toBe(true);\n    expect(store.ids()).toEqual([]);\n\n    expect(isSignal(store.entities)).toBe(true);\n    expect(store.entities()).toEqual([]);\n\n    store.addUsers();\n\n    expect(store.entityMap()).toEqual({ 1: user1, 2: user2 });\n    expect(store.ids()).toEqual([1, 2]);\n    expect(store.entities()).toEqual([user1, user2]);\n  });\n\n  it('adds named entity feature to the store', () => {\n    const Store = signalStore(\n      withEntities({ entity: type<User>(), collection: 'user' }),\n      withMethods((store) => ({\n        addUsers(): void {\n          patchState(\n            store,\n            addEntities([user2, user1], { collection: 'user' })\n          );\n        },\n      }))\n    );\n    const store = new Store();\n\n    expect(isSignal(store.userEntityMap)).toBe(true);\n    expect(store.userEntityMap()).toEqual({});\n\n    expect(isSignal(store.userIds)).toBe(true);\n    expect(store.userIds()).toEqual([]);\n\n    expect(isSignal(store.userEntities)).toBe(true);\n    expect(store.userEntities()).toEqual([]);\n\n    store.addUsers();\n\n    expect(store.userEntityMap()).toEqual({ 2: user2, 1: user1 });\n    expect(store.userIds()).toEqual([2, 1]);\n    expect(store.userEntities()).toEqual([user2, user1]);\n  });\n\n  it('combines multiple entity features', () => {\n    const todoConfig = entityConfig({\n      entity: type<Todo>(),\n      collection: 'todo',\n      selectId: selectTodoId,\n    });\n\n    const Store = signalStore(\n      withEntities<User>(),\n      withEntities(todoConfig),\n      withMethods((store) => ({\n        addEntities(): void {\n          patchState(\n            store,\n            addEntities([user2, user1]),\n            addEntities([todo2, todo3], todoConfig)\n          );\n        },\n      }))\n    );\n    const store = new Store();\n\n    expect(isSignal(store.entityMap)).toBe(true);\n    expect(store.entityMap()).toEqual({});\n    expect(isSignal(store.todoEntityMap)).toBe(true);\n    expect(store.todoEntityMap()).toEqual({});\n\n    expect(isSignal(store.ids)).toBe(true);\n    expect(store.ids()).toEqual([]);\n    expect(isSignal(store.todoIds)).toBe(true);\n    expect(store.todoIds()).toEqual([]);\n\n    expect(isSignal(store.entities)).toBe(true);\n    expect(store.entities()).toEqual([]);\n    expect(isSignal(store.todoEntities)).toBe(true);\n    expect(store.todoEntities()).toEqual([]);\n\n    store.addEntities();\n\n    expect(store.entityMap()).toEqual({ 2: user2, 1: user1 });\n    expect(store.ids()).toEqual([2, 1]);\n    expect(store.entities()).toEqual([user2, user1]);\n    expect(store.todoEntityMap()).toEqual({ y: todo2, z: todo3 });\n    expect(store.todoIds()).toEqual(['y', 'z']);\n    expect(store.todoEntities()).toEqual([todo2, todo3]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/entities/src/entity-config.ts",
    "content": "import { SelectEntityId } from './models';\n\nexport function entityConfig<Entity, Collection extends string>(config: {\n  entity: Entity;\n  collection: Collection;\n  selectId: SelectEntityId<NoInfer<Entity>>;\n}): typeof config;\nexport function entityConfig<Entity>(config: {\n  entity: Entity;\n  selectId: SelectEntityId<NoInfer<Entity>>;\n}): typeof config;\nexport function entityConfig<Entity, Collection extends string>(config: {\n  entity: Entity;\n  collection: Collection;\n}): typeof config;\nexport function entityConfig<Entity>(config: { entity: Entity }): typeof config;\n/**\n * @description\n *\n * Creates a custom entity configuration and ensures strong typing.\n * Allows defining named entity collections and a custom id selector.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore, type, withMethods } from '@ngrx/signals';\n * import { addEntity, entityConfig, withEntities } from '@ngrx/signals/entities';\n *\n * type Todo = { key: number; text: string };\n *\n * const todoConfig = entityConfig({\n *   entity: type<Todo>(),\n *   collection: 'todo',\n *   selectId: (todo) => todo.key,\n * });\n *\n * export const TodosStore = signalStore(\n *   // 👇 Adds `todoEntityMap`, `todoIds`, and `todoEntities` signals to the store.\n *   withEntities(todoConfig),\n *   withMethods((store) => ({\n *     addTodo(todo: Todo): void {\n *       patchState(store, addEntity(todo, todoConfig));\n *     },\n *   }))\n * );\n * ```\n */\nexport function entityConfig<Entity>(config: {\n  entity: Entity;\n  collection?: string;\n  selectId?: SelectEntityId<Entity>;\n}): typeof config {\n  return config;\n}\n"
  },
  {
    "path": "modules/signals/entities/src/helpers.ts",
    "content": "import {\n  DidMutate,\n  EntityChanges,\n  EntityId,\n  EntityPredicate,\n  EntityState,\n  SelectEntityId,\n} from './models';\n\nconst defaultSelectId: SelectEntityId<{ id: EntityId }> = (entity) => entity.id;\n\nexport function getEntityIdSelector(config?: {\n  selectId?: SelectEntityId<any>;\n}): SelectEntityId<any> {\n  return config?.selectId ?? defaultSelectId;\n}\n\nexport function getEntityStateKeys(config?: { collection?: string }): {\n  entityMapKey: string;\n  idsKey: string;\n  entitiesKey: string;\n} {\n  const collection = config?.collection;\n  const entityMapKey =\n    collection === undefined ? 'entityMap' : `${collection}EntityMap`;\n  const idsKey = collection === undefined ? 'ids' : `${collection}Ids`;\n  const entitiesKey =\n    collection === undefined ? 'entities' : `${collection}Entities`;\n\n  return { entityMapKey, idsKey, entitiesKey };\n}\n\nexport function cloneEntityState(\n  state: Record<string, any>,\n  stateKeys: {\n    entityMapKey: string;\n    idsKey: string;\n  }\n): EntityState<any> {\n  return {\n    entityMap: { ...state[stateKeys.entityMapKey] },\n    ids: [...state[stateKeys.idsKey]],\n  };\n}\n\nexport function getEntityUpdaterResult(\n  state: EntityState<any>,\n  stateKeys: {\n    entityMapKey: string;\n    idsKey: string;\n  },\n  didMutate: DidMutate\n): Record<string, any> {\n  switch (didMutate) {\n    case DidMutate.Both: {\n      return {\n        [stateKeys.entityMapKey]: state.entityMap,\n        [stateKeys.idsKey]: state.ids,\n      };\n    }\n    case DidMutate.Entities: {\n      return { [stateKeys.entityMapKey]: state.entityMap };\n    }\n    default: {\n      return {};\n    }\n  }\n}\n\nexport function addEntityMutably(\n  state: EntityState<any>,\n  entity: any,\n  selectId: SelectEntityId<any>,\n  prepend = false\n): DidMutate {\n  const id = selectId(entity);\n\n  if (state.entityMap[id]) {\n    return DidMutate.None;\n  }\n\n  state.entityMap[id] = entity;\n\n  if (prepend) {\n    state.ids.unshift(id);\n  } else {\n    state.ids.push(id);\n  }\n\n  return DidMutate.Both;\n}\n\nexport function addEntitiesMutably(\n  state: EntityState<any>,\n  entities: any[],\n  selectId: SelectEntityId<any>,\n  prepend = false\n): DidMutate {\n  let didMutate = DidMutate.None;\n\n  for (const entity of entities) {\n    const result = addEntityMutably(state, entity, selectId, prepend);\n\n    if (result === DidMutate.Both) {\n      didMutate = result;\n    }\n  }\n\n  return didMutate;\n}\n\nexport function setEntityMutably(\n  state: EntityState<any>,\n  entity: any,\n  selectId: SelectEntityId<any>,\n  replace = true\n): DidMutate {\n  const id = selectId(entity);\n\n  if (state.entityMap[id]) {\n    state.entityMap[id] = replace\n      ? entity\n      : { ...state.entityMap[id], ...entity };\n\n    return DidMutate.Entities;\n  }\n\n  state.entityMap[id] = entity;\n  state.ids.push(id);\n\n  return DidMutate.Both;\n}\n\nexport function setEntitiesMutably(\n  state: EntityState<any>,\n  entities: any[],\n  selectId: SelectEntityId<any>,\n  replace = true\n): DidMutate {\n  let didMutate = DidMutate.None;\n\n  for (const entity of entities) {\n    const result = setEntityMutably(state, entity, selectId, replace);\n\n    if (didMutate === DidMutate.Both) {\n      continue;\n    }\n\n    didMutate = result;\n  }\n\n  return didMutate;\n}\n\nexport function removeEntitiesMutably(\n  state: EntityState<any>,\n  idsOrPredicate: EntityId[] | EntityPredicate<any>\n): DidMutate {\n  const ids = Array.isArray(idsOrPredicate)\n    ? idsOrPredicate\n    : state.ids.filter((id) => idsOrPredicate(state.entityMap[id]));\n  let didMutate = DidMutate.None;\n\n  for (const id of ids) {\n    if (state.entityMap[id]) {\n      delete state.entityMap[id];\n      didMutate = DidMutate.Both;\n    }\n  }\n\n  if (didMutate === DidMutate.Both) {\n    state.ids = state.ids.filter((id) => id in state.entityMap);\n  }\n\n  return didMutate;\n}\n\nexport function updateEntitiesMutably(\n  state: EntityState<any>,\n  idsOrPredicate: EntityId[] | EntityPredicate<any>,\n  changes: EntityChanges<any>,\n  selectId: SelectEntityId<any>\n): DidMutate {\n  const ids = Array.isArray(idsOrPredicate)\n    ? idsOrPredicate\n    : state.ids.filter((id) => idsOrPredicate(state.entityMap[id]));\n  let newIds: Record<EntityId, EntityId> | undefined = undefined;\n  let didMutate = DidMutate.None;\n\n  for (const id of ids) {\n    const entity = state.entityMap[id];\n\n    if (entity) {\n      const changesRecord =\n        typeof changes === 'function' ? changes(entity) : changes;\n      state.entityMap[id] = { ...entity, ...changesRecord };\n      didMutate = DidMutate.Entities;\n\n      const newId = selectId(state.entityMap[id]);\n      if (newId !== id) {\n        state.entityMap[newId] = state.entityMap[id];\n        delete state.entityMap[id];\n\n        newIds = newIds || {};\n        newIds[id] = newId;\n      }\n    }\n  }\n\n  if (newIds) {\n    state.ids = state.ids.map((id) => newIds[id] ?? id);\n    didMutate = DidMutate.Both;\n  }\n\n  if (\n    typeof ngDevMode !== 'undefined' &&\n    ngDevMode &&\n    state.ids.length !== Object.keys(state.entityMap).length\n  ) {\n    console.warn(\n      '@ngrx/signals/entities: Entities with IDs:',\n      ids,\n      'are not updated correctly.',\n      'Make sure to apply valid changes when using `updateEntity`,',\n      '`updateEntities`, and `updateAllEntities` updaters.'\n    );\n  }\n\n  return didMutate;\n}\n"
  },
  {
    "path": "modules/signals/entities/src/index.ts",
    "content": "export { addEntity } from './updaters/add-entity';\nexport { addEntities } from './updaters/add-entities';\nexport { prependEntity } from './updaters/prepend-entity';\nexport { prependEntities } from './updaters/prepend-entities';\nexport { removeEntity } from './updaters/remove-entity';\nexport { removeEntities } from './updaters/remove-entities';\nexport { removeAllEntities } from './updaters/remove-all-entities';\nexport { setEntity } from './updaters/set-entity';\nexport { setEntities } from './updaters/set-entities';\nexport { setAllEntities } from './updaters/set-all-entities';\nexport { updateEntity } from './updaters/update-entity';\nexport { updateEntities } from './updaters/update-entities';\nexport { updateAllEntities } from './updaters/update-all-entities';\nexport { upsertEntity } from './updaters/upsert-entity';\nexport { upsertEntities } from './updaters/upsert-entities';\n\nexport { entityConfig } from './entity-config';\nexport {\n  EntityChanges,\n  EntityId,\n  EntityMap,\n  EntityProps,\n  EntityState,\n  NamedEntityProps,\n  NamedEntityState,\n  SelectEntityId,\n} from './models';\nexport { withEntities } from './with-entities';\n"
  },
  {
    "path": "modules/signals/entities/src/models.ts",
    "content": "import { Signal } from '@angular/core';\n\nexport type EntityId = string | number;\n\nexport type EntityMap<Entity> = Record<EntityId, Entity>;\n\nexport type EntityState<Entity> = {\n  entityMap: EntityMap<Entity>;\n  ids: EntityId[];\n};\n\nexport type NamedEntityState<Entity, Collection extends string> = {\n  [K in keyof EntityState<Entity> as `${Collection}${Capitalize<K>}`]: EntityState<Entity>[K];\n};\n\nexport type EntityProps<Entity> = {\n  entities: Signal<Entity[]>;\n};\n\nexport type NamedEntityProps<Entity, Collection extends string> = {\n  [K in keyof EntityProps<Entity> as `${Collection}${Capitalize<K>}`]: EntityProps<Entity>[K];\n};\n\nexport type SelectEntityId<Entity> = (entity: Entity) => EntityId;\n\nexport type EntityPredicate<Entity> = (entity: Entity) => boolean;\n\nexport type EntityChanges<Entity> =\n  | Partial<Entity>\n  | ((entity: Entity) => Partial<Entity>);\n\nexport enum DidMutate {\n  None,\n  Entities,\n  Both,\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/add-entities.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  addEntitiesMutably,\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n} from '../helpers';\n\nexport function addEntities<Entity extends { id: EntityId }>(\n  entities: Entity[]\n): PartialStateUpdater<EntityState<Entity>>;\nexport function addEntities<Entity, Collection extends string>(\n  entities: Entity[],\n  config: { collection: Collection; selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function addEntities<\n  Entity extends { id: EntityId },\n  Collection extends string,\n>(\n  entities: Entity[],\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function addEntities<Entity>(\n  entities: Entity[],\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Adds multiple entities to the collection.\n * Does not override existing entities with same IDs.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { addEntities } from '@ngrx/signals/entities';\n *\n * patchState(store, addEntities([todo1, todo2]));\n * ```\n */\nexport function addEntities(\n  entities: any[],\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = addEntitiesMutably(clonedState, entities, selectId);\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/add-entity.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  addEntityMutably,\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n} from '../helpers';\n\nexport function addEntity<Entity extends { id: EntityId }>(\n  entity: Entity\n): PartialStateUpdater<EntityState<Entity>>;\nexport function addEntity<Entity, Collection extends string>(\n  entity: Entity,\n  config: { collection: Collection; selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function addEntity<\n  Entity extends { id: EntityId },\n  Collection extends string,\n>(\n  entity: Entity,\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function addEntity<Entity>(\n  entity: Entity,\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Adds an entity to the collection.\n * Does not override if entity with same ID exists.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { addEntity } from '@ngrx/signals/entities';\n *\n * patchState(store, addEntity(todo));\n * ```\n */\nexport function addEntity(\n  entity: any,\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = addEntityMutably(clonedState, entity, selectId);\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/prepend-entities.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  addEntitiesMutably,\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n} from '../helpers';\n\nexport function prependEntities<Entity extends { id: EntityId }>(\n  entities: Entity[]\n): PartialStateUpdater<EntityState<Entity>>;\nexport function prependEntities<Entity, Collection extends string>(\n  entities: Entity[],\n  config: { collection: Collection; selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function prependEntities<\n  Entity extends { id: EntityId },\n  Collection extends string,\n>(\n  entities: Entity[],\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function prependEntities<Entity>(\n  entities: Entity[],\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Adds multiple entities to the beginning of the collection.\n * Does not add existing entities with same IDs.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { prependEntities } from '@ngrx/signals/entities';\n *\n * patchState(store, prependEntities([todo1, todo2]));\n * ```\n */\nexport function prependEntities(\n  entities: any[],\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n\n    const uniqueEntities: any[] = [];\n    const seenIds = new Set<EntityId>();\n\n    for (const entity of entities) {\n      const id = selectId(entity);\n\n      if (!seenIds.has(id)) {\n        uniqueEntities.unshift(entity);\n        seenIds.add(id);\n      }\n    }\n\n    const didMutate = addEntitiesMutably(\n      clonedState,\n      uniqueEntities,\n      selectId,\n      true\n    );\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/prepend-entity.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  addEntityMutably,\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n} from '../helpers';\n\nexport function prependEntity<Entity extends { id: EntityId }>(\n  entity: Entity\n): PartialStateUpdater<EntityState<Entity>>;\nexport function prependEntity<Entity, Collection extends string>(\n  entity: Entity,\n  config: { collection: Collection; selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function prependEntity<\n  Entity extends { id: EntityId },\n  Collection extends string,\n>(\n  entity: Entity,\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function prependEntity<Entity>(\n  entity: Entity,\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Adds an entity to the beginning of the collection.\n * Does not add if entity with same ID exists.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { prependEntity } from '@ngrx/signals/entities';\n *\n * patchState(store, prependEntity(todo));\n * ```\n */\nexport function prependEntity(\n  entity: any,\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = addEntityMutably(clonedState, entity, selectId, true);\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/remove-all-entities.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport { EntityState, NamedEntityState } from '../models';\nimport { getEntityStateKeys } from '../helpers';\n\nexport function removeAllEntities(): PartialStateUpdater<EntityState<any>>;\nexport function removeAllEntities<Collection extends string>(config: {\n  collection: Collection;\n}): PartialStateUpdater<NamedEntityState<any, Collection>>;\n/**\n * @description\n *\n * Removes all entities from the collection.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { removeAllEntities } from '@ngrx/signals/entities';\n *\n * patchState(store, removeAllEntities());\n * ```\n */\nexport function removeAllEntities(config?: {\n  collection?: string;\n}): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const stateKeys = getEntityStateKeys(config);\n\n  return () => ({\n    [stateKeys.entityMapKey]: {},\n    [stateKeys.idsKey]: [],\n  });\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/remove-entities.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityPredicate,\n  EntityState,\n  NamedEntityState,\n} from '../models';\nimport {\n  cloneEntityState,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n  removeEntitiesMutably,\n} from '../helpers';\n\nexport function removeEntities(\n  ids: EntityId[]\n): PartialStateUpdater<EntityState<any>>;\nexport function removeEntities<Entity>(\n  predicate: EntityPredicate<Entity>\n): PartialStateUpdater<EntityState<Entity>>;\nexport function removeEntities<Collection extends string>(\n  ids: EntityId[],\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<any, Collection>>;\nexport function removeEntities<\n  Collection extends string,\n  State extends NamedEntityState<any, Collection>,\n  Entity = State extends NamedEntityState<infer E, Collection> ? E : never,\n>(\n  predicate: EntityPredicate<Entity>,\n  config: { collection: Collection }\n): PartialStateUpdater<State>;\n/**\n * @description\n *\n * Removes multiple entities from the collection by IDs or predicate.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { removeEntities } from '@ngrx/signals/entities';\n *\n * // Remove by IDs\n * patchState(store, removeEntities([1, 2, 3]));\n *\n * // Remove by predicate\n * patchState(store, removeEntities((todo) => todo.completed));\n * ```\n */\nexport function removeEntities(\n  idsOrPredicate: EntityId[] | EntityPredicate<any>,\n  config?: { collection?: string }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = removeEntitiesMutably(clonedState, idsOrPredicate);\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/remove-entity.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport { EntityId, EntityState, NamedEntityState } from '../models';\nimport {\n  cloneEntityState,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n  removeEntitiesMutably,\n} from '../helpers';\n\nexport function removeEntity(\n  id: EntityId\n): PartialStateUpdater<EntityState<any>>;\nexport function removeEntity<Collection extends string>(\n  id: EntityId,\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<any, Collection>>;\n/**\n * @description\n *\n * Removes an entity from the collection by ID.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { removeEntity } from '@ngrx/signals/entities';\n *\n * patchState(store, removeEntity(1));\n * ```\n */\nexport function removeEntity(\n  id: EntityId,\n  config?: { collection?: string }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = removeEntitiesMutably(clonedState, [id]);\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/set-all-entities.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  getEntityIdSelector,\n  getEntityStateKeys,\n  setEntitiesMutably,\n} from '../helpers';\n\nexport function setAllEntities<Entity extends { id: EntityId }>(\n  entities: Entity[]\n): PartialStateUpdater<EntityState<Entity>>;\nexport function setAllEntities<Entity, Collection extends string>(\n  entities: Entity[],\n  config: { collection: Collection; selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function setAllEntities<\n  Entity extends { id: EntityId },\n  Collection extends string,\n>(\n  entities: Entity[],\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function setAllEntities<Entity>(\n  entities: Entity[],\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Replaces the entire entity collection with the provided entities.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { setAllEntities } from '@ngrx/signals/entities';\n *\n * patchState(store, setAllEntities([todo1, todo2, todo3]));\n * ```\n */\nexport function setAllEntities(\n  entities: any[],\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return () => {\n    const state: EntityState<any> = { entityMap: {}, ids: [] };\n    setEntitiesMutably(state, entities, selectId);\n\n    return {\n      [stateKeys.entityMapKey]: state.entityMap,\n      [stateKeys.idsKey]: state.ids,\n    };\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/set-entities.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n  setEntitiesMutably,\n} from '../helpers';\n\nexport function setEntities<Entity extends { id: EntityId }>(\n  entities: Entity[]\n): PartialStateUpdater<EntityState<Entity>>;\nexport function setEntities<Entity, Collection extends string>(\n  entities: Entity[],\n  config: { collection: Collection; selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function setEntities<\n  Entity extends { id: EntityId },\n  Collection extends string,\n>(\n  entities: Entity[],\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function setEntities<Entity>(\n  entities: Entity[],\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Adds or replaces multiple entities in the collection.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { setEntities } from '@ngrx/signals/entities';\n *\n * patchState(store, setEntities([todo1, todo2]));\n * ```\n */\nexport function setEntities(\n  entities: any[],\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = setEntitiesMutably(clonedState, entities, selectId);\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/set-entity.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n  setEntityMutably,\n} from '../helpers';\n\nexport function setEntity<Entity extends { id: EntityId }>(\n  entity: Entity\n): PartialStateUpdater<EntityState<Entity>>;\nexport function setEntity<Entity, Collection extends string>(\n  entity: Entity,\n  config: { collection: Collection; selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function setEntity<\n  Entity extends { id: EntityId },\n  Collection extends string,\n>(\n  entity: Entity,\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function setEntity<Entity>(\n  entity: Entity,\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Adds or replaces an entity in the collection.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { setEntity } from '@ngrx/signals/entities';\n *\n * patchState(store, setEntity(todo));\n * ```\n */\nexport function setEntity(\n  entity: any,\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = setEntityMutably(clonedState, entity, selectId);\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/update-all-entities.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityChanges,\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n  updateEntitiesMutably,\n} from '../helpers';\n\nexport function updateAllEntities<\n  Collection extends string,\n  State extends NamedEntityState<any, Collection>,\n  Entity = State extends NamedEntityState<infer E, Collection> ? E : never,\n>(\n  changes: EntityChanges<NoInfer<Entity>>,\n  config: {\n    collection: Collection;\n    selectId: SelectEntityId<NoInfer<Entity>>;\n  }\n): PartialStateUpdater<State>;\nexport function updateAllEntities<\n  Collection extends string,\n  State extends NamedEntityState<any, Collection>,\n  Entity = State extends NamedEntityState<\n    infer E extends { id: EntityId },\n    Collection\n  >\n    ? E\n    : never,\n>(\n  changes: EntityChanges<NoInfer<Entity>>,\n  config: { collection: Collection }\n): PartialStateUpdater<State>;\nexport function updateAllEntities<Entity>(\n  changes: EntityChanges<NoInfer<Entity>>,\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\nexport function updateAllEntities<Entity extends { id: EntityId }>(\n  changes: EntityChanges<NoInfer<Entity>>\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Updates all entities in the collection. Supports partial updates.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { updateAllEntities } from '@ngrx/signals/entities';\n *\n * patchState(store, updateAllEntities({ completed: false }));\n * ```\n */\nexport function updateAllEntities(\n  changes: EntityChanges<any>,\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = updateEntitiesMutably(\n      clonedState,\n      (state as Record<string, any>)[stateKeys.idsKey],\n      changes,\n      selectId\n    );\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/update-entities.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityChanges,\n  EntityId,\n  EntityPredicate,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n  updateEntitiesMutably,\n} from '../helpers';\n\nexport function updateEntities<\n  Collection extends string,\n  State extends NamedEntityState<any, Collection>,\n  Entity = State extends NamedEntityState<infer E, Collection> ? E : never,\n>(\n  update: {\n    ids: EntityId[];\n    changes: EntityChanges<NoInfer<Entity>>;\n  },\n  config: {\n    collection: Collection;\n    selectId: SelectEntityId<NoInfer<Entity>>;\n  }\n): PartialStateUpdater<State>;\nexport function updateEntities<\n  Collection extends string,\n  State extends NamedEntityState<any, Collection>,\n  Entity = State extends NamedEntityState<infer E, Collection> ? E : never,\n>(\n  update: {\n    predicate: EntityPredicate<Entity>;\n    changes: EntityChanges<NoInfer<Entity>>;\n  },\n  config: {\n    collection: Collection;\n    selectId: SelectEntityId<NoInfer<Entity>>;\n  }\n): PartialStateUpdater<State>;\nexport function updateEntities<\n  Collection extends string,\n  State extends NamedEntityState<any, Collection>,\n  Entity = State extends NamedEntityState<\n    infer E extends { id: EntityId },\n    Collection\n  >\n    ? E\n    : never,\n>(\n  update: {\n    ids: EntityId[];\n    changes: EntityChanges<NoInfer<Entity>>;\n  },\n  config: { collection: Collection }\n): PartialStateUpdater<State>;\nexport function updateEntities<\n  Collection extends string,\n  State extends NamedEntityState<any, Collection>,\n  Entity = State extends NamedEntityState<\n    infer E extends { id: EntityId },\n    Collection\n  >\n    ? E\n    : never,\n>(\n  update: {\n    predicate: EntityPredicate<Entity>;\n    changes: EntityChanges<NoInfer<Entity>>;\n  },\n  config: { collection: Collection }\n): PartialStateUpdater<State>;\nexport function updateEntities<Entity>(\n  update: {\n    ids: EntityId[];\n    changes: EntityChanges<NoInfer<Entity>>;\n  },\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\nexport function updateEntities<Entity>(\n  update: {\n    predicate: EntityPredicate<Entity>;\n    changes: EntityChanges<NoInfer<Entity>>;\n  },\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\nexport function updateEntities<Entity extends { id: EntityId }>(update: {\n  ids: EntityId[];\n  changes: EntityChanges<NoInfer<Entity>>;\n}): PartialStateUpdater<EntityState<Entity>>;\nexport function updateEntities<Entity extends { id: EntityId }>(update: {\n  predicate: EntityPredicate<Entity>;\n  changes: EntityChanges<NoInfer<Entity>>;\n}): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Updates multiple entities in the collection by IDs or predicate.\n * Supports partial updates.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { updateEntities } from '@ngrx/signals/entities';\n *\n * // Update by IDs\n * patchState(\n *   store,\n *   updateEntities({ ids: [1, 2], changes: { completed: true } })\n * );\n *\n * // Update by predicate\n * patchState(\n *   store,\n *   updateEntities({\n *     predicate: (todo) => !todo.completed,\n *     changes: { text: '' },\n *   })\n * );\n * ```\n */\nexport function updateEntities(\n  update: ({ ids: EntityId[] } | { predicate: EntityPredicate<any> }) & {\n    changes: EntityChanges<any>;\n  },\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n  const idsOrPredicate = 'ids' in update ? update.ids : update.predicate;\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = updateEntitiesMutably(\n      clonedState,\n      idsOrPredicate,\n      update.changes,\n      selectId\n    );\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/update-entity.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityChanges,\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n  updateEntitiesMutably,\n} from '../helpers';\n\nexport function updateEntity<\n  Collection extends string,\n  State extends NamedEntityState<any, Collection>,\n  Entity = State extends NamedEntityState<infer E, Collection> ? E : never,\n>(\n  update: {\n    id: EntityId;\n    changes: EntityChanges<NoInfer<Entity>>;\n  },\n  config: {\n    collection: Collection;\n    selectId: SelectEntityId<NoInfer<Entity>>;\n  }\n): PartialStateUpdater<State>;\nexport function updateEntity<\n  Collection extends string,\n  State extends NamedEntityState<any, Collection>,\n  Entity = State extends NamedEntityState<\n    infer E extends { id: EntityId },\n    Collection\n  >\n    ? E\n    : never,\n>(\n  update: {\n    id: EntityId;\n    changes: EntityChanges<NoInfer<Entity>>;\n  },\n  config: { collection: Collection }\n): PartialStateUpdater<State>;\nexport function updateEntity<Entity>(\n  update: {\n    id: EntityId;\n    changes: EntityChanges<NoInfer<Entity>>;\n  },\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\nexport function updateEntity<Entity extends { id: EntityId }>(update: {\n  id: EntityId;\n  changes: EntityChanges<NoInfer<Entity>>;\n}): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Updates an entity in the collection by ID. Supports partial updates.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { updateEntity } from '@ngrx/signals/entities';\n *\n * patchState(store, updateEntity({ id: 1, changes: { completed: true } }));\n * ```\n */\nexport function updateEntity(\n  update: {\n    id: EntityId;\n    changes: EntityChanges<any>;\n  },\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = updateEntitiesMutably(\n      clonedState,\n      [update.id],\n      update.changes,\n      selectId\n    );\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/upsert-entities.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n  setEntitiesMutably,\n} from '../helpers';\n\nexport function upsertEntities<Entity extends { id: EntityId }>(\n  entities: Entity[]\n): PartialStateUpdater<EntityState<Entity>>;\nexport function upsertEntities<Entity, Collection extends string>(\n  entities: Entity[],\n  config: { collection: Collection; selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function upsertEntities<\n  Entity extends { id: EntityId },\n  Collection extends string,\n>(\n  entities: Entity[],\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function upsertEntities<Entity>(\n  entities: Entity[],\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Adds or updates multiple entities in the collection.\n * When updating, merges with existing entities.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { upsertEntities } from '@ngrx/signals/entities';\n *\n * patchState(store, upsertEntities([todo1, todo2]));\n * ```\n */\nexport function upsertEntities(\n  entities: any[],\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = setEntitiesMutably(\n      clonedState,\n      entities,\n      selectId,\n      false\n    );\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/updaters/upsert-entity.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport {\n  EntityId,\n  EntityState,\n  NamedEntityState,\n  SelectEntityId,\n} from '../models';\nimport {\n  cloneEntityState,\n  getEntityIdSelector,\n  getEntityStateKeys,\n  getEntityUpdaterResult,\n  setEntityMutably,\n} from '../helpers';\n\nexport function upsertEntity<Entity extends { id: EntityId }>(\n  entity: Entity\n): PartialStateUpdater<EntityState<Entity>>;\nexport function upsertEntity<Entity, Collection extends string>(\n  entity: Entity,\n  config: { collection: Collection; selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function upsertEntity<\n  Entity extends { id: EntityId },\n  Collection extends string,\n>(\n  entity: Entity,\n  config: { collection: Collection }\n): PartialStateUpdater<NamedEntityState<Entity, Collection>>;\nexport function upsertEntity<Entity>(\n  entity: Entity,\n  config: { selectId: SelectEntityId<NoInfer<Entity>> }\n): PartialStateUpdater<EntityState<Entity>>;\n/**\n * @description\n *\n * Adds or updates an entity in the collection.\n * When updating, merges with existing entity.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState } from '@ngrx/signals';\n * import { upsertEntity } from '@ngrx/signals/entities';\n *\n * patchState(store, upsertEntity(todo));\n * ```\n */\nexport function upsertEntity(\n  entity: any,\n  config?: { collection?: string; selectId?: SelectEntityId<any> }\n): PartialStateUpdater<EntityState<any> | NamedEntityState<any, string>> {\n  const selectId = getEntityIdSelector(config);\n  const stateKeys = getEntityStateKeys(config);\n\n  return (state) => {\n    const clonedState = cloneEntityState(state, stateKeys);\n    const didMutate = setEntityMutably(clonedState, entity, selectId, false);\n\n    return getEntityUpdaterResult(clonedState, stateKeys, didMutate);\n  };\n}\n"
  },
  {
    "path": "modules/signals/entities/src/with-entities.ts",
    "content": "import { computed, Signal } from '@angular/core';\nimport {\n  EmptyFeatureResult,\n  SignalStoreFeature,\n  signalStoreFeature,\n  withComputed,\n  withState,\n} from '@ngrx/signals';\nimport {\n  EntityProps,\n  EntityId,\n  EntityMap,\n  EntityState,\n  NamedEntityProps,\n  NamedEntityState,\n} from './models';\nimport { getEntityStateKeys } from './helpers';\n\nexport function withEntities<Entity>(): SignalStoreFeature<\n  EmptyFeatureResult,\n  {\n    state: EntityState<Entity>;\n    props: EntityProps<Entity>;\n    methods: {};\n  }\n>;\nexport function withEntities<Entity, Collection extends string>(config: {\n  entity: Entity;\n  collection: Collection;\n}): SignalStoreFeature<\n  EmptyFeatureResult,\n  {\n    state: NamedEntityState<Entity, Collection>;\n    props: NamedEntityProps<Entity, Collection>;\n    methods: {};\n  }\n>;\nexport function withEntities<Entity>(config: {\n  entity: Entity;\n}): SignalStoreFeature<\n  EmptyFeatureResult,\n  {\n    state: EntityState<Entity>;\n    props: EntityProps<Entity>;\n    methods: {};\n  }\n>;\n/**\n * @description\n *\n * Provides entity management capabilities to the SignalStore.\n * Adds `entityMap`, `ids`, and `entities` signals to the store.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore } from '@ngrx/signals';\n * import { withEntities } from '@ngrx/signals/entities';\n *\n * type Todo = { id: number; text: string; completed: boolean };\n *\n * export const TodosStore = signalStore(withEntities<Todo>());\n * ```\n */\nexport function withEntities<Entity>(config?: {\n  entity: Entity;\n  collection?: string;\n}): SignalStoreFeature {\n  const { entityMapKey, idsKey, entitiesKey } = getEntityStateKeys(config);\n\n  return signalStoreFeature(\n    withState({\n      [entityMapKey]: {},\n      [idsKey]: [],\n    }),\n    withComputed((store: Record<string, Signal<unknown>>) => ({\n      [entitiesKey]: computed(() => {\n        const entityMap = store[entityMapKey]() as EntityMap<Entity>;\n        const ids = store[idsKey]() as EntityId[];\n\n        return ids.map((id) => entityMap[id]);\n      }),\n    }))\n  );\n}\n"
  },
  {
    "path": "modules/signals/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist', '**/schematics-core/**/*.ts'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        '@nx/enforce-module-boundaries': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/signals/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n  {\n    ignores: [\n      'schematics-core',\n      '**/vite.config.*.timestamp*',\n      '**/vitest.config.*.timestamp*',\n    ],\n  },\n];\n"
  },
  {
    "path": "modules/signals/events/index.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/signals/events/ng-package.json",
    "content": "{\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/signals/events/spec/dispatcher.spec.ts",
    "content": "import { Injector } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { take } from 'rxjs';\nimport { type } from '@ngrx/signals';\nimport {\n  Dispatcher,\n  event,\n  Events,\n  provideDispatcher,\n  ReducerEvents,\n} from '../src';\nimport { EVENTS } from '../src/events-service';\n\ndescribe('Dispatcher', () => {\n  it('is provided globally', () => {\n    const dispatcher = TestBed.inject(Dispatcher);\n    expect(dispatcher).toBeDefined();\n  });\n\n  it('emits dispatched events to the ReducerEvents service before the Events service', () => {\n    const dispatcher = TestBed.inject(Dispatcher);\n    const events = TestBed.inject(Events);\n    const reducerEvents = TestBed.inject(ReducerEvents);\n    const set = event('set', type<number>());\n    const result: Array<ReturnType<typeof set> & { order: number }> = [];\n\n    events\n      .on(set)\n      .pipe(take(1))\n      .subscribe((event) => result.push({ ...event, order: 2 }));\n    reducerEvents\n      .on(set)\n      .pipe(take(1))\n      .subscribe((event) => result.push({ ...event, order: 1 }));\n\n    dispatcher.dispatch(set(10));\n\n    expect(result).toEqual([\n      { type: 'set', payload: 10, order: 1 },\n      { type: 'set', payload: 10, order: 2 },\n    ]);\n  });\n\n  describe('hierarchical dispatchers', () => {\n    function setup() {\n      const parentInjector = Injector.create({\n        providers: [provideDispatcher()],\n        parent: TestBed.inject(Injector),\n      });\n      const childInjector = Injector.create({\n        providers: [provideDispatcher()],\n        parent: parentInjector,\n      });\n\n      const globalDispatcher = TestBed.inject(Dispatcher);\n      const parentDispatcher = parentInjector.get(Dispatcher);\n      const childDispatcher = childInjector.get(Dispatcher);\n\n      const globalEvents = TestBed.inject(Events)[EVENTS];\n      const parentEvents = parentInjector.get(Events)[EVENTS];\n      const childEvents = childInjector.get(Events)[EVENTS];\n\n      vitest.spyOn(globalDispatcher, 'dispatch');\n      vitest.spyOn(parentDispatcher, 'dispatch');\n      vitest.spyOn(globalEvents, 'next');\n      vitest.spyOn(parentEvents, 'next');\n      vitest.spyOn(childEvents, 'next');\n\n      return {\n        globalDispatcher,\n        parentDispatcher,\n        childDispatcher,\n        globalEvents,\n        parentEvents,\n        childEvents,\n      };\n    }\n\n    it('dispatches an event to the local dispatcher by default', () => {\n      const {\n        globalDispatcher,\n        parentDispatcher,\n        childDispatcher,\n        globalEvents,\n        parentEvents,\n        childEvents,\n      } = setup();\n      const increment = event('increment');\n\n      childDispatcher.dispatch(increment());\n\n      expect(childEvents.next).toHaveBeenCalledWith(increment());\n      expect(parentEvents.next).not.toHaveBeenCalled();\n      expect(globalEvents.next).not.toHaveBeenCalled();\n      expect(parentDispatcher.dispatch).not.toHaveBeenCalled();\n      expect(globalDispatcher.dispatch).not.toHaveBeenCalled();\n    });\n\n    it('dispatches an event to the local dispatcher when scope is self', () => {\n      const {\n        globalDispatcher,\n        parentDispatcher,\n        childDispatcher,\n        globalEvents,\n        parentEvents,\n        childEvents,\n      } = setup();\n      const increment = event('increment');\n\n      childDispatcher.dispatch(increment(), { scope: 'self' });\n\n      expect(childEvents.next).toHaveBeenCalledWith(increment());\n      expect(parentEvents.next).not.toHaveBeenCalled();\n      expect(globalEvents.next).not.toHaveBeenCalled();\n      expect(parentDispatcher.dispatch).not.toHaveBeenCalled();\n      expect(globalDispatcher.dispatch).not.toHaveBeenCalled();\n    });\n\n    it('dispatches an event to the parent dispatcher when scope is parent', () => {\n      const {\n        globalDispatcher,\n        parentDispatcher,\n        childDispatcher,\n        globalEvents,\n        parentEvents,\n        childEvents,\n      } = setup();\n      const increment = event('increment');\n\n      childDispatcher.dispatch(increment(), { scope: 'parent' });\n\n      expect(childEvents.next).not.toHaveBeenCalled();\n      expect(parentEvents.next).toHaveBeenCalledWith(increment());\n      expect(globalEvents.next).not.toHaveBeenCalled();\n      expect(parentDispatcher.dispatch).toHaveBeenCalledWith(\n        increment(),\n        undefined\n      );\n      expect(globalDispatcher.dispatch).not.toHaveBeenCalled();\n    });\n\n    it('dispatches an event to the parent dispatcher when scope is global', () => {\n      const {\n        globalDispatcher,\n        parentDispatcher,\n        childDispatcher,\n        globalEvents,\n        parentEvents,\n        childEvents,\n      } = setup();\n      const increment = event('increment');\n\n      childDispatcher.dispatch(increment(), { scope: 'global' });\n\n      expect(childEvents.next).not.toHaveBeenCalled();\n      expect(parentEvents.next).not.toHaveBeenCalled();\n      expect(globalEvents.next).toHaveBeenCalledWith(increment());\n      expect(parentDispatcher.dispatch).toHaveBeenCalledWith(increment(), {\n        scope: 'global',\n      });\n      expect(globalDispatcher.dispatch).toHaveBeenCalledWith(increment(), {\n        scope: 'global',\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/signals/events/spec/event-creator-group.spec.ts",
    "content": "import { type } from '@ngrx/signals';\nimport { EventCreator, eventGroup } from '../src';\n\ndescribe('eventGroup', () => {\n  it('creates a group of event creators', () => {\n    const counterPageEvents = eventGroup({\n      source: 'Counter Page',\n      events: {\n        increment: type<void>(),\n        decrement: type<void>(),\n        set: type<number>(),\n      },\n    });\n\n    const incrementEvent = counterPageEvents.increment();\n    const decrementEvent = counterPageEvents.decrement();\n    const setEvent = counterPageEvents.set(10);\n\n    expect(incrementEvent).toEqual({ type: '[Counter Page] increment' });\n    expect(decrementEvent).toEqual({ type: '[Counter Page] decrement' });\n    expect(setEvent).toEqual({ type: '[Counter Page] set', payload: 10 });\n  });\n\n  it('allows creating custom event group factories', () => {\n    function apiEventGroup<Source extends string, Entity>(\n      source: Source,\n      _entity: Entity\n    ): {\n      loadedSuccess: EventCreator<`[${Source} API] loadedSuccess`, Entity[]>;\n      loadedFailure: EventCreator<`[${Source} API] loadedFailure`, void>;\n    } {\n      return eventGroup({\n        source: `${source} API`,\n        events: {\n          loadedSuccess: type<Entity[]>(),\n          loadedFailure: type<void>(),\n        },\n      });\n    }\n\n    type User = { id: number; name: string };\n    const usersApiEvents = apiEventGroup('Users', type<User>());\n\n    const loadedSuccessEvent = usersApiEvents.loadedSuccess([\n      { id: 1, name: 'John Doe' },\n    ]);\n    const loadedFailureEvent = usersApiEvents.loadedFailure();\n\n    expect(loadedSuccessEvent).toEqual({\n      type: '[Users API] loadedSuccess',\n      payload: [{ id: 1, name: 'John Doe' }],\n    });\n    expect(loadedFailureEvent).toEqual({ type: '[Users API] loadedFailure' });\n  });\n});\n"
  },
  {
    "path": "modules/signals/events/spec/event-creator.spec.ts",
    "content": "import { type } from '@ngrx/signals';\nimport { event, EventCreator } from '../src';\n\ndescribe('event', () => {\n  it('creates an event creator without payload', () => {\n    const increment = event('increment');\n    expect(increment()).toEqual({ type: 'increment' });\n  });\n\n  it('creates an event creator with payload', () => {\n    const set = event('set', type<{ count: number }>());\n    expect(set({ count: 10 })).toEqual({ type: 'set', payload: { count: 10 } });\n  });\n\n  it('allows creating custom event creator factories', () => {\n    function formattedEventCreator<Source extends string, Event extends string>(\n      source: Source,\n      eventName: Event\n    ): EventCreator<`[${Source}] ${Event}`, void> {\n      return event(`[${source}] ${eventName}`);\n    }\n\n    const increment = formattedEventCreator('Counter Page', 'Increment');\n    expect(increment()).toEqual({ type: '[Counter Page] Increment' });\n  });\n});\n"
  },
  {
    "path": "modules/signals/events/spec/events-service.spec.ts",
    "content": "import { Injector } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { type } from '@ngrx/signals';\nimport {\n  Dispatcher,\n  event,\n  EventInstance,\n  Events,\n  provideDispatcher,\n} from '../src';\nimport { SOURCE_TYPE } from '../src/events-service';\n\ndescribe('Events', () => {\n  it('is provided globally', () => {\n    const events = TestBed.inject(Events);\n    expect(events).toBeDefined();\n  });\n\n  describe('on', () => {\n    const foo = event('foo');\n    const bar = event('bar', type<{ value: number }>());\n    const baz = event('baz');\n\n    it('emits events matching the provided event creators', () => {\n      const events = TestBed.inject(Events);\n      const dispatcher = TestBed.inject(Dispatcher);\n      const emittedEvents: EventInstance<string, unknown>[] = [];\n\n      events.on(foo, bar).subscribe((event) => emittedEvents.push(event));\n\n      dispatcher.dispatch(bar({ value: 10 }));\n      dispatcher.dispatch(foo());\n      dispatcher.dispatch(baz());\n      dispatcher.dispatch(bar({ value: 100 }));\n\n      expect(emittedEvents).toEqual([\n        { type: 'bar', payload: { value: 10 } },\n        { type: 'foo' },\n        { type: 'bar', payload: { value: 100 } },\n      ]);\n    });\n\n    it('emits all events when called without arguments', () => {\n      const events = TestBed.inject(Events);\n      const dispatcher = TestBed.inject(Dispatcher);\n      const emittedEvents: EventInstance<string, unknown>[] = [];\n\n      events.on().subscribe((event) => emittedEvents.push(event));\n\n      dispatcher.dispatch(foo());\n      dispatcher.dispatch(bar({ value: 10 }));\n      dispatcher.dispatch(baz());\n      dispatcher.dispatch(foo());\n\n      expect(emittedEvents).toEqual([\n        { type: 'foo' },\n        { type: 'bar', payload: { value: 10 } },\n        { type: 'baz' },\n        { type: 'foo' },\n      ]);\n    });\n\n    it('adds SOURCE_TYPE to emitted events', () => {\n      const events = TestBed.inject(Events);\n      const dispatcher = TestBed.inject(Dispatcher);\n      const sourceTypes: string[] = [];\n\n      events\n        .on()\n        .subscribe((event) => sourceTypes.push((event as any)[SOURCE_TYPE]));\n\n      dispatcher.dispatch(foo());\n      dispatcher.dispatch(bar({ value: 10 }));\n\n      expect(sourceTypes).toEqual(['foo', 'bar']);\n    });\n  });\n\n  it('receives dispatched events from ancestor Events services', () => {\n    const parentInjector = Injector.create({\n      providers: [provideDispatcher()],\n      parent: TestBed.inject(Injector),\n    });\n    const childInjector = Injector.create({\n      providers: [provideDispatcher()],\n      parent: parentInjector,\n    });\n\n    const globalEvents = TestBed.inject(Events);\n    const parentEvents = parentInjector.get(Events);\n    const childEvents = childInjector.get(Events);\n    const childDispatcher = childInjector.get(Dispatcher);\n\n    const foo = event('foo', type<string>());\n\n    const globalResult: string[] = [];\n    const parentResult: string[] = [];\n    const childResult: string[] = [];\n\n    globalEvents.on(foo).subscribe(({ payload }) => globalResult.push(payload));\n    parentEvents.on(foo).subscribe(({ payload }) => parentResult.push(payload));\n    childEvents.on(foo).subscribe(({ payload }) => childResult.push(payload));\n\n    childDispatcher.dispatch(foo('self by default'));\n    childDispatcher.dispatch(foo('explicit self'), { scope: 'self' });\n    childDispatcher.dispatch(foo('parent'), { scope: 'parent' });\n    childDispatcher.dispatch(foo('global'), { scope: 'global' });\n\n    expect(globalResult).toEqual(['global']);\n    expect(parentResult).toEqual(['parent', 'global']);\n    expect(childResult).toEqual([\n      'self by default',\n      'explicit self',\n      'parent',\n      'global',\n    ]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/events/spec/inject-dispatch.spec.ts",
    "content": "import { EnvironmentInjector } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { type } from '@ngrx/signals';\nimport { Dispatcher, event, eventGroup, injectDispatch } from '../src';\n\ndescribe('injectDispatch', () => {\n  it('creates self-dispatching events', () => {\n    const counterPageEvents = eventGroup({\n      source: 'Counter Page',\n      events: {\n        increment: type<void>(),\n        set: type<{ count: number }>(),\n      },\n    });\n    const dispatcher = TestBed.inject(Dispatcher);\n    const dispatch = TestBed.runInInjectionContext(() =>\n      injectDispatch(counterPageEvents)\n    );\n    vitest.spyOn(dispatcher, 'dispatch');\n\n    dispatch.increment();\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      {\n        type: '[Counter Page] increment',\n        payload: undefined,\n      },\n      { scope: 'self' }\n    );\n\n    dispatch.set({ count: 10 });\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      {\n        type: '[Counter Page] set',\n        payload: { count: 10 },\n      },\n      { scope: 'self' }\n    );\n  });\n\n  it('creates self-dispatching events with a custom scope', () => {\n    const usersPageEvents = eventGroup({\n      source: 'Users Page',\n      events: {\n        opened: type<void>(),\n        queryChanged: type<string>(),\n        paginationChanged: type<{ currentPage: number; pageSize: number }>(),\n      },\n    });\n    const dispatcher = TestBed.inject(Dispatcher);\n    const dispatch = TestBed.runInInjectionContext(() =>\n      injectDispatch(usersPageEvents)\n    );\n    vitest.spyOn(dispatcher, 'dispatch');\n\n    dispatch({ scope: 'self' }).opened();\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      {\n        type: '[Users Page] opened',\n        payload: undefined,\n      },\n      { scope: 'self' }\n    );\n\n    dispatch({ scope: 'parent' }).queryChanged('ngrx');\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      {\n        type: '[Users Page] queryChanged',\n        payload: 'ngrx',\n      },\n      { scope: 'parent' }\n    );\n\n    dispatch({ scope: 'global' }).paginationChanged({\n      currentPage: 10,\n      pageSize: 100,\n    });\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      {\n        type: '[Users Page] paginationChanged',\n        payload: { currentPage: 10, pageSize: 100 },\n      },\n      { scope: 'global' }\n    );\n  });\n\n  it('allows defining event names equal to predefined function properties', () => {\n    const fooEvents = eventGroup({\n      source: 'foo',\n      events: {\n        name: type<boolean>(),\n        toString: type<{ bar: number }>(),\n      },\n    });\n\n    const dispatcher = TestBed.inject(Dispatcher);\n    const dispatch = TestBed.runInInjectionContext(() =>\n      injectDispatch(fooEvents)\n    );\n    vitest.spyOn(dispatcher, 'dispatch');\n\n    dispatch.name(true);\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      { type: '[foo] name', payload: true },\n      { scope: 'self' }\n    );\n\n    dispatch({ scope: 'parent' }).name(false);\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      { type: '[foo] name', payload: false },\n      { scope: 'parent' }\n    );\n\n    dispatch.toString({ bar: 10 });\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      { type: '[foo] toString', payload: { bar: 10 } },\n      { scope: 'self' }\n    );\n\n    dispatch({ scope: 'global' }).toString({ bar: 100 });\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      { type: '[foo] toString', payload: { bar: 100 } },\n      { scope: 'global' }\n    );\n  });\n\n  it('creates self-dispatching events with a custom injector', () => {\n    const increment = event('increment');\n    const injector = TestBed.inject(EnvironmentInjector);\n    const dispatcher = TestBed.inject(Dispatcher);\n    const dispatch = injectDispatch({ increment }, { injector });\n    vitest.spyOn(dispatcher, 'dispatch');\n\n    dispatch.increment();\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(\n      { type: 'increment', payload: undefined },\n      { scope: 'self' }\n    );\n  });\n\n  it('throws an error when called outside of an injection context', () => {\n    const increment = event('increment');\n\n    expect(() => injectDispatch({ increment })).toThrowError(\n      'injectDispatch() can only be used within an injection context'\n    );\n  });\n});\n"
  },
  {
    "path": "modules/signals/events/spec/integration.spec.ts",
    "content": "import { computed, inject, Injectable } from '@angular/core';\nimport { fakeAsync, TestBed, tick } from '@angular/core/testing';\nimport {\n  delay,\n  exhaustMap,\n  map,\n  mergeMap,\n  Observable,\n  of,\n  tap,\n  throwError,\n  timer,\n} from 'rxjs';\nimport { mapResponse } from '@ngrx/operators';\nimport {\n  getState,\n  signalStore,\n  signalStoreFeature,\n  type,\n  withComputed,\n  withState,\n} from '@ngrx/signals';\nimport {\n  EntityState,\n  setAllEntities,\n  withEntities,\n} from '@ngrx/signals/entities';\nimport {\n  Dispatcher,\n  event,\n  eventGroup,\n  Events,\n  injectDispatch,\n  on,\n  withEventHandlers,\n  withReducer,\n} from '../src';\n\ndescribe('Integration Tests', () => {\n  type Book = { id: number; title: string };\n\n  const book1: Book = { id: 1, title: 'Book 1' };\n  const book2: Book = { id: 2, title: 'Book 2' };\n  const book3: Book = { id: 3, title: 'Book 3' };\n\n  @Injectable({ providedIn: 'root' })\n  class BooksService {\n    getAll(): Observable<Book[]> {\n      return of([]);\n    }\n\n    getByQuery(_query: string): Observable<Book[]> {\n      return of([]);\n    }\n  }\n\n  const booksApiEvents = eventGroup({\n    source: 'Books API',\n    events: {\n      loadedSuccess: type<Book[]>(),\n      loadedFailure: type<string>(),\n    },\n  });\n\n  type RequestStatus = 'idle' | 'pending' | 'fulfilled' | { error: string };\n\n  type RequestStatusState = { requestStatus: RequestStatus };\n\n  function withRequestStatus() {\n    return signalStoreFeature(\n      withState<RequestStatusState>({ requestStatus: 'idle' }),\n      withComputed(({ requestStatus }) => ({\n        isPending: computed(() => requestStatus() === 'pending'),\n        isFulfilled: computed(() => requestStatus() === 'fulfilled'),\n        error: computed(() => {\n          const status = requestStatus();\n          return typeof status === 'object' ? status.error : null;\n        }),\n      }))\n    );\n  }\n\n  function setPending(): RequestStatusState {\n    return { requestStatus: 'pending' };\n  }\n\n  function setFulfilled(): RequestStatusState {\n    return { requestStatus: 'fulfilled' };\n  }\n\n  function setError(error: string): RequestStatusState {\n    return { requestStatus: { error } };\n  }\n\n  describe('withReducer and withEventHandlers', () => {\n    const booksPageEvents = eventGroup({\n      source: 'Books Page',\n      events: {\n        opened: type<void>(),\n        refreshed: type<void>(),\n      },\n    });\n\n    const BooksStore = signalStore(\n      { providedIn: 'root' },\n      withEntities<Book>(),\n      withRequestStatus(),\n      withReducer(\n        on(booksPageEvents.opened, booksPageEvents.refreshed, setPending),\n        on(booksApiEvents.loadedSuccess, ({ payload }) => [\n          setAllEntities(payload),\n          setFulfilled(),\n        ]),\n        on(booksApiEvents.loadedFailure, ({ payload }) => setError(payload))\n      ),\n      withEventHandlers(\n        (_, events = inject(Events), booksService = inject(BooksService)) => ({\n          loadUsers$: events\n            .on(booksPageEvents.opened, booksPageEvents.refreshed)\n            .pipe(\n              exhaustMap(() =>\n                booksService.getAll().pipe(\n                  mapResponse({\n                    next: (books) => booksApiEvents.loadedSuccess(books),\n                    error: (error: { message: string }) =>\n                      booksApiEvents.loadedFailure(error.message),\n                  })\n                )\n              )\n            ),\n          logError$: events\n            .on(booksApiEvents.loadedFailure)\n            .pipe(tap(({ payload }) => console.error(payload))),\n        })\n      )\n    );\n\n    it('loads entities when opened and refreshed events are dispatched', fakeAsync(() => {\n      const booksService = TestBed.inject(BooksService);\n      const booksStore = TestBed.inject(BooksStore);\n      const dispatch = TestBed.runInInjectionContext(() =>\n        injectDispatch(booksPageEvents)\n      );\n\n      expect(getState(booksStore)).toEqual({\n        ids: [],\n        entityMap: {},\n        requestStatus: 'idle',\n      });\n\n      vitest\n        .spyOn(booksService, 'getAll')\n        .mockImplementation(() => of([book1, book2]).pipe(delay(500)));\n      dispatch.opened();\n\n      expect(getState(booksStore)).toEqual({\n        ids: [],\n        entityMap: {},\n        requestStatus: 'pending',\n      });\n\n      tick(500);\n\n      expect(getState(booksStore)).toEqual({\n        ids: [book1.id, book2.id],\n        entityMap: {\n          [book1.id]: book1,\n          [book2.id]: book2,\n        },\n        requestStatus: 'fulfilled',\n      });\n\n      vitest\n        .spyOn(booksService, 'getAll')\n        .mockImplementation(() => of([book1, book2, book3]).pipe(delay(1_000)));\n      dispatch.refreshed();\n\n      expect(getState(booksStore)).toEqual({\n        ids: [book1.id, book2.id],\n        entityMap: {\n          [book1.id]: book1,\n          [book2.id]: book2,\n        },\n        requestStatus: 'pending',\n      });\n\n      tick(1_000);\n\n      expect(getState(booksStore)).toEqual({\n        ids: [book1.id, book2.id, book3.id],\n        entityMap: {\n          [book1.id]: book1,\n          [book2.id]: book2,\n          [book3.id]: book3,\n        },\n        requestStatus: 'fulfilled',\n      });\n    }));\n\n    it('logs an error when load entities fail', fakeAsync(() => {\n      const booksService = TestBed.inject(BooksService);\n      const booksStore = TestBed.inject(BooksStore);\n      const dispatch = TestBed.runInInjectionContext(() =>\n        injectDispatch(booksPageEvents)\n      );\n\n      expect(getState(booksStore)).toEqual({\n        ids: [],\n        entityMap: {},\n        requestStatus: 'idle',\n      });\n\n      vitest\n        .spyOn(booksService, 'getAll')\n        .mockImplementation(() =>\n          timer(500).pipe(mergeMap(() => throwError(() => new Error('Error!'))))\n        );\n      dispatch.opened();\n\n      expect(getState(booksStore)).toEqual({\n        ids: [],\n        entityMap: {},\n        requestStatus: 'pending',\n      });\n\n      vitest.spyOn(console, 'error').mockImplementation(() => {});\n      tick(500);\n\n      expect(getState(booksStore)).toEqual({\n        ids: [],\n        entityMap: {},\n        requestStatus: { error: 'Error!' },\n      });\n      expect(console.error).toHaveBeenCalledWith('Error!');\n    }));\n\n    it('handles events in the order they are dispatched', () => {\n      const handledEventsLog: string[] = [];\n      const first = event('first');\n      const second = event('second');\n      const save = event('save', type<string>());\n      const Store = signalStore(\n        { providedIn: 'root' },\n        withState({ savedEvents: [] as string[] }),\n        withEventHandlers((_, events = inject(Events)) => ({\n          emitSecond$: events.on(first).pipe(\n            tap(({ type }) =>\n              handledEventsLog.push(`emitSecond$ handler: ${type}`)\n            ),\n            map(() => second())\n          ),\n          save$: events.on(first, second).pipe(\n            tap(({ type }) => handledEventsLog.push(`save$ handler: ${type}`)),\n            map(({ type }) => save(type))\n          ),\n        })),\n        withReducer(\n          on(first, second, save, ({ type, payload }) => {\n            handledEventsLog.push(`reducer: ${type}${payload ?? ''}`);\n            return {};\n          }),\n          on(save, ({ payload }, state) => ({\n            savedEvents: [...state.savedEvents, payload],\n          }))\n        )\n      );\n\n      const dispatcher = TestBed.inject(Dispatcher);\n      const store = TestBed.inject(Store);\n\n      dispatcher.dispatch(first());\n      expect(store.savedEvents()).toEqual(['first', 'second']);\n      expect(handledEventsLog).toEqual([\n        'reducer: first',\n        'emitSecond$ handler: first',\n        'reducer: second',\n        'save$ handler: first',\n        'reducer: savefirst',\n        'save$ handler: second',\n        'reducer: savesecond',\n      ]);\n    });\n  });\n\n  describe('custom withReducer and withEventHandlers', () => {\n    const booksPageEvents = eventGroup({\n      source: 'Books Page',\n      events: {\n        queryChanged: type<string>(),\n        refreshed: type<void>(),\n      },\n    });\n\n    type QueryState = { query: string };\n\n    function withBooksReducer() {\n      return signalStoreFeature(\n        { state: type<QueryState & EntityState<Book> & RequestStatusState>() },\n        withReducer(\n          on(booksPageEvents.queryChanged, ({ payload: query }) => ({ query })),\n          on(\n            booksPageEvents.queryChanged,\n            booksPageEvents.refreshed,\n            setPending\n          ),\n          on(booksApiEvents.loadedSuccess, ({ payload }) => [\n            setAllEntities(payload),\n            setFulfilled(),\n          ]),\n          on(booksApiEvents.loadedFailure, ({ payload }) => setError(payload))\n        )\n      );\n    }\n\n    function withBooksEventHandlers() {\n      return signalStoreFeature(\n        { state: type<QueryState>() },\n        withEventHandlers(\n          (\n            { query },\n            events = inject(Events),\n            booksService = inject(BooksService)\n          ) => ({\n            loadUsers$: events\n              .on(booksPageEvents.queryChanged, booksPageEvents.refreshed)\n              .pipe(\n                exhaustMap(() =>\n                  booksService.getByQuery(query()).pipe(\n                    mapResponse({\n                      next: (books) => booksApiEvents.loadedSuccess(books),\n                      error: (error: { message: string }) =>\n                        booksApiEvents.loadedFailure(error.message),\n                    })\n                  )\n                )\n              ),\n            logError$: events\n              .on(booksApiEvents.loadedFailure)\n              .pipe(tap(({ payload }) => console.error(payload))),\n          })\n        )\n      );\n    }\n\n    const BooksStore = signalStore(\n      { providedIn: 'root' },\n      withState<QueryState>({ query: '' }),\n      withEntities<Book>(),\n      withRequestStatus(),\n      withBooksReducer(),\n      withBooksEventHandlers()\n    );\n\n    it('loads entities when queryChanged and refreshed events are dispatched', fakeAsync(() => {\n      const booksService = TestBed.inject(BooksService);\n      const booksStore = TestBed.inject(BooksStore);\n      const dispatch = TestBed.runInInjectionContext(() =>\n        injectDispatch(booksPageEvents)\n      );\n\n      expect(getState(booksStore)).toEqual({\n        query: '',\n        ids: [],\n        entityMap: {},\n        requestStatus: 'idle',\n      });\n\n      vitest\n        .spyOn(booksService, 'getByQuery')\n        .mockImplementation(() => of([book1]).pipe(delay(500)));\n      dispatch.queryChanged('book');\n\n      expect(getState(booksStore)).toEqual({\n        query: 'book',\n        ids: [],\n        entityMap: {},\n        requestStatus: 'pending',\n      });\n\n      tick(500);\n\n      expect(getState(booksStore)).toEqual({\n        query: 'book',\n        ids: [book1.id],\n        entityMap: { [book1.id]: book1 },\n        requestStatus: 'fulfilled',\n      });\n\n      vitest\n        .spyOn(booksService, 'getByQuery')\n        .mockImplementation(() => of([book1, book2]).pipe(delay(1_000)));\n      dispatch.refreshed();\n\n      expect(getState(booksStore)).toEqual({\n        query: 'book',\n        ids: [book1.id],\n        entityMap: { [book1.id]: book1 },\n        requestStatus: 'pending',\n      });\n\n      tick(1_000);\n\n      expect(getState(booksStore)).toEqual({\n        query: 'book',\n        ids: [book1.id, book2.id],\n        entityMap: {\n          [book1.id]: book1,\n          [book2.id]: book2,\n        },\n        requestStatus: 'fulfilled',\n      });\n    }));\n\n    it('logs an error when load entities fail', fakeAsync(() => {\n      const booksService = TestBed.inject(BooksService);\n      const booksStore = TestBed.inject(BooksStore);\n      const dispatch = TestBed.runInInjectionContext(() =>\n        injectDispatch(booksPageEvents)\n      );\n\n      expect(getState(booksStore)).toEqual({\n        query: '',\n        ids: [],\n        entityMap: {},\n        requestStatus: 'idle',\n      });\n\n      vitest\n        .spyOn(booksService, 'getByQuery')\n        .mockImplementation(() =>\n          timer(500).pipe(mergeMap(() => throwError(() => new Error('Error!'))))\n        );\n      dispatch.queryChanged('search');\n\n      expect(getState(booksStore)).toEqual({\n        query: 'search',\n        ids: [],\n        entityMap: {},\n        requestStatus: 'pending',\n      });\n\n      vitest.spyOn(console, 'error').mockImplementation(() => {});\n      tick(500);\n\n      expect(getState(booksStore)).toEqual({\n        query: 'search',\n        ids: [],\n        entityMap: {},\n        requestStatus: { error: 'Error!' },\n      });\n      expect(console.error).toHaveBeenCalledWith('Error!');\n    }));\n  });\n});\n"
  },
  {
    "path": "modules/signals/events/spec/with-event-handlers.spec.ts",
    "content": "import { computed, inject } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { map, tap } from 'rxjs';\nimport {\n  getState,\n  signalStore,\n  type,\n  withComputed,\n  withMethods,\n  withProps,\n  withState,\n} from '@ngrx/signals';\nimport {\n  Dispatcher,\n  event,\n  EventInstance,\n  Events,\n  mapToScope,\n  toScope,\n  withEventHandlers,\n} from '../src';\nimport { createLocalService } from '../../spec/helpers';\n\ndescribe('withEventHandlers', () => {\n  const event1 = event('event1');\n  const event2 = event('event2');\n  const event3 = event('event3', type<string>());\n  const event4 = event('event4', type<{ value: string }>());\n\n  it('has access to SignalStore state slices, props, methods, and state source', () => {\n    const values: string[] = [];\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withState({ k: 'k' }),\n      withComputed(({ k }) => ({\n        l: computed(() => `${k()} l`),\n      })),\n      withProps(() => ({ m: 'm' })),\n      withMethods(() => ({\n        n(): string {\n          return 'n';\n        },\n      })),\n      withEventHandlers(({ k, l, m, n, ...store }) => {\n        values.push(k(), l(), m, n(), getState(store).k);\n        return {};\n      })\n    );\n\n    TestBed.inject(Store);\n\n    expect(values).toEqual(['k', 'k l', 'm', 'n', 'k']);\n  });\n\n  it('dispatches events returned by event handlers', () => {\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withEventHandlers((_, events = inject(Events)) => ({\n        $: events.on(event1, event2).pipe(map(({ type }) => event3(type))),\n        $$: events\n          .on(event3)\n          .pipe(map(({ payload }) => event4({ value: payload }))),\n      }))\n    );\n\n    const dispatcher = TestBed.inject(Dispatcher);\n    const events = TestBed.inject(Events);\n    const emittedEvents: EventInstance<string, unknown>[] = [];\n\n    events.on().subscribe((event) => emittedEvents.push(event));\n    TestBed.inject(Store);\n\n    dispatcher.dispatch(event1());\n    dispatcher.dispatch(event2());\n\n    expect(emittedEvents).toEqual([\n      { type: 'event1' },\n      { type: 'event3', payload: 'event1' },\n      { type: 'event4', payload: { value: 'event1' } },\n      { type: 'event2' },\n      { type: 'event3', payload: 'event2' },\n      { type: 'event4', payload: { value: 'event2' } },\n    ]);\n  });\n\n  it('does not re-dispatch the same event when an event handler does not return a new event', () => {\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withEventHandlers((_, events = inject(Events)) => ({\n        $: events.on(event1).pipe(tap(() => {})),\n        $$: events.on(event2).pipe(tap(() => {})),\n      }))\n    );\n\n    const dispatcher = TestBed.inject(Dispatcher);\n    const events = TestBed.inject(Events);\n    const emittedEvents: EventInstance<string, unknown>[] = [];\n\n    events.on().subscribe((event) => emittedEvents.push(event));\n    TestBed.inject(Store);\n\n    dispatcher.dispatch(event1());\n    dispatcher.dispatch(event2());\n\n    expect(emittedEvents).toEqual([{ type: 'event1' }, { type: 'event2' }]);\n  });\n\n  it('re-dispatches the same event when it is explicitly returned from an event handler', () => {\n    let executionCount = 0;\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withEventHandlers((_, events = inject(Events)) => ({\n        $: events.on(event1).pipe(\n          map(() => (executionCount < 2 ? event1() : null)),\n          tap(() => executionCount++)\n        ),\n      }))\n    );\n\n    const dispatcher = TestBed.inject(Dispatcher);\n    const events = TestBed.inject(Events);\n    const emittedEvents: EventInstance<string, unknown>[] = [];\n\n    events.on().subscribe((event) => emittedEvents.push(event));\n    TestBed.inject(Store);\n\n    dispatcher.dispatch(event1());\n\n    expect(emittedEvents).toEqual([\n      { type: 'event1' },\n      { type: 'event1' },\n      { type: 'event1' },\n    ]);\n  });\n\n  it('handles observables returned as an array', () => {\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withEventHandlers((_, events = inject(Events)) => [\n        events.on(event1, event2).pipe(map(({ type }) => event3(type))),\n        events.on(event3).pipe(tap(() => {})),\n      ])\n    );\n\n    const events = TestBed.inject(Events);\n    const dispatcher = TestBed.inject(Dispatcher);\n    const emittedEvents: EventInstance<string, unknown>[] = [];\n\n    events.on().subscribe((event) => emittedEvents.push(event));\n    TestBed.inject(Store);\n\n    dispatcher.dispatch(event1());\n    dispatcher.dispatch(event2());\n\n    expect(emittedEvents).toEqual([\n      { type: 'event1' },\n      { type: 'event3', payload: 'event1' },\n      { type: 'event2' },\n      { type: 'event3', payload: 'event2' },\n    ]);\n  });\n\n  it('dispatches an event with provided scope via toScope', () => {\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withEventHandlers((_, events = inject(Events)) => ({\n        $: events.on(event1).pipe(map(() => [event2(), toScope('parent')])),\n      }))\n    );\n\n    const dispatcher = TestBed.inject(Dispatcher);\n    vitest.spyOn(dispatcher, 'dispatch');\n\n    TestBed.inject(Store);\n\n    dispatcher.dispatch(event1());\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(event2(), {\n      scope: 'parent',\n    });\n  });\n\n  it('dispatches an event with provided scope via mapToScope', () => {\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withEventHandlers((_, events = inject(Events)) => ({\n        $: events.on(event1).pipe(\n          map(() => event3('ngrx')),\n          mapToScope('global')\n        ),\n      }))\n    );\n\n    const dispatcher = TestBed.inject(Dispatcher);\n    vitest.spyOn(dispatcher, 'dispatch');\n\n    TestBed.inject(Store);\n\n    dispatcher.dispatch(event1());\n    expect(dispatcher.dispatch).toHaveBeenCalledWith(event3('ngrx'), {\n      scope: 'global',\n    });\n  });\n\n  it('unsubscribes from event handlers when the store is destroyed', () => {\n    let executionCount = 0;\n\n    const Store = signalStore(\n      withEventHandlers((_, events = inject(Events)) => ({\n        $: events.on(event1).pipe(tap(() => executionCount++)),\n      }))\n    );\n\n    const { destroy } = createLocalService(Store);\n    const dispatcher = TestBed.inject(Dispatcher);\n\n    expect(executionCount).toBe(0);\n\n    dispatcher.dispatch(event1());\n    expect(executionCount).toBe(1);\n\n    destroy();\n\n    dispatcher.dispatch(event1());\n    expect(executionCount).toBe(1);\n  });\n});\n"
  },
  {
    "path": "modules/signals/events/spec/with-reducer.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport {\n  getState,\n  PartialStateUpdater,\n  signalStore,\n  type,\n  withState,\n} from '@ngrx/signals';\nimport { Dispatcher, event, on, withReducer } from '../src';\nimport { createLocalService } from '../../spec/helpers';\n\ndescribe('withReducer', () => {\n  it('updates the state with a partial state object', () => {\n    const set = event('set', type<number>());\n\n    const CounterStore = signalStore(\n      { providedIn: 'root' },\n      withState({ count: 10, count2: 0 }),\n      withReducer(on(set, ({ payload }) => ({ count: payload })))\n    );\n\n    const store = TestBed.inject(CounterStore);\n    const dispatcher = TestBed.inject(Dispatcher);\n\n    dispatcher.dispatch(set(20));\n\n    expect(getState(store)).toEqual({ count: 20, count2: 0 });\n  });\n\n  it('updates the state with a partial state updater', () => {\n    const increment = event('increment');\n\n    const CounterStore = signalStore(\n      { providedIn: 'root' },\n      withState({ count: 10, count2: 0 }),\n      withReducer(on(increment, () => ({ count }) => ({ count: count + 1 })))\n    );\n\n    const store = TestBed.inject(CounterStore);\n    const dispatcher = TestBed.inject(Dispatcher);\n\n    dispatcher.dispatch(increment());\n\n    expect(getState(store)).toEqual({ count: 11, count2: 0 });\n  });\n\n  it('updates the state with an array of partial state objects and updaters', () => {\n    const set2 = event('set2', type<{ count2: number }>());\n\n    function incrementCount(): PartialStateUpdater<{ count: number }> {\n      return ({ count }) => ({ count: count + 1 });\n    }\n\n    function setCount2(count2: number): { count2: number } {\n      return { count2 };\n    }\n\n    const CounterStore = signalStore(\n      { providedIn: 'root' },\n      withState({ count: 0, count2: 0 }),\n      withReducer(\n        on(set2, ({ payload: { count2 } }) => [\n          { count: 1 },\n          incrementCount(),\n          setCount2(count2),\n          (s) => ({ count2: s.count2 + 1 }),\n        ])\n      )\n    );\n\n    const store = TestBed.inject(CounterStore);\n    const dispatcher = TestBed.inject(Dispatcher);\n\n    dispatcher.dispatch(set2({ count2: 10 }));\n\n    expect(getState(store)).toEqual({ count: 2, count2: 11 });\n  });\n\n  it('has access to the current state', () => {\n    const incrementBy = event('incrementBy', type<number>());\n\n    const CounterStore = signalStore(\n      { providedIn: 'root' },\n      withState({ count: 10, count2: 0 }),\n      withReducer(\n        on(incrementBy, ({ payload }, state) => ({\n          count: state.count + payload,\n        }))\n      )\n    );\n\n    const store = TestBed.inject(CounterStore);\n    const dispatcher = TestBed.inject(Dispatcher);\n\n    dispatcher.dispatch(incrementBy(20));\n\n    expect(getState(store)).toEqual({ count: 30, count2: 0 });\n  });\n\n  it('allows listening to multiple events', () => {\n    const set = event('set', type<number>());\n    const set_ = event('set_', type<number>());\n\n    const CounterStore = signalStore(\n      { providedIn: 'root' },\n      withState({ count: 0, count2: 0 }),\n      withReducer(on(set, set_, ({ payload }) => ({ count: payload })))\n    );\n\n    const store = TestBed.inject(CounterStore);\n    const dispatcher = TestBed.inject(Dispatcher);\n\n    dispatcher.dispatch(set(10));\n    expect(getState(store)).toEqual({ count: 10, count2: 0 });\n\n    dispatcher.dispatch(set(100));\n    expect(getState(store)).toEqual({ count: 100, count2: 0 });\n  });\n\n  it('provides an intersection of the payload when listening to multiple events', () => {\n    const e1 = event('e1', type<number>());\n    const e2 = event('e2', type<{ numbers: number[] }>());\n\n    let e1Payload = 0;\n    let e2Payload: number[] = [];\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withReducer(\n        on(e1, e2, (event) => {\n          if (event.type === 'e1') {\n            e1Payload = event.payload;\n          } else {\n            e2Payload = event.payload.numbers;\n          }\n\n          return {};\n        })\n      )\n    );\n\n    TestBed.inject(Store);\n    const dispatcher = TestBed.inject(Dispatcher);\n\n    dispatcher.dispatch(e1(10));\n    expect(e1Payload).toBe(10);\n\n    dispatcher.dispatch(e2({ numbers: [1, 2, 3] }));\n    expect(e2Payload).toEqual([1, 2, 3]);\n  });\n\n  it('allows listening to the same event in multiple case reducers', () => {\n    const set2 = event('set2', type<{ count2: number }>());\n\n    const CounterStore = signalStore(\n      { providedIn: 'root' },\n      withState({ count: 10, count2: 10 }),\n      withReducer(\n        on(set2, ({ payload: { count2 } }) => ({ count2 })),\n        on(set2, () => ({ count: 0 }))\n      )\n    );\n\n    const store = TestBed.inject(CounterStore);\n    const dispatcher = TestBed.inject(Dispatcher);\n\n    dispatcher.dispatch(set2({ count2: 100 }));\n    expect(getState(store)).toEqual({ count: 0, count2: 100 });\n  });\n\n  it('unsubscribes from events when the store is destroyed', () => {\n    const trigger = event('trigger');\n    let executionCount = 0;\n\n    const Store = signalStore(\n      withReducer(\n        on(trigger, () => {\n          executionCount++;\n          return {};\n        })\n      )\n    );\n\n    const { destroy } = createLocalService(Store);\n    const dispatcher = TestBed.inject(Dispatcher);\n\n    expect(executionCount).toBe(0);\n\n    dispatcher.dispatch(trigger());\n    expect(executionCount).toBe(1);\n\n    destroy();\n\n    dispatcher.dispatch(trigger());\n    expect(executionCount).toBe(1);\n  });\n});\n"
  },
  {
    "path": "modules/signals/events/src/case-reducer.ts",
    "content": "import { PartialStateUpdater } from '@ngrx/signals';\nimport { EventCreator } from './event-creator';\n\nexport type CaseReducerResult<\n  State extends object,\n  EventCreators extends EventCreator<string, any>[],\n> = {\n  reducer: CaseReducer<State, EventCreators>;\n  events: EventCreators;\n};\n\ntype CaseReducer<\n  State extends object,\n  EventCreators extends EventCreator<string, any>[],\n> = (\n  event: { [K in keyof EventCreators]: ReturnType<EventCreators[K]> }[number],\n  state: State\n) =>\n  | Partial<State>\n  | PartialStateUpdater<State>\n  | Array<Partial<State> | PartialStateUpdater<State>>;\n\n/**\n * @description\n *\n * Creates a case reducer that can be used with the `withReducer` feature.\n */\nexport function on<\n  State extends object,\n  EventCreators extends EventCreator<string, any>[],\n>(\n  ...args: [\n    ...events: [...EventCreators],\n    reducer: CaseReducer<NoInfer<State>, NoInfer<EventCreators>>,\n  ]\n): CaseReducerResult<State, EventCreators> {\n  const reducer = args.pop() as CaseReducer<State, EventCreators>;\n  const events = args as unknown as EventCreators;\n\n  return { reducer, events };\n}\n"
  },
  {
    "path": "modules/signals/events/src/dispatcher.ts",
    "content": "import { inject, Injectable, Provider } from '@angular/core';\nimport { queueScheduler } from 'rxjs';\nimport { EventInstance } from './event-instance';\nimport { EventScope, EventScopeConfig } from './event-scope';\nimport { Events, EVENTS, ReducerEvents } from './events-service';\n\n/**\n * @description\n *\n * Globally provided service for dispatching events.\n *\n * @usageNotes\n *\n * ```ts\n * import { Dispatcher, event } from '@ngrx/signals/events';\n *\n * const increment = event('[Counter Page] Increment');\n *\n * \\@Component(...)\n * class Counter {\n *   readonly #dispatcher = inject(Dispatcher);\n *\n *   increment(): void {\n *     this.#dispatcher.dispatch(increment());\n *   }\n * }\n * ```\n */\n@Injectable({ providedIn: 'platform' })\nexport class Dispatcher {\n  protected readonly reducerEvents = inject(ReducerEvents);\n  protected readonly events = inject(Events);\n  protected readonly parentDispatcher = inject(Dispatcher, {\n    skipSelf: true,\n    optional: true,\n  });\n\n  dispatch(\n    event: EventInstance<string, unknown>,\n    config?: EventScopeConfig\n  ): void {\n    if (this.parentDispatcher && hasParentOrGlobalScope(config)) {\n      this.parentDispatcher.dispatch(\n        event,\n        config.scope === 'global' ? config : undefined\n      );\n    } else {\n      this.reducerEvents[EVENTS].next(event);\n      queueScheduler.schedule(() => this.events[EVENTS].next(event));\n    }\n  }\n}\n\n/**\n * @description\n *\n * Provides scoped instances of Dispatcher and Events services.\n * Enables event dispatching within a specific component or feature scope.\n *\n * @usageNotes\n *\n * ```ts\n * import { Dispatcher, event } from '@ngrx/signals/events';\n *\n * const increment = event('[Counter Page] Increment');\n *\n * \\@Component({\n *   // ...\n *   providers: [provideDispatcher()],\n * })\n * class Counter {\n *   readonly #dispatcher = inject(Dispatcher);\n *\n *   increment(): void {\n *     // Dispatching an event to the local Dispatcher.\n *     this.#dispatcher.dispatch(increment());\n *\n *     // Dispatching an event to the parent Dispatcher.\n *     this.#dispatcher.dispatch(increment(), { scope: 'parent' });\n *\n *     // Dispatching an event to the global Dispatcher.\n *     this.#dispatcher.dispatch(increment(), { scope: 'global' });\n *   }\n * }\n * ```\n */\nexport function provideDispatcher(): Provider[] {\n  return [Events, ReducerEvents, Dispatcher];\n}\n\nfunction hasParentOrGlobalScope(\n  config?: EventScopeConfig\n): config is { scope: Exclude<EventScope, 'self'> } {\n  return config?.scope === 'parent' || config?.scope === 'global';\n}\n"
  },
  {
    "path": "modules/signals/events/src/event-creator-group.ts",
    "content": "import { Prettify } from '@ngrx/signals';\nimport { event, EventCreator } from './event-creator';\n\ntype EventType<\n  Source extends string,\n  EventName extends string,\n> = `[${Source}] ${EventName}`;\n\ntype EventCreatorGroup<\n  Source extends string,\n  Events extends Record<string, any>,\n> = {\n  readonly [EventName in keyof Events]: EventName extends string\n    ? EventCreator<EventType<Source, EventName>, Events[EventName]>\n    : never;\n};\n\n/**\n * @description\n *\n * Creates a group of event creators.\n *\n * @usageNotes\n *\n * ```ts\n * import { type } from '@ngrx/signals';\n * import { eventGroup } from '@ngrx/signals/events';\n *\n * const counterPageEvents = eventGroup({\n *   source: 'Counter Page',\n *   events: {\n *     increment: type<void>(),\n *     decrement: type<void>(),\n *     set: type<number>(),\n *   },\n * });\n * ```\n */\nexport function eventGroup<\n  Source extends string,\n  Events extends Record<string, unknown>,\n>(config: {\n  source: Source;\n  events: Events;\n}): Prettify<EventCreatorGroup<Source, Events>> {\n  return Object.entries(config.events).reduce(\n    (acc, [eventName]) => {\n      const eventType = `[${config.source}] ${eventName}`;\n      return { ...acc, [eventName]: event(eventType) };\n    },\n    {} as EventCreatorGroup<Source, Events>\n  );\n}\n"
  },
  {
    "path": "modules/signals/events/src/event-creator.ts",
    "content": "import { EventInstance } from './event-instance';\n\nexport type EventCreator<Type extends string, Payload> = ((\n  payload: Payload\n) => EventInstance<Type, Payload>) & { type: Type };\n\nexport function event<Type extends string>(\n  type: Type\n): EventCreator<Type, void>;\nexport function event<Type extends string, Payload>(\n  type: Type,\n  payload: Payload\n): EventCreator<Type, Payload>;\n/**\n * @description\n *\n * Creates an event creator.\n *\n * @usageNotes\n *\n * ### Creating an event creator without payload\n *\n * ```ts\n * import { event } from '@ngrx/signals/events';\n *\n * const increment = event('[Counter Page] Increment');\n * ```\n *\n * ### Creating an event creator with payload\n *\n * ```ts\n * import { type } from '@ngrx/signals';\n * import { event } from '@ngrx/signals/events';\n *\n * const set = event('[Counter Page] Set', type<number>());\n * ```\n */\nexport function event(type: string): EventCreator<string, any> {\n  const creator = (payload?: unknown) => ({ type, payload });\n  (creator as any).type = type;\n\n  return creator as EventCreator<string, unknown>;\n}\n"
  },
  {
    "path": "modules/signals/events/src/event-instance.ts",
    "content": "export type EventInstance<Type extends string, Payload> = {\n  type: Type;\n  payload: Payload;\n};\n\nexport function isEventInstance(\n  value: unknown\n): value is EventInstance<string, unknown> {\n  return typeof value === 'object' && value !== null && 'type' in value;\n}\n"
  },
  {
    "path": "modules/signals/events/src/event-scope.ts",
    "content": "import { map, OperatorFunction } from 'rxjs';\nimport { EventInstance } from './event-instance';\n\nexport type EventScope = 'self' | 'parent' | 'global';\n\nexport type EventScopeConfig = { scope: EventScope };\n\n/**\n * @description\n *\n * Marks a single event to be dispatched in the specified scope.\n * Used in a tuple alongside an event to indicate its dispatch scope.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore, type } from '@ngrx/signals';\n * import { event, Events, withEffects } from '@ngrx/signals/events';\n * import { mapResponse } from '@ngrx/operators';\n *\n * const opened = event('[Users Page] Opened');\n * const loadedSuccess = event('[Users API] Loaded Success', type<User[]>());\n * const loadedFailure = event('[Users API] Loaded Failure', type<string>());\n *\n * const UsersStore = signalStore(\n *   withEffects((\n *     _,\n *     events = inject(Events),\n *     usersService = inject(UsersService)\n *   ) => ({\n *     loadUsers$: events.on(opened).pipe(\n *       exhaustMap(() =>\n *         usersService.getAll().pipe(\n *           mapResponse({\n *             next: (users) => loadedSuccess(users),\n *             error: (error: { message: string }) => [\n *               loadedFailure(error.message),\n *               toScope('global'),\n *             ],\n *           }),\n *         ),\n *       ),\n *     ),\n *   })),\n * );\n * ```\n */\nexport function toScope(scope: EventScope): EventScopeConfig {\n  return { scope };\n}\n\n/**\n * @description\n *\n * RxJS operator that maps all emitted events in the stream to be dispatched\n * in the specified scope.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore, type } from '@ngrx/signals';\n * import { event, Events, withEffects } from '@ngrx/signals/events';\n * import { mapResponse } from '@ngrx/operators';\n *\n * const opened = event('[Users Page] Opened');\n * const loadedSuccess = event('[Users API] Loaded Success', type<User[]>());\n * const loadedFailure = event('[Users API] Loaded Failure', type<string>());\n *\n * const UsersStore = signalStore(\n *   withEffects((\n *     _,\n *     events = inject(Events),\n *     usersService = inject(UsersService)\n *   ) => ({\n *     loadUsers$: events.on(opened).pipe(\n *       exhaustMap(() =>\n *         usersService.getAll().pipe(\n *           mapResponse({\n *             next: (users) => loadedSuccess(users),\n *             error: (error: { message: string }) =>\n *               loadedFailure(error.message),\n *           }),\n *           mapToScope('parent'),\n *         ),\n *       ),\n *     ),\n *   })),\n * );\n * ```\n */\nexport function mapToScope<T extends EventInstance<string, unknown>>(\n  scope: EventScope\n): OperatorFunction<T, [T, EventScopeConfig]> {\n  return map((event) => [event, toScope(scope)]);\n}\n"
  },
  {
    "path": "modules/signals/events/src/events-service.ts",
    "content": "import { inject, Injectable, Type } from '@angular/core';\nimport {\n  filter,\n  map,\n  merge,\n  MonoTypeOperatorFunction,\n  Observable,\n  Subject,\n} from 'rxjs';\nimport { EventInstance } from './event-instance';\nimport { EventCreator } from './event-creator';\n\nexport const EVENTS = Symbol(\n  typeof ngDevMode !== 'undefined' && ngDevMode ? 'EVENTS' : ''\n);\nexport const SOURCE_TYPE = Symbol(\n  typeof ngDevMode !== 'undefined' && ngDevMode ? 'SOURCE_TYPE' : ''\n);\n\nabstract class BaseEvents {\n  /**\n   * @internal\n   */\n  readonly [EVENTS] = new Subject<EventInstance<string, unknown>>();\n  protected readonly events$: Observable<EventInstance<string, unknown>>;\n\n  protected constructor(parentEventsToken: Type<BaseEvents>) {\n    const parentEvents = inject(parentEventsToken, {\n      skipSelf: true,\n      optional: true,\n    });\n    this.events$ = parentEvents\n      ? merge(parentEvents.events$, this[EVENTS])\n      : this[EVENTS].asObservable();\n  }\n\n  on(): Observable<EventInstance<string, unknown>>;\n  on<EventCreators extends EventCreator<string, any>[]>(\n    ...events: [...EventCreators]\n  ): Observable<\n    { [K in keyof EventCreators]: ReturnType<EventCreators[K]> }[number]\n  >;\n  on(\n    ...events: EventCreator<string, unknown>[]\n  ): Observable<EventInstance<string, unknown>> {\n    return this.events$.pipe(filterByType(events), withSourceType());\n  }\n}\n\n/**\n * @description\n *\n * Globally provided service for listening to dispatched events.\n *\n * @usageNotes\n *\n * ```ts\n * import { event, Events } from '@ngrx/signals/events';\n *\n * const increment = event('[Counter Page] Increment');\n *\n * \\@Component(...)\n * class Counter {\n *   readonly #events = inject(Events);\n *\n *   constructor() {\n *     this.#events\n *       .on(increment)\n *       .pipe(takeUntilDestroyed())\n *       .subscribe(() => /* handle increment event *\\/);\n *   }\n * }\n * ```\n */\n@Injectable({ providedIn: 'platform' })\nexport class Events extends BaseEvents {\n  constructor() {\n    super(Events);\n  }\n}\n\n/**\n * @description\n *\n * Globally provided service for listening to dispatched events.\n * Receives events before the `Events` service and is primarily used for\n * handling state transitions.\n */\n@Injectable({ providedIn: 'platform' })\nexport class ReducerEvents extends BaseEvents {\n  constructor() {\n    super(ReducerEvents);\n  }\n}\n\nfunction filterByType<T extends EventInstance<string, unknown>>(\n  events: EventCreator<string, unknown>[]\n): MonoTypeOperatorFunction<T> {\n  if (events.length === 0) {\n    return (source$) => source$;\n  }\n\n  const eventMap = toEventCreatorMap(events);\n  return filter<T>(({ type }) => !!eventMap[type]);\n}\n\nfunction toEventCreatorMap(\n  events: EventCreator<string, unknown>[]\n): Record<string, EventCreator<string, unknown>> {\n  return events.reduce((acc, event) => ({ ...acc, [event.type]: event }), {});\n}\n\nfunction withSourceType<\n  T extends EventInstance<string, unknown>,\n>(): MonoTypeOperatorFunction<T> {\n  return map(({ ...event }) => {\n    Object.defineProperty(event, SOURCE_TYPE, { value: event.type });\n    return event;\n  });\n}\n"
  },
  {
    "path": "modules/signals/events/src/index.ts",
    "content": "export { on } from './case-reducer';\nexport { Dispatcher, provideDispatcher } from './dispatcher';\nexport { event, EventCreator } from './event-creator';\nexport { eventGroup } from './event-creator-group';\nexport { EventInstance } from './event-instance';\nexport {\n  EventScope,\n  EventScopeConfig,\n  mapToScope,\n  toScope,\n} from './event-scope';\nexport { Events, ReducerEvents } from './events-service';\nexport { injectDispatch } from './inject-dispatch';\nexport { withEventHandlers } from './with-event-handlers';\nexport { withReducer } from './with-reducer';\n"
  },
  {
    "path": "modules/signals/events/src/inject-dispatch.ts",
    "content": "import {\n  assertInInjectionContext,\n  inject,\n  Injector,\n  untracked,\n} from '@angular/core';\nimport { Prettify } from '@ngrx/signals';\nimport { Dispatcher } from './dispatcher';\nimport { EventCreator } from './event-creator';\nimport { EventScope, EventScopeConfig } from './event-scope';\n\ntype SelfDispatchingEvents<\n  EventGroup extends Record<string, EventCreator<string, any>>,\n> = {\n  readonly [EventName in keyof EventGroup]: Parameters<\n    EventGroup[EventName]\n  > extends [infer Payload]\n    ? (payload: Payload) => void\n    : () => void;\n};\n\n/**\n * @description\n *\n * Creates self-dispatching events for a given event group.\n *\n * @usageNotes\n *\n * ```ts\n * import { type } from '@ngrx/signals';\n * import { eventGroup, injectDispatch } from '@ngrx/signals/events';\n *\n * const counterPageEvents = eventGroup({\n *   source: 'Counter Page',\n *   events: {\n *     increment: type<void>(),\n *     decrement: type<void>(),\n *   },\n * });\n *\n * \\@Component(...)\n * class Counter {\n *   readonly dispatch = injectDispatch(counterPageEvents);\n *\n *   increment(): void {\n *     this.dispatch.increment();\n *   }\n *\n *   decrement(): void {\n *     this.dispatch.decrement();\n *   }\n * }\n * ```\n */\nexport function injectDispatch<\n  EventGroup extends Record<string, EventCreator<string, any>>,\n>(\n  events: EventGroup,\n  config?: { injector?: Injector }\n): ((config: EventScopeConfig) => Prettify<SelfDispatchingEvents<EventGroup>>) &\n  Prettify<SelfDispatchingEvents<EventGroup>> {\n  if (typeof ngDevMode !== 'undefined' && ngDevMode && !config?.injector) {\n    assertInInjectionContext(injectDispatch);\n  }\n\n  const injector = config?.injector ?? inject(Injector);\n  const dispatcher = injector.get(Dispatcher);\n\n  const eventsCache = {} as Record<\n    EventScope,\n    SelfDispatchingEvents<EventGroup>\n  >;\n\n  const dispatch = (config: EventScopeConfig) => {\n    if (!eventsCache[config.scope]) {\n      eventsCache[config.scope] = Object.entries(events).reduce(\n        (acc, [eventName, eventCreator]) => ({\n          ...acc,\n          [eventName]: (payload?: unknown) =>\n            untracked(() => dispatcher.dispatch(eventCreator(payload), config)),\n        }),\n        {} as SelfDispatchingEvents<EventGroup>\n      );\n    }\n\n    return eventsCache[config.scope];\n  };\n\n  const defaultEventGroup = dispatch({ scope: 'self' });\n  const defaultEventGroupProps = Object.keys(defaultEventGroup).reduce(\n    (acc, eventName) => ({\n      ...acc,\n      [eventName]: {\n        value: defaultEventGroup[eventName],\n        enumerable: true,\n      },\n    }),\n    {} as PropertyDescriptorMap\n  );\n  Object.defineProperties(dispatch, defaultEventGroupProps);\n\n  return dispatch as ReturnType<typeof injectDispatch<EventGroup>>;\n}\n"
  },
  {
    "path": "modules/signals/events/src/with-event-handlers.ts",
    "content": "import { inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { merge, Observable, tap } from 'rxjs';\nimport {\n  EmptyFeatureResult,\n  Prettify,\n  signalStoreFeature,\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n  StateSignals,\n  type,\n  withHooks,\n  WritableStateSource,\n} from '@ngrx/signals';\nimport { Dispatcher } from './dispatcher';\nimport { isEventInstance } from './event-instance';\nimport { SOURCE_TYPE } from './events-service';\n\n/**\n * @description\n *\n * SignalStore feature for defining event handlers.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore, withState } from '@ngrx/signals';\n * import { event, Events, withEventHandlers } from '@ngrx/signals/events';\n *\n * const increment = event('[Counter Page] Increment');\n * const decrement = event('[Counter Page] Decrement');\n *\n * const CounterStore = signalStore(\n *   withState({ count: 0 }),\n *   withEventHandlers(({ count }, events = inject(Events)) => ({\n *     logCount$: events.on(increment, decrement).pipe(\n *       tap(({ type }) => console.log(type, count())),\n *     ),\n *   })),\n * );\n * ```\n */\nexport function withEventHandlers<Input extends SignalStoreFeatureResult>(\n  handlersFactory: (\n    store: Prettify<\n      StateSignals<Input['state']> &\n        Input['props'] &\n        Input['methods'] &\n        WritableStateSource<Prettify<Input['state']>>\n    >\n  ) => Record<string, Observable<unknown>> | Observable<unknown>[]\n): SignalStoreFeature<Input, EmptyFeatureResult> {\n  return signalStoreFeature(\n    type<Input>(),\n    withHooks({\n      onInit(store, dispatcher = inject(Dispatcher)) {\n        const handlerSources = handlersFactory(store);\n        const handlers = Object.values(handlerSources).map((handlerSource$) =>\n          handlerSource$.pipe(\n            tap((result) => {\n              const [potentialEvent, config] = Array.isArray(result)\n                ? result\n                : [result];\n\n              if (\n                isEventInstance(potentialEvent) &&\n                !(SOURCE_TYPE in potentialEvent)\n              ) {\n                dispatcher.dispatch(potentialEvent, config);\n              }\n            })\n          )\n        );\n\n        merge(...handlers)\n          .pipe(takeUntilDestroyed())\n          .subscribe();\n      },\n    })\n  );\n}\n"
  },
  {
    "path": "modules/signals/events/src/with-reducer.ts",
    "content": "import { inject, untracked } from '@angular/core';\nimport { tap } from 'rxjs';\nimport {\n  EmptyFeatureResult,\n  getState,\n  patchState,\n  SignalStoreFeature,\n  signalStoreFeature,\n  type,\n} from '@ngrx/signals';\nimport { CaseReducerResult } from './case-reducer';\nimport { EventCreator } from './event-creator';\nimport { ReducerEvents } from './events-service';\nimport { withEventHandlers } from './with-event-handlers';\n\n/**\n * @description\n *\n * SignalStore feature for defining state transitions based on dispatched events.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore, type, withState } from '@ngrx/signals';\n * import { event, on, withReducer } from '@ngrx/signals/events';\n *\n * const set = event('[Counter Page] Set', type<number>());\n *\n * const CounterStore = signalStore(\n *   withState({ count: 0 }),\n *   withReducer(\n *     on(set, ({ payload }) => ({ count: payload })),\n *   ),\n * );\n * ```\n */\nexport function withReducer<State extends object>(\n  ...caseReducers: CaseReducerResult<State, EventCreator<string, any>[]>[]\n): SignalStoreFeature<\n  { state: State; props: {}; methods: {} },\n  EmptyFeatureResult\n> {\n  return signalStoreFeature(\n    { state: type<State>() },\n    withEventHandlers((store, events = inject(ReducerEvents)) =>\n      caseReducers.map((caseReducer) =>\n        events.on(...caseReducer.events).pipe(\n          tap((event) => {\n            const state = untracked(() => getState(store));\n            const result = caseReducer.reducer(event, state);\n            const updaters = Array.isArray(result) ? result : [result];\n\n            patchState(store, ...updaters);\n          })\n        )\n      )\n    )\n  );\n}\n"
  },
  {
    "path": "modules/signals/index.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/signals/migrations/18_0_0-rc_3-protected-state/index.spec.ts",
    "content": "import * as path from 'path';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\nimport { tags } from '@angular-devkit/core';\n\ndescribe('migrate protectedState', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/signals/migrations/migration.json'\n  );\n  const schematicRunner = new SchematicTestRunner('schematics', collectionPath);\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  const verifySchematic = async (input: string, output: string) => {\n    appTree.create('main.ts', input);\n\n    const tree = await schematicRunner.runSchematic(\n      `18_0_0-rc_3-protected-state`,\n      {},\n      appTree\n    );\n\n    const actual = tree.readContent('main.ts');\n\n    expect(actual).toBe(output);\n  };\n\n  it('should disable the state protection for an existing signalStore', async () => {\n    const input = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst store = signalStore(withState({id: 1, name: 'Rob'}));\n`;\n\n    const output = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst store = signalStore({ protectedState: false }, withState({id: 1, name: 'Rob'}));\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('should append it to an existing config', async () => {\n    const input = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst store = signalStore({ providedIn: 'root' }, withState({id: 1, name: 'Rob'}));\n`;\n\n    const output = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst store = signalStore({ providedIn: 'root', protectedState: false }, withState({id: 1, name: 'Rob'}));\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('should migrate signalStores without features', async () => {\n    const input = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst store = signalStore();\n`;\n\n    const output = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst store = signalStore({ protectedState: false });\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('should also cover a signalStore with multiple lines (with config)', async () => {\n    const input = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nexport const TalkStore = signalStore(\n  { providedIn: 'root' },\n  withState(initialValue),\n);\n`;\n\n    const output = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nexport const TalkStore = signalStore(\n  { providedIn: 'root', protectedState: false },\n  withState(initialValue),\n);\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('should also cover a signalStore with multiple lines (without config)', async () => {\n    const input = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nexport const TalkStore = signalStore(\n  withState(initialValue),\n  withMethods(() => ({}))\n);\n`;\n\n    const output = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nexport const TalkStore = signalStore(\n  { protectedState: false }, withState(initialValue),\n  withMethods(() => ({}))\n);\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('should do migrations for multiple signalStores', async () => {\n    const input = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst initialState = {id: 1, name: 'Rob'};\nconst store1 = signalStore(withState(initialState));\n\nfunction createStore() {\n  return signalStore(withState(initialState));\n}\n\nclass PersonStore {\n  #store = signalStore(withState(initialState));\n}\n`;\n\n    const output = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst initialState = {id: 1, name: 'Rob'};\nconst store1 = signalStore({ protectedState: false }, withState(initialState));\n\nfunction createStore() {\n  return signalStore({ protectedState: false }, withState(initialState));\n}\n\nclass PersonStore {\n  #store = signalStore({ protectedState: false }, withState(initialState));\n}\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('should ensure files are running isolated', async () => {\n    const inputMain1 = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst store1 = signalStore(withState(initialState));\n    `;\n\n    const inputMain2 = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nclass PersonStore {\n  #store = signalStore(withState(initialState));\n}\n    `;\n\n    const outputMain1 = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nconst store1 = signalStore({ protectedState: false }, withState(initialState));\n    `;\n\n    const outputMain2 = tags.stripIndent`\nimport { signalStore } from '@ngrx/signals';\n\nclass PersonStore {\n  #store = signalStore({ protectedState: false }, withState(initialState));\n}\n    `;\n\n    appTree.create('main1.ts', inputMain1);\n    appTree.create('main2.ts', inputMain2);\n\n    const tree = await schematicRunner.runSchematic(\n      `18_0_0-rc_3-protected-state`,\n      {},\n      appTree\n    );\n\n    expect(tree.readContent('main1.ts')).toBe(outputMain1);\n    expect(tree.readContent('main2.ts')).toBe(outputMain2);\n  });\n\n  it('should be able to process import aliases', async () => {\n    const input = tags.stripIndent`\nimport { of } from 'rxjs';\nimport { withState, withMethods, signalStore as createStoreClass } from '@ngrx/signals';\nimport { Injectable } from '@angular/common';\n\nconst store = createStoreClass(withState({id: 1, name: 'Rob'}));\n`;\n\n    const output = tags.stripIndent`\nimport { of } from 'rxjs';\nimport { withState, withMethods, signalStore as createStoreClass } from '@ngrx/signals';\nimport { Injectable } from '@angular/common';\n\nconst store = createStoreClass({ protectedState: false }, withState({id: 1, name: 'Rob'}));\n`;\n\n    await verifySchematic(input, output);\n  });\n});\n"
  },
  {
    "path": "modules/signals/migrations/18_0_0-rc_3-protected-state/index.ts",
    "content": "import {\n  Change,\n  createReplaceChange,\n  visitTSSourceFiles,\n  commitChanges,\n} from '../../schematics-core';\nimport { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { visitImportDeclaration } from '../../schematics-core/utility/visitors';\n\nexport default function migrateWritableStateSource(): Rule {\n  return (tree: Tree, ctx: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const signalStoreImportedName = findImportedName(sourceFile);\n      if (!signalStoreImportedName) {\n        return;\n      }\n\n      const changes: Change[] = [];\n      visitCallExpression(sourceFile, signalStoreImportedName, (node) => {\n        if (node.arguments.length > 0) {\n          if (ts.isObjectLiteralExpression(node.arguments[0])) {\n            // signalStore({ providedIn: 'root' })\n            const providedInProperty = node.arguments[0].properties[0];\n\n            if (\n              ts.isPropertyAssignment(providedInProperty) &&\n              ts.isIdentifier(providedInProperty.name) &&\n              providedInProperty.name.text === 'providedIn'\n            ) {\n              changes.push(\n                createReplaceChange(\n                  sourceFile,\n                  providedInProperty,\n                  providedInProperty.getText(),\n                  `${providedInProperty.getText()}, protectedState: false`\n                )\n              );\n            }\n          } else {\n            // signalStore(withState({}))\n            const firstFeature = node.arguments[0];\n            changes.push(\n              createReplaceChange(\n                sourceFile,\n                firstFeature,\n                firstFeature.getText(),\n                `{ protectedState: false }, ${firstFeature.getText()}`\n              )\n            );\n          }\n        } else {\n          // signalStore()\n          changes.push(\n            createReplaceChange(\n              sourceFile,\n              node,\n              node.getText(),\n              `${signalStoreImportedName}({ protectedState: false })`\n            )\n          );\n        }\n      });\n\n      if (changes.length) {\n        commitChanges(tree, sourceFile.fileName, changes);\n        ctx.logger.info(\n          `[@ngrx/signals] Disable protected state in ${sourceFile.fileName}`\n        );\n      }\n    });\n  };\n}\n\nfunction visitCallExpression(\n  node: ts.Node,\n  name: string,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (\n    ts.isCallExpression(node) &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === name\n  ) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, name, callback);\n  });\n}\n\nfunction findImportedName(source: ts.SourceFile) {\n  let importedName = '';\n  visitImportDeclaration(source, (importDeclaration) => {\n    if (importDeclaration.moduleSpecifier.getText().includes('@ngrx/signals')) {\n      if (importedName) {\n        return;\n      }\n\n      const namedBindings = importDeclaration.importClause?.namedBindings;\n      if (namedBindings && ts.isNamedImports(namedBindings)) {\n        const foundImportedName = namedBindings.elements\n          .map((importSpecifier) => {\n            if (\n              importSpecifier.propertyName &&\n              importSpecifier.propertyName.text === 'signalStore'\n            ) {\n              return importSpecifier.name.text;\n            } else if (importSpecifier.name.text === 'signalStore') {\n              return 'signalStore';\n            }\n            return undefined;\n          })\n          .find(Boolean);\n\n        if (foundImportedName) {\n          importedName = foundImportedName;\n          return;\n        }\n      }\n    }\n  });\n\n  return importedName;\n}\n"
  },
  {
    "path": "modules/signals/migrations/18_0_0-rc_3-writablestatesource/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\nimport { tags, logging } from '@angular-devkit/core';\nimport * as path from 'path';\n\ndescribe('18_0_0-rc_3-writablestatesource', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/signals/migrations/migration.json'\n  );\n  const schematicRunner = new SchematicTestRunner('schematics', collectionPath);\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  const verifySchematic = async (\n    input: string,\n    output: string\n  ): Promise<logging.LogEntry[]> => {\n    appTree.create('main.ts', input);\n\n    const logEntries: logging.LogEntry[] = [];\n    schematicRunner.logger.subscribe((logEntry) => logEntries.push(logEntry));\n\n    const tree = await schematicRunner.runSchematic(\n      `18_0_0-rc_3-writablestatesource`,\n      {},\n      appTree\n    );\n\n    const actual = tree.readContent('main.ts');\n    expect(actual).toBe(output);\n\n    return logEntries;\n  };\n\n  it('replaces StateSignal references to WritableStateSource', async () => {\n    const input = tags.stripIndent`\nimport { StateSignal, patchState } from '@ngrx/signals';\n\nfunction updateCount(state: StateSignal<{ count: number }>, count: number): void {\n  patchState(state, { count });\n}\n\nfunction updateCount2(state: StateSignal<{ count: number }>, count: number): void {\n  patchState(state, { count });\n}`;\n    const output = tags.stripIndent`\nimport { WritableStateSource, patchState } from '@ngrx/signals';\n\nfunction updateCount(state: WritableStateSource<{ count: number }>, count: number): void {\n  patchState(state, { count });\n}\n\nfunction updateCount2(state: WritableStateSource<{ count: number }>, count: number): void {\n  patchState(state, { count });\n}`;\n\n    const logEntries = await verifySchematic(input, output);\n\n    expect(logEntries).toHaveLength(2);\n    expect(logEntries[0]).toMatchObject({\n      message: `[@ngrx/signals] Migrating 'StateSignal' to 'WritableStateSource'`,\n      level: 'info',\n    });\n    expect(logEntries[1]).toMatchObject({\n      message: `[@ngrx/signals] Updated 2 references from 'StateSignal' to 'WritableStateSource'`,\n      level: 'info',\n    });\n  });\n\n  it('does nothing if StateSignal is imported from other package', async () => {\n    const input = tags.stripIndent`\nimport { StateSignal, patchState } from '@awesome-lib';\n\nfunction updateCount(state: StateSignal<{ count: number }>, count: number): void {\n  patchState(state, { count });\n}`;\n\n    const logEntries = await verifySchematic(input, input);\n\n    expect(logEntries).toHaveLength(2);\n    expect(logEntries[0]).toMatchObject({\n      message: `[@ngrx/signals] Migrating 'StateSignal' to 'WritableStateSource'`,\n      level: 'info',\n    });\n    expect(logEntries[1]).toMatchObject({\n      message: `[@ngrx/signals] No 'StateSignal' refences found to, skipping the migration`,\n      level: 'info',\n    });\n  });\n});\n"
  },
  {
    "path": "modules/signals/migrations/18_0_0-rc_3-writablestatesource/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Tree,\n  Rule,\n  chain,\n  SchematicContext,\n} from '@angular-devkit/schematics';\nimport {\n  commitChanges,\n  createReplaceChange,\n  replaceImport,\n  visitTSSourceFiles,\n} from '../../schematics-core';\nimport { Path } from '@angular-devkit/core';\n\nexport function migrateWritableStateSource(): Rule {\n  return (tree: Tree, ctx: SchematicContext) => {\n    let updateCounter = 0;\n    ctx.logger.info(\n      `[@ngrx/signals] Migrating 'StateSignal' to 'WritableStateSource'`\n    );\n\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const changes = replaceImport(\n        sourceFile,\n        sourceFile.fileName as Path,\n        '@ngrx/signals',\n        'StateSignal',\n        'WritableStateSource'\n      );\n\n      if (changes.length) {\n        visitIdentifiers(sourceFile, (node) => {\n          if (\n            node.getText() === 'StateSignal' &&\n            !ts.isImportSpecifier(node.parent)\n          ) {\n            changes.push(\n              createReplaceChange(\n                sourceFile,\n                node,\n                'StateSignal',\n                'WritableStateSource'\n              )\n            );\n            updateCounter++;\n          }\n        });\n      }\n\n      commitChanges(tree, sourceFile.fileName, changes);\n    });\n\n    if (updateCounter) {\n      ctx.logger.info(\n        `[@ngrx/signals] Updated ${updateCounter} references from 'StateSignal' to 'WritableStateSource'`\n      );\n    } else {\n      ctx.logger.info(\n        `[@ngrx/signals] No 'StateSignal' refences found to, skipping the migration`\n      );\n    }\n  };\n}\n\nfunction visitIdentifiers(\n  node: ts.Node,\n  visitor: (node: ts.Identifier) => void\n) {\n  if (ts.isIdentifier(node)) {\n    visitor(node);\n  }\n\n  ts.forEachChild(node, (childNode) => visitIdentifiers(childNode, visitor));\n}\n\nexport default function (): Rule {\n  return chain([migrateWritableStateSource()]);\n}\n"
  },
  {
    "path": "modules/signals/migrations/19_0_0-rc_0-props/index.spec.ts",
    "content": "import * as path from 'path';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\nimport { tags } from '@angular-devkit/core';\n\ndescribe('migrate to props', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/signals/migrations/migration.json'\n  );\n  const schematicRunner = new SchematicTestRunner('schematics', collectionPath);\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  const verifySchematic = async (input: string, output: string) => {\n    appTree.create('main.ts', input);\n\n    const logEntries: string[] = [];\n    schematicRunner.logger.subscribe((logEntry) =>\n      logEntries.push(logEntry.message)\n    );\n\n    const tree = await schematicRunner.runSchematic(\n      `19_0_0-rc_0-props`,\n      {},\n      appTree\n    );\n\n    const actual = tree.readContent('main.ts');\n\n    expect(actual).toBe(output);\n\n    return logEntries;\n  };\n\n  it('renames (Named)EntityComputed imports to (Named)EntityProps', async () => {\n    const input = tags.stripIndent`\nimport { EntityComputed, NamedEntityComputed } from '@ngrx/signals/entities';\n`;\n\n    const output = tags.stripIndent`\nimport { EntityProps, NamedEntityProps } from '@ngrx/signals/entities';\n`;\n\n    const logEntries = await verifySchematic(input, output);\n    expect(logEntries).toEqual([\n      \"[@ngrx/signals] Renamed '(Named)EntityComputed' to '(Named)EntityProps' in /main.ts\",\n    ]);\n  });\n\n  it('replaces property `computed` in `SignalStoreFeature` to `props`', async () => {\n    const input = tags.stripIndent`\nimport { signalStoreFeature, type } from '@ngrx/signals';\nimport { Signal } from '@angular/core';\n\nexport function withMyFeature() {\n  return signalStoreFeature({ computed: type<{ num: Signal<number> }>() });\n}\n`;\n\n    const output = tags.stripIndent`\nimport { signalStoreFeature, type } from '@ngrx/signals';\nimport { Signal } from '@angular/core';\n\nexport function withMyFeature() {\n  return signalStoreFeature({ props: type<{ num: Signal<number> }>() });\n}\n`;\n\n    const logEntries = await verifySchematic(input, output);\n    expect(logEntries).toEqual([\n      \"[@ngrx/signals] Renamed 'computed' to 'props' in signalStoreFeature() in /main.ts\",\n    ]);\n  });\n\n  it('replaces property `computed` in `type` with `signalStoreFeature` to `props`', async () => {\n    const input = tags.stripIndent`\nexport function withMyFeature() {\n  return signalStoreFeature(\n    type<{ computed: { num: Signal<number> } }>()\n  );\n}\n`;\n\n    const output = tags.stripIndent`\nexport function withMyFeature() {\n  return signalStoreFeature(\n    type<{ props: { num: Signal<number> } }>()\n  );\n}\n`;\n\n    await verifySchematic(input, output);\n  });\n\n  it('replaces `computed` in `SignalStoreFeature` to `props`', async () => {\n    const input = tags.stripIndent`\nimport { SignalStoreFeature } from '@ngrx/signals';\nimport { Signal } from '@angular/core';\n\ndeclare function withMyFeature(): SignalStoreFeature<\n  { state: {}; computed: { num1: Signal<number> }; methods: {} },\n  { state: {}; computed: { num2: Signal<number> }; methods: {} }\n>;\n`;\n\n    const output = tags.stripIndent`\nimport { SignalStoreFeature } from '@ngrx/signals';\nimport { Signal } from '@angular/core';\n\ndeclare function withMyFeature(): SignalStoreFeature<\n  { state: {}; props: { num1: Signal<number> }; methods: {} },\n  { state: {}; props: { num2: Signal<number> }; methods: {} }\n>;\n`;\n\n    const logEntries = await verifySchematic(input, output);\n    expect(logEntries).toEqual([\n      \"[@ngrx/signals] Renamed 'computed' to 'props' in SignalStoreFeature<> in /main.ts\",\n    ]);\n  });\n\n  test('kitchen sink', async () => {\n    const input = tags.stripIndent`\nimport { EntityComputed, NamedEntityComputed } from '@ngrx/signals/entities';\nimport {\n  EmptyFeatureResult,\n  SignalStoreFeature,\n  signalStoreFeature,\n  type,\n  withHooks,\n  withMethods,\n} from '@ngrx/signals';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\nimport { tap } from 'rxjs/operators';\nimport { Signal } from '@angular/core';\n\ndeclare function withNamedDataService<\n  E extends { id: number },\n  Collection extends string\n>(): SignalStoreFeature<\n  EmptyFeatureResult & { computed: NamedEntityComputed<E, Collection> }\n>;\n\ndeclare function withDataService<\n  E extends { id: number },\n  Collection extends string\n>(): SignalStoreFeature<EmptyFeatureResult & { computed: EntityComputed<E> }>;\n\nexport function withConsoleLogger() {\n  return signalStoreFeature(\n    { computed: type<{ pretty: Signal<string> }>() },\n    withMethods(() => ({\n      log: rxMethod<string>(tap((message) => console.log(message))),\n    })),\n    withHooks((store) => ({\n      onInit() {\n        store.log(store.pretty());\n      },\n    }))\n  );\n}`;\n\n    const output = tags.stripIndent`\nimport { EntityProps, NamedEntityProps } from '@ngrx/signals/entities';\nimport {\n  EmptyFeatureResult,\n  SignalStoreFeature,\n  signalStoreFeature,\n  type,\n  withHooks,\n  withMethods,\n} from '@ngrx/signals';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\nimport { tap } from 'rxjs/operators';\nimport { Signal } from '@angular/core';\n\ndeclare function withNamedDataService<\n  E extends { id: number },\n  Collection extends string\n>(): SignalStoreFeature<\n  EmptyFeatureResult & { props: NamedEntityProps<E, Collection> }\n>;\n\ndeclare function withDataService<\n  E extends { id: number },\n  Collection extends string\n>(): SignalStoreFeature<EmptyFeatureResult & { props: EntityProps<E> }>;\n\nexport function withConsoleLogger() {\n  return signalStoreFeature(\n    { props: type<{ pretty: Signal<string> }>() },\n    withMethods(() => ({\n      log: rxMethod<string>(tap((message) => console.log(message))),\n    })),\n    withHooks((store) => ({\n      onInit() {\n        store.log(store.pretty());\n      },\n    }))\n  );\n}`;\n\n    const logEntries = await verifySchematic(input, output);\n    expect(logEntries).toEqual([\n      \"[@ngrx/signals] Renamed '(Named)EntityComputed' to '(Named)EntityProps' in /main.ts\",\n      \"[@ngrx/signals] Renamed 'computed' to 'props' in SignalStoreFeature<> in /main.ts\",\n      \"[@ngrx/signals] Renamed 'computed' to 'props' in signalStoreFeature() in /main.ts\",\n    ]);\n  });\n\n  it('creates two files with minimal changes and checks both files', async () => {\n    const input1 = tags.stripIndent`\nimport { EntityComputed } from '@ngrx/signals/entities';\n`;\n\n    const output1 = tags.stripIndent`\nimport { EntityProps } from '@ngrx/signals/entities';\n`;\n\n    const input2 = tags.stripIndent`\nimport { NamedEntityComputed } from '@ngrx/signals/entities';\n`;\n\n    const output2 = tags.stripIndent`\nimport { NamedEntityProps } from '@ngrx/signals/entities';\n  `;\n\n    appTree.create('file1.ts', input1);\n    appTree.create('file2.ts', input2);\n\n    const logEntries: string[] = [];\n    schematicRunner.logger.subscribe((logEntry) =>\n      logEntries.push(logEntry.message)\n    );\n\n    const tree = await schematicRunner.runSchematic(\n      `19_0_0-rc_0-props`,\n      {},\n      appTree\n    );\n\n    const actual1 = tree.readContent('file1.ts');\n    const actual2 = tree.readContent('file2.ts');\n\n    expect(actual1).toBe(output1);\n    expect(actual2).toBe(output2);\n\n    expect(logEntries).toEqual([\n      \"[@ngrx/signals] Renamed '(Named)EntityComputed' to '(Named)EntityProps' in /file1.ts\",\n      \"[@ngrx/signals] Renamed '(Named)EntityComputed' to '(Named)EntityProps' in /file2.ts\",\n    ]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/migrations/19_0_0-rc_0-props/index.ts",
    "content": "import {\n  chain,\n  Rule,\n  SchematicContext,\n  Tree,\n} from '@angular-devkit/schematics';\nimport {\n  Change,\n  commitChanges,\n  createReplaceChange,\n  visitTSSourceFiles,\n} from '../../schematics-core';\nimport {\n  visitCallExpression,\n  visitImportDeclaration,\n  visitImportSpecifier,\n  visitTypeLiteral,\n  visitTypeReference,\n} from '../../schematics-core/utility/visitors';\nimport * as ts from 'typescript';\n\nfunction migratedToEntityProps(sourceFile: ts.SourceFile) {\n  const changes: Change[] = [];\n  visitImportDeclaration(sourceFile, (importDeclaration, moduleName) => {\n    if (moduleName !== '@ngrx/signals/entities') {\n      return;\n    }\n\n    visitImportSpecifier(importDeclaration, (importSpecifier) => {\n      if (importSpecifier.name.getText() === 'EntityComputed') {\n        changes.push(\n          createReplaceChange(\n            sourceFile,\n            importSpecifier,\n            importSpecifier.getText(),\n            'EntityProps'\n          )\n        );\n\n        visitTypeReference(sourceFile, (type) => {\n          if (type.typeName.getText() === 'EntityComputed') {\n            changes.push(\n              createReplaceChange(\n                sourceFile,\n                type,\n                type.typeName.getText(),\n                'EntityProps'\n              )\n            );\n          }\n        });\n      }\n\n      if (importSpecifier.name.getText() === 'NamedEntityComputed') {\n        changes.push(\n          createReplaceChange(\n            sourceFile,\n            importSpecifier,\n            importSpecifier.getText(),\n            'NamedEntityProps'\n          )\n        );\n\n        visitTypeReference(sourceFile, (typeReference) => {\n          if (typeReference.typeName.getText() === 'NamedEntityComputed') {\n            changes.push(\n              createReplaceChange(\n                sourceFile,\n                typeReference.typeName,\n                typeReference.typeName.getText(),\n                'NamedEntityProps'\n              )\n            );\n          }\n        });\n      }\n    });\n  });\n\n  return changes;\n}\n\nfunction migrateToPropsInSignalStoreFeatureType(\n  sourceFile: ts.SourceFile\n): Change[] {\n  const changes: Change[] = [];\n  visitTypeReference(sourceFile, (typeReference) => {\n    if (typeReference.typeName.getText() !== 'SignalStoreFeature') {\n      return;\n    }\n\n    visitTypeLiteral(typeReference, (typeLiteral) => {\n      const typeLiteralChildren = typeLiteral.members;\n      for (const propertySignature of typeLiteralChildren) {\n        if (ts.isPropertySignature(propertySignature)) {\n          if (propertySignature.name.getText() === 'computed') {\n            changes.push(\n              createReplaceChange(\n                sourceFile,\n                propertySignature.name,\n                'computed',\n                'props'\n              )\n            );\n          }\n        }\n      }\n    });\n  });\n\n  return changes;\n}\n\nfunction migrateToPropsInSignalStoreFeatureWithObjectLiteral(\n  objectLiteral: ts.ObjectLiteralExpression,\n  sourceFile: ts.SourceFile\n): Change[] {\n  const computedKey = objectLiteral.properties\n    .filter(ts.isPropertyAssignment)\n    .find((property) => property.name.getText() === 'computed');\n  if (computedKey) {\n    return [createReplaceChange(sourceFile, computedKey, 'computed', 'props')];\n  }\n\n  return [];\n}\n\nfunction migrateToPropsInSignalStoreFeatureWithCallExpression(\n  callExpression: ts.CallExpression,\n  sourceFile: ts.SourceFile\n): Change[] {\n  if (callExpression.expression.getText() === 'type') {\n    const typeArgument = callExpression.typeArguments?.at(0);\n\n    if (typeArgument && ts.isTypeLiteralNode(typeArgument)) {\n      const computedKey = typeArgument.members\n        .filter(ts.isPropertySignature)\n        .find(\n          (propertySignature) => propertySignature.name.getText() === 'computed'\n        );\n\n      if (computedKey) {\n        return [\n          createReplaceChange(sourceFile, computedKey, 'computed', 'props'),\n        ];\n      }\n    }\n  }\n\n  return [];\n}\n\nfunction migrateToPropsInSignalStoreFeatureFunction(\n  sourceFile: ts.SourceFile\n): Change[] {\n  const changes: Change[] = [];\n  visitCallExpression(sourceFile, (callExpression) => {\n    if (callExpression.expression.getText() !== 'signalStoreFeature') {\n      return;\n    }\n\n    const objectLiteralOrCallExpression = callExpression.arguments[0];\n    if (!objectLiteralOrCallExpression) {\n      return;\n    }\n\n    if (ts.isObjectLiteralExpression(objectLiteralOrCallExpression)) {\n      changes.push(\n        ...migrateToPropsInSignalStoreFeatureWithObjectLiteral(\n          objectLiteralOrCallExpression,\n          sourceFile\n        )\n      );\n    } else if (ts.isCallExpression(objectLiteralOrCallExpression)) {\n      changes.push(\n        ...migrateToPropsInSignalStoreFeatureWithCallExpression(\n          objectLiteralOrCallExpression,\n          sourceFile\n        )\n      );\n    }\n  });\n\n  return changes;\n}\n\nexport function migrate(): Rule {\n  return (tree: Tree, ctx: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const entityPropsChanges = migratedToEntityProps(sourceFile);\n      const propsInSignalStoreFeatureTypeChanges =\n        migrateToPropsInSignalStoreFeatureType(sourceFile);\n      const propsInSignalStoreFeatureFunctionChanges =\n        migrateToPropsInSignalStoreFeatureFunction(sourceFile);\n      const changes = [\n        ...entityPropsChanges,\n        ...propsInSignalStoreFeatureTypeChanges,\n        ...propsInSignalStoreFeatureFunctionChanges,\n      ];\n\n      commitChanges(tree, sourceFile.fileName, changes);\n\n      if (entityPropsChanges.length) {\n        ctx.logger.info(\n          `[@ngrx/signals] Renamed '(Named)EntityComputed' to '(Named)EntityProps' in ${sourceFile.fileName}`\n        );\n      }\n      if (propsInSignalStoreFeatureTypeChanges.length) {\n        ctx.logger.info(\n          `[@ngrx/signals] Renamed 'computed' to 'props' in SignalStoreFeature<> in ${sourceFile.fileName}`\n        );\n      }\n      if (propsInSignalStoreFeatureFunctionChanges.length) {\n        ctx.logger.info(\n          `[@ngrx/signals] Renamed 'computed' to 'props' in signalStoreFeature() in ${sourceFile.fileName}`\n        );\n      }\n    });\n  };\n}\n\nexport default function (): Rule {\n  return chain([migrate()]);\n}\n"
  },
  {
    "path": "modules/signals/migrations/21_0_0-beta_0-rename-withEffects-to-withEventHandlers/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\nimport { tags, logging } from '@angular-devkit/core';\nimport * as path from 'path';\n\ndescribe('21_0_0-beta_0-rename-withEffects-to-withEventHandlers', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/signals/migrations/migration.json'\n  );\n  const schematicRunner = new SchematicTestRunner('schematics', collectionPath);\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  const verify = async (input: string, output: string) => {\n    appTree.create('main.ts', input);\n\n    const logs: logging.LogEntry[] = [];\n    schematicRunner.logger.subscribe((e) => logs.push(e));\n\n    const tree = await schematicRunner.runSchematic(\n      `21_0_0-beta_0-rename-withEffects-to-withEventHandlers`,\n      {},\n      appTree\n    );\n\n    const actual = tree.readContent('main.ts');\n    expect(actual).toBe(output);\n\n    return logs;\n  };\n\n  it('renames named import', async () => {\n    const input = tags.stripIndent`\n      import { withEffects } from '@ngrx/signals/events';\n      const S = signalStore(withEffects(() => {}));\n    `;\n\n    const output = tags.stripIndent`\n      import { withEventHandlers } from '@ngrx/signals/events';\n      const S = signalStore(withEventHandlers(() => {}));\n    `;\n\n    const logs = await verify(input, output);\n    expect(logs[0].message).toContain(\n      \"Renamed 'withEffects' to 'withEventHandlers'\"\n    );\n  });\n\n  it('preserves alias', async () => {\n    const input = tags.stripIndent`\n      import { withEffects as withAlias } from '@ngrx/signals/events';\n      const S = signalStore(withAlias(() => {}));\n    `;\n\n    const output = tags.stripIndent`\n      import { withEventHandlers as withAlias } from '@ngrx/signals/events';\n      const S = signalStore(withAlias(() => {}));\n    `;\n\n    await verify(input, output);\n  });\n\n  it('renames namespace usage', async () => {\n    const input = tags.stripIndent`\n      import * as events from '@ngrx/signals/events';\n      const S = signalStore(events.withEffects(() => {}));\n    `;\n\n    const output = tags.stripIndent`\n      import * as events from '@ngrx/signals/events';\n      const S = signalStore(events.withEventHandlers(() => {}));\n    `;\n\n    await verify(input, output);\n  });\n\n  it('ignores other packages', async () => {\n    const input = tags.stripIndent`\n      import { withEffects } from 'other';\n      const S = withEffects(() => {});\n    `;\n\n    await verify(input, input);\n  });\n\n  it('handles multiple imports and usages', async () => {\n    const input = tags.stripIndent`\n    import { withEffects, event } from '@ngrx/signals/events';\n    const S1 = signalStore(withEffects(() => {}));\n    const S2 = signalStore(withEffects(() => {}));\n  `;\n\n    const output = tags.stripIndent`\n    import { withEventHandlers, event } from '@ngrx/signals/events';\n    const S1 = signalStore(withEventHandlers(() => {}));\n    const S2 = signalStore(withEventHandlers(() => {}));\n  `;\n\n    await verify(input, output);\n  });\n});\n"
  },
  {
    "path": "modules/signals/migrations/21_0_0-beta_0-rename-withEffects-to-withEventHandlers/index.ts",
    "content": "import {\n  Change,\n  commitChanges,\n  createReplaceChange,\n  findNodes,\n  visitTSSourceFiles,\n} from '../../schematics-core';\nimport { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\n\nconst EVENTS_PKG = '@ngrx/signals/events';\nconst OLD_NAME = 'withEffects';\nconst NEW_NAME = 'withEventHandlers';\n\nexport default function migrateWithEventHandlers(): Rule {\n  return (tree: Tree, ctx: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const changes: Change[] = [];\n      const namespaceImports = new Set<string>();\n\n      const directImports = new Set<string>();\n\n      const importDeclarations = findNodes(\n        sourceFile,\n        ts.SyntaxKind.ImportDeclaration\n      ) as ts.ImportDeclaration[];\n\n      for (const decl of importDeclarations) {\n        const moduleSpecifier = decl.moduleSpecifier as ts.StringLiteral;\n        if (!moduleSpecifier || moduleSpecifier.text !== EVENTS_PKG) {\n          continue;\n        }\n\n        const importClause = decl.importClause;\n        if (!importClause || !importClause.namedBindings) {\n          continue;\n        }\n\n        if (ts.isNamedImports(importClause.namedBindings)) {\n          const named = importClause.namedBindings;\n\n          named.elements.forEach((spec) => {\n            const importedName = spec.propertyName ?? spec.name;\n            const localName = spec.name;\n            const hasAlias = spec.propertyName !== undefined;\n\n            if (importedName.text === OLD_NAME) {\n              changes.push(\n                createReplaceChange(\n                  sourceFile,\n                  importedName,\n                  importedName.getText(),\n                  NEW_NAME\n                )\n              );\n\n              if (!hasAlias) {\n                directImports.add(localName.text);\n              }\n            }\n          });\n        }\n\n        if (ts.isNamespaceImport(importClause.namedBindings)) {\n          namespaceImports.add(importClause.namedBindings.name.text);\n        }\n      }\n\n      if (directImports.size > 0) {\n        const callExpressions = findNodes(\n          sourceFile,\n          ts.SyntaxKind.CallExpression\n        ) as ts.CallExpression[];\n\n        callExpressions.forEach((call) => {\n          if (\n            ts.isIdentifier(call.expression) &&\n            directImports.has(call.expression.text)\n          ) {\n            changes.push(\n              createReplaceChange(\n                sourceFile,\n                call.expression,\n                call.expression.getText(),\n                NEW_NAME\n              )\n            );\n          }\n        });\n      }\n\n      if (namespaceImports.size > 0) {\n        const propertyAccesses = findNodes(\n          sourceFile,\n          ts.SyntaxKind.PropertyAccessExpression\n        ) as ts.PropertyAccessExpression[];\n\n        propertyAccesses.forEach((pa) => {\n          if (\n            ts.isIdentifier(pa.expression) &&\n            namespaceImports.has(pa.expression.text) &&\n            ts.isIdentifier(pa.name) &&\n            pa.name.text === OLD_NAME\n          ) {\n            changes.push(\n              createReplaceChange(\n                sourceFile,\n                pa.name,\n                pa.name.getText(),\n                NEW_NAME\n              )\n            );\n          }\n        });\n      }\n\n      if (changes.length) {\n        commitChanges(tree, sourceFile.fileName, changes);\n        ctx.logger.info(\n          `[@ngrx/signals] Renamed '${OLD_NAME}' to '${NEW_NAME}' in /${sourceFile.fileName}`\n        );\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "modules/signals/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"18_0_0-rc_3-protected-state\": {\n      \"description\": \"Make the state of all existing SignalStores unprotected\",\n      \"version\": \"18.0.0-rc.3\",\n      \"factory\": \"./18_0_0-rc_3-protected-state/index\"\n    },\n    \"18_0_0-rc_3-writablestatesource\": {\n      \"description\": \"Replace StateSignal usages with WritableStateSource\",\n      \"version\": \"18.0.0-rc.3\",\n      \"factory\": \"./18_0_0-rc_3-writablestatesource/index\"\n    },\n    \"19_0_0-rc_0-props\": {\n      \"description\": \"Replace several properties with a single props object\",\n      \"version\": \"19.0.0-rc.0\",\n      \"factory\": \"./19_0_0-rc_0-props/index\"\n    },\n    \"21_0_0-beta_0-rename-withEffects-to-withEventHandlers\": {\n      \"description\": \"Rename withEffects to withEventHandlers\",\n      \"version\": \"21.0.0-beta.0\",\n      \"factory\": \"./21_0_0-beta_0-rename-withEffects-to-withEventHandlers/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/signals/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/signals\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/signals/package.json",
    "content": "{\n  \"name\": \"@ngrx/signals\",\n  \"version\": \"21.0.1\",\n  \"description\": \"Reactive Store and Set of Utilities for Angular Signals\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"Angular\",\n    \"NgRx\",\n    \"Signals\",\n    \"Signal Store\",\n    \"Signal State\",\n    \"State Management\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"@angular/core\": \"^21.0.0\",\n    \"rxjs\": \"^6.5.3 || ^7.4.0\"\n  },\n  \"peerDependenciesMeta\": {\n    \"rxjs\": {\n      \"optional\": true\n    }\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"sideEffects\": false,\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/signals\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"dependencies\": {\n    \"tslib\": \"^2.3.0\"\n  }\n}\n"
  },
  {
    "path": "modules/signals/project.json",
    "content": "{\n  \"name\": \"signals\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/signals/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/signals/tsconfig.build.json\",\n        \"project\": \"modules/signals/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package signals\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/signals/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/signals\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/signals\"\n          },\n          {\n            \"command\": \"ncp dist/modules/signals node_modules/@ngrx/signals\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/signals\"\n          }\n        ]\n      },\n      \"outputs\": [\n        \"{workspaceRoot}/dist/modules/signals\",\n        \"{workspaceRoot}/node_modules/@ngrx/signals\"\n      ]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/signals/*/**/*.ts\",\n          \"modules/signals/*/**/*.html\",\n          \"modules/signals/entities/**/*.ts\",\n          \"modules/signals/entities/**/*.html\",\n          \"modules/signals/rxjs-interop/**/*.ts\",\n          \"modules/signals/rxjs-interop/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/signals\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/signals/rxjs-interop/index.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/signals/rxjs-interop/ng-package.json",
    "content": "{\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/signals/rxjs-interop/spec/rx-method.spec.ts",
    "content": "import {\n  createEnvironmentInjector,\n  EnvironmentInjector,\n  Injectable,\n  Injector,\n  runInInjectionContext,\n  signal,\n} from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { BehaviorSubject, of, pipe, Subject, tap } from 'rxjs';\nimport { rxMethod } from '../src';\nimport { createLocalService } from '../../spec/helpers';\n\ndescribe('rxMethod', () => {\n  it('runs with a value', () => {\n    const results: number[] = [];\n    const method = TestBed.runInInjectionContext(() =>\n      rxMethod<number>(pipe(tap((value) => results.push(value))))\n    );\n\n    method(1);\n    expect(results.length).toBe(1);\n    expect(results[0]).toBe(1);\n\n    method(2);\n    expect(results.length).toBe(2);\n    expect(results[1]).toBe(2);\n  });\n\n  it('runs with an observable', () => {\n    const results: string[] = [];\n    const method = TestBed.runInInjectionContext(() =>\n      rxMethod<string>(pipe(tap((value) => results.push(value))))\n    );\n    const subject$ = new Subject<string>();\n\n    method(subject$);\n    expect(results.length).toBe(0);\n\n    subject$.next('ngrx');\n    expect(results[0]).toBe('ngrx');\n\n    subject$.next('rocks');\n    expect(results[1]).toBe('rocks');\n  });\n\n  it('runs with a signal', () =>\n    TestBed.runInInjectionContext(() => {\n      const results: number[] = [];\n      const method = rxMethod<number>(\n        pipe(tap((value) => results.push(value)))\n      );\n      const sig = signal(1);\n\n      method(sig);\n      expect(results.length).toBe(0);\n\n      TestBed.tick();\n      expect(results[0]).toBe(1);\n\n      sig.set(10);\n      expect(results.length).toBe(1);\n\n      TestBed.tick();\n      expect(results[1]).toBe(10);\n    }));\n\n  it('runs with a computation function', () => {\n    TestBed.runInInjectionContext(() => {\n      const results: number[] = [];\n      const method = rxMethod<number>(\n        pipe(tap((value) => results.push(value)))\n      );\n\n      const a = signal(1);\n      const b = signal(1);\n      const multiplier = () => a() * b();\n\n      method(multiplier);\n      expect(results.length).toBe(0);\n\n      TestBed.tick();\n      expect(results[0]).toBe(1);\n\n      a.set(5);\n      expect(results.length).toBe(1);\n\n      TestBed.tick();\n      expect(results[1]).toBe(5);\n\n      b.set(2);\n      TestBed.tick();\n      expect(results[2]).toBe(10);\n    });\n  });\n\n  it('runs with void input', () => {\n    const results: number[] = [];\n    const subject$ = new Subject<void>();\n    const method = TestBed.runInInjectionContext(() =>\n      rxMethod<void>(pipe(tap(() => results.push(1))))\n    );\n\n    method();\n    expect(results.length).toBe(1);\n\n    method(subject$);\n    expect(results.length).toBe(1);\n\n    subject$.next();\n    expect(results.length).toBe(2);\n  });\n\n  it('manually unsubscribes from method instance', () =>\n    TestBed.runInInjectionContext(() => {\n      const results: number[] = [];\n      const method = rxMethod<number>(\n        pipe(tap((value) => results.push(value)))\n      );\n      const subject$ = new Subject<number>();\n      const sig = signal(0);\n\n      const ref1 = method(subject$);\n      const ref2 = method(sig);\n      expect(results).toEqual([]);\n\n      subject$.next(1);\n      sig.set(1);\n      TestBed.tick();\n      expect(results).toEqual([1, 1]);\n\n      ref1.destroy();\n      subject$.next(2);\n      sig.set(2);\n      TestBed.tick();\n      expect(results).toEqual([1, 1, 2]);\n\n      ref2.destroy();\n      sig.set(3);\n      TestBed.tick();\n      expect(results).toEqual([1, 1, 2]);\n    }));\n\n  it('manually unsubscribes from method and all instances', () => {\n    const results: number[] = [];\n    let destroyed = false;\n    const method = TestBed.runInInjectionContext(() =>\n      rxMethod<number>(\n        pipe(\n          tap({\n            next: (value) => results.push(value),\n            finalize: () => (destroyed = true),\n          })\n        )\n      )\n    );\n    const subject1$ = new BehaviorSubject(1);\n    const subject2$ = new BehaviorSubject(1);\n\n    method(subject1$);\n    method(subject2$);\n    method(1);\n    expect(results).toEqual([1, 1, 1]);\n\n    method.destroy();\n    expect(destroyed).toBe(true);\n\n    subject1$.next(2);\n    subject2$.next(2);\n    method(2);\n    expect(results).toEqual([1, 1, 1]);\n  });\n\n  it('unsubscribes from method and all instances on destroy', () => {\n    const results: string[] = [];\n    let destroyed = false;\n    const subject$ = new BehaviorSubject('subject');\n    const sig = signal('signal');\n\n    @Injectable()\n    class TestService {\n      method = rxMethod<string>(\n        pipe(\n          tap({\n            next: (value) => results.push(value),\n            finalize: () => (destroyed = true),\n          })\n        )\n      );\n    }\n\n    const { service, flush, destroy } = createLocalService(TestService);\n\n    service.method(subject$);\n    service.method(sig);\n    service.method('value');\n\n    flush();\n    expect(results).toEqual(['subject', 'value', 'signal']);\n\n    destroy();\n    expect(destroyed).toBe(true);\n\n    subject$.next('subject 2');\n    sig.set('signal 2');\n    service.method('value 2');\n\n    flush();\n    expect(results).toEqual(['subject', 'value', 'signal']);\n  });\n\n  it('throws an error when it is called out of injection context', () => {\n    expect(() => rxMethod(($) => $)).toThrow(\n      /NG0203: rxMethod\\(\\) can only be used within an injection context/\n    );\n  });\n\n  it('allows signal updates', () => {\n    const counter = signal(1);\n    const increment = TestBed.runInInjectionContext(() =>\n      rxMethod<number>(tap((n) => counter.update((value) => value + n)))\n    );\n\n    const num = signal(3);\n    increment(num);\n\n    TestBed.tick();\n    expect(counter()).toBe(4);\n  });\n\n  /**\n   * This test suite verifies that a signal or observable passed to a reactive\n   * method that is initialized at the ancestor injector level is tracked within\n   * the correct injection context and untracked at the specified time.\n   *\n   * Different injection contexts use `globalSignal` or `globalObservable`\n   * from `GlobalService` and pass it to the reactive method.\n   * If the injector is destroyed but the signal or the observable still\n   * increases the corresponding counter, the internal effect or subscription\n   * is still active.\n   */\n  describe('with instance injector', () => {\n    @Injectable({ providedIn: 'root' })\n    class GlobalService {\n      readonly globalSignal = signal(1);\n      readonly globalObservable = new BehaviorSubject(1);\n\n      globalSignalChangeCounter = 0;\n      globalObservableChangeCounter = 0;\n\n      readonly trackSignal = rxMethod<number>(\n        tap(() => this.globalSignalChangeCounter++)\n      );\n      readonly trackObservable = rxMethod<number>(\n        tap(() => this.globalObservableChangeCounter++)\n      );\n\n      incrementSignal(): void {\n        this.globalSignal.update((value) => value + 1);\n      }\n\n      incrementObservable(): void {\n        this.globalObservable.next(this.globalObservable.value + 1);\n      }\n    }\n\n    it('tracks a signal until the instanceInjector is destroyed', () => {\n      const instanceInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      const globalService = TestBed.inject(GlobalService);\n      runInInjectionContext(instanceInjector, () => {\n        globalService.trackSignal(globalService.globalSignal);\n      });\n\n      TestBed.tick();\n      expect(globalService.globalSignalChangeCounter).toBe(1);\n\n      globalService.incrementSignal();\n      TestBed.tick();\n      expect(globalService.globalSignalChangeCounter).toBe(2);\n\n      globalService.incrementSignal();\n      TestBed.tick();\n      expect(globalService.globalSignalChangeCounter).toBe(3);\n\n      instanceInjector.destroy();\n      globalService.incrementSignal();\n      TestBed.tick();\n\n      expect(globalService.globalSignalChangeCounter).toBe(3);\n    });\n\n    it('tracks an observable until the instanceInjector is destroyed', () => {\n      const instanceInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      const globalService = TestBed.inject(GlobalService);\n      runInInjectionContext(instanceInjector, () =>\n        globalService.trackObservable(globalService.globalObservable)\n      );\n\n      TestBed.tick();\n      expect(globalService.globalObservableChangeCounter).toBe(1);\n\n      globalService.incrementObservable();\n      expect(globalService.globalObservableChangeCounter).toBe(2);\n\n      globalService.incrementObservable();\n      expect(globalService.globalObservableChangeCounter).toBe(3);\n\n      instanceInjector.destroy();\n      globalService.incrementObservable();\n\n      expect(globalService.globalObservableChangeCounter).toBe(3);\n    });\n\n    it('tracks a signal until the provided injector is destroyed', () => {\n      const instanceInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      const globalService = TestBed.inject(GlobalService);\n      globalService.trackSignal(globalService.globalSignal, {\n        injector: instanceInjector,\n      });\n\n      TestBed.tick();\n      globalService.incrementSignal();\n      TestBed.tick();\n\n      expect(globalService.globalSignalChangeCounter).toBe(2);\n\n      instanceInjector.destroy();\n      globalService.incrementSignal();\n      TestBed.tick();\n\n      expect(globalService.globalSignalChangeCounter).toBe(2);\n    });\n\n    it('tracks an observable until the provided injector is destroyed', async () => {\n      const instanceInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      const globalService = TestBed.inject(GlobalService);\n      globalService.trackObservable(globalService.globalObservable, {\n        injector: instanceInjector,\n      });\n\n      globalService.incrementObservable();\n      expect(globalService.globalObservableChangeCounter).toBe(2);\n\n      instanceInjector.destroy();\n      globalService.incrementObservable();\n\n      expect(globalService.globalObservableChangeCounter).toBe(2);\n    });\n\n    it('falls back to source injector when reactive method is called outside of the injection context', () => {\n      const globalService = TestBed.inject(GlobalService);\n\n      globalService.trackSignal(globalService.globalSignal);\n      globalService.trackObservable(globalService.globalObservable);\n\n      TestBed.tick();\n      expect(globalService.globalSignalChangeCounter).toBe(1);\n      expect(globalService.globalObservableChangeCounter).toBe(1);\n\n      globalService.incrementSignal();\n      globalService.incrementObservable();\n      TestBed.tick();\n\n      expect(globalService.globalSignalChangeCounter).toBe(2);\n      expect(globalService.globalObservableChangeCounter).toBe(2);\n    });\n  });\n\n  describe('deprecation warning for reactive calls outside injection context', () => {\n    const warnSpy = vitest.spyOn(console, 'warn');\n    warnSpy.mockImplementation(() => void true);\n\n    beforeEach(() => {\n      warnSpy.mockClear();\n    });\n\n    const createAdder = (callback: (value: number) => void) =>\n      TestBed.runInInjectionContext(() => rxMethod<number>(tap(callback)));\n\n    it('does not warn on non-reactive value', () => {\n      let a = 1;\n      const adder = createAdder((value) => (a += value));\n      adder(1);\n\n      expect(warnSpy).not.toHaveBeenCalled();\n    });\n\n    for (const [createReactiveValue, name] of [\n      [() => signal(1), 'Signal'],\n      [() => of(1), 'Observable'],\n    ] as const) {\n      describe(`${name}`, () => {\n        it('warns when called outside of an injection context', () => {\n          let a = 1;\n          const adder = createAdder((value) => (a += value));\n          adder(createReactiveValue());\n\n          expect(warnSpy).toHaveBeenCalled();\n\n          const warning = (warnSpy.mock.lastCall || []).join(' ');\n          expect(warning).toMatch(\n            /reactive method outside of an injection context with a signal or observable is deprecated/\n          );\n        });\n\n        it('does not warn when explicit injector is provided', () => {\n          let a = 1;\n          const adder = createAdder((value) => (a += value));\n          const injector = TestBed.inject(Injector);\n          adder(createReactiveValue(), { injector });\n\n          expect(warnSpy).not.toHaveBeenCalled();\n        });\n\n        it('does not warn when called within an injection context', () => {\n          let a = 1;\n          const adder = createAdder((value) => (a += value));\n          TestBed.runInInjectionContext(() => adder(createReactiveValue()));\n\n          expect(warnSpy).not.toHaveBeenCalled();\n        });\n\n        it('does not warn when called within a child injection context', () => {\n          let a = 1;\n          const adder = createAdder((value) => (a += value));\n          const childInjector = createEnvironmentInjector(\n            [],\n            TestBed.inject(EnvironmentInjector)\n          );\n          runInInjectionContext(childInjector, () =>\n            adder(createReactiveValue())\n          );\n\n          expect(warnSpy).not.toHaveBeenCalled();\n        });\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "modules/signals/rxjs-interop/src/index.ts",
    "content": "export { rxMethod, RxMethod } from './rx-method';\n"
  },
  {
    "path": "modules/signals/rxjs-interop/src/rx-method.ts",
    "content": "import {\n  assertInInjectionContext,\n  DestroyRef,\n  effect,\n  inject,\n  Injector,\n  untracked,\n} from '@angular/core';\nimport { isObservable, noop, Observable, Subject } from 'rxjs';\n\ntype RxMethodRef = {\n  destroy: () => void;\n};\n\nexport type RxMethod<Input> = ((\n  input: Input | (() => Input) | Observable<Input>,\n  config?: { injector?: Injector }\n) => RxMethodRef) &\n  RxMethodRef;\n\n/**\n * @description\n *\n * Creates a reactive method for managing side effects by utilizing RxJS APIs.\n * The method accepts an observable, a signal, a computation function, or\n * a static value.\n *\n * @usageNotes\n *\n * ```ts\n * import { Component, inject, signal } from '@angular/core';\n * import { switchMap } from 'rxjs';\n * import { rxMethod } from '@ngrx/signals/rxjs-interop';\n * import { tapResponse } from '@ngrx/operators';\n *\n * \\@Component(...)\n * export class TodoList {\n *   readonly #todosService = inject(TodosService);\n *   readonly userId = signal(1);\n *   readonly todos = signal<Todo[]>([]);\n *\n *   readonly loadTodos = rxMethod<number>(\n *     switchMap((id) =>\n *       this.#todosService.getByUserId(id).pipe(\n *         tapResponse({\n *           next: (todos) => this.todos.set(todos),\n *           error: console.error,\n *         })\n *       )\n *     )\n *   );\n *\n *   constructor() {\n *     // 👇 Load todos on `userId` changes.\n *     this.loadTodos(this.userId);\n *   }\n * }\n * ```\n */\nexport function rxMethod<Input>(\n  generator: (source$: Observable<Input>) => Observable<unknown>,\n  config?: { injector?: Injector }\n): RxMethod<Input> {\n  if (typeof ngDevMode !== 'undefined' && ngDevMode && !config?.injector) {\n    assertInInjectionContext(rxMethod);\n  }\n\n  const sourceInjector = config?.injector ?? inject(Injector);\n  const source$ = new Subject<Input>();\n  const sourceSub = generator(source$).subscribe();\n  sourceInjector.get(DestroyRef).onDestroy(() => sourceSub.unsubscribe());\n\n  const rxMethodFn = (\n    input: Input | (() => Input) | Observable<Input>,\n    config?: { injector?: Injector }\n  ): RxMethodRef => {\n    if (isStatic(input)) {\n      source$.next(input);\n      return { destroy: noop };\n    }\n\n    const callerInjector = getCallerInjector();\n\n    if (\n      typeof ngDevMode !== 'undefined' &&\n      ngDevMode &&\n      config?.injector === undefined &&\n      callerInjector === undefined\n    ) {\n      console.warn(\n        '@ngrx/signals/rxjs-interop: Calling a reactive method outside of',\n        'an injection context with a signal or observable is deprecated.',\n        'In a future version, this will throw an error.',\n        'Either call it within an injection context',\n        '(e.g. in a constructor or field initializer) or pass an injector',\n        'explicitly via the config parameter.\\n\\nFor more information, see:',\n        'https://ngrx.io/guide/signals/rxjs-integration#reactive-methods-and-injector-hierarchies'\n      );\n    }\n\n    const instanceInjector =\n      config?.injector ?? callerInjector ?? sourceInjector;\n\n    if (typeof input === 'function') {\n      const watcher = effect(\n        () => {\n          const value = input();\n          untracked(() => source$.next(value));\n        },\n        { injector: instanceInjector }\n      );\n      sourceSub.add({ unsubscribe: () => watcher.destroy() });\n\n      return watcher;\n    }\n\n    const instanceSub = input.subscribe((value) => source$.next(value));\n    sourceSub.add(instanceSub);\n\n    if (instanceInjector !== sourceInjector) {\n      instanceInjector\n        .get(DestroyRef)\n        .onDestroy(() => instanceSub.unsubscribe());\n    }\n\n    return { destroy: () => instanceSub.unsubscribe() };\n  };\n  rxMethodFn.destroy = sourceSub.unsubscribe.bind(sourceSub);\n\n  return rxMethodFn;\n}\n\nfunction isStatic<T>(value: T | (() => T) | Observable<T>): value is T {\n  return typeof value !== 'function' && !isObservable(value);\n}\n\nfunction getCallerInjector(): Injector | undefined {\n  try {\n    return inject(Injector);\n  } catch {\n    return undefined;\n  }\n}\n"
  },
  {
    "path": "modules/signals/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Add @ngrx/signals to your application\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/signals/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as SchemaOptions } from './schema';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\n\ndescribe('Signals ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/signals',\n    path.join(process.cwd(), 'dist/modules/signals/schematics/collection.json')\n  );\n  const defaultOptions: SchemaOptions = {\n    skipPackageJson: false,\n  };\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/signals']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/signals']).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "modules/signals/schematics/ng-add/index.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  chain,\n  noop,\n} from '@angular-devkit/schematics';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\nimport {\n  addPackageToPackageJson,\n  platformVersion,\n} from '../../schematics-core';\nimport { Schema as SchemaOptions } from './schema';\n\nfunction addModuleToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/signals',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nexport default function (options: SchemaOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    return chain([\n      options && options.skipPackageJson ? noop() : addModuleToPackageJson(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/signals/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxSignals\",\n  \"title\": \"NgRx Signals Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/signals as dependency to package.json (e.g., --skipPackageJson).\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/signals/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  skipPackageJson?: boolean;\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/signals/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/signals/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/signals/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/signals/spec/deep-computed.spec.ts",
    "content": "import { isSignal, signal } from '@angular/core';\nimport { deepComputed } from '../src';\n\ndescribe('deepComputed', () => {\n  it('creates a deep computed signal when computation result is an object literal', () => {\n    const source = signal(0);\n    const result = deepComputed(() => ({ count: { value: source() + 1 } }));\n\n    expect(isSignal(result)).toBe(true);\n    expect(isSignal(result.count)).toBe(true);\n    expect(isSignal(result.count.value)).toBe(true);\n\n    expect(result()).toEqual({ count: { value: 1 } });\n    expect(result.count()).toEqual({ value: 1 });\n    expect(result.count.value()).toBe(1);\n\n    source.set(1);\n\n    expect(result()).toEqual({ count: { value: 2 } });\n    expect(result.count()).toEqual({ value: 2 });\n    expect(result.count.value()).toBe(2);\n  });\n\n  it('does not create a deep computed signal when computation result is an array', () => {\n    const source = signal(0);\n    const result = deepComputed(() => [{ value: source() + 1 }]);\n\n    expect(isSignal(result)).toBe(true);\n    expect(result()).toEqual([{ value: 1 }]);\n    expect((result as any)[0]).toBe(undefined);\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/deep-signal.spec.ts",
    "content": "import { isSignal, signal } from '@angular/core';\nimport { toDeepSignal } from '../src/deep-signal';\n\ndescribe('toDeepSignal', () => {\n  it('creates deep signals for plain objects', () => {\n    const sig = signal({ m: { s: 't' } });\n    const deepSig = toDeepSignal(sig);\n\n    expect(sig).not.toBe(deepSig);\n\n    expect(isSignal(deepSig)).toBe(true);\n    expect(deepSig()).toEqual({ m: { s: 't' } });\n\n    expect(isSignal(deepSig.m)).toBe(true);\n    expect(deepSig.m()).toEqual({ s: 't' });\n\n    expect(isSignal(deepSig.m.s)).toBe(true);\n    expect(deepSig.m.s()).toBe('t');\n  });\n\n  it('creates deep signals for custom class instances', () => {\n    class User {\n      constructor(readonly firstName: string) {}\n    }\n\n    class UserState {\n      constructor(readonly user: User) {}\n    }\n\n    const sig = signal(new UserState(new User('John')));\n    const deepSig = toDeepSignal(sig);\n\n    expect(sig).not.toBe(deepSig);\n\n    expect(isSignal(deepSig)).toBe(true);\n    expect(deepSig()).toEqual({ user: { firstName: 'John' } });\n\n    expect(isSignal(deepSig.user)).toBe(true);\n    expect(deepSig.user()).toEqual({ firstName: 'John' });\n\n    expect(isSignal(deepSig.user.firstName)).toBe(true);\n    expect(deepSig.user.firstName()).toBe('John');\n  });\n\n  it('allows lazy initialization', () => {\n    const sig = signal(undefined as unknown as { m: { s: 't' } });\n    const deepSig = toDeepSignal(sig);\n\n    sig.set({ m: { s: 't' } });\n\n    expect(deepSig()).toEqual({ m: { s: 't' } });\n    expect(deepSig.m()).toEqual({ s: 't' });\n    expect(deepSig.m.s()).toBe('t');\n  });\n\n  it('creates a deep signal when value is a union of objects', () => {\n    const sig = signal({ m: { s: 't' } } as\n      | { s: 'asdf' }\n      | { m: { s: string } });\n    const deepSig = toDeepSignal(sig);\n\n    expect('m' in deepSig).toBe(true);\n    expect('m' in deepSig && deepSig.m()).toEqual({ s: 't' });\n    expect('m' in deepSig && deepSig.m.s()).toBe('t');\n\n    sig.set({ s: 'asdf' });\n\n    expect('m' in deepSig).toBe(false);\n    expect('s' in deepSig).toBe(true);\n    expect('s' in deepSig && deepSig.s()).toBe('asdf');\n\n    sig.set({ m: { s: 'ngrx' } });\n\n    expect('s' in deepSig).toBe(false);\n    expect('m' in deepSig).toBe(true);\n    expect('m' in deepSig && deepSig.m()).toEqual({ s: 'ngrx' });\n    expect('m' in deepSig && deepSig.m.s()).toBe('ngrx');\n  });\n\n  it('does not affect signals with primitives as values', () => {\n    const num = signal(0);\n    const str = signal('str');\n    const bool = signal(true);\n\n    const deepNum = toDeepSignal(num);\n    const deepStr = toDeepSignal(str);\n    const deepBool = toDeepSignal(bool);\n\n    expect(isSignal(deepNum)).toBe(true);\n    expect(deepNum()).toBe(num());\n\n    expect(isSignal(deepStr)).toBe(true);\n    expect(deepStr()).toBe(str());\n\n    expect(isSignal(deepBool)).toBe(true);\n    expect(deepBool()).toBe(bool());\n  });\n\n  it('does not affect signals with iterables as values', () => {\n    const array = signal([]);\n    const set = signal(new Set());\n    const map = signal(new Map());\n    const uintArray = signal(new Uint32Array());\n    const floatArray = signal(new Float64Array());\n\n    const deepArray = toDeepSignal(array);\n    const deepSet = toDeepSignal(set);\n    const deepMap = toDeepSignal(map);\n    const deepUintArray = toDeepSignal(uintArray);\n    const deepFloatArray = toDeepSignal(floatArray);\n\n    expect(isSignal(deepArray)).toBe(true);\n    expect(deepArray()).toBe(array());\n\n    expect(isSignal(deepSet)).toBe(true);\n    expect(deepSet()).toBe(set());\n\n    expect(isSignal(deepMap)).toBe(true);\n    expect(deepMap()).toBe(map());\n\n    expect(isSignal(deepUintArray)).toBe(true);\n    expect(deepUintArray()).toBe(uintArray());\n\n    expect(isSignal(deepFloatArray)).toBe(true);\n    expect(deepFloatArray()).toBe(floatArray());\n  });\n\n  it('does not affect signals with built-in object types as values', () => {\n    const weakSet = signal(new WeakSet());\n    const weakMap = signal(new WeakMap());\n    const promise = signal(Promise.resolve(10));\n    const date = signal(new Date());\n    const error = signal(new Error());\n    const regExp = signal(new RegExp(''));\n    const arrayBuffer = signal(new ArrayBuffer(10));\n    const dataView = signal(new DataView(new ArrayBuffer(10)));\n\n    const deepWeakSet = toDeepSignal(weakSet);\n    const deepWeakMap = toDeepSignal(weakMap);\n    const deepPromise = toDeepSignal(promise);\n    const deepDate = toDeepSignal(date);\n    const deepError = toDeepSignal(error);\n    const deepRegExp = toDeepSignal(regExp);\n    const deepArrayBuffer = toDeepSignal(arrayBuffer);\n    const deepDataView = toDeepSignal(dataView);\n\n    expect(isSignal(deepWeakSet)).toBe(true);\n    expect(deepWeakSet()).toBe(weakSet());\n\n    expect(isSignal(deepWeakMap)).toBe(true);\n    expect(deepWeakMap()).toBe(weakMap());\n\n    expect(isSignal(deepPromise)).toBe(true);\n    expect(deepPromise()).toBe(promise());\n\n    expect(isSignal(deepDate)).toBe(true);\n    expect(deepDate()).toBe(date());\n\n    expect(isSignal(deepError)).toBe(true);\n    expect(deepError()).toBe(error());\n\n    expect(isSignal(deepRegExp)).toBe(true);\n    expect(deepRegExp()).toBe(regExp());\n\n    expect(isSignal(deepArrayBuffer)).toBe(true);\n    expect(deepArrayBuffer()).toBe(arrayBuffer());\n\n    expect(isSignal(deepDataView)).toBe(true);\n    expect(deepDataView()).toBe(dataView());\n  });\n\n  it('does not affect signals with functions as values', () => {\n    const fn1 = signal(new Function());\n    const fn2 = signal(function () {});\n    const fn3 = signal(() => {});\n\n    const deepFn1 = toDeepSignal(fn1);\n    const deepFn2 = toDeepSignal(fn2);\n    const deepFn3 = toDeepSignal(fn3);\n\n    expect(isSignal(deepFn1)).toBe(true);\n    expect(deepFn1()).toBe(fn1());\n\n    expect(isSignal(deepFn2)).toBe(true);\n    expect(deepFn2()).toBe(fn2());\n\n    expect(isSignal(deepFn3)).toBe(true);\n    expect(deepFn3()).toBe(fn3());\n  });\n\n  it('does not affect signals with custom class instances that are iterables as values', () => {\n    class CustomArray extends Array {}\n\n    class CustomSet extends Set {}\n\n    class CustomFloatArray extends Float32Array {}\n\n    const array = signal(new CustomArray());\n    const floatArray = signal(new CustomFloatArray());\n    const set = signal(new CustomSet());\n\n    const deepArray = toDeepSignal(array);\n    const deepFloatArray = toDeepSignal(floatArray);\n    const deepSet = toDeepSignal(set);\n\n    expect(isSignal(deepArray)).toBe(true);\n    expect(deepArray()).toBe(array());\n\n    expect(isSignal(deepFloatArray)).toBe(true);\n    expect(deepFloatArray()).toBe(floatArray());\n\n    expect(isSignal(deepSet)).toBe(true);\n    expect(deepSet()).toBe(set());\n  });\n\n  it('does not affect signals with custom class instances that extend built-in object types as values', () => {\n    class CustomWeakMap extends WeakMap {}\n\n    class CustomError extends Error {}\n\n    class CustomArrayBuffer extends ArrayBuffer {}\n\n    const weakMap = signal(new CustomWeakMap());\n    const error = signal(new CustomError());\n    const arrayBuffer = signal(new CustomArrayBuffer(10));\n\n    const deepWeakMap = toDeepSignal(weakMap);\n    const deepError = toDeepSignal(error);\n    const deepArrayBuffer = toDeepSignal(arrayBuffer);\n\n    expect(isSignal(deepWeakMap)).toBe(true);\n    expect(deepWeakMap()).toBe(weakMap());\n\n    expect(isSignal(deepError)).toBe(true);\n    expect(deepError()).toBe(error());\n\n    expect(isSignal(deepArrayBuffer)).toBe(true);\n    expect(deepArrayBuffer()).toBe(arrayBuffer());\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/helpers.ts",
    "content": "import { Component, inject, Type } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { SignalsDictionary } from '../src/signal-store-models';\n\nexport function createLocalService<Service extends Type<unknown>>(\n  serviceToken: Service\n): {\n  service: InstanceType<Service>;\n  flush: () => void;\n  destroy: () => void;\n} {\n  @Component({\n    template: '',\n    providers: [serviceToken],\n  })\n  class TestComponent {\n    service = inject(serviceToken);\n  }\n\n  const fixture = TestBed.configureTestingModule({\n    imports: [TestComponent],\n  }).createComponent(TestComponent);\n  fixture.detectChanges();\n\n  return {\n    service: fixture.componentInstance.service,\n    flush: () => {\n      TestBed.tick();\n      fixture.detectChanges();\n    },\n    destroy: () => fixture.destroy(),\n  };\n}\n\n/**\n * This could be done by using `getState`, but\n * 1. We don't want to depend on the implementation of `getState` in the test.\n * 2. We want to be able to provide the state in its actual type (with slice signals).\n */\nexport function assertStateSource(\n  state: SignalsDictionary,\n  expected: SignalsDictionary\n): void {\n  const stateKeys = Reflect.ownKeys(state);\n  const expectedKeys = Reflect.ownKeys(expected);\n\n  const currentState = stateKeys.reduce(\n    (acc, key) => {\n      acc[key] = state[key]();\n      return acc;\n    },\n    {} as Record<string | symbol, unknown>\n  );\n  const expectedState = expectedKeys.reduce(\n    (acc, key) => {\n      acc[key] = expected[key]();\n      return acc;\n    },\n    {} as Record<string | symbol, unknown>\n  );\n  expect(currentState).toEqual(expectedState);\n}\n"
  },
  {
    "path": "modules/signals/spec/signal-method.spec.ts",
    "content": "import { signalMethod } from '../src';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  createEnvironmentInjector,\n  EnvironmentInjector,\n  Injector,\n  runInInjectionContext,\n  signal,\n} from '@angular/core';\n\ndescribe('signalMethod', () => {\n  const createAdder = (processingFn: (value: number) => void) =>\n    TestBed.runInInjectionContext(() => signalMethod<number>(processingFn));\n\n  it('processes a non-signal input', () => {\n    let a = 1;\n    const adder = createAdder((value) => (a += value));\n    adder(2);\n    expect(a).toBe(3);\n  });\n\n  it('processes a signal input', () => {\n    let a = 1;\n    const summand = signal(1);\n    const adder = createAdder((value) => (a += value));\n\n    adder(summand);\n    expect(a).toBe(1);\n\n    TestBed.tick();\n    expect(a).toBe(2);\n\n    summand.set(2);\n    summand.set(2);\n    TestBed.tick();\n    expect(a).toBe(4);\n\n    TestBed.tick();\n    expect(a).toBe(4);\n  });\n\n  it('processes a computation function', () => {\n    const a = signal(1);\n    const b = signal(1);\n    const add = () => a() + b();\n    let sum = 0;\n    const adder = createAdder((value) => (sum += value));\n\n    adder(add);\n    expect(sum).toBe(0);\n\n    TestBed.tick();\n    expect(sum).toBe(2);\n\n    a.set(2);\n    b.set(2);\n    TestBed.tick();\n    expect(sum).toBe(6);\n\n    TestBed.tick();\n    expect(sum).toBe(6);\n  });\n\n  it('throws if is a not created in an injection context', () => {\n    expect(() => signalMethod<void>(() => void true)).toThrowError();\n  });\n\n  describe('destroying signalMethod', () => {\n    it('stops signal tracking, when signalMethod gets destroyed', () => {\n      let a = 1;\n      const summand = signal(1);\n      const adder = createAdder((value) => (a += value));\n      adder(summand);\n\n      summand.set(2);\n      TestBed.tick();\n      expect(a).toBe(3);\n\n      adder.destroy();\n\n      summand.set(2);\n      TestBed.tick();\n      expect(a).toBe(3);\n    });\n\n    it('can also destroy a signalMethod that processes non-signal inputs', () => {\n      const adder = createAdder(() => void true);\n      expect(() => adder(1).destroy()).not.toThrowError();\n    });\n\n    it('stops tracking all signals on signalMethod destroy', () => {\n      let a = 1;\n      const summand1 = signal(1);\n      const summand2 = signal(2);\n      const adder = createAdder((value) => (a += value));\n      adder(summand1);\n      adder(summand2);\n      TestBed.tick();\n      expect(a).toBe(4);\n\n      adder.destroy();\n\n      summand1.set(2);\n      summand2.set(3);\n      TestBed.tick();\n      expect(a).toBe(4);\n    });\n\n    it('does not cause issues if destroyed signalMethodFn contains destroyed effectRefs', () => {\n      let a = 1;\n      const summand1 = signal(1);\n      const summand2 = signal(2);\n      const adder = createAdder((value) => (a += value));\n\n      const childInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n\n      adder(summand1, { injector: childInjector });\n      adder(summand2);\n\n      TestBed.tick();\n      expect(a).toBe(4);\n      childInjector.destroy();\n\n      summand1.set(2);\n      summand2.set(3);\n      TestBed.tick();\n\n      adder.destroy();\n      expect(a).toBe(7);\n    });\n\n    it('uses the provided injector (source injector) on creation', () => {\n      let a = 1;\n      const sourceInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      const adder = signalMethod((value: number) => (a += value), {\n        injector: sourceInjector,\n      });\n      const value = signal(1);\n\n      adder(value);\n      TestBed.tick();\n      expect(a).toBe(2);\n\n      sourceInjector.destroy();\n      value.set(2);\n      TestBed.tick();\n\n      expect(a).toBe(2);\n    });\n\n    it('prioritizes the provided caller injector over source injector', () => {\n      let a = 1;\n      const callerInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      const sourceInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      const adder = signalMethod((value: number) => (a += value), {\n        injector: sourceInjector,\n      });\n      const value = signal(1);\n\n      TestBed.runInInjectionContext(() => {\n        adder(value, { injector: callerInjector });\n      });\n      TestBed.tick();\n      expect(a).toBe(2);\n\n      sourceInjector.destroy();\n      value.set(2);\n      TestBed.tick();\n\n      expect(a).toBe(4);\n    });\n\n    it('prioritizes the provided injector over source and caller injector', () => {\n      let a = 1;\n      const callerInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      const sourceInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      const providedInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n\n      const adder = signalMethod((value: number) => (a += value), {\n        injector: sourceInjector,\n      });\n      const value = signal(1);\n\n      runInInjectionContext(callerInjector, () =>\n        adder(value, { injector: providedInjector })\n      );\n      TestBed.tick();\n      expect(a).toBe(2);\n\n      sourceInjector.destroy();\n      value.set(2);\n      TestBed.tick();\n      expect(a).toBe(4);\n\n      callerInjector.destroy();\n      value.set(1);\n      TestBed.tick();\n      expect(a).toBe(5);\n    });\n  });\n\n  it('stops specific tracking when calling destroy manually on an instance', () => {\n    let a = 1;\n    const summand1 = signal(1);\n    const summand2 = signal(2);\n    const adder = createAdder((value) => (a += value));\n    adder(summand1);\n    const s2 = adder(summand2);\n\n    TestBed.tick();\n    s2.destroy();\n    expect(a).toBe(4);\n\n    summand1.set(100);\n    summand2.set(3000);\n\n    TestBed.tick();\n    expect(a).toBe(104);\n  });\n\n  describe('deprecation warning for reactive calls outside injection context', () => {\n    const warnSpy = vi.spyOn(console, 'warn');\n    warnSpy.mockImplementation(() => void true);\n    const n = signal(1);\n\n    beforeEach(() => {\n      warnSpy.mockClear();\n    });\n\n    it('warns when called outside of an injection context', () => {\n      let a = 1;\n      const adder = createAdder((value) => (a += value));\n      adder(n);\n\n      expect(warnSpy).toHaveBeenCalled();\n\n      const warning = (warnSpy.mock.lastCall || []).join(' ');\n      expect(warning).toMatch(\n        /signalMethod outside of an injection context with a signal is deprecated/\n      );\n    });\n\n    it('does not warn on non-reactive value', () => {\n      let a = 1;\n      const adder = createAdder((value) => (a += value));\n      adder(1);\n\n      expect(warnSpy).not.toHaveBeenCalled();\n    });\n\n    it('does not warn when explicit injector is provided', () => {\n      let a = 1;\n      const adder = createAdder((value) => (a += value));\n      const injector = TestBed.inject(Injector);\n      adder(n, { injector });\n\n      expect(warnSpy).not.toHaveBeenCalled();\n    });\n\n    it('does not warn when called within an injection context', () => {\n      let a = 1;\n      const adder = createAdder((value) => (a += value));\n      TestBed.runInInjectionContext(() => adder(n));\n\n      expect(warnSpy).not.toHaveBeenCalled();\n    });\n\n    it('does not warn when called within a child injection context', () => {\n      let a = 1;\n      const adder = createAdder((value) => (a += value));\n      const childInjector = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      runInInjectionContext(childInjector, () => adder(n));\n\n      expect(warnSpy).not.toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/signal-state.spec.ts",
    "content": "import { computed, effect, isSignal } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { patchState, signalState } from '../src';\nimport { SignalsDictionary } from '../src/signal-store-models';\nimport { STATE_SOURCE } from '../src/state-source';\n\nvi.mock('@angular/core', { spy: true });\n\ndescribe('signalState', () => {\n  const initialState = {\n    user: {\n      firstName: 'John',\n      lastName: 'Smith',\n    },\n    foo: 'bar',\n    numbers: [1, 2, 3],\n    ngrx: 'signals',\n  };\n\n  afterEach(() => {\n    vi.clearAllMocks();\n  });\n\n  it('creates its properties as Signals', () => {\n    const state = signalState({ foo: 'bar' });\n    const stateSource: SignalsDictionary = state[STATE_SOURCE];\n\n    expect(isSignal(state)).toBe(true);\n    for (const key of Reflect.ownKeys(stateSource)) {\n      expect(isSignal(stateSource[key])).toBe(true);\n      expect(typeof stateSource[key].update === 'function').toBe(true);\n    }\n  });\n\n  it('does not keep the object reference of the initial state', () => {\n    const state = signalState(initialState);\n    expect(state()).not.toBe(initialState);\n    expect(state()).toEqual(initialState);\n  });\n\n  it('creates signals for nested state slices', () => {\n    const state = signalState(initialState);\n\n    expect(state()).toEqual(initialState);\n    expect(isSignal(state)).toBe(true);\n\n    expect(state.user()).toEqual(initialState.user);\n    expect(isSignal(state.user)).toBe(true);\n\n    expect(state.user.firstName()).toBe(initialState.user.firstName);\n    expect(isSignal(state.user.firstName)).toBe(true);\n\n    expect(state.foo()).toBe(initialState.foo);\n    expect(isSignal(state.foo)).toBe(true);\n\n    expect(state.numbers()).toBe(initialState.numbers);\n    expect(isSignal(state.numbers)).toBe(true);\n\n    expect(state.ngrx()).toBe(initialState.ngrx);\n    expect(isSignal(state.ngrx)).toBe(true);\n  });\n\n  it('caches previously created signals', () => {\n    const state = signalState(initialState);\n    const user1 = state.user;\n    const user2 = state.user;\n\n    expect(computed).toHaveBeenCalledTimes(1);\n\n    const _ = state.user.firstName;\n    const __ = user1.firstName;\n    const ___ = user2.firstName;\n\n    expect(computed).toHaveBeenCalledTimes(2);\n  });\n\n  it('does not modify props that are not state slices', () => {\n    const state = signalState(initialState);\n    (state as any).x = 1;\n    (state.user as any).x = 2;\n    (state.user.firstName as any).x = 3;\n\n    expect((state as any).x).toBe(1);\n    expect((state.user as any).x).toBe(2);\n    expect((state.user.firstName as any).x).toBe(3);\n\n    expect((state as any).y).toBe(undefined);\n    expect((state.user as any).y).toBe(undefined);\n    expect((state.user.firstName as any).y).toBe(undefined);\n  });\n\n  it('overrides Function properties if state keys have the same name', () => {\n    const initialState = { name: { length: { length: 'ngrx' }, name: 20 } };\n    const state = signalState(initialState);\n\n    expect(state()).toEqual(initialState);\n\n    expect(state.name()).toBe(initialState.name);\n    expect(isSignal(state.name)).toBe(true);\n\n    expect(state.name.name()).toBe(20);\n    expect(isSignal(state.name.name)).toBe(true);\n\n    expect(state.name.length()).toBe(initialState.name.length);\n    expect(isSignal(state.name.length)).toBe(true);\n\n    expect(state.name.length.length()).toBe('ngrx');\n    expect(isSignal(state.name.length.length)).toBe(true);\n  });\n\n  it('emits new values only for affected signals', () => {\n    TestBed.runInInjectionContext(() => {\n      const state = signalState(initialState);\n      let numbersEmitted = 0;\n      let userEmitted = 0;\n      let firstNameEmitted = 0;\n\n      effect(() => {\n        state.numbers();\n        numbersEmitted++;\n      });\n\n      effect(() => {\n        state.user();\n        userEmitted++;\n      });\n\n      effect(() => {\n        state.user.firstName();\n        firstNameEmitted++;\n      });\n\n      expect(numbersEmitted).toBe(0);\n      expect(userEmitted).toBe(0);\n      expect(firstNameEmitted).toBe(0);\n\n      TestBed.tick();\n\n      expect(numbersEmitted).toBe(1);\n      expect(userEmitted).toBe(1);\n      expect(firstNameEmitted).toBe(1);\n\n      patchState(state, { numbers: [1, 2, 3] });\n      TestBed.tick();\n\n      expect(numbersEmitted).toBe(2);\n      expect(userEmitted).toBe(1);\n      expect(firstNameEmitted).toBe(1);\n\n      patchState(state, (state) => ({\n        user: { ...state.user, lastName: 'Schmidt' },\n      }));\n      TestBed.tick();\n\n      expect(numbersEmitted).toBe(2);\n      expect(userEmitted).toBe(2);\n      expect(firstNameEmitted).toBe(1);\n\n      patchState(state, (state) => ({\n        user: { ...state.user, firstName: 'Johannes' },\n      }));\n      TestBed.tick();\n\n      expect(numbersEmitted).toBe(2);\n      expect(userEmitted).toBe(3);\n      expect(firstNameEmitted).toBe(2);\n    });\n  });\n\n  it('does not emit if there was no change', () =>\n    TestBed.runInInjectionContext(() => {\n      let stateCounter = 0;\n      let userCounter = 0;\n      const state = signalState(initialState);\n      const user = state.user;\n\n      effect(() => {\n        state();\n        stateCounter++;\n      });\n\n      effect(() => {\n        user();\n        userCounter++;\n      });\n\n      TestBed.tick();\n      expect(stateCounter).toBe(1);\n      expect(userCounter).toBe(1);\n\n      patchState(state, {});\n      TestBed.tick();\n      expect(stateCounter).toBe(1);\n      expect(userCounter).toBe(1);\n\n      patchState(state, (state) => state);\n      TestBed.tick();\n      expect(stateCounter).toBe(1);\n      expect(userCounter).toBe(1);\n    }));\n});\n"
  },
  {
    "path": "modules/signals/spec/signal-store-feature.spec.ts",
    "content": "import { computed, Signal, signal } from '@angular/core';\nimport {\n  signalStore,\n  signalStoreFeature,\n  type,\n  withComputed,\n  withMethods,\n  withState,\n} from '../src';\nimport { STATE_SOURCE } from '../src/state-source';\nimport { assertStateSource } from './helpers';\n\ndescribe('signalStoreFeature', () => {\n  function withCustomFeature1() {\n    return signalStoreFeature(\n      withState({ foo: 'foo' }),\n      withComputed(({ foo }) => ({ bar: computed(() => foo() + 1) })),\n      withMethods(({ foo, bar }) => ({\n        baz: () => foo() + bar() + 2,\n      }))\n    );\n  }\n\n  function withCustomFeature2() {\n    return signalStoreFeature(\n      withCustomFeature1(),\n      withMethods(({ foo, baz }) => ({\n        m: () => foo() + baz() + 3,\n      }))\n    );\n  }\n\n  function withCustomFeatureWithInput<_>() {\n    return signalStoreFeature(\n      {\n        state: type<{ foo: string }>(),\n        props: type<{ s: Signal<number> }>(),\n      },\n      withState({ foo1: 1 }),\n      withState({ foo2: 2 })\n    );\n  }\n\n  it('creates a custom feature by combining base features', () => {\n    const Store = signalStore(\n      withCustomFeature1(),\n      withComputed(({ bar }) => ({\n        s: computed(() => bar() + 's'),\n      }))\n    );\n\n    const store = new Store();\n\n    assertStateSource(store[STATE_SOURCE], { foo: signal('foo') });\n    expect(store.foo()).toBe('foo');\n    expect(store.bar()).toBe('foo1');\n    expect(store.baz()).toBe('foofoo12');\n    expect(store.s()).toBe('foo1s');\n  });\n\n  it('creates a custom feature by combining base and custom features', () => {\n    const Store = signalStore(\n      withCustomFeature2(),\n      withMethods(({ foo }) => ({ m1: () => foo() + 10 }))\n    );\n\n    const store = new Store();\n\n    assertStateSource(store[STATE_SOURCE], { foo: signal('foo') });\n    expect(store.foo()).toBe('foo');\n    expect(store.bar()).toBe('foo1');\n    expect(store.m()).toBe('foofoofoo123');\n    expect(store.m1()).toBe('foo10');\n  });\n\n  it('creates a custom feature with input', () => {\n    const Store = signalStore(\n      withCustomFeature1(),\n      withComputed(() => ({ s: signal(1).asReadonly() })),\n      withCustomFeatureWithInput()\n    );\n\n    const store = new Store();\n\n    assertStateSource(store[STATE_SOURCE], {\n      foo: signal('foo'),\n      foo1: signal(1),\n      foo2: signal(2),\n    });\n    expect(store.foo()).toBe('foo');\n    expect(store.bar()).toBe('foo1');\n    expect(store.baz()).toBe('foofoo12');\n    expect(store.s()).toBe(1);\n    expect(store.foo1()).toBe(1);\n    expect(store.foo2()).toBe(2);\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/signal-store.spec.ts",
    "content": "import {\n  computed,\n  inject,\n  InjectionToken,\n  isSignal,\n  linkedSignal,\n  signal,\n} from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  patchState,\n  signalStore,\n  signalStoreFeature,\n  withComputed,\n  withHooks,\n  withLinkedState,\n  withMethods,\n  withProps,\n  withState,\n} from '../src';\nimport { STATE_SOURCE } from '../src/state-source';\nimport { assertStateSource, createLocalService } from './helpers';\n\ndescribe('signalStore', () => {\n  const consoleWarnSpy = vi.spyOn(console, 'warn');\n  consoleWarnSpy.mockImplementation(() => void true);\n\n  beforeEach(() => {\n    consoleWarnSpy.mockClear();\n  });\n\n  describe('creation', () => {\n    it('creates a store via new operator', () => {\n      const Store = signalStore(withState({ foo: 'bar' }));\n      const store = new Store();\n\n      expect(store.foo()).toBe('bar');\n    });\n\n    it('creates a store as injectable service', () => {\n      const Store = signalStore(withState({ foo: 'bar' }));\n      TestBed.configureTestingModule({ providers: [Store] });\n      const store = TestBed.inject(Store);\n\n      expect(store.foo()).toBe('bar');\n    });\n\n    it('creates a store that is provided in root when providedIn option is root', () => {\n      const Store = signalStore(\n        { providedIn: 'root' },\n        withState({ foo: 'bar' })\n      );\n      const store1 = TestBed.inject(Store);\n      const store2 = TestBed.inject(Store);\n\n      expect(store1).toBe(store2);\n      expect(store1.foo()).toBe('bar');\n    });\n\n    it('creates a store with state source as Record holding slices as signals by default', () => {\n      const Store = signalStore(withState({ foo: 'bar' }));\n      const store = new Store();\n      const stateSource = store[STATE_SOURCE];\n\n      expect(isSignal(stateSource)).toBe(false);\n      expect(Object.keys(stateSource)).toEqual(['foo']);\n      expect(isSignal(stateSource.foo)).toBe(true);\n      assertStateSource(stateSource, {\n        foo: signal('bar'),\n      });\n    });\n\n    it('creates a store with state source as Record holding slices as signals when protectedState option is true', () => {\n      const Store = signalStore(\n        { protectedState: true },\n        withState({ foo: 'bar' })\n      );\n      const store = new Store();\n      const stateSource = store[STATE_SOURCE];\n\n      expect(isSignal(stateSource)).toBe(false);\n      expect(Object.keys(stateSource)).toEqual(['foo']);\n      expect(isSignal(stateSource.foo)).toBe(true);\n      assertStateSource(stateSource, {\n        foo: signal('bar'),\n      });\n    });\n\n    it('creates a store with state source as Record holding slices as writeable signals when protectedState option is false', () => {\n      const Store = signalStore(\n        { protectedState: false },\n        withState({ foo: 'bar' })\n      );\n      const store = new Store();\n      const stateSource = store[STATE_SOURCE];\n\n      expect(isSignal(stateSource)).toBe(false);\n      expect(Object.keys(stateSource)).toEqual(['foo']);\n      expect(isSignal(stateSource.foo)).toBe(true);\n      assertStateSource(stateSource, {\n        foo: signal('bar'),\n      });\n      expect(typeof stateSource.foo.update === 'function').toBe(true);\n\n      patchState(store, { foo: 'baz' });\n\n      assertStateSource(stateSource, {\n        foo: signal('baz'),\n      });\n    });\n  });\n\n  describe('withState', () => {\n    it('adds deep signals to the store for each state slice', () => {\n      const Store = signalStore(\n        withState({\n          foo: 'foo',\n          x: { y: { z: 10 } },\n        })\n      );\n\n      const store = new Store();\n\n      assertStateSource(store[STATE_SOURCE], {\n        foo: signal('foo'),\n        x: signal({ y: { z: 10 } }),\n      });\n\n      expect(store.foo()).toBe('foo');\n      expect(store.x()).toEqual({ y: { z: 10 } });\n      expect(store.x.y()).toEqual({ z: 10 });\n      expect(store.x.y.z()).toBe(10);\n    });\n\n    it('overrides Function properties if nested state keys have the same name', () => {\n      const Store = signalStore(\n        withState({ name: { length: { name: false } } })\n      );\n      const store = new Store();\n\n      expect(store.name()).toEqual({ length: { name: false } });\n      expect(isSignal(store.name)).toBe(true);\n\n      expect(store.name.length()).toEqual({ name: false });\n      expect(isSignal(store.name.length)).toBe(true);\n\n      expect(store.name.length.name()).toBe(false);\n      expect(isSignal(store.name.length.name)).toBe(true);\n    });\n\n    it('does not create signals for optional state slices without initial value', () => {\n      type State = { x?: number; y?: { z: number } };\n\n      const Store = signalStore(\n        { protectedState: false },\n        withState<State>({ x: 10 })\n      );\n      const store = new Store();\n\n      expect(store.x!()).toBe(10);\n      expect(store.y).toBe(undefined);\n\n      patchState(store, { y: { z: 100 } });\n      expect(store.y).toBe(undefined);\n    });\n\n    it('executes withState factory in injection context', () => {\n      const TOKEN = new InjectionToken('TOKEN', {\n        providedIn: 'root',\n        factory: () => ({ foo: 'foo' }),\n      });\n      const Store = signalStore(withState(() => inject(TOKEN)));\n\n      TestBed.configureTestingModule({ providers: [Store] });\n      const store = TestBed.inject(Store);\n\n      expect(store.foo()).toBe('foo');\n    });\n\n    it('allows symbols as state keys', () => {\n      const SECRET = Symbol('SECRET');\n      const Store = signalStore(withState({ [SECRET]: 'bar' }));\n      const store = new Store();\n\n      expect(store[SECRET]()).toBe('bar');\n    });\n  });\n\n  describe('withLinkedState', () => {\n    describe('updates automatically if the source changes', () =>\n      [\n        {\n          name: 'with computed function',\n          linkedStateFeature: signalStoreFeature(\n            withState({ userId: 1 }),\n            withLinkedState(({ userId }) => ({\n              books: () => {\n                userId();\n                return [] as string[];\n              },\n            }))\n          ),\n        },\n        {\n          name: 'with explicit linkedSignal',\n          linkedStateFeature: signalStoreFeature(\n            withState({ userId: 1 }),\n            withLinkedState(({ userId }) => ({\n              books: linkedSignal({\n                source: userId,\n                computation: () => [] as string[],\n              }),\n            }))\n          ),\n        },\n      ].forEach(({ name, linkedStateFeature }) => {\n        it(name, () => {\n          const BookStore = signalStore(\n            { providedIn: 'root', protectedState: false },\n            linkedStateFeature,\n            withState({ version: 1 }),\n            withMethods((store) => ({\n              updateUser() {\n                patchState(store, (value) => value);\n                patchState(store, ({ userId }) => ({ userId: userId + 1 }));\n              },\n              addBook(title: string) {\n                patchState(store, ({ books }) => ({\n                  books: [...books, title],\n                }));\n              },\n              increaseVersion() {\n                patchState(store, ({ version }) => ({ version: version + 1 }));\n              },\n            }))\n          );\n\n          const bookStore = TestBed.inject(BookStore);\n          bookStore.addBook('The Neverending Story');\n          bookStore.increaseVersion();\n          expect(bookStore.books()).toEqual(['The Neverending Story']);\n          expect(bookStore.version()).toEqual(2);\n\n          patchState(bookStore, { userId: 2 });\n          expect(bookStore.books()).toEqual([]);\n          expect(bookStore.version()).toEqual(2);\n        });\n      }));\n\n    describe('can depend on a Signal from another SignalStore', () => {\n      const UserStore = signalStore(\n        { providedIn: 'root', protectedState: false },\n        withState({ userId: 1 })\n      );\n\n      [\n        {\n          name: 'with computation function',\n          linkedStateFeature: signalStoreFeature(\n            withLinkedState((_, userStore = inject(UserStore)) => ({\n              books: () => {\n                userStore.userId();\n                return [] as string[];\n              },\n            }))\n          ),\n        },\n        {\n          name: 'with explicit linkedSignal',\n          linkedStateFeature: signalStoreFeature(\n            withLinkedState(() => {\n              const userStore = inject(UserStore);\n              return {\n                books: linkedSignal({\n                  source: userStore.userId,\n                  computation: () => [] as string[],\n                }),\n              };\n            })\n          ),\n        },\n      ].forEach(({ name, linkedStateFeature }) => {\n        it(name, () => {\n          const BookStore = signalStore(\n            { providedIn: 'root' },\n            linkedStateFeature,\n            withMethods((store) => ({\n              addBook(title: string) {\n                patchState(store, ({ books }) => ({\n                  books: [...books, title],\n                }));\n              },\n            }))\n          );\n\n          const userStore = TestBed.inject(UserStore);\n          const bookStore = TestBed.inject(BookStore);\n\n          bookStore.addBook('The Neverending Story');\n          expect(bookStore.books()).toEqual(['The Neverending Story']);\n\n          patchState(userStore, { userId: 2 });\n\n          expect(bookStore.books()).toEqual([]);\n        });\n      });\n    });\n\n    it('links user-defined writable signals', () => {\n      const user = signal({ name: 'Mark' });\n      const UserStore = signalStore(\n        { providedIn: 'root' },\n        withLinkedState(() => ({ user })),\n        withMethods((store) => ({\n          setUserName(name: string): void {\n            patchState(store, { user: { name } });\n          },\n        }))\n      );\n\n      const userStore = TestBed.inject(UserStore);\n      expect(userStore.user()).toEqual({ name: 'Mark' });\n\n      userStore.setUserName('John');\n      expect(userStore.user()).toEqual({ name: 'John' });\n      expect(user()).toEqual({ name: 'John' });\n\n      user.set({ name: 'Tom' });\n      expect(userStore.user()).toEqual({ name: 'Tom' });\n      expect(user()).toEqual({ name: 'Tom' });\n    });\n\n    it('has access to state signals', () => {\n      const UserStore = signalStore(\n        { providedIn: 'root' },\n        withState({ userId: 1 }),\n        withLinkedState(({ userId }) => ({ value: userId }))\n      );\n\n      const userStore = TestBed.inject(UserStore);\n\n      expect(userStore.value()).toBe(1);\n    });\n\n    it('has access to props', () => {\n      const UserStore = signalStore(\n        { providedIn: 'root' },\n        withProps(() => ({ userId: 1 })),\n        withLinkedState(({ userId }) => ({ value: () => userId }))\n      );\n\n      const userStore = TestBed.inject(UserStore);\n\n      expect(userStore.value()).toBe(1);\n    });\n  });\n\n  describe('withProps', () => {\n    it('provides previously defined state slices and properties as input argument', () => {\n      const Store = signalStore(\n        withState(() => ({ foo: 'foo' })),\n        withComputed(() => ({ bar: signal('bar').asReadonly() })),\n        withProps(() => ({ num: 10 })),\n        withProps(({ foo, bar, num }) => {\n          expect(foo()).toBe('foo');\n          expect(bar()).toBe('bar');\n          expect(num).toBe(10);\n\n          return { baz: num + 1 };\n        })\n      );\n\n      const store = new Store();\n\n      assertStateSource(store[STATE_SOURCE], {\n        foo: signal('foo'),\n      });\n      expect(store.foo()).toBe('foo');\n      expect(store.bar()).toBe('bar');\n      expect(store.num).toBe(10);\n      expect(store.baz).toBe(11);\n    });\n\n    it('executes withProps factory in injection context', () => {\n      const TOKEN = new InjectionToken('TOKEN', {\n        providedIn: 'root',\n        factory: () => ({ foo: 'bar' }),\n      });\n      const Store = signalStore(withProps(() => inject(TOKEN)));\n\n      TestBed.configureTestingModule({ providers: [Store] });\n      const store = TestBed.inject(Store);\n\n      expect(store.foo).toBe('bar');\n    });\n\n    it('allows symbols as property keys', () => {\n      const SECRET = Symbol('SECRET');\n\n      const Store = signalStore(withProps(() => ({ [SECRET]: 'secret' })));\n      const store = TestBed.configureTestingModule({\n        providers: [Store],\n      }).inject(Store);\n\n      expect(store[SECRET]).toBe('secret');\n    });\n\n    it('allows numbers as property keys', () => {\n      const Store = signalStore(withProps(() => ({ 1: 'Number One' })));\n      const store = TestBed.configureTestingModule({\n        providers: [Store],\n      }).inject(Store);\n\n      expect(store[1]).toBe('Number One');\n    });\n  });\n\n  describe('withComputed', () => {\n    it('provides previously defined state slices and properties as input argument', () => {\n      const Store = signalStore(\n        withState(() => ({ foo: 'foo' })),\n        withComputed(() => ({ bar: signal('bar').asReadonly() })),\n        withProps(() => ({ num: 10 })),\n        withComputed(({ foo, bar, num }) => {\n          expect(foo()).toBe('foo');\n          expect(bar()).toBe('bar');\n          expect(num).toBe(10);\n\n          return { baz: signal('baz').asReadonly() };\n        })\n      );\n\n      const store = new Store();\n\n      assertStateSource(store[STATE_SOURCE], {\n        foo: signal('foo'),\n      });\n      expect(store.foo()).toBe('foo');\n      expect(store.bar()).toBe('bar');\n      expect(store.num).toBe(10);\n      expect(store.baz()).toBe('baz');\n    });\n\n    it('executes withComputed factory in injection context', () => {\n      const TOKEN = new InjectionToken('TOKEN', {\n        providedIn: 'root',\n        factory: () => ({ bar: signal('bar').asReadonly() }),\n      });\n      const Store = signalStore(withComputed(() => inject(TOKEN)));\n\n      TestBed.configureTestingModule({ providers: [Store] });\n      const store = TestBed.inject(Store);\n\n      expect(store.bar()).toBe('bar');\n    });\n\n    it('allows symbols as computed keys', () => {\n      const SECRET = Symbol('SECRET');\n      const SecretStore = signalStore(\n        { providedIn: 'root' },\n        withComputed(() => ({\n          [SECRET]: computed(() => 'secret'),\n        }))\n      );\n\n      const secretStore = TestBed.inject(SecretStore);\n\n      expect(secretStore[SECRET]()).toBe('secret');\n    });\n  });\n\n  describe('withMethods', () => {\n    it('provides previously defined store properties as an input argument', () => {\n      const Store = signalStore(\n        withState(() => ({ foo: 'foo' })),\n        withComputed(() => ({ bar: signal('bar').asReadonly() })),\n        withMethods(() => ({ baz: () => 'baz' })),\n        withProps(() => ({ num: 100 })),\n        withMethods((store) => {\n          assertStateSource(store[STATE_SOURCE], {\n            foo: signal('foo'),\n          });\n          expect(store.foo()).toBe('foo');\n          expect(store.bar()).toBe('bar');\n          expect(store.baz()).toBe('baz');\n          expect(store.num).toBe(100);\n\n          return { m: () => 'm' };\n        })\n      );\n\n      const store = new Store();\n\n      assertStateSource(store[STATE_SOURCE], {\n        foo: signal('foo'),\n      });\n      expect(store.foo()).toBe('foo');\n      expect(store.bar()).toBe('bar');\n      expect(store.baz()).toBe('baz');\n      expect(store.num).toBe(100);\n      expect(store.m()).toBe('m');\n    });\n\n    it('executes withMethods factory in injection context', () => {\n      const TOKEN = new InjectionToken('TOKEN', {\n        providedIn: 'root',\n        factory: () => ({ baz: () => 'baz' }),\n      });\n      const Store = signalStore(withMethods(() => inject(TOKEN)));\n\n      TestBed.configureTestingModule({ providers: [Store] });\n      const store = TestBed.inject(Store);\n\n      expect(store.baz()).toBe('baz');\n    });\n\n    it('allows symbols as method keys', () => {\n      const SECRET = Symbol('SECRET');\n      const SecretStore = signalStore(\n        { providedIn: 'root' },\n        withMethods(() => ({\n          [SECRET]: () => 'my secret',\n        }))\n      );\n      const secretStore = TestBed.inject(SecretStore);\n\n      expect(secretStore[SECRET]()).toBe('my secret');\n    });\n  });\n\n  describe('withHooks', () => {\n    it('calls onInit hook on store init', () => {\n      let message = '';\n      const Store = signalStore(\n        withHooks({\n          onInit() {\n            message = 'onInit';\n          },\n        })\n      );\n\n      new Store();\n\n      expect(message).toBe('onInit');\n\n      message = '';\n      TestBed.configureTestingModule({ providers: [Store] });\n      TestBed.inject(Store);\n\n      expect(message).toBe('onInit');\n    });\n\n    it('calls onDestroy hook on store destroy', () => {\n      let message = '';\n      const Store = signalStore(\n        withHooks({\n          onDestroy() {\n            message = 'onDestroy';\n          },\n        })\n      );\n\n      createLocalService(Store).destroy();\n\n      expect(message).toBe('onDestroy');\n    });\n\n    it('provides previously defined store properties as onInit input argument', () => {\n      let message = '';\n      const Store = signalStore(\n        withState(() => ({ foo: 'foo' })),\n        withComputed(() => ({ bar: signal('bar').asReadonly() })),\n        withMethods(() => ({ baz: () => 'baz' })),\n        withProps(() => ({ num: 10 })),\n        withHooks({\n          onInit(store) {\n            assertStateSource(store[STATE_SOURCE], {\n              foo: signal('foo'),\n            });\n            expect(store.foo()).toBe('foo');\n            expect(store.bar()).toBe('bar');\n            expect(store.baz()).toBe('baz');\n            expect(store.num).toBe(10);\n            message = 'onInit';\n          },\n        })\n      );\n\n      new Store();\n\n      expect(message).toBe('onInit');\n    });\n\n    it('provides previously defined store properties as onDestroy input argument', () => {\n      let message = '';\n      const Store = signalStore(\n        withState(() => ({ foo: 'foo' })),\n        withComputed(() => ({ bar: signal('bar').asReadonly() })),\n        withMethods(() => ({ baz: () => 'baz' })),\n        withProps(() => ({ num: 100 })),\n        withHooks({\n          onDestroy(store) {\n            expect(store.foo()).toBe('foo');\n            expect(store.bar()).toBe('bar');\n            expect(store.baz()).toBe('baz');\n            expect(store.num).toBe(100);\n            message = 'onDestroy';\n          },\n        })\n      );\n\n      createLocalService(Store).destroy();\n\n      expect(message).toBe('onDestroy');\n    });\n\n    it('executes hooks factory in injection context', () => {\n      const messages: string[] = [];\n      const TOKEN_INIT = new InjectionToken('TOKEN_INIT', {\n        providedIn: 'root',\n        factory: () => 'init',\n      });\n      const TOKEN_DESTROY = new InjectionToken('TOKEN_DESTROY', {\n        providedIn: 'root',\n        factory: () => 'destroy',\n      });\n      const Store = signalStore(\n        withState({ name: 'NgRx Store' }),\n        withHooks((store) => {\n          const tokenInit = inject(TOKEN_INIT);\n          const tokenDestroy = inject(TOKEN_DESTROY);\n          return {\n            onInit() {\n              messages.push(`${tokenInit} ${store.name()}`);\n            },\n            onDestroy() {\n              messages.push(`${tokenDestroy} ${store.name()}`);\n            },\n          };\n        })\n      );\n      const { destroy } = createLocalService(Store);\n\n      expect(messages).toEqual(['init NgRx Store']);\n\n      destroy();\n      expect(messages).toEqual(['init NgRx Store', 'destroy NgRx Store']);\n    });\n\n    it('executes onInit hook in injection context', () => {\n      const messages: string[] = [];\n      const TOKEN = new InjectionToken('TOKEN', {\n        providedIn: 'root',\n        factory: () => 'init',\n      });\n      const Store = signalStore(\n        withState({ name: 'NgRx Store' }),\n        withHooks({\n          onInit(store, token = inject(TOKEN)) {\n            messages.push(`${token} ${store.name()}`);\n          },\n        })\n      );\n\n      TestBed.configureTestingModule({ providers: [Store] });\n      TestBed.inject(Store);\n\n      expect(messages).toEqual(['init NgRx Store']);\n    });\n\n    it('succeeds with onDestroy and providedIn: root', () => {\n      const messages: string[] = [];\n      const Store = signalStore(\n        { providedIn: 'root' },\n        withHooks({\n          onDestroy() {\n            messages.push('ending...');\n          },\n        })\n      );\n      TestBed.inject(Store);\n      TestBed.resetTestingModule();\n\n      expect(messages).toEqual(['ending...']);\n    });\n  });\n\n  describe('composition', () => {\n    it('logs warning if previously defined signal store members have the same name', () => {\n      const Store = signalStore(\n        withState({ i: 1, j: 2, k: 3, l: 4 }),\n        withComputed(() => ({\n          l: signal('l').asReadonly(),\n          m: signal('m').asReadonly(),\n        })),\n        withMethods(() => ({\n          j: () => 'j',\n          m: () => true,\n          n: (value: number) => value,\n        }))\n      );\n\n      new Store();\n\n      expect(consoleWarnSpy).toHaveBeenCalledTimes(2);\n      expect(consoleWarnSpy.mock.calls).toEqual([\n        [\n          '@ngrx/signals: SignalStore members cannot be overridden.',\n          'Trying to override:',\n          'l',\n        ],\n        [\n          '@ngrx/signals: SignalStore members cannot be overridden.',\n          'Trying to override:',\n          'j, m',\n        ],\n      ]);\n    });\n\n    it('passes on a symbol key to the features', () => {\n      const SECRET = Symbol('SECRET');\n      const SecretStore = signalStore(\n        withProps(() => ({\n          [SECRET]: 'not your business',\n        })),\n        withComputed((store) => ({\n          secret: computed(() => store[SECRET]),\n        })),\n        withMethods((store) => ({\n          reveil() {\n            return store[SECRET];\n          },\n        }))\n      );\n\n      const secretStore = new SecretStore();\n\n      expect(secretStore.reveil()).toBe('not your business');\n      expect(secretStore.secret()).toBe('not your business');\n      expect(secretStore[SECRET]).toBe('not your business');\n    });\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/state-source.spec.ts",
    "content": "import {\n  createEnvironmentInjector,\n  effect,\n  EnvironmentInjector,\n  Injectable,\n  signal,\n} from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  getState,\n  isWritableStateSource,\n  patchState,\n  signalState,\n  signalStore,\n  StateSource,\n  watchState,\n  withHooks,\n  withMethods,\n  withState,\n} from '../src';\nimport { STATE_SOURCE } from '../src/state-source';\nimport { assertStateSource, createLocalService } from './helpers';\n\nconst SECRET = Symbol('SECRET');\n\ndescribe('StateSource', () => {\n  const initialState = {\n    user: {\n      firstName: 'John',\n      lastName: 'Smith',\n    },\n    foo: 'bar',\n    numbers: [1, 2, 3],\n    ngrx: 'signals',\n    [SECRET]: 'secret',\n  };\n\n  const consoleWarnSpy = vi.spyOn(console, 'warn');\n  consoleWarnSpy.mockImplementation(() => void true);\n\n  beforeEach(() => {\n    consoleWarnSpy.mockClear();\n  });\n\n  describe('isWritableStateSource', () => {\n    it('returns true for a writable StateSource', () => {\n      const stateSource: StateSource<{ value: typeof initialState }> = {\n        [STATE_SOURCE]: { value: signal(initialState) },\n      };\n\n      expect(isWritableStateSource(stateSource)).toBe(true);\n    });\n\n    it('returns false for a readonly StateSource', () => {\n      const stateSource: StateSource<{ value: typeof initialState }> = {\n        [STATE_SOURCE]: { value: signal(initialState).asReadonly() },\n      };\n\n      expect(isWritableStateSource(stateSource)).toBe(false);\n    });\n  });\n\n  describe('patchState', () => {\n    [\n      {\n        name: 'with signalState',\n        stateFactory: () => signalState(initialState),\n      },\n      {\n        name: 'with signalStore',\n        stateFactory: () => {\n          const SignalStore = signalStore(\n            { protectedState: false },\n            withState(initialState)\n          );\n\n          return new SignalStore();\n        },\n      },\n    ].forEach(({ name, stateFactory }) => {\n      describe(name, () => {\n        it('patches state via partial state object', () => {\n          const state = stateFactory();\n\n          patchState(state, {\n            user: { firstName: 'Johannes', lastName: 'Schmidt' },\n            foo: 'baz',\n          });\n\n          assertStateSource(state[STATE_SOURCE], {\n            user: signal({ firstName: 'Johannes', lastName: 'Schmidt' }),\n            foo: signal('baz'),\n            numbers: signal([1, 2, 3]),\n            ngrx: signal('signals'),\n            [SECRET]: signal('secret'),\n          });\n        });\n\n        it('patches state via updater function', () => {\n          const state = stateFactory();\n\n          patchState(state, (state) => ({\n            numbers: [...state.numbers, 4],\n            ngrx: 'rocks',\n          }));\n\n          assertStateSource(state[STATE_SOURCE], {\n            user: signal({ firstName: 'John', lastName: 'Smith' }),\n            foo: signal('bar'),\n            numbers: signal([1, 2, 3, 4]),\n            ngrx: signal('rocks'),\n            [SECRET]: signal('secret'),\n          });\n        });\n\n        it('patches state slice with symbol key', () => {\n          const state = stateFactory();\n\n          patchState(state, { [SECRET]: 'another secret' });\n          expect(state[SECRET]()).toBe('another secret');\n        });\n\n        it('patches state via sequence of partial state objects and updater functions', () => {\n          const state = stateFactory();\n\n          patchState(\n            state,\n            { user: { firstName: 'Johannes', lastName: 'Schmidt' } },\n            (state) => ({ numbers: [...state.numbers, 4], foo: 'baz' }),\n            (state) => ({ user: { ...state.user, firstName: 'Jovan' } }),\n            { foo: 'foo' }\n          );\n\n          assertStateSource(state[STATE_SOURCE], {\n            user: signal({ firstName: 'Jovan', lastName: 'Schmidt' }),\n            foo: signal('foo'),\n            numbers: signal([1, 2, 3, 4]),\n            ngrx: signal('signals'),\n            [SECRET]: signal('secret'),\n          });\n        });\n\n        it('patches state immutably', () => {\n          const state = stateFactory();\n\n          patchState(state, {\n            foo: 'bar',\n            numbers: [3, 2, 1],\n            ngrx: 'rocks',\n          });\n\n          expect(state.user()).toBe(initialState.user);\n          expect(state.foo()).toBe(initialState.foo);\n          expect(state.numbers()).not.toBe(initialState.numbers);\n          expect(state.ngrx()).not.toBe(initialState.ngrx);\n        });\n      });\n    });\n\n    describe('undefined root properties', () => {\n      it('skips and warns on optional root properties, when they are missing in the init state', () => {\n        type UserState = {\n          id: number;\n          middleName?: string;\n        };\n        const initialState: UserState = { id: 1 };\n        const userState = signalState(initialState);\n\n        patchState(userState, { middleName: 'Michael' });\n\n        expect(consoleWarnSpy).toHaveBeenCalledWith(\n          \"@ngrx/signals: patchState was called with an unknown state slice 'middleName'.\",\n          'Ensure that all state properties are explicitly defined in the initial state.',\n          'Updates to properties not present in the initial state will be ignored.'\n        );\n        expect(userState()).toEqual({ id: 1 });\n      });\n\n      it('updates optional properties with an initialized value', () => {\n        type UserState = {\n          id: number;\n          middleName?: string;\n        };\n        const initialState: UserState = { id: 1, middleName: 'Michael' };\n        const userState = signalState(initialState);\n\n        patchState(userState, { middleName: undefined });\n        expect(userState()).toEqual({ id: 1, middleName: undefined });\n\n        patchState(userState, { middleName: 'Martin' });\n        expect(userState()).toEqual({ id: 1, middleName: 'Martin' });\n\n        expect(consoleWarnSpy).not.toHaveBeenCalled();\n      });\n\n      it('supports root properties with union type of undefined and does not warn', () => {\n        type UserState = {\n          id: number;\n          middleName: string | undefined;\n        };\n        const initialState: UserState = { id: 1, middleName: undefined };\n        const userState = signalState(initialState);\n\n        patchState(userState, { middleName: 'Michael' });\n\n        expect(userState()).toEqual({ id: 1, middleName: 'Michael' });\n        expect(consoleWarnSpy).not.toHaveBeenCalled();\n      });\n    });\n\n    it('sets only root properties which values have changed (equal check)', () => {\n      const UserStore = signalStore(\n        { providedIn: 'root', protectedState: false },\n        withState({\n          user: { firstName: 'John', lastName: 'Smith' },\n          city: 'Changan',\n        })\n      );\n      const store = TestBed.inject(UserStore);\n      let userChangedCount = 0;\n      TestBed.runInInjectionContext(() => {\n        effect(() => {\n          store.user();\n          userChangedCount++;\n        });\n      });\n\n      TestBed.tick();\n      expect(userChangedCount).toBe(1);\n\n      patchState(store, { city: 'Xian' });\n      TestBed.tick();\n      expect(userChangedCount).toBe(1);\n\n      patchState(store, (state) => state);\n      TestBed.tick();\n      expect(userChangedCount).toBe(1);\n\n      patchState(store, ({ user }) => ({ user }));\n      TestBed.tick();\n      expect(userChangedCount).toBe(1);\n\n      patchState(store, ({ user }) => ({\n        user: { ...user, firstName: 'Jane' },\n      }));\n      TestBed.tick();\n      expect(userChangedCount).toBe(2);\n    });\n  });\n\n  describe('getState', () => {\n    describe('with signalStore', () => {\n      function storeFactory() {\n        const Store = signalStore(\n          withState(initialState),\n          withMethods((store) => ({\n            setFoo(foo: string): void {\n              patchState(store, { foo });\n            },\n          }))\n        );\n\n        return new Store();\n      }\n\n      it('returns the state object', () => {\n        const store = storeFactory();\n\n        expect(getState(store)).toEqual(initialState);\n\n        store.setFoo('baz');\n\n        expect(getState(store)).toEqual({ ...initialState, foo: 'baz' });\n      });\n\n      it('executes in the reactive context', () => {\n        const store = storeFactory();\n        let executionCount = 0;\n\n        TestBed.runInInjectionContext(() => {\n          effect(() => {\n            getState(store);\n            executionCount++;\n          });\n        });\n\n        TestBed.tick();\n        expect(executionCount).toBe(1);\n\n        store.setFoo('baz');\n\n        TestBed.tick();\n        expect(executionCount).toBe(2);\n      });\n    });\n\n    it('does not support a dynamic dictionary as state', () => {\n      const Store = signalStore(\n        { providedIn: 'root' },\n        withState<Record<number, number>>({}),\n        withMethods((store) => ({\n          addNumber(num: number): void {\n            patchState(store, {\n              [num]: num,\n            });\n          },\n        }))\n      );\n      const store = TestBed.inject(Store);\n\n      store.addNumber(1);\n      store.addNumber(2);\n      store.addNumber(3);\n\n      expect(getState(store)).toEqual({});\n    });\n  });\n\n  describe('watchState', () => {\n    describe('with signalState', () => {\n      it('watches state changes', () => {\n        const state = signalState({ count: 0 });\n        const stateHistory: number[] = [];\n\n        TestBed.runInInjectionContext(() => {\n          watchState(state, (state) => stateHistory.push(state.count));\n        });\n\n        patchState(state, { count: 1 });\n        patchState(state, { count: 2 });\n        patchState(state, { count: 3 });\n\n        expect(stateHistory).toEqual([0, 1, 2, 3]);\n      });\n\n      it('stops watching on injector destroy', () => {\n        const stateHistory: number[] = [];\n        const state = signalState({ count: 0 });\n\n        @Injectable()\n        class TestService {\n          constructor() {\n            watchState(state, (state) => stateHistory.push(state.count));\n          }\n        }\n\n        const { destroy } = createLocalService(TestService);\n\n        patchState(state, { count: 1 });\n\n        destroy();\n\n        patchState(state, { count: 2 });\n        patchState(state, { count: 3 });\n\n        expect(stateHistory).toEqual([0, 1]);\n      });\n\n      it('stops watching on manual destroy', () => {\n        const state = signalState({ count: 0 });\n        const stateHistory: number[] = [];\n\n        const { destroy } = TestBed.runInInjectionContext(() =>\n          watchState(state, (state) => stateHistory.push(state.count))\n        );\n\n        patchState(state, { count: 1 });\n        patchState(state, { count: 2 });\n\n        destroy();\n\n        patchState(state, { count: 3 });\n\n        expect(stateHistory).toEqual([0, 1, 2]);\n      });\n\n      it('stops watching on provided injector destroy', () => {\n        const injector1 = createEnvironmentInjector(\n          [],\n          TestBed.inject(EnvironmentInjector)\n        );\n        const injector2 = createEnvironmentInjector(\n          [],\n          TestBed.inject(EnvironmentInjector)\n        );\n        const state = signalState({ count: 0 });\n        const stateHistory1: number[] = [];\n        const stateHistory2: number[] = [];\n\n        watchState(state, (state) => stateHistory1.push(state.count), {\n          injector: injector1,\n        });\n        watchState(state, (state) => stateHistory2.push(state.count), {\n          injector: injector2,\n        });\n\n        patchState(state, { count: 1 });\n        patchState(state, { count: 2 });\n\n        injector1.destroy();\n\n        patchState(state, { count: 3 });\n\n        injector2.destroy();\n\n        patchState(state, { count: 4 });\n\n        expect(stateHistory1).toEqual([0, 1, 2]);\n        expect(stateHistory2).toEqual([0, 1, 2, 3]);\n      });\n\n      it('throws an error when called out of injection context', () => {\n        expect(() => watchState(signalState({}), () => {})).toThrow(\n          /NG0203: watchState\\(\\) can only be used within an injection context/\n        );\n      });\n    });\n\n    describe('with signalStore', () => {\n      it('watches state changes when used within the store', () => {\n        const stateHistory: number[] = [];\n        const CounterStore = signalStore(\n          withState({ count: 0 }),\n          withHooks({\n            onInit(store) {\n              patchState(store, { count: 1 });\n\n              watchState(store, (state) => stateHistory.push(state.count));\n\n              patchState(store, { count: 2 });\n              patchState(store, { count: 3 });\n            },\n          })\n        );\n\n        TestBed.configureTestingModule({ providers: [CounterStore] });\n        TestBed.inject(CounterStore);\n\n        expect(stateHistory).toEqual([1, 2, 3]);\n      });\n\n      it('watches state changes when used outside of store', () => {\n        const stateHistory: number[] = [];\n        const CounterStore = signalStore(\n          withState({ count: 0 }),\n          withMethods((store) => ({\n            increment(): void {\n              patchState(store, (state) => ({ count: state.count + 1 }));\n            },\n          }))\n        );\n\n        TestBed.configureTestingModule({ providers: [CounterStore] });\n        const store = TestBed.inject(CounterStore);\n        const injector = TestBed.inject(EnvironmentInjector);\n\n        watchState(store, (state) => stateHistory.push(state.count), {\n          injector,\n        });\n\n        store.increment();\n        store.increment();\n        store.increment();\n\n        expect(stateHistory).toEqual([0, 1, 2, 3]);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/types/helpers.ts",
    "content": "export const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  target: 'ES2022',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  strict: true,\n  noImplicitAny: true,\n  paths: {\n    '@ngrx/signals': ['./modules/signals'],\n  },\n});\n"
  },
  {
    "path": "modules/signals/spec/types/patch-state.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './helpers';\n\ndescribe('patchState', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import {\n          PartialStateUpdater,\n          patchState,\n          signalState,\n        } from '@ngrx/signals';\n\n        const state = signalState({ count: 1, foo: 'bar' });\n\n        function increment(): PartialStateUpdater<{ count: number }> {\n          return ({ count }) => ({ count: count + 1 });\n        }\n\n        function addNumber(num: number): PartialStateUpdater<{\n          numbers: number[];\n        }> {\n          return ({ numbers }) => ({ numbers: [...numbers, num] });\n        }\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('infers the state type from WritableStateSource with updater', () => {\n    expectSnippet('patchState(state, increment())').toSucceed();\n  });\n\n  it('infers the state type from WritableStateSource with object', () => {\n    expectSnippet(\"patchState(state, { foo: 'baz' })\").toSucceed();\n  });\n\n  it('infers the state type from WritableStateSource with updater and object', () => {\n    expectSnippet(\"patchState(state, { foo: 'baz' }, increment())\").toSucceed();\n    expectSnippet(\"patchState(state, increment(), { foo: 'baz' })\").toSucceed();\n  });\n\n  it('fails with wrong partial state object', () => {\n    expectSnippet('patchState(state, { x: 1 })').toFail(\n      /'x' does not exist in type 'Partial<NoInfer<{ count: number; foo: string; }>>/\n    );\n    expectSnippet(\"patchState(state, { foo: 'baz' }, { x: 1 })\").toFail(\n      /'x' does not exist in type 'Partial<NoInfer<{ count: number; foo: string; }>>/\n    );\n    expectSnippet('patchState(state, { x: 1 }, { count: 0 })').toFail(\n      /'x' does not exist in type 'Partial<NoInfer<{ count: number; foo: string; }>>/\n    );\n    expectSnippet('patchState(state, increment(), { x: 1 })').toFail(\n      /'x' does not exist in type 'Partial<NoInfer<{ count: number; foo: string; }>>/\n    );\n    expectSnippet('patchState(state, { x: 1 }, increment())').toFail(\n      /'x' does not exist in type 'Partial<NoInfer<{ count: number; foo: string; }>>/\n    );\n  });\n\n  it('fails with wrong partial state updater', () => {\n    expectSnippet('patchState(state, addNumber(10))').toFail(\n      /Property 'numbers' is missing in type '{ count: number; foo: string; }'/\n    );\n    expectSnippet('patchState(state, { count: 10 }, addNumber(10))').toFail(\n      /Property 'numbers' is missing in type '{ count: number; foo: string; }'/\n    );\n    expectSnippet('patchState(state, addNumber(10), { count: 10 })').toFail(\n      /Property 'numbers' is missing in type '{ count: number; foo: string; }'/\n    );\n    expectSnippet('patchState(state, increment(), addNumber(10))').toFail(\n      /Property 'numbers' is missing in type '{ count: number; foo: string; }'/\n    );\n    expectSnippet('patchState(state, addNumber(10), increment())').toFail(\n      /Property 'numbers' is missing in type '{ count: number; foo: string; }'/\n    );\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/signals/spec/types/signal-state.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './helpers';\n\ndescribe('signalState', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import { patchState, signalState } from '@ngrx/signals';\n\n        const initialState = {\n          user: {\n            age: 30,\n            details: {\n              first: 'John',\n              last: 'Smith',\n            },\n            address: ['Belgrade', 'Serbia'],\n          },\n          numbers: [1, 2, 3],\n          ngrx: 'rocks',\n        };\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('allows passing state as a generic argument', () => {\n    const snippet = `\n      type FooState = { foo: string; bar: number };\n      const state = signalState<FooState>({ foo: 'bar', bar: 1 });\n    `;\n\n    const result = expectSnippet(snippet);\n\n    result.toInfer('state', 'SignalState<FooState>');\n  });\n\n  it('creates deep signals for nested state slices', () => {\n    const snippet = `\n      const state = signalState(initialState);\n\n      const user = state.user;\n      const age = state.user.age;\n      const details = state.user.details;\n      const first = state.user.details.first;\n      const last = state.user.details.last;\n      const address = state.user.address;\n      const numbers = state.numbers;\n      const ngrx = state.ngrx;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'state',\n      'SignalState<{ user: { age: number; details: { first: string; last: string; }; address: string[]; }; numbers: number[]; ngrx: string; }>'\n    );\n    result.toInfer(\n      'user',\n      'DeepSignal<{ age: number; details: { first: string; last: string; }; address: string[]; }>'\n    );\n    result.toInfer('details', 'DeepSignal<{ first: string; last: string; }>');\n    result.toInfer('first', 'Signal<string>');\n    result.toInfer('last', 'Signal<string>');\n    result.toInfer('address', 'Signal<string[]>');\n    result.toInfer('numbers', 'Signal<number[]>');\n    result.toInfer('ngrx', 'Signal<string>');\n  });\n\n  it('creates deep signals when state type is an interface', () => {\n    const snippet = `\n      interface User {\n        firstName: string;\n        lastName: string;\n      }\n\n      interface State {\n        user: User;\n        bool: boolean;\n        map: Map<string, string>;\n        set: Set<{ foo: number }>;\n      };\n\n      const state = signalState<State>({\n        user: { firstName: 'John', lastName: 'Smith' },\n        bool: true,\n        map: new Map<string, string>(),\n        set: new Set<{ foo: number }>(),\n      });\n\n      const user = state.user;\n      const lastName = state.user.lastName;\n      const bool = state.bool;\n      const map = state.map;\n      const set = state.set;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('user', 'DeepSignal<User>');\n    result.toInfer('lastName', 'Signal<string>');\n    result.toInfer('bool', 'Signal<boolean>');\n    result.toInfer('map', 'Signal<Map<string, string>>');\n    result.toInfer('set', 'Signal<Set<{ foo: number; }>>');\n  });\n\n  it('does not create deep signals for iterables', () => {\n    const snippet = `\n      const arrayState = signalState<string[]>([]);\n      const arrayStateValue = arrayState();\n      declare const arrayStateKeys: keyof typeof arrayState;\n\n      const setState = signalState(new Set<number>());\n      const setStateValue = setState();\n      declare const setStateKeys: keyof typeof setState;\n\n      const mapState = signalState(new Map<number, { bar: boolean }>());\n      const mapStateValue = mapState();\n      declare const mapStateKeys: keyof typeof mapState;\n\n      const uintArrayState = signalState(new Uint8ClampedArray());\n      const uintArrayStateValue = uintArrayState();\n      declare const uintArrayStateKeys: keyof typeof uintArrayState;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('arrayStateValue', 'string[]');\n    result.toInfer('arrayStateKeys', 'unique symbol | unique symbol');\n    result.toInfer('setStateValue', 'Set<number>');\n    result.toInfer('setStateKeys', 'unique symbol | unique symbol');\n    result.toInfer('mapStateValue', 'Map<number, { bar: boolean; }>');\n    result.toInfer('mapStateKeys', 'unique symbol | unique symbol');\n    result.toInfer('uintArrayStateValue', 'Uint8ClampedArray<ArrayBuffer>');\n    result.toInfer('uintArrayStateKeys', 'unique symbol | unique symbol');\n  });\n\n  it('does not create deep signals for built-in object types', () => {\n    const snippet = `\n      const weakSetState = signalState(new WeakSet<{ foo: string }>());\n      const weakSetStateValue = weakSetState();\n      declare const weakSetStateKeys: keyof typeof weakSetState;\n\n      const dateState = signalState(new Date());\n      const dateStateValue = dateState();\n      declare const dateStateKeys: keyof typeof dateState;\n\n      const errorState = signalState(new Error());\n      const errorStateValue = errorState();\n      declare const errorStateKeys: keyof typeof errorState;\n\n      const regExpState = signalState(new RegExp(''));\n      const regExpStateValue = regExpState();\n      declare const regExpStateKeys: keyof typeof regExpState;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('weakSetStateValue', 'WeakSet<{ foo: string; }>');\n    result.toInfer('weakSetStateKeys', 'unique symbol | unique symbol');\n    result.toInfer('dateStateValue', 'Date');\n    result.toInfer('dateStateKeys', 'unique symbol | unique symbol');\n    result.toInfer('errorStateValue', 'Error');\n    result.toInfer('errorStateKeys', 'unique symbol | unique symbol');\n    result.toInfer('regExpStateValue', 'RegExp');\n    result.toInfer('regExpStateKeys', 'unique symbol | unique symbol');\n  });\n\n  it('does not create deep signals for functions', () => {\n    const snippet = `\n      const state = signalState(() => {});\n      const stateValue = state();\n      declare const stateKeys: keyof typeof state;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('stateValue', '() => void');\n    result.toInfer('stateKeys', 'unique symbol | unique symbol');\n  });\n\n  it('does not create deep signals for optional state slices', () => {\n    const snippet = `\n      type State = {\n        foo?: string;\n        bar: { baz?: number };\n        x?: { y: { z?: boolean } };\n      };\n\n      const state = signalState<State>({ bar: {} });\n      const foo = state.foo;\n      const bar = state.bar;\n      const baz = state.bar.baz;\n      const x = state.x;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('state', 'SignalState<State>');\n    result.toInfer('foo', 'Signal<string | undefined> | undefined');\n    result.toInfer('bar', 'DeepSignal<{ baz?: number | undefined; }>');\n    result.toInfer('baz', 'Signal<number | undefined> | undefined');\n    result.toInfer(\n      'x',\n      'Signal<{ y: { z?: boolean | undefined; }; } | undefined> | undefined'\n    );\n  });\n\n  it('does not create deep signals for unknown records', () => {\n    const snippet = `\n      const state1 = signalState<{ [key: string]: number }>({});\n      declare const state1Keys: keyof typeof state1;\n\n      const state2 = signalState<{ [key: number]: { foo: string } }>({\n         1: { foo: 'bar' },\n      });\n      declare const state2Keys: keyof typeof state2;\n\n      const state3 = signalState<Record<string, { bar: number }>>({});\n      declare const state3Keys: keyof typeof state3;\n\n      const state4 = signalState({\n        foo: {} as Record<string, { bar: boolean } | number>,\n      });\n      const foo = state4.foo;\n\n      const state5 = signalState({\n        bar: { baz: {} as Record<number, unknown> }\n      });\n      const bar = state5.bar;\n      const baz = bar.baz;\n\n      const state6 = signalState({\n        x: {} as Record<symbol, string>\n      });\n      const x = state6.x;\n\n      const state7 = signalState({ y: {} });\n      const y = state7.y;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('state1', 'SignalState<{ [key: string]: number; }>');\n    result.toInfer('state1Keys', 'unique symbol | unique symbol');\n    result.toInfer(\n      'state2',\n      'SignalState<{ [key: number]: { foo: string; }; }>'\n    );\n    result.toInfer('state2Keys', 'unique symbol | unique symbol');\n    result.toInfer('state3', 'SignalState<Record<string, { bar: number; }>>');\n    result.toInfer('state3Keys', 'unique symbol | unique symbol');\n    result.toInfer(\n      'state4',\n      'SignalState<{ foo: Record<string, number | { bar: boolean; }>; }>'\n    );\n    result.toInfer('foo', 'Signal<Record<string, number | { bar: boolean; }>>');\n    result.toInfer(\n      'state5',\n      'SignalState<{ bar: { baz: Record<number, unknown>; }; }>'\n    );\n    result.toInfer('bar', 'DeepSignal<{ baz: Record<number, unknown>; }>');\n    result.toInfer('baz', 'Signal<Record<number, unknown>>');\n    result.toInfer('state6', 'SignalState<{ x: Record<symbol, string>; }>');\n    result.toInfer('x', 'Signal<Record<symbol, string>>');\n    result.toInfer('state7', 'SignalState<{ y: {}; }>');\n    result.toInfer('y', 'Signal<{}>');\n  });\n\n  it('succeeds when state is an empty object', () => {\n    const snippet = `const state = signalState({})`;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('state', 'SignalState<{}>');\n  });\n\n  it('succeeds when state slices are union types', () => {\n    const snippet = `\n      type State = {\n        foo: { s: string } | number;\n        bar: { baz: { n: number } | null };\n        x: { y: { z: boolean | undefined } };\n      };\n\n      const state = signalState<State>({\n        foo: { s: 's' },\n        bar: { baz: null },\n        x: { y: { z: undefined } },\n      });\n      const foo = state.foo;\n      const bar = state.bar;\n      const baz = state.bar.baz;\n      const x = state.x;\n      const y = state.x.y;\n      const z = state.x.y.z;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('state', 'SignalState<State>');\n    result.toInfer('foo', 'Signal<number | { s: string; }>');\n    result.toInfer('bar', 'DeepSignal<{ baz: { n: number; } | null; }>');\n    result.toInfer('baz', 'Signal<{ n: number; } | null>');\n    result.toInfer('x', 'DeepSignal<{ y: { z: boolean | undefined; }; }>');\n    result.toInfer('y', 'DeepSignal<{ z: boolean | undefined; }>');\n    result.toInfer('z', 'Signal<boolean | undefined>');\n  });\n\n  it('succeeds when state contains Function properties', () => {\n    const snippet = `\n      const state1 = signalState({ name: 0 });\n      const state2 = signalState({ foo: { length: [] as boolean[] } });\n      const state3 = signalState({ name: { length: '' } });\n\n      const name = state1.name;\n      const length1 = state2.foo.length;\n      const name2 = state3.name;\n      const length2 = state3.name.length;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('name', 'Signal<number>');\n    result.toInfer('length1', 'Signal<boolean[]>');\n    result.toInfer('name2', 'DeepSignal<{ length: string; }>');\n    result.toInfer('length2', 'Signal<string>');\n  });\n\n  it('fails when state is not an object', () => {\n    expectSnippet(`const state = signalState(10);`).toFail();\n\n    expectSnippet(`const state = signalState('');`).toFail();\n\n    expectSnippet(`const state = signalState(null);`).toFail();\n\n    expectSnippet(`const state = signalState(true);`).toFail();\n  });\n\n  it('patches state via sequence of partial state objects and updater functions', () => {\n    expectSnippet(`\n      const state = signalState(initialState);\n\n      patchState(\n        state,\n        { numbers: [10, 100, 1000] },\n        (state) => ({ user: { ...state.user, age: state.user.age + 1 } }),\n        { ngrx: 'signals' }\n      );\n    `).toSucceed();\n  });\n\n  it('fails when state is patched with a non-record', () => {\n    expectSnippet(`\n      const state = signalState(initialState);\n      patchState(state, 10);\n    `).toFail();\n\n    expectSnippet(`\n      const state = signalState(initialState);\n      patchState(state, undefined);\n    `).toFail();\n\n    expectSnippet(`\n      const state = signalState(initialState);\n      patchState(state, [1, 2, 3]);\n    `).toFail();\n  });\n\n  it('fails when state is patched with a wrong record', () => {\n    expectSnippet(`\n      const state = signalState(initialState);\n      patchState(state, { ngrx: 10 });\n    `).toFail(/Type 'number' is not assignable to type 'string'/);\n  });\n\n  it('fails when state is patched with a wrong updater function', () => {\n    expectSnippet(`\n      const state = signalState(initialState);\n      patchState(state, (state) => ({ user: { ...state.user, age: '30' } }));\n    `).toFail(/Type 'string' is not assignable to type 'number'/);\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/signals/spec/types/signal-store.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './helpers';\n\ndescribe('signalStore', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import { computed, inject, Signal } from '@angular/core';\n        import {\n          getState,\n          patchState,\n          signalStore,\n          signalStoreFeature,\n          type,\n          withComputed,\n          withHooks,\n          withMethods,\n          withState,\n        } from '@ngrx/signals';\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('allows passing state as a generic argument', () => {\n    const snippet = `\n      type State = { foo: string; bar: number[] };\n      const Store = signalStore(\n        withState<State>({ foo: 'bar', bar: [1, 2] })\n      );\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'Store',\n      'Type<{ foo: Signal<string>; bar: Signal<number[]>; } & StateSource<{ foo: string; bar: number[]; }>>'\n    );\n  });\n\n  it('creates deep signals for nested state slices', () => {\n    const snippet = `\n      const Store = signalStore(\n        withState({\n          user: {\n            age: 10,\n            details: {\n              first: 'John',\n              flags: [true, false],\n            },\n          },\n        })\n      );\n\n      const store = new Store();\n      const user = store.user;\n      const age = store.user.age;\n      const details = store.user.details;\n      const first = store.user.details.first;\n      const flags = store.user.details.flags;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'store',\n      '{ user: DeepSignal<{ age: number; details: { first: string; flags: boolean[]; }; }>; } & StateSource<{ user: { age: number; details: { first: string; flags: boolean[]; }; }; }>'\n    );\n    result.toInfer(\n      'user',\n      'DeepSignal<{ age: number; details: { first: string; flags: boolean[]; }; }>'\n    );\n    result.toInfer(\n      'details',\n      'DeepSignal<{ first: string; flags: boolean[]; }>'\n    );\n    result.toInfer('first', 'Signal<string>');\n    result.toInfer('flags', 'Signal<boolean[]>');\n  });\n\n  it('does not create deep signals when state slices are unknown records', () => {\n    const snippet = `\n      type State = {\n        foo: { [key: string]: string };\n        bar: { baz: Record<number, boolean> };\n        x: { y: { z: Record<string, { foo: number } | boolean> } };\n      }\n\n      const Store = signalStore(\n        withState<State>({\n          foo: {},\n          bar: { baz: {} },\n          x: { y: { z: {} } },\n        })\n      );\n\n      const store = new Store();\n      const foo = store.foo;\n      const baz = store.bar.baz;\n      const z = store.x.y.z;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('foo', 'Signal<{ [key: string]: string; }>');\n    result.toInfer('baz', 'Signal<Record<number, boolean>>');\n    result.toInfer('z', 'Signal<Record<string, boolean | { foo: number; }>>');\n  });\n\n  it('creates deep signals when state type is an interface', () => {\n    const snippet = `\n      interface User {\n        firstName: string;\n        lastName: string;\n      }\n\n      interface State {\n        user: User;\n        num: number;\n        map: Map<string, { foo: number }>;\n        set: Set<number>;\n      }\n\n      const Store = signalStore(\n        withState<State>({\n          user: { firstName: 'John', lastName: 'Smith' },\n          num: 10,\n          map: new Map<string, { foo: number }>(),\n          set: new Set<number>(),\n        })\n      );\n\n      const store = new Store();\n      const user = store.user;\n      const firstName = store.user.firstName;\n      const num = store.num;\n      const map = store.map;\n      const set = store.set;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('user', 'DeepSignal<User>');\n    result.toInfer('firstName', 'Signal<string>');\n    result.toInfer('num', 'Signal<number>');\n    result.toInfer('map', 'Signal<Map<string, { foo: number; }>>');\n    result.toInfer('set', 'Signal<Set<number>>');\n  });\n\n  it('does not create deep signals when state type is an iterable', () => {\n    const snippet = `\n      const ArrayStore = signalStore(withState<number[]>([]));\n      const arrayStore = new ArrayStore();\n      declare const arrayStoreKeys: keyof typeof arrayStore;\n\n      const SetStore = signalStore(withState(new Set<{ foo: string }>()));\n      const setStore = new SetStore();\n      declare const setStoreKeys: keyof typeof setStore;\n\n      const MapStore = signalStore(withState(new Map<string, { foo: number }>()));\n      const mapStore = new MapStore();\n      declare const mapStoreKeys: keyof typeof mapStore;\n\n      const FloatArrayStore = signalStore(withState(new Float32Array()));\n      const floatArrayStore = new FloatArrayStore();\n      declare const floatArrayStoreKeys: keyof typeof floatArrayStore;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('arrayStoreKeys', 'unique symbol');\n    result.toInfer('setStoreKeys', 'unique symbol');\n    result.toInfer('mapStoreKeys', 'unique symbol');\n    result.toInfer('floatArrayStoreKeys', 'unique symbol');\n  });\n\n  it('does not create deep signals when state type is a built-in object type', () => {\n    const snippet = `\n      const WeakMapStore = signalStore(withState(new WeakMap<{ foo: string }, { bar: number }>()));\n      const weakMapStore = new WeakMapStore();\n      declare const weakMapStoreKeys: keyof typeof weakMapStore;\n\n      const DateStore = signalStore(withState(new Date()));\n      const dateStore = new DateStore();\n      declare const dateStoreKeys: keyof typeof dateStore;\n\n      const ErrorStore = signalStore(withState(new Error()));\n      const errorStore = new ErrorStore();\n      declare const errorStoreKeys: keyof typeof errorStore;\n\n      const RegExpStore = signalStore(withState(new RegExp('')));\n      const regExpStore = new RegExpStore();\n      declare const regExpStoreKeys: keyof typeof regExpStore;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('weakMapStoreKeys', 'unique symbol');\n    result.toInfer('dateStoreKeys', 'unique symbol');\n    result.toInfer('errorStoreKeys', 'unique symbol');\n    result.toInfer('regExpStoreKeys', 'unique symbol');\n  });\n\n  it('does not create deep signals when state type is a function', () => {\n    const snippet = `\n      const Store = signalStore(withState(() => () => {}));\n      const store = new Store();\n      declare const storeKeys: keyof typeof store;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('storeKeys', 'unique symbol');\n  });\n\n  it('succeeds when state is an empty object', () => {\n    const snippet = `const Store = signalStore(withState({}))`;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('Store', 'Type<{} & StateSource<{}>>');\n  });\n\n  it('succeeds when state slices are union types', () => {\n    const snippet = `\n      type State = {\n        foo: { s: string } | number;\n        bar: { baz: { b: boolean } | null };\n        x: { y: { z: number | undefined } };\n      };\n\n      const Store = signalStore(\n        withState<State>({\n          foo: { s: 's' },\n          bar: { baz: null },\n          x: { y: { z: undefined } },\n        })\n      );\n      const store = inject(Store);\n      const foo = store.foo;\n      const bar = store.bar;\n      const baz = store.bar.baz;\n      const x = store.x;\n      const y = store.x.y;\n      const z = store.x.y.z;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'store',\n      '{ foo: Signal<number | { s: string; }>; bar: DeepSignal<{ baz: { b: boolean; } | null; }>; x: DeepSignal<{ y: { z: number | undefined; }; }>; } & StateSource<{ foo: number | { ...; }; bar: { ...; }; x: { ...; }; }>'\n    );\n    result.toInfer('foo', 'Signal<number | { s: string; }>');\n    result.toInfer('bar', 'DeepSignal<{ baz: { b: boolean; } | null; }>');\n    result.toInfer('baz', 'Signal<{ b: boolean; } | null>');\n    result.toInfer('x', 'DeepSignal<{ y: { z: number | undefined; }; }>');\n    result.toInfer('y', 'DeepSignal<{ z: number | undefined; }>');\n    result.toInfer('z', 'Signal<number | undefined>');\n  });\n\n  it('succeeds when root state slices contain Function properties', () => {\n    const snippet1 = `\n      const Store = signalStore(\n        withState({\n          name: { x: { y: 'z' } },\n          arguments: [1, 2, 3],\n          call: false,\n        })\n      );\n    `;\n\n    const result1 = expectSnippet(snippet1);\n    result1.toInfer(\n      'Store',\n      'Type<{ name: DeepSignal<{ x: { y: string; }; }>; arguments: Signal<number[]>; call: Signal<boolean>; } & StateSource<{ name: { x: { y: string; }; }; arguments: number[]; call: boolean; }>>'\n    );\n\n    const snippet2 = `\n      const Store = signalStore(\n        withState({\n          apply: 'apply',\n          bind: { foo: 'bar' },\n          prototype: ['ngrx'],\n        })\n      );\n    `;\n\n    const result2 = expectSnippet(snippet2);\n    result2.toInfer(\n      'Store',\n      'Type<{ apply: Signal<string>; bind: DeepSignal<{ foo: string; }>; prototype: Signal<string[]>; } & StateSource<{ apply: string; bind: { foo: string; }; prototype: string[]; }>>'\n    );\n\n    const snippet3 = `\n      const Store = signalStore(\n        withState({\n          length: 10,\n          caller: undefined,\n        })\n      );\n    `;\n\n    const result3 = expectSnippet(snippet3);\n    result3.toInfer(\n      'Store',\n      'Type<{ length: Signal<number>; caller: Signal<undefined>; } & StateSource<{ length: number; caller: undefined; }>>'\n    );\n  });\n\n  it('succeeds when nested state slices contain Function properties', () => {\n    const snippet1 = `\n      type State = { x: { name?: string } };\n      const Store = signalStore(withState<State>({ x: { name: '' } }));\n      const store = new Store();\n      const name = store.x.name;\n    `;\n\n    const result1 = expectSnippet(snippet1);\n    result1.toInfer('name', 'Signal<string | undefined> | undefined');\n\n    const snippet2 = `\n      const Store = signalStore(\n        withState({ x: { length: { name: false }, baz: 1 } })\n      );\n      const store = new Store();\n      const length = store.x.length;\n      const name = store.x.length.name;\n    `;\n\n    const result2 = expectSnippet(snippet2);\n    result2.toInfer('length', 'DeepSignal<{ name: boolean; }>');\n    result2.toInfer('name', 'Signal<boolean>');\n  });\n\n  it('succeeds when nested state slices are optional', () => {\n    const snippet = `\n      type State = {\n        bar: { baz?: number };\n        x: { y?: { z: boolean } };\n      };\n\n      const Store = signalStore(withState<State>({ bar: {}, x: {} }));\n\n      const store = new Store();\n      const bar = store.bar;\n      const baz = store.bar.baz;\n      const x = store.x;\n      const y = store.x.y;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'store',\n      '{ bar: DeepSignal<{ baz?: number | undefined; }>; x: DeepSignal<{ y?: { z: boolean; } | undefined; }>; } & StateSource<{ bar: { baz?: number | undefined; }; x: { y?: { z: boolean; } | undefined; }; }>'\n    );\n    result.toInfer('bar', 'DeepSignal<{ baz?: number | undefined; }>');\n    result.toInfer('baz', 'Signal<number | undefined> | undefined');\n    result.toInfer('x', 'DeepSignal<{ y?: { z: boolean; } | undefined; }>');\n    result.toInfer('y', 'Signal<{ z: boolean; } | undefined> | undefined');\n  });\n\n  it('succeeds when root state slices are optional', () => {\n    const snippet = `\n      type State = {\n        foo?: { s: string };\n        bar: number;\n      };\n\n      const Store = signalStore(\n        withState<State>({ foo: { s: '' }, bar: 1 })\n      );\n      const store = new Store();\n      const foo = store.foo;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('foo', 'Signal<{ s: string; } | undefined> | undefined');\n  });\n\n  it('does not create deep signals when state is an unknown record', () => {\n    const snippet1 = `\n      const Store = signalStore(withState<{ [key: string]: number }>({}));\n      const store = new Store();\n      declare const storeKeys: keyof typeof store;\n    `;\n\n    const result1 = expectSnippet(snippet1);\n    result1.toInfer('storeKeys', 'unique symbol');\n\n    const snippet2 = `\n      const Store = signalStore(\n        withState<{ [key: number]: { bar: string } }>({})\n      );\n      const store = new Store();\n      declare const storeKeys: keyof typeof store;\n    `;\n\n    const result2 = expectSnippet(snippet2);\n    result2.toInfer('storeKeys', 'unique symbol');\n\n    const snippet3 = `\n      const Store = signalStore(\n        withState<Record<string, { foo: boolean } | number>>({\n          x: { foo: true },\n          y: 1,\n        })\n      );\n      const store = new Store();\n      declare const storeKeys: keyof typeof store;\n    `;\n\n    const result3 = expectSnippet(snippet3);\n    result3.toInfer('storeKeys', 'unique symbol');\n  });\n\n  it('fails when state is not an object', () => {\n    expectSnippet(`const Store = signalStore(withState(10));`).toFail();\n\n    expectSnippet(`const Store = signalStore(withState(''));`).toFail();\n\n    expectSnippet(`const Store = signalStore(withState(null));`).toFail();\n\n    expectSnippet(`const Store = signalStore(withState(true));`).toFail();\n  });\n\n  it('exposes readonly state source when protectedState is not provided', () => {\n    const snippet = `\n      const CounterStore1 = signalStore(withState({ count: 0 }));\n      const CounterStore2 = signalStore(\n        { providedIn: 'root' },\n        withState({ count: 0 })\n      );\n\n      const store1 = new CounterStore1();\n      const state1 = getState(store1);\n\n      const store2 = new CounterStore2();\n      const state2 = getState(store2);\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'store1',\n      '{ count: Signal<number>; } & StateSource<{ count: number; }>'\n    );\n    result.toInfer('state1', '{ count: number; }');\n    result.toInfer(\n      'store2',\n      '{ count: Signal<number>; } & StateSource<{ count: number; }>'\n    );\n    result.toInfer('state2', '{ count: number; }');\n\n    expectSnippet(`\n      ${snippet}\n      patchState(store1, { count: 1 });\n    `).toFail();\n\n    expectSnippet(`\n      ${snippet}\n      patchState(store2, { count: 1 });\n    `).toFail();\n  });\n\n  it('exposes readonly state source when protectedState is true', () => {\n    const snippet = `\n      const CounterStore1 = signalStore(\n        { protectedState: true },\n        withState({ count: 0 })\n      );\n      const CounterStore2 = signalStore(\n        { providedIn: 'root', protectedState: true },\n        withState({ count: 0 })\n      );\n\n      const store1 = new CounterStore1();\n      const state1 = getState(store1);\n\n      const store2 = new CounterStore2();\n      const state2 = getState(store2);\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'store1',\n      '{ count: Signal<number>; } & StateSource<{ count: number; }>'\n    );\n    result.toInfer('state1', '{ count: number; }');\n    result.toInfer(\n      'store2',\n      '{ count: Signal<number>; } & StateSource<{ count: number; }>'\n    );\n    result.toInfer('state2', '{ count: number; }');\n\n    expectSnippet(`\n      ${snippet}\n      patchState(store1, { count: 10 });\n    `).toFail();\n\n    expectSnippet(`\n      ${snippet}\n      patchState(store2, { count: 10 });\n    `).toFail();\n  });\n\n  it('exposes writable state source when protectedState is false', () => {\n    const snippet = `\n      const CounterStore1 = signalStore(\n        { protectedState: false },\n        withState({ count: 0 })\n      );\n      const CounterStore2 = signalStore(\n        { providedIn: 'root', protectedState: false },\n        withState({ count: 0 })\n      );\n\n      const store1 = new CounterStore1();\n      const state1 = getState(store1);\n\n      const store2 = new CounterStore2();\n      const state2 = getState(store2);\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'store1',\n      '{ count: Signal<number>; } & WritableStateSource<{ count: number; }>'\n    );\n    result.toInfer('state1', '{ count: number; }');\n    result.toInfer(\n      'store2',\n      '{ count: Signal<number>; } & WritableStateSource<{ count: number; }>'\n    );\n    result.toInfer('state2', '{ count: number; }');\n\n    expectSnippet(`\n      ${snippet}\n      patchState(store1, { count: 100 });\n      patchState(store2, { count: 100 });\n    `).toSucceed();\n  });\n\n  it('patches state via sequence of partial state objects and updater functions', () => {\n    expectSnippet(`\n      const Store = signalStore(\n        withState({ ngrx: 'signals' }),\n        withState({ user: { age: 10, first: 'John' } }),\n        withMethods((store) => {\n          patchState(\n            store,\n            (state) => ({ user: { ...state.user, first: 'Peter' } }),\n            { ngrx: 'rocks' }\n          );\n\n          return {};\n        }),\n        withState({ flags: [true, false, true] }),\n        withMethods(({ ngrx, flags, ...store }) => {\n          patchState(\n            store,\n            { ngrx: 'rocks' },\n            (state) => ({ flags: [...state.flags, true] })\n          );\n\n          patchState(\n            store,\n            { flags: [true] },\n            (state) => ({ user: { ...state.user, age: state.user.age + 1 } }),\n            { ngrx: 'store' }\n          );\n\n          return {};\n        })\n      );\n    `).toSucceed();\n  });\n\n  it('fails when state is patched with a non-record', () => {\n    expectSnippet(`\n      const Store = signalStore(\n        { protectedState: false },\n        withState({ foo: 'bar' })\n      );\n\n      const store = new Store();\n      patchState(store, 10);\n    `).toFail();\n\n    expectSnippet(`\n      const Store = signalStore(\n        { protectedState: false },\n        withState({ foo: 'bar' })\n      );\n\n      const store = new Store();\n      patchState(store, undefined);\n    `).toFail();\n\n    expectSnippet(`\n      const Store = signalStore(\n        { protectedState: false },\n        withState({ foo: 'bar' })\n      );\n\n      const store = new Store();\n      patchState(store, [1, 2, 3]);\n    `).toFail();\n  });\n\n  it('fails when state is patched with a wrong record', () => {\n    expectSnippet(`\n      const Store = signalStore(\n        { protectedState: false },\n        withState({ foo: 'bar' })\n      );\n\n      const store = new Store();\n      patchState(store, { foo: 10 });\n    `).toFail(/Type 'number' is not assignable to type 'string'/);\n\n    expectSnippet(`\n      const Store = signalStore(\n        withState({ foo: 'bar' }),\n        withMethods((store) => {\n          patchState(store, { foo: 10 });\n          return {};\n        })\n      );\n    `).toFail(/Type 'number' is not assignable to type 'string'/);\n\n    expectSnippet(`\n      const Store = signalStore(\n        withState({ foo: 'bar' }),\n        withMethods(({ foo, ...store }) => {\n          patchState(store, { foo: 10 });\n          return {};\n        })\n      );\n    `).toFail(/Type 'number' is not assignable to type 'string'/);\n  });\n\n  it('fails when state is patched with a wrong updater function', () => {\n    expectSnippet(`\n      const Store = signalStore(\n        { protectedState: false },\n        withState({ user: { first: 'John', age: 20 } })\n      );\n\n      const store = new Store();\n      patchState(store, (state) => ({ user: { ...state.user, age: '30' } }));\n    `).toFail(/Type 'string' is not assignable to type 'number'/);\n\n    expectSnippet(`\n      const Store = signalStore(\n        withState({ user: { first: 'John', age: 20 } }),\n        withMethods((store) => {\n          patchState(store, (state) => ({ user: { ...state.user, age: '30' } }));\n          return {};\n        })\n      );\n    `).toFail(/Type 'string' is not assignable to type 'number'/);\n\n    expectSnippet(`\n      const Store = signalStore(\n        withState({ user: { first: 'John', age: 20 } }),\n        withMethods(({ user, ...store }) => {\n          patchState(store, (state) => ({ user: { ...state.user, first: 10 } }));\n          return {};\n        })\n      );\n    `).toFail(/Type 'number' is not assignable to type 'string'/);\n  });\n\n  it('allows injecting store using the `inject` function', () => {\n    const snippet = `\n      const Store = signalStore(\n        withState({ ngrx: 'rocks', x: { y: 'z' } }),\n        withComputed(() => ({ signals: computed(() => [1, 2, 3]) })),\n        withMethods(() => ({\n          mgmt(arg: boolean): number {\n            return 1;\n          }\n        }))\n      );\n\n      const store = inject(Store);\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'store',\n      '{ ngrx: Signal<string>; x: DeepSignal<{ y: string; }>; signals: Signal<number[]>; mgmt: (arg: boolean) => number; } & StateSource<{ ngrx: string; x: { y: string; }; }>'\n    );\n  });\n\n  it('allows using store via constructor-based dependency injection', () => {\n    const snippet = `\n      const Store = signalStore(\n        withState({ foo: 10 }),\n        withComputed(({ foo }) => ({ bar: computed(() => foo() + '1') })),\n        withMethods(() => ({\n          baz(x: number): void {}\n        }))\n      );\n\n      type Store = InstanceType<typeof Store>;\n\n      class Component {\n        constructor (readonly store: Store) {}\n      }\n\n      const component = new Component(new Store());\n      const store = component.store;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'store',\n      '{ foo: Signal<number>; bar: Signal<string>; baz: (x: number) => void; } & StateSource<{ foo: number; }>'\n    );\n  });\n\n  it('correctly infers the type of methods with generics', () => {\n    const snippet = `\n      const Store = signalStore(\n        withMethods(() => ({\n          log<Str extends string>(str: Str) {\n            console.log(str);\n          },\n        }))\n      );\n\n      const store = inject(Store);\n    `;\n\n    expectSnippet(snippet + `store.log('ngrx');`).toSucceed();\n    expectSnippet(snippet + `store.log(10);`).toFail();\n  });\n\n  it('omits private store members from the public instance', () => {\n    const snippet = `\n      const CounterStore = signalStore(\n        withState({ count1: 0, _count2: 0 }),\n        withComputed(({ count1, _count2 }) => ({\n          _doubleCount1: computed(() => count1() * 2),\n          doubleCount2: computed(() => _count2() * 2),\n        })),\n        withMethods(() => ({\n          increment1() {},\n          _increment2() {},\n        })),\n        withHooks({\n          onInit({ increment1, _increment2 }) {\n            increment1();\n            _increment2();\n          },\n        })\n      );\n\n      const store = new CounterStore();\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'store',\n      '{ count1: Signal<number>; doubleCount2: Signal<number>; increment1: () => void; } & StateSource<{ count1: number; }>'\n    );\n  });\n\n  it('prevents private state slices from being updated from the outside', () => {\n    const snippet = `\n      const CounterStore = signalStore(\n        { protectedState: false },\n        withState({ count1: 0, _count2: 0 }),\n      );\n\n      const store = new CounterStore();\n    `;\n\n    expectSnippet(`\n      ${snippet}\n      patchState(store, { count1: 1 });\n    `).toSucceed();\n\n    expectSnippet(`\n      ${snippet}\n      patchState(store, { count1: 1, _count2: 1 });\n    `).toFail(/'_count2' does not exist in type/);\n  });\n\n  describe('custom features', () => {\n    const baseSnippet = `\n      function withX() {\n        return signalStoreFeature(withState({ x: 1 }));\n      }\n\n      type Y = { a: string; b: number };\n      const initialY: Y = { a: '', b: 5 };\n\n      function withY<_>() {\n        return signalStoreFeature(\n          {\n            state: type<{ q1: string }>(),\n            props: type<{ sig: Signal<boolean> }>(),\n          },\n          withState({ y: initialY }),\n          withComputed(() => ({ sigY: computed(() => 'sigY') })),\n          withHooks({\n            onInit({ q1, y, sigY, ...store }) {\n              patchState(store, { q1: '', y: { a: 'a', b: 2 } });\n            },\n          })\n        );\n      }\n\n      function withZ<_>() {\n        return signalStoreFeature(\n          { methods: type<{ f: () => void }>() },\n          withMethods(({ f }) => ({\n            z() {\n              f();\n            }\n          }))\n        );\n      }\n    `;\n\n    it('combines custom features', () => {\n      expectSnippet(`\n        function withFoo() {\n          return withState({ foo: 'foo' });\n        }\n\n        function withBar() {\n          return signalStoreFeature(\n            { state: type<{ foo: string }>() },\n            withState({ bar: 'bar' })\n          );\n        }\n\n        function withBaz() {\n          return signalStoreFeature(\n            withFoo(),\n            withState({ count: 0 }),\n            withBar()\n          );\n        }\n\n        function withBaz2() {\n          return signalStoreFeature(\n            withState({ foo: 'foo' }),\n            withState({ count: 0 }),\n            withBar()\n          );\n        }\n\n        const BazStore = signalStore(\n          withFoo(),\n          withState({ count: 0 }),\n          withBar()\n        );\n\n        const Baz2Store = signalStore(\n          withState({ foo: 'foo' }),\n          withState({ count: 0 }),\n          withBar()\n        );\n      `).toSucceed();\n\n      expectSnippet(`\n        ${baseSnippet}\n\n        const Store = signalStore(\n          withMethods((store) => ({\n            f() {},\n            g() {},\n          })),\n          withComputed(() => ({ sig: computed(() => false) })),\n          withState({ q1: 'q1', q2: 'q2' }),\n          withX(),\n          withY(),\n          withZ()\n        );\n      `).toSucceed();\n\n      expectSnippet(`\n        ${baseSnippet}\n\n        const feature = signalStoreFeature(\n          { props: type<{ sig: Signal<boolean> }>() },\n          withX(),\n          withState({ q1: 'q1' }),\n          withY(),\n          withMethods((store) => ({\n            f() {\n              patchState(store, { x: 1, q1: 'xyz', y: { a: '', b: 0 } });\n            },\n          })),\n          withZ()\n        );\n      `).toSucceed();\n    });\n\n    it('fails when custom feature is used with wrong input', () => {\n      expectSnippet(\n        `${baseSnippet} const Store = signalStore(withY());`\n      ).toFail();\n\n      expectSnippet(\n        `${baseSnippet} const withY2 = () => signalStoreFeature(withY());`\n      ).toFail();\n\n      expectSnippet(`\n        ${baseSnippet}\n\n        const Store = signalStore(\n          withState({ q1: 1, q2: 'q2' }),\n          withComputed(() => ({ sig: computed(() => false) })),\n          withX(),\n          withY(),\n          withComputed(() => ({ q1: computed(() => 10) })),\n          withMethods((store) => ({\n            f() {\n              patchState(store, { x: 1, y: { a: '', b: 0 }, q2: 'q2new' });\n            },\n          }))\n        );\n      `).toFail();\n\n      expectSnippet(`\n        ${baseSnippet}\n\n        const feature = signalStoreFeature(\n          { props: type<{ sig: Signal<string> }>() },\n          withX(),\n          withState({ q1: 'q1' }),\n          withY(),\n          withMethods((store) => ({\n            f() {\n              patchState(store, { x: 1, q1: 'xyz', y: { a: '', b: 0 } });\n            },\n          }))\n        );\n      `).toFail();\n\n      expectSnippet(`\n        ${baseSnippet}\n\n        const Store = signalStore(\n          withState({ q1: 'q1', q2: 'q2' }),\n          withComputed(() => ({ sig: computed(() => false) })),\n          withX(),\n          withMethods((store) => ({\n            f() {\n              patchState(store, { q1: 'q1new', q2: 'q2new', x: 100 });\n            },\n            g: (str: string) => console.log(str),\n          })),\n          withY(),\n          withZ(),\n        );\n\n        const feature = signalStoreFeature(\n          {\n            props: type<{ sig: Signal<boolean> }>(),\n            methods: type<{ f(): void; g(arg: string): string; }>(),\n          },\n          withX(),\n          withZ(),\n          withState({ q1: 'q1' }),\n          withY(),\n        );\n      `).toSucceed();\n    });\n  });\n\n  describe('custom features with generics', () => {\n    const baseSnippet = `\n      function withSelectedEntity<Entity>() {\n        return signalStoreFeature(\n          type<{\n            state: {\n              entities: Entity[];\n            };\n          }>(),\n          withState({ selectedEntity: null as Entity | null }),\n          withComputed(({ selectedEntity, entities }) => ({\n            selectedEntity2: computed(() =>\n              selectedEntity()\n                ? entities().find((e) => e === selectedEntity())\n                : undefined\n            ),\n          }))\n        );\n      }\n\n      function withLoadEntities<Entity extends { id: string }>() {\n        return signalStoreFeature(\n          type<{\n            state: {\n              entities: Entity[];\n              selectedEntity: Entity | null;\n            };\n            props: {\n              selectedEntity2: Signal<Entity | undefined>;\n            };\n            methods: {\n              logEntity: (entity: Entity) => void;\n            };\n          }>(),\n          withMethods(({ entities, selectedEntity, selectedEntity2, logEntity }) => {\n            const e: Signal<Entity[]> = entities;\n            const se: Signal<Entity | null> = selectedEntity;\n            const se2: Signal<Entity | undefined> = selectedEntity2;\n            const le: (entity: Entity) => void = logEntity;\n\n            return {\n              loadEntities(): Promise<Entity[]> {\n                return Promise.resolve([]);\n              },\n            };\n          })\n        );\n      }\n\n      type User = {\n        id: string;\n        firstName: string;\n        lastName: string;\n      };\n    `;\n\n    it('combines custom features with generics', () => {\n      const snippet = `\n        ${baseSnippet}\n\n        const Store = signalStore(\n          withState({ entities: [] as User[] }),\n          withSelectedEntity(),\n          withMethods(() => ({\n            logEntity(user: User) {\n              console.log(user);\n            },\n          })),\n          withLoadEntities()\n        );\n\n        const store = new Store();\n        const selectedEntity = store.selectedEntity;\n        const selectedEntity2 = store.selectedEntity2;\n        const loadEntities = store.loadEntities;\n\n        const feature = signalStoreFeature(\n          {\n            state: type<{\n              entities: User[];\n            }>(),\n            methods: type<{\n              logEntity: (entity: User) => void;\n            }>(),\n          },\n          withSelectedEntity(),\n          withLoadEntities()\n        );\n      `;\n\n      const result = expectSnippet(snippet);\n      result.toInfer('selectedEntity', 'Signal<User | null>');\n      result.toInfer('selectedEntity2', 'Signal<User | undefined>');\n      result.toInfer('loadEntities', '() => Promise<User[]>');\n    });\n\n    it('fails when custom feature with generics is used with wrong input', () => {\n      expectSnippet(`\n        ${baseSnippet}\n\n        const Store = signalStore(\n          withState({ entities: [] as User[] }),\n          withSelectedEntity(),\n          withLoadEntities()\n        );\n      `).toFail();\n\n      expectSnippet(`\n        ${baseSnippet}\n\n        const feature = signalStoreFeature(\n          {\n            state: type<{\n              entities: User[];\n            }>(),\n            methods: type<{\n              logEntity: (entity: number) => void;\n            }>(),\n          },\n          withSelectedEntity(),\n          withLoadEntities()\n        );\n      `).toFail();\n\n      expectSnippet(`\n        ${baseSnippet}\n\n        const feature = signalStoreFeature(\n          {\n            state: type<{\n              entities: User[];\n            }>(),\n          },\n          withSelectedEntity(),\n          withLoadEntities()\n        );\n      `).toFail();\n\n      expectSnippet(`\n        ${baseSnippet}\n\n        const feature = signalStoreFeature(\n          {\n            state: type<{\n              entities: boolean;\n            }>(),\n            methods: type<{\n              logEntity: (entity: User) => void;\n            }>(),\n          },\n          withSelectedEntity(),\n          withLoadEntities()\n        );\n      `).toFail();\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/signals/spec/types/with-computed.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './helpers';\n\ndescribe('withComputed', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import {\n          deepComputed,\n          patchState,\n          signalStore,\n          withComputed,\n          withMethods,\n          withProps,\n          withState,\n        } from '@ngrx/signals';\n        import { TestBed } from '@angular/core/testing';\n        import { signal } from '@angular/core';\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('has access to props, state signals and methods', () => {\n    const snippet = `\n      signalStore(\n        withState({\n          a: 1,\n        }),\n        withProps(() => {\n          return {\n            b: 2,\n          };\n        }),\n        withMethods(({ a, b }) => ({\n          sum: () => a() + b,\n        })),\n        withComputed(({ a, b, sum }) => ({\n          prettySum: () => \\`Sum: \\${a()} + \\${b} = \\${sum()}\\`,\n        }))\n      );\n    `;\n\n    expectSnippet(snippet).toSucceed();\n  });\n\n  it('has no access to the state source', () => {\n    const snippet = `\n      signalStore(\n        withState({\n          a: 1,\n        }),\n        withComputed((store) => ({\n          prettySum: () => {\n            patchState(store, { a: 2 });\n            return store.a();\n          },\n        }))\n      );\n    `;\n\n    expectSnippet(snippet).toFail(\n      /not assignable to parameter of type 'WritableStateSource<object>'/\n    );\n  });\n\n  it('creates a Signal automatically', () => {\n    const snippet = `\n      const Store = signalStore(\n        withComputed(() => ({\n          user: () => ({ firstName: 'John', lastName: 'Doe' })\n        }))\n      );\n\n      const store = TestBed.inject(Store);\n      const user = store.user;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('user', 'Signal<{ firstName: string; lastName: string; }>');\n  });\n\n  it('keeps a WritableSignal intact, if passed', () => {\n    const snippet = `\n      const user = signal({ firstName: 'John', lastName: 'Doe' });\n\n      const Store = signalStore(\n        withComputed(() => ({\n          user,\n        }))\n      );\n\n      const store = TestBed.inject(Store);\n      const userSignal = store.user;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'userSignal',\n      'WritableSignal<{ firstName: string; lastName: string; }>'\n    );\n  });\n\n  it('keeps a DeepSignal intact, if passed', () => {\n    const snippet = `\n    const user = deepComputed(\n      signal({\n        name: 'John Doe',\n        address: {\n          street: '123 Main St',\n          city: 'Anytown',\n        },\n      })\n    );\n\n    const Store = signalStore(\n      withComputed(() => ({\n        user,\n      }))\n    );\n\n    const store = TestBed.inject(Store);\n    const userSignal = store.user;\n  `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'userSignal',\n      'DeepSignal<{ name: string; address: { street: string; city: string; }; }>'\n    );\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/signals/spec/types/with-linked-state.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './helpers';\n\ndescribe('withLinkedState', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import { computed, inject, linkedSignal, Signal, signal } from '@angular/core';\n        import {\n          patchState,\n          signalStore,\n          withState,\n          withLinkedState,\n          withMethods\n        } from '@ngrx/signals';\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('does not have access to methods', () => {\n    const snippet = `\n      signalStore(\n        withMethods(() => ({\n          foo: () => 'bar',\n        })),\n        withLinkedState(({ foo }) => ({ value: foo() }))\n      );\n      `;\n\n    expectSnippet(snippet).toFail(/Property 'foo' does not exist on type '{}'/);\n  });\n\n  it('does not have access to STATE_SOURCE', () => {\n    const snippet = `\n      signalStore(\n        withState({ foo: 'bar' }),\n        withLinkedState((store) => {\n          patchState(store, { foo: 'baz' });\n          return { bar: 'foo' };\n        })\n      )\n      `;\n\n    expectSnippet(snippet).toFail(\n      /is not assignable to parameter of type 'WritableStateSource<object>'./\n    );\n  });\n\n  it('cannot return a primitive value', () => {\n    const snippet = `\n      signalStore(\n        withLinkedState(() => ({ foo: 'bar' }))\n      )\n      `;\n\n    expectSnippet(snippet).toFail(\n      /Type 'string' is not assignable to type 'WritableSignal<unknown> | (() => unknown)'./\n    );\n  });\n\n  it('adds state slice with computation function', () => {\n    const snippet = `\n      const UserStore = signalStore(\n        { providedIn: 'root' },\n        withLinkedState(() => ({ firstname: () => 'John', lastname: () => 'Doe' }))\n      );\n\n      const userStore = new UserStore();\n\n      const firstname = userStore.firstname;\n      const lastname = userStore.lastname;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('firstname', 'Signal<string>');\n    result.toInfer('lastname', 'Signal<string>');\n  });\n\n  it('adds state slice with explicit linkedSignal', () => {\n    const snippet = `\n      const UserStore = signalStore(\n        { providedIn: 'root' },\n        withLinkedState(() => ({\n          firstname: linkedSignal(() => 'John'),\n          lastname: linkedSignal(() => 'Doe')\n        }))\n      );\n\n      const userStore = new UserStore();\n\n      const firstname = userStore.firstname;\n      const lastname = userStore.lastname;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('firstname', 'Signal<string>');\n    result.toInfer('lastname', 'Signal<string>');\n  });\n\n  it('creates deep signals with computation functions', () => {\n    const snippet = `\n      const UserStore = signalStore(\n        { providedIn: 'root' },\n        withLinkedState(() => ({\n          user: () => ({ id: 1, name: 'John Doe' }),\n          location: () => ({ city: 'Berlin', country: 'Germany' }),\n        }))\n      );\n\n      const userStore = new UserStore();\n\n      const location = userStore.location;\n      const user = userStore.user;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'location',\n      'DeepSignal<{ city: string; country: string; }>'\n    );\n    result.toInfer('user', 'DeepSignal<{ id: number; name: string; }>');\n  });\n\n  it('creates deep signals with explicit linked signals', () => {\n    const snippet = `\n      const UserStore = signalStore(\n        { providedIn: 'root' },\n        withLinkedState(() => ({\n          user: linkedSignal(() => ({ id: 1, name: 'John Doe' })),\n          location: linkedSignal(() => ({ city: 'Berlin', country: 'Germany' })),\n        }))\n      );\n\n      const userStore = new UserStore();\n\n      const location = userStore.location;\n      const user = userStore.user;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer(\n      'location',\n      'DeepSignal<{ city: string; country: string; }>'\n    );\n    result.toInfer('user', 'DeepSignal<{ id: number; name: string; }>');\n  });\n\n  it('infers the types for a mixed setting', () => {\n    const snippet = `\n      const Store = signalStore(\n        withState({ foo: 'bar' }),\n        withLinkedState(({ foo }) => ({\n          bar: () => foo(),\n          baz: linkedSignal(() => foo()),\n          qux: signal({ x: 1 }),\n        }))\n      );\n\n      const store = new Store();\n\n      const bar = store.bar;\n      const baz = store.baz;\n      const qux = store.qux;\n    `;\n\n    const result = expectSnippet(snippet);\n    result.toInfer('bar', 'Signal<string>');\n    result.toInfer('baz', 'Signal<string>');\n    result.toInfer('qux', 'DeepSignal<{ x: number; }>');\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/signals/spec/with-computed.spec.ts",
    "content": "import { computed, signal } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  deepComputed,\n  signalStoreFeature,\n  withComputed,\n  withMethods,\n  withState,\n} from '../src';\nimport { getInitialInnerStore, signalStore } from '../src/signal-store';\n\ndescribe('withComputed', () => {\n  it('adds computed signals to the store immutably', () => {\n    const initialStore = getInitialInnerStore();\n\n    const s1 = signal('s1').asReadonly();\n    const s2 = signal(10).asReadonly();\n\n    const store = withComputed(() => ({ s1, s2 }))(initialStore);\n\n    expect(Object.keys(store.props)).toEqual(['s1', 's2']);\n    expect(Object.keys(initialStore.props)).toEqual([]);\n\n    expect(store.props.s1).toBe(s1);\n    expect(store.props.s2).toBe(s2);\n  });\n\n  it('logs warning if previously defined signal store members have the same name', () => {\n    const COMPUTED_SECRET = Symbol('computed_secret');\n    const initialStore = [\n      withState({\n        p1: 10,\n        p2: 'p2',\n      }),\n      withComputed(() => ({\n        s1: signal('s1').asReadonly(),\n        s2: signal({ s: 2 }).asReadonly(),\n        [COMPUTED_SECRET]: signal(1).asReadonly(),\n      })),\n      withMethods(() => ({\n        m1() {},\n        m2() {},\n      })),\n    ].reduce((acc, feature) => feature(acc), getInitialInnerStore());\n    const s2 = signal(10).asReadonly();\n    vi.spyOn(console, 'warn').mockImplementation(() => {});\n\n    withComputed(() => ({\n      p: signal(0).asReadonly(),\n      p1: signal('p1').asReadonly(),\n      s2,\n      m1: signal({ m: 1 }).asReadonly(),\n      m3: signal({ m: 3 }).asReadonly(),\n      s3: signal({ s: 3 }).asReadonly(),\n      [COMPUTED_SECRET]: signal(10).asReadonly(),\n    }))(initialStore);\n\n    expect(console.warn).toHaveBeenCalledWith(\n      '@ngrx/signals: SignalStore members cannot be overridden.',\n      'Trying to override:',\n      'p1, s2, m1, Symbol(computed_secret)'\n    );\n  });\n\n  it('adds computed automatically if the value is a function', () => {\n    const initialStore = getInitialInnerStore();\n\n    const store = signalStoreFeature(\n      withState({ a: 2, b: 3 }),\n      withComputed(({ a, b }) => ({\n        sum: () => a() + b(),\n        product: () => a() * b(),\n      }))\n    )(initialStore);\n\n    expect(store.props.sum()).toBe(5);\n    expect(store.props.product()).toBe(6);\n  });\n\n  it('allows to mix user-provided computeds and automatically computed ones', () => {\n    const initialStore = getInitialInnerStore();\n\n    const store = signalStoreFeature(\n      withState({ a: 2, b: 3 }),\n      withComputed(({ a, b }) => ({\n        sum: () => a() + b(),\n        product: computed(() => a() * b()),\n      }))\n    )(initialStore);\n\n    expect(store.props.sum()).toBe(5);\n    expect(store.props.product()).toBe(6);\n  });\n\n  it('does not change a WritableSignal', () => {\n    const user = signal({ firstName: 'John', lastName: 'Doe' });\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withComputed(() => ({\n        user,\n      }))\n    );\n\n    const store = TestBed.inject(Store);\n\n    expect(store.user).toBe(user);\n  });\n\n  it('does not change a DeepSignal', () => {\n    const user = deepComputed(\n      signal({\n        name: 'John Doe',\n        address: {\n          street: '123 Main St',\n          city: 'Anytown',\n        },\n      })\n    );\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withComputed(() => ({\n        user,\n      }))\n    );\n\n    const store = TestBed.inject(Store);\n\n    expect(store.user).toBe(user);\n  });\n\n  it('does not change a Signal', () => {\n    const user = computed(() => ({ firstName: 'John', lastName: 'Doe' }));\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withComputed(() => ({\n        user,\n      }))\n    );\n\n    const store = TestBed.inject(Store);\n\n    expect(store.user).toBe(user);\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/with-feature.spec.ts",
    "content": "import {\n  computed,\n  inject,\n  Injectable,\n  ResourceStatus,\n  Signal,\n} from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { tapResponse } from '@ngrx/operators';\nimport { lastValueFrom, Observable, of, pipe, switchMap, tap } from 'rxjs';\nimport { describe, expect, it } from 'vitest';\nimport { EntityState, setAllEntities, withEntities } from '../entities';\nimport { rxMethod } from '../rxjs-interop';\nimport {\n  getState,\n  patchState,\n  signalStore,\n  signalStoreFeature,\n  type,\n  withComputed,\n  withFeature,\n  withHooks,\n  withMethods,\n  withProps,\n  withState,\n} from '../src';\n\ntype User = {\n  id: number;\n  name: string;\n};\n\ndescribe('withFeature', () => {\n  it('provides methods', async () => {\n    function withMyEntity<Entity>(loadMethod: (id: number) => Promise<Entity>) {\n      return signalStoreFeature(\n        withState({\n          currentId: 1 as number | undefined,\n          entity: undefined as undefined | Entity,\n        }),\n        withMethods((store) => ({\n          async load(id: number) {\n            const entity = await loadMethod(1);\n            patchState(store, { entity, currentId: id });\n          },\n        }))\n      );\n    }\n\n    const UserStore = signalStore(\n      { providedIn: 'root' },\n      withMethods(() => ({\n        findById(id: number) {\n          return of({ id: 1, name: 'Konrad' });\n        },\n      })),\n      withFeature((store) => {\n        const loader = (id: number) => lastValueFrom(store.findById(id));\n        return withMyEntity<User>(loader);\n      })\n    );\n\n    const userStore = TestBed.inject(UserStore);\n    await userStore.load(1);\n    expect(getState(userStore)).toEqual({\n      currentId: 1,\n      entity: { id: 1, name: 'Konrad' },\n    });\n  });\n\n  it('provides state signals', async () => {\n    const withDouble = (n: Signal<number>) =>\n      signalStoreFeature(\n        withComputed((state) => ({ double: computed(() => n() * 2) }))\n      );\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withState({ counter: 1 }),\n      withMethods((store) => ({\n        increaseCounter() {\n          patchState(store, ({ counter }) => ({ counter: counter + 1 }));\n        },\n      })),\n      withFeature(({ counter }) => withDouble(counter))\n    );\n\n    const store = TestBed.inject(Store);\n\n    expect(store.double()).toBe(2);\n    store.increaseCounter();\n    expect(store.double()).toBe(4);\n  });\n\n  it('provides properties', () => {\n    @Injectable({ providedIn: 'root' })\n    class Config {\n      baseUrl = 'https://www.ngrx.io';\n    }\n    const withUrlizer = (baseUrl: string) =>\n      signalStoreFeature(\n        withMethods(() => ({\n          createUrl: (path: string) =>\n            `${baseUrl}${path.startsWith('/') ? '' : '/'}${path}`,\n        }))\n      );\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withProps(() => ({\n        _config: inject(Config),\n      })),\n      withFeature((store) => withUrlizer(store._config.baseUrl))\n    );\n\n    const store = TestBed.inject(Store);\n    expect(store.createUrl('docs')).toBe('https://www.ngrx.io/docs');\n  });\n\n  it('provides writable state source', () => {\n    const withCallback = (cb: () => void) =>\n      signalStoreFeature(\n        withMethods(() => ({\n          executeCallBack: () => cb(),\n        }))\n      );\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withState({ counter: 1 }),\n      withFeature((store) =>\n        withCallback(() => patchState(store, { counter: 2 }))\n      )\n    );\n\n    const store = TestBed.inject(Store);\n    expect(getState(store)).toEqual({ counter: 1 });\n    store.executeCallBack();\n    expect(getState(store)).toEqual({ counter: 2 });\n  });\n\n  it('can be combined with inputs', () => {\n    function withLoadEntities<Entity extends { id: number }, Filter>(config: {\n      filter: Signal<Filter>;\n      loader: (filter: Filter) => Observable<Entity[]>;\n    }) {\n      return signalStoreFeature(\n        type<{ state: EntityState<Entity> & { status: ResourceStatus } }>(),\n        withMethods((store) => ({\n          _loadEntities: rxMethod<Filter>(\n            pipe(\n              tap(() => patchState(store, { status: 'loading' })),\n              switchMap((filter) =>\n                config.loader(filter).pipe(\n                  tapResponse({\n                    next: (entities) =>\n                      patchState(\n                        store,\n                        { status: 'resolved' },\n                        setAllEntities(entities)\n                      ),\n                    error: () => patchState(store, { status: 'error' }),\n                  })\n                )\n              )\n            )\n          ),\n        })),\n        withHooks({\n          onInit: ({ _loadEntities }) => _loadEntities(config.filter),\n        })\n      );\n    }\n\n    const Store = signalStore(\n      { providedIn: 'root' },\n      withEntities<User>(),\n      withState({ filter: { name: '' }, status: 'idle' as ResourceStatus }),\n      withMethods((store) => ({\n        setFilter(name: string) {\n          patchState(store, { filter: { name } });\n        },\n        _load(filters: { name: string }) {\n          return of(\n            [{ id: 1, name: 'Konrad' }].filter((person) =>\n              person.name.startsWith(filters.name)\n            )\n          );\n        },\n      })),\n      withFeature((store) =>\n        withLoadEntities({ filter: store.filter, loader: store._load })\n      )\n    );\n\n    const store = TestBed.inject(Store);\n\n    expect(store.entities()).toEqual([]);\n    store.setFilter('K');\n    TestBed.tick();\n    expect(store.entities()).toEqual([{ id: 1, name: 'Konrad' }]);\n    store.setFilter('Sabine');\n    TestBed.tick();\n    expect(store.entities()).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/with-hooks.spec.ts",
    "content": "import { withHooks } from '../src';\nimport { getInitialInnerStore } from '../src/signal-store';\n\ndescribe('withHooks', () => {\n  it('adds onInit hook to the store', () => {\n    const initialStore = getInitialInnerStore();\n    let message = '';\n\n    const store = withHooks({\n      onInit() {\n        message = 'onInit';\n      },\n    })(initialStore);\n    store.hooks.onInit?.();\n\n    expect(message).toBe('onInit');\n  });\n\n  it('executes new onInit hook after previously defined one', () => {\n    const messages: string[] = [];\n    const initialStore = withHooks({\n      onInit() {\n        messages.push('onInit1');\n      },\n    })(getInitialInnerStore());\n\n    const store = withHooks({\n      onInit() {\n        messages.push('onInit2');\n      },\n    })(initialStore);\n    store.hooks.onInit?.();\n\n    expect(messages).toEqual(['onInit1', 'onInit2']);\n  });\n\n  it('adds onDestroy hook to the store', () => {\n    const initialStore = getInitialInnerStore();\n    let message = '';\n\n    const store = withHooks({\n      onDestroy() {\n        message = 'onDestroy';\n      },\n    })(initialStore);\n    store.hooks.onDestroy?.();\n\n    expect(message).toBe('onDestroy');\n  });\n\n  it('executes new onDestroy hook after previously defined one', () => {\n    const messages: string[] = [];\n    const initialStore = withHooks({\n      onDestroy() {\n        messages.push('onDestroy1');\n      },\n    })(getInitialInnerStore());\n\n    const store = withHooks({\n      onDestroy() {\n        messages.push('onDestroy2');\n      },\n    })(initialStore);\n    store.hooks.onDestroy?.();\n\n    expect(messages).toEqual(['onDestroy1', 'onDestroy2']);\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/with-linked-state.spec.ts",
    "content": "import { linkedSignal, signal } from '@angular/core';\nimport {\n  getState,\n  patchState,\n  signalStoreFeature,\n  withLinkedState,\n  withState,\n} from '../src';\nimport { getInitialInnerStore } from '../src/signal-store';\nimport { isWritableSignal, STATE_SOURCE } from '../src/state-source';\n\ndescribe('withLinkedState', () => {\n  describe('adds linked state slices to the STATE_SOURCE', () => {\n    [\n      {\n        name: 'with computation function',\n        linkedStateFeature: () =>\n          withLinkedState(() => ({\n            user: () => ({ id: 1, name: 'John Doe' }),\n            location: () => ({ city: 'Berlin', country: 'Germany' }),\n          })),\n      },\n      {\n        name: 'with explicit linkedSignal',\n        linkedStateFeature: () =>\n          withLinkedState(() => ({\n            user: linkedSignal(() => ({ id: 1, name: 'John Doe' })),\n            location: linkedSignal(() => ({\n              city: 'Berlin',\n              country: 'Germany',\n            })),\n          })),\n      },\n      {\n        name: 'with user-defined WritableSignal',\n        linkedStateFeature: () =>\n          withLinkedState(() => ({\n            user: signal(() => ({ id: 1, name: 'John Doe' })),\n            location: signal(() => ({\n              city: 'Berlin',\n              country: 'Germany',\n            })),\n          })),\n      },\n    ].forEach(({ name, linkedStateFeature }) => {\n      it(name, () => {\n        const initialStore = getInitialInnerStore();\n        const userStore = linkedStateFeature()(initialStore);\n\n        const stateSource = userStore[STATE_SOURCE];\n\n        expect(isWritableSignal(stateSource.location)).toBe(true);\n        expect(isWritableSignal(stateSource.user)).toBe(true);\n      });\n    });\n  });\n\n  describe('spreads properties of the linked state slice to the state', () =>\n    [\n      {\n        name: 'with computation function',\n        linkedStateFeature: () =>\n          withLinkedState(() => ({\n            user: () => ({ id: 1, name: 'John Doe' }),\n            location: () => ({ city: 'Berlin', country: 'Germany' }),\n          })),\n      },\n      {\n        name: 'with explicit linkedSignal',\n        linkedStateFeature: () =>\n          withLinkedState(() => ({\n            user: linkedSignal(() => ({ id: 1, name: 'John Doe' })),\n            location: linkedSignal(() => ({\n              city: 'Berlin',\n              country: 'Germany',\n            })),\n          })),\n      },\n      {\n        name: 'with user-defined WritableSignal',\n        linkedStateFeature: () =>\n          withLinkedState(() => ({\n            user: signal({ id: 1, name: 'John Doe' }),\n            location: signal({\n              city: 'Berlin',\n              country: 'Germany',\n            }),\n          })),\n      },\n    ].forEach(({ name, linkedStateFeature }) => {\n      it(name, () => {\n        const initialStore = getInitialInnerStore();\n        const userStore = linkedStateFeature()(initialStore);\n\n        const state = getState(userStore);\n        expect(state).toEqual({\n          user: { id: 1, name: 'John Doe' },\n          location: { city: 'Berlin', country: 'Germany' },\n        });\n      });\n    }));\n\n  describe('can depend on another withLinkedState', () => {\n    [\n      {\n        name: 'with computation function',\n        linkedStateFeature: () =>\n          signalStoreFeature(\n            withState({ id: 1 }),\n            withLinkedState(({ id }) => ({\n              level1: () => id() * 2,\n            })),\n            withLinkedState(({ level1 }) => ({\n              level2: () => level1() * 10,\n            }))\n          ),\n      },\n      {\n        name: 'with explicit linkedSignal',\n        linkedStateFeature: () =>\n          signalStoreFeature(\n            withState({ id: 1 }),\n            withLinkedState(({ id }) => ({\n              level1: linkedSignal({\n                source: id,\n                computation: () => id() * 2,\n              }),\n            })),\n            withLinkedState(({ level1 }) => ({\n              level2: linkedSignal({\n                source: level1,\n                computation: () => level1() * 10,\n              }),\n            }))\n          ),\n      },\n      {\n        name: 'with user-defined WritableSignal',\n        linkedStateFeature: () =>\n          signalStoreFeature(\n            withLinkedState(() => ({ id: signal(1) })),\n            withLinkedState(({ id }) => ({\n              level1: linkedSignal({\n                source: id,\n                computation: () => id() * 2,\n              }),\n            })),\n            withLinkedState(({ level1 }) => ({\n              level2: linkedSignal({\n                source: level1,\n                computation: () => level1() * 10,\n              }),\n            }))\n          ),\n      },\n    ].forEach(({ name, linkedStateFeature }) => {\n      it(name, () => {\n        const initialStore = getInitialInnerStore();\n        const numberStore = linkedStateFeature()(initialStore);\n\n        expect(getState(numberStore)).toEqual({ id: 1, level1: 2, level2: 20 });\n\n        patchState(numberStore, { id: 2 });\n        expect(getState(numberStore)).toEqual({ id: 2, level1: 4, level2: 40 });\n\n        patchState(numberStore, { level1: 5 });\n        expect(getState(numberStore)).toEqual({ id: 2, level1: 5, level2: 50 });\n\n        patchState(numberStore, { level2: 100 });\n        expect(getState(numberStore)).toEqual({\n          id: 2,\n          level1: 5,\n          level2: 100,\n        });\n\n        patchState(numberStore, { id: 3 });\n        expect(getState(numberStore)).toEqual({ id: 3, level1: 6, level2: 60 });\n      });\n    });\n  });\n\n  describe('keeps DeepSignal updated', () => {\n    [\n      {\n        name: 'with computation function',\n        linkedStateFeature: () =>\n          signalStoreFeature(\n            withState({ userId: 1 }),\n            withLinkedState(({ userId }) => ({\n              user: () => ({ id: userId(), name: 'John Doe' }),\n            }))\n          ),\n      },\n      {\n        name: 'with explicit linkedSignal',\n        linkedStateFeature: () =>\n          signalStoreFeature(\n            withState({ userId: 1 }),\n            withLinkedState(({ userId }) => ({\n              user: linkedSignal({\n                source: userId,\n                computation: (id) => ({ id, name: 'John Doe' }),\n              }),\n            }))\n          ),\n      },\n    ].forEach(({ name, linkedStateFeature }) => {\n      it(name, () => {\n        const initialStore = getInitialInnerStore();\n        const userStore = linkedStateFeature()(initialStore);\n\n        const name = userStore.stateSignals.user.name;\n        expect(name()).toBe('John Doe');\n\n        patchState(userStore, { user: { id: 2, name: 'Tom Smith' } });\n        expect(name()).toBe('Tom Smith');\n\n        patchState(userStore, { userId: 2 });\n        expect(name()).toBe('John Doe');\n      });\n    });\n\n    it('with user-defined WritableSignal', () => {\n      const initialStore = getInitialInnerStore();\n      const user = signal({ name: 'John' });\n      const userStore = withLinkedState(() => ({ user }))(initialStore);\n\n      const name = userStore.stateSignals.user.name;\n      expect(name()).toBe('John');\n\n      patchState(userStore, { user: { name: 'Tom' } });\n      expect(name()).toBe('Tom');\n\n      user.set({ name: 'Mark' });\n      expect(name()).toBe('Mark');\n    });\n  });\n\n  it('logs a warning if previously defined signal store members have the same name', () => {\n    vi.spyOn(console, 'warn').mockImplementation(() => {});\n\n    const linkedStateFeature = signalStoreFeature(\n      withState({ value: 1 }),\n      withLinkedState(() => ({\n        value: () => 1,\n      }))\n    );\n\n    linkedStateFeature(getInitialInnerStore());\n\n    expect(console.warn).toHaveBeenCalledWith(\n      '@ngrx/signals: SignalStore members cannot be overridden.',\n      'Trying to override:',\n      'value'\n    );\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/with-methods.spec.ts",
    "content": "import { signal } from '@angular/core';\nimport { withComputed, withMethods, withState } from '../src';\nimport { getInitialInnerStore } from '../src/signal-store';\n\ndescribe('withMethods', () => {\n  it('adds methods to the store immutably', () => {\n    const initialStore = getInitialInnerStore();\n\n    const m1 = () => 100;\n    const m2 = () => 'm2';\n\n    const store = withMethods(() => ({ m1, m2 }))(initialStore);\n\n    expect(Object.keys(store.methods)).toEqual(['m1', 'm2']);\n    expect(Object.keys(initialStore.methods)).toEqual([]);\n\n    expect(store.methods.m1).toBe(m1);\n    expect(store.methods.m2).toBe(m2);\n  });\n\n  it('logs warning if previously defined signal store members have the same name', () => {\n    const STATE_SECRET = Symbol('state_secret');\n    const COMPUTED_SECRET = Symbol('computed_secret');\n    const initialStore = [\n      withState({\n        p1: 'p1',\n        p2: false,\n        [STATE_SECRET]: 1,\n      }),\n      withComputed(() => ({\n        s1: signal(true).asReadonly(),\n        s2: signal({ s: 2 }).asReadonly(),\n        [COMPUTED_SECRET]: signal(1).asReadonly(),\n      })),\n      withMethods(() => ({\n        m1() {},\n        m2() {},\n      })),\n    ].reduce((acc, feature) => feature(acc), getInitialInnerStore());\n    const m2 = () => 10;\n    vi.spyOn(console, 'warn').mockImplementation(() => {});\n\n    withMethods(() => ({\n      p() {},\n      p2() {},\n      s: () => {},\n      s1: () => 100,\n      m2,\n      m3: () => 'm3',\n      [STATE_SECRET]() {},\n      [COMPUTED_SECRET]() {},\n    }))(initialStore);\n\n    expect(console.warn).toHaveBeenCalledWith(\n      '@ngrx/signals: SignalStore members cannot be overridden.',\n      'Trying to override:',\n      'p2, s1, m2, Symbol(state_secret), Symbol(computed_secret)'\n    );\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/with-props.spec.ts",
    "content": "import { signal } from '@angular/core';\nimport { of } from 'rxjs';\nimport { withMethods, withProps, withState } from '../src';\nimport { getInitialInnerStore } from '../src/signal-store';\n\ndescribe('withProps', () => {\n  it('adds properties to the store immutably', () => {\n    const initialStore = getInitialInnerStore();\n\n    const store = withProps(() => ({ p1: 1, p2: 2 }))(initialStore);\n\n    expect(Object.keys(store.props)).toEqual(['p1', 'p2']);\n    expect(Object.keys(initialStore.props)).toEqual([]);\n\n    expect(store.props.p1).toBe(1);\n    expect(store.props.p2).toBe(2);\n  });\n\n  it('logs warning if previously defined signal store members have the same name', () => {\n    const STATE_SECRET = Symbol('state_secret');\n    const METHOD_SECRET = Symbol('method_secret');\n    const initialStore = [\n      withState({\n        s1: 10,\n        s2: 's2',\n        [STATE_SECRET]: 1,\n      }),\n      withProps(() => ({\n        p1: of(100),\n        p2: 10,\n      })),\n      withMethods(() => ({\n        m1() {},\n        m2() {},\n        [METHOD_SECRET]() {},\n      })),\n    ].reduce((acc, feature) => feature(acc), getInitialInnerStore());\n    vi.spyOn(console, 'warn').mockImplementation(() => {});\n\n    withProps(() => ({\n      s1: { foo: 'bar' },\n      p: 10,\n      p2: signal(100),\n      m1: { ngrx: 'rocks' },\n      m3: of('m3'),\n      [STATE_SECRET]: 10,\n      [METHOD_SECRET]: { x: 'y' },\n    }))(initialStore);\n\n    expect(console.warn).toHaveBeenCalledWith(\n      '@ngrx/signals: SignalStore members cannot be overridden.',\n      'Trying to override:',\n      's1, p2, m1, Symbol(state_secret), Symbol(method_secret)'\n    );\n  });\n});\n"
  },
  {
    "path": "modules/signals/spec/with-state.spec.ts",
    "content": "import { isSignal, signal } from '@angular/core';\nimport { getState, withComputed, withMethods, withState } from '../src';\nimport { getInitialInnerStore } from '../src/signal-store';\n\ndescribe('withState', () => {\n  it('patches state source and updates slices immutably', () => {\n    const initialStore = getInitialInnerStore();\n    const initialState = getState(initialStore);\n\n    const store = withState({\n      foo: 'bar',\n      x: { y: 'z' },\n    })(initialStore);\n    const state = getState(store);\n\n    expect(state).toEqual({ foo: 'bar', x: { y: 'z' } });\n    expect(initialState).toEqual({});\n\n    expect(Object.keys(store.stateSignals)).toEqual(['foo', 'x']);\n    expect(Object.keys(initialStore.stateSignals)).toEqual([]);\n  });\n\n  it('creates deep signals for each state slice', () => {\n    const initialStore = getInitialInnerStore();\n\n    const store = withState({\n      foo: 'bar',\n      x: { y: 'z' },\n    })(initialStore);\n\n    expect(store.stateSignals.foo()).toBe('bar');\n    expect(isSignal(store.stateSignals.foo)).toBe(true);\n\n    expect(store.stateSignals.x()).toEqual({ y: 'z' });\n    expect(isSignal(store.stateSignals.x)).toBe(true);\n\n    expect(store.stateSignals.x.y()).toBe('z');\n    expect(isSignal(store.stateSignals.x.y)).toBe(true);\n  });\n\n  it('patches state source and creates deep signals for state slices provided via factory', () => {\n    const initialStore = getInitialInnerStore();\n\n    const store = withState(() => ({\n      foo: 'bar',\n      x: { y: 'z' },\n    }))(initialStore);\n    const state = getState(store);\n\n    expect(state).toEqual({ foo: 'bar', x: { y: 'z' } });\n    expect(store.stateSignals.foo()).toBe('bar');\n    expect(store.stateSignals.x()).toEqual({ y: 'z' });\n    expect(store.stateSignals.x.y()).toBe('z');\n  });\n\n  it('logs warning if previously defined signal store members have the same name', () => {\n    const COMPUTED_SECRET = Symbol('computed_secret');\n    const METHOD_SECRET = Symbol('method_secret');\n    const initialStore = [\n      withState({\n        p1: 10,\n        p2: 'p2',\n      }),\n      withComputed(() => ({\n        s1: signal('s1').asReadonly(),\n        s2: signal({ s: 2 }).asReadonly(),\n        [COMPUTED_SECRET]: signal(1).asReadonly(),\n      })),\n      withMethods(() => ({\n        m1() {},\n        m2() {},\n        [METHOD_SECRET]() {},\n      })),\n    ].reduce((acc, feature) => feature(acc), getInitialInnerStore());\n    vi.spyOn(console, 'warn').mockImplementation(() => {});\n\n    withState(() => ({\n      p2: 100,\n      s: 's',\n      s2: 's2',\n      m: { s: 10 },\n      m2: { m: 2 },\n      p3: 'p3',\n      [COMPUTED_SECRET]: 10,\n      [METHOD_SECRET]: 100,\n    }))(initialStore);\n\n    expect(console.warn).toHaveBeenCalledWith(\n      '@ngrx/signals: SignalStore members cannot be overridden.',\n      'Trying to override:',\n      'p2, s2, m2, Symbol(computed_secret), Symbol(method_secret)'\n    );\n  });\n});\n"
  },
  {
    "path": "modules/signals/src/deep-computed.ts",
    "content": "import { computed } from '@angular/core';\nimport { DeepSignal, toDeepSignal } from './deep-signal';\n\n/**\n * @description\n *\n * Creates a computed signal with deeply nested signals for each property when\n * the result is an object literal.\n *\n * @usageNotes\n *\n * ```ts\n * import { signal } from '@angular/core';\n * import { deepComputed } from '@ngrx/signals';\n *\n * const limit = signal(10);\n * const offset = signal(0);\n *\n * const pagination = deepComputed(() => ({\n *   currentPage: Math.floor(offset() / limit()) + 1,\n *   pageSize: limit(),\n * }));\n *\n * console.log(pagination()); // { currentPage: 1, pageSize: 10 }\n * console.log(pagination.currentPage()); // 1\n * console.log(pagination.pageSize()); // 10\n * ```\n */\nexport function deepComputed<T extends object>(\n  computation: () => T\n): DeepSignal<T> {\n  return toDeepSignal(computed(computation));\n}\n"
  },
  {
    "path": "modules/signals/src/deep-signal.ts",
    "content": "import { computed, isSignal, Signal, untracked } from '@angular/core';\nimport { IsKnownRecord } from './ts-helpers';\n\nconst DEEP_SIGNAL = Symbol(\n  typeof ngDevMode !== 'undefined' && ngDevMode ? 'DEEP_SIGNAL' : ''\n);\n\nexport type DeepSignal<T> = Signal<T> &\n  (IsKnownRecord<T> extends true\n    ? Readonly<{\n        [K in keyof T]: IsKnownRecord<T[K]> extends true\n          ? DeepSignal<T[K]>\n          : Signal<T[K]>;\n      }>\n    : unknown);\n\nexport function toDeepSignal<T>(signal: Signal<T>): DeepSignal<T> {\n  return new Proxy(signal, {\n    has(target: any, prop) {\n      return !!this.get!(target, prop, undefined);\n    },\n    get(target: any, prop) {\n      const value = untracked(target);\n      if (!isRecord(value) || !(prop in value)) {\n        if (isSignal(target[prop]) && (target[prop] as any)[DEEP_SIGNAL]) {\n          delete target[prop];\n        }\n\n        return target[prop];\n      }\n\n      if (!isSignal(target[prop])) {\n        Object.defineProperty(target, prop, {\n          value: computed(() => target()[prop]),\n          configurable: true,\n        });\n        target[prop][DEEP_SIGNAL] = true;\n      }\n\n      return toDeepSignal(target[prop]);\n    },\n  });\n}\n\nconst nonRecords = [\n  WeakSet,\n  WeakMap,\n  Promise,\n  Date,\n  Error,\n  RegExp,\n  ArrayBuffer,\n  DataView,\n  Function,\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  if (value === null || typeof value !== 'object' || isIterable(value)) {\n    return false;\n  }\n\n  let proto = Object.getPrototypeOf(value);\n  if (proto === Object.prototype) {\n    return true;\n  }\n\n  while (proto && proto !== Object.prototype) {\n    if (nonRecords.includes(proto.constructor)) {\n      return false;\n    }\n    proto = Object.getPrototypeOf(proto);\n  }\n\n  return proto === Object.prototype;\n}\n\nfunction isIterable(value: any): value is Iterable<any> {\n  return typeof value?.[Symbol.iterator] === 'function';\n}\n"
  },
  {
    "path": "modules/signals/src/index.ts",
    "content": "export { deepComputed } from './deep-computed';\nexport { DeepSignal } from './deep-signal';\nexport { signalMethod, SignalMethod } from './signal-method';\nexport { signalState, SignalState } from './signal-state';\nexport { signalStore } from './signal-store';\nexport { signalStoreFeature, type } from './signal-store-feature';\nexport {\n  EmptyFeatureResult,\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n  StateSignals,\n} from './signal-store-models';\nexport {\n  getState,\n  isWritableStateSource,\n  PartialStateUpdater,\n  patchState,\n  StateSource,\n  StateWatcher,\n  watchState,\n  WritableStateSource,\n} from './state-source';\nexport { Prettify } from './ts-helpers';\n\nexport { withComputed } from './with-computed';\nexport { withFeature } from './with-feature';\nexport { withHooks } from './with-hooks';\nexport { withLinkedState } from './with-linked-state';\nexport { withMethods } from './with-methods';\nexport { withProps } from './with-props';\nexport { withState } from './with-state';\n"
  },
  {
    "path": "modules/signals/src/signal-method.ts",
    "content": "import {\n  assertInInjectionContext,\n  DestroyRef,\n  effect,\n  EffectRef,\n  inject,\n  Injector,\n  untracked,\n} from '@angular/core';\n\nexport type SignalMethod<Input> = ((\n  input: Input | (() => Input),\n  config?: { injector?: Injector }\n) => EffectRef) &\n  EffectRef;\n\n/**\n * @description\n *\n * Creates a method for managing side effects with signals.\n * The method accepts a signal, a computation function, or a static value.\n *\n * @usageNotes\n *\n * ```ts\n * import { Component, signal } from '@angular/core';\n * import { signalMethod } from '@ngrx/signals';\n *\n * \\@Component(...)\n * export class Counter {\n *   readonly count = signal(1);\n *   readonly logDoubledNumber = signalMethod<number>(\n *     (num) => console.log(num * 2)\n *   );\n *\n *   constructor() {\n *     this.logDoubledNumber(10); // logs: 20\n *\n *     this.logDoubledNumber(this.count); // logs: 2\n *     setTimeout(() => this.count.set(2), 1_000); // logs: 4 (after 1s)\n *   }\n * }\n * ```\n */\nexport function signalMethod<Input>(\n  processingFn: (value: Input) => void,\n  config?: { injector?: Injector }\n): SignalMethod<Input> {\n  if (typeof ngDevMode !== 'undefined' && ngDevMode && !config?.injector) {\n    assertInInjectionContext(signalMethod);\n  }\n\n  const watchers: EffectRef[] = [];\n  const sourceInjector = config?.injector ?? inject(Injector);\n\n  const signalMethodFn = (\n    input: Input | (() => Input),\n    config?: { injector?: Injector }\n  ): EffectRef => {\n    if (isReactiveComputation(input)) {\n      const callerInjector = getCallerInjector();\n\n      if (\n        typeof ngDevMode !== 'undefined' &&\n        ngDevMode &&\n        config?.injector === undefined &&\n        callerInjector === undefined\n      ) {\n        console.warn(\n          '@ngrx/signals: Calling signalMethod outside of an injection',\n          'context with a signal is deprecated. In a future version,',\n          'this will throw an error. Either call it within an injection',\n          'context (e.g. in a constructor or field initializer) or pass',\n          'an injector explicitly via the config parameter.',\n          '\\n\\nFor more information, see:',\n          'https://ngrx.io/guide/signals/signal-method#automatic-cleanup'\n        );\n      }\n\n      const instanceInjector =\n        config?.injector ?? callerInjector ?? sourceInjector;\n\n      const watcher = effect(\n        () => {\n          const value = input();\n          untracked(() => processingFn(value));\n        },\n        { injector: instanceInjector }\n      );\n      watchers.push(watcher);\n\n      instanceInjector.get(DestroyRef).onDestroy(() => {\n        const ix = watchers.indexOf(watcher);\n        if (ix !== -1) {\n          watchers.splice(ix, 1);\n        }\n      });\n\n      return watcher;\n    } else {\n      processingFn(input);\n      return { destroy: () => void true };\n    }\n  };\n\n  signalMethodFn.destroy = () =>\n    watchers.forEach((watcher) => watcher.destroy());\n\n  return signalMethodFn;\n}\n\nfunction getCallerInjector(): Injector | undefined {\n  try {\n    return inject(Injector);\n  } catch {\n    return undefined;\n  }\n}\n\nfunction isReactiveComputation<T>(value: T | (() => T)): value is () => T {\n  return typeof value === 'function';\n}\n"
  },
  {
    "path": "modules/signals/src/signal-state.ts",
    "content": "import { computed, signal } from '@angular/core';\nimport { DeepSignal, toDeepSignal } from './deep-signal';\nimport { SignalsDictionary } from './signal-store-models';\nimport { STATE_SOURCE, WritableStateSource } from './state-source';\n\nexport type SignalState<State extends object> = DeepSignal<State> &\n  WritableStateSource<State>;\n\n/**\n * @description\n *\n * Creates a state container with deeply nested signals for each property that\n * is an object literal.\n *\n * @usageNotes\n *\n * ```ts\n * import { Component } from '@angular/core';\n * import { signalState, patchState } from '@ngrx/signals';\n *\n * \\@Component(...)\n * export class Counter {\n *   readonly state = signalState({ count: 0 });\n *\n *   logCount(): void {\n *     console.log(this.state.count());\n *   }\n *\n *   increment(): void {\n *     patchState(this.state, ({ count }) => ({ count: count + 1 }));\n *   }\n * }\n * ```\n */\nexport function signalState<State extends object>(\n  initialState: State\n): SignalState<State> {\n  const stateKeys = Reflect.ownKeys(initialState);\n\n  const stateSource = stateKeys.reduce(\n    (signalsDict, key) => ({\n      ...signalsDict,\n      [key]: signal((initialState as Record<string | symbol, unknown>)[key]),\n    }),\n    {} as SignalsDictionary\n  );\n\n  const signalState = computed(() =>\n    stateKeys.reduce(\n      (state, key) => ({ ...state, [key]: stateSource[key]() }),\n      {}\n    )\n  );\n\n  Object.defineProperty(signalState, STATE_SOURCE, {\n    value: stateSource,\n  });\n\n  for (const key of stateKeys) {\n    Object.defineProperty(signalState, key, {\n      value: toDeepSignal(stateSource[key]),\n    });\n  }\n\n  return signalState as SignalState<State>;\n}\n"
  },
  {
    "path": "modules/signals/src/signal-store-assertions.ts",
    "content": "import { InnerSignalStore } from './signal-store-models';\n\nexport function assertUniqueStoreMembers(\n  store: InnerSignalStore,\n  newMemberKeys: Array<string | symbol>\n): void {\n  const storeMembers = {\n    ...store.stateSignals,\n    ...store.props,\n    ...store.methods,\n  };\n  const overriddenKeys = Reflect.ownKeys(storeMembers).filter((memberKey) =>\n    newMemberKeys.includes(memberKey)\n  );\n\n  if (overriddenKeys.length > 0) {\n    console.warn(\n      '@ngrx/signals: SignalStore members cannot be overridden.',\n      'Trying to override:',\n      overriddenKeys.map((key) => String(key)).join(', ')\n    );\n  }\n}\n"
  },
  {
    "path": "modules/signals/src/signal-store-feature.ts",
    "content": "import {\n  EmptyFeatureResult,\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n} from './signal-store-models';\nimport { Prettify } from './ts-helpers';\n\ntype PrettifyFeatureResult<Result extends SignalStoreFeatureResult> = Prettify<{\n  state: Prettify<Result['state']>;\n  props: Prettify<Result['props']>;\n  methods: Prettify<Result['methods']>;\n}>;\n\nexport function signalStoreFeature<F1 extends SignalStoreFeatureResult>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>\n): SignalStoreFeature<EmptyFeatureResult, F1>;\nexport function signalStoreFeature<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>\n): SignalStoreFeature<EmptyFeatureResult, PrettifyFeatureResult<F1 & F2>>;\nexport function signalStoreFeature<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>\n): SignalStoreFeature<EmptyFeatureResult, PrettifyFeatureResult<F1 & F2 & F3>>;\nexport function signalStoreFeature<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>\n): SignalStoreFeature<\n  EmptyFeatureResult,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4>\n>;\nexport function signalStoreFeature<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>\n): SignalStoreFeature<\n  EmptyFeatureResult,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5>\n>;\nexport function signalStoreFeature<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>\n): SignalStoreFeature<\n  EmptyFeatureResult,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6>\n>;\nexport function signalStoreFeature<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>\n): SignalStoreFeature<\n  EmptyFeatureResult,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6 & F7>\n>;\nexport function signalStoreFeature<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>\n): SignalStoreFeature<\n  EmptyFeatureResult,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8>\n>;\nexport function signalStoreFeature<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>\n): SignalStoreFeature<\n  EmptyFeatureResult,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9>\n>;\nexport function signalStoreFeature<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>\n): SignalStoreFeature<\n  EmptyFeatureResult,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10>\n>;\n\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n>(\n  input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>\n): SignalStoreFeature<Prettify<EmptyFeatureResult & Input>, F1>;\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n>(\n  input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>,\n  f2: SignalStoreFeature<NoInfer<Input> & F1, F2>\n): SignalStoreFeature<\n  Prettify<EmptyFeatureResult & Input>,\n  PrettifyFeatureResult<F1 & F2>\n>;\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n>(\n  input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>,\n  f2: SignalStoreFeature<NoInfer<Input> & F1, F2>,\n  f3: SignalStoreFeature<NoInfer<Input> & F1 & F2, F3>\n): SignalStoreFeature<\n  Prettify<EmptyFeatureResult & Input>,\n  PrettifyFeatureResult<F1 & F2 & F3>\n>;\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n>(\n  Input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>,\n  f2: SignalStoreFeature<NoInfer<Input> & F1, F2>,\n  f3: SignalStoreFeature<NoInfer<Input> & F1 & F2, F3>,\n  f4: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3, F4>\n): SignalStoreFeature<\n  Prettify<EmptyFeatureResult & Input>,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4>\n>;\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n>(\n  input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>,\n  f2: SignalStoreFeature<NoInfer<Input> & F1, F2>,\n  f3: SignalStoreFeature<NoInfer<Input> & F1 & F2, F3>,\n  f4: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4, F5>\n): SignalStoreFeature<\n  Prettify<EmptyFeatureResult & Input>,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5>\n>;\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n>(\n  input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>,\n  f2: SignalStoreFeature<NoInfer<Input> & F1, F2>,\n  f3: SignalStoreFeature<NoInfer<Input> & F1 & F2, F3>,\n  f4: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5, F6>\n): SignalStoreFeature<\n  Prettify<EmptyFeatureResult & Input>,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6>\n>;\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n>(\n  input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>,\n  f2: SignalStoreFeature<NoInfer<Input> & F1, F2>,\n  f3: SignalStoreFeature<NoInfer<Input> & F1 & F2, F3>,\n  f4: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6, F7>\n): SignalStoreFeature<\n  Prettify<EmptyFeatureResult & Input>,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6 & F7>\n>;\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n>(\n  input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>,\n  f2: SignalStoreFeature<NoInfer<Input> & F1, F2>,\n  f3: SignalStoreFeature<NoInfer<Input> & F1 & F2, F3>,\n  f4: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>\n): SignalStoreFeature<\n  Prettify<EmptyFeatureResult & Input>,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8>\n>;\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n>(\n  input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>,\n  f2: SignalStoreFeature<NoInfer<Input> & F1, F2>,\n  f3: SignalStoreFeature<NoInfer<Input> & F1 & F2, F3>,\n  f4: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<\n    NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8,\n    F9\n  >\n): SignalStoreFeature<\n  Prettify<EmptyFeatureResult & Input>,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9>\n>;\nexport function signalStoreFeature<\n  Input extends Partial<SignalStoreFeatureResult>,\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n>(\n  input: Input,\n  f1: SignalStoreFeature<EmptyFeatureResult & NoInfer<Input>, F1>,\n  f2: SignalStoreFeature<NoInfer<Input> & F1, F2>,\n  f3: SignalStoreFeature<NoInfer<Input> & F1 & F2, F3>,\n  f4: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<\n    NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8,\n    F9\n  >,\n  f10: SignalStoreFeature<\n    NoInfer<Input> & F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9,\n    F10\n  >\n): SignalStoreFeature<\n  Prettify<EmptyFeatureResult & Input>,\n  PrettifyFeatureResult<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10>\n>;\n/**\n * @description\n *\n * Combines multiple store features into a single feature.\n *\n * @usageNotes\n *\n * ```ts\n * import {\n *   patchState,\n *   signalStore,\n *   signalStoreFeature,\n *   withMethods,\n *   withState,\n * } from '@ngrx/signals';\n *\n * export function withCounter() {\n *   return signalStoreFeature(\n *     withState({ count: 0 }),\n *     withMethods((store) => ({\n *       increment(): void {\n *         patchState(store, ({ count }) => ({ count: count + 1 }));\n *       },\n *     }))\n *   );\n * }\n *\n * export const CounterStore = signalStore(withCounter());\n * ```\n */\nexport function signalStoreFeature(\n  ...args:\n    | [Partial<SignalStoreFeatureResult>, ...SignalStoreFeature[]]\n    | SignalStoreFeature[]\n): SignalStoreFeature<EmptyFeatureResult, EmptyFeatureResult> {\n  const features = (\n    typeof args[0] === 'function' ? args : args.slice(1)\n  ) as SignalStoreFeature[];\n\n  return (inputStore) =>\n    features.reduce((store, feature) => feature(store), inputStore);\n}\n\nexport function type<T>(): T {\n  return undefined as T;\n}\n"
  },
  {
    "path": "modules/signals/src/signal-store-models.ts",
    "content": "import { Signal } from '@angular/core';\nimport { DeepSignal } from './deep-signal';\nimport { WritableStateSource } from './state-source';\nimport { IsKnownRecord, Prettify } from './ts-helpers';\n\nexport type StateSignals<State> =\n  IsKnownRecord<Prettify<State>> extends true\n    ? {\n        [Key in keyof State]: IsKnownRecord<State[Key]> extends true\n          ? DeepSignal<State[Key]>\n          : Signal<State[Key]>;\n      }\n    : {};\n\nexport type SignalsDictionary = Record<string | symbol, Signal<unknown>>;\n\nexport type MethodsDictionary = Record<string, Function>;\n\nexport type SignalStoreHooks = {\n  onInit?: () => void;\n  onDestroy?: () => void;\n};\n\nexport type InnerSignalStore<\n  State extends object = object,\n  Props extends object = object,\n  Methods extends MethodsDictionary = MethodsDictionary,\n> = {\n  stateSignals: StateSignals<State>;\n  props: Props;\n  methods: Methods;\n  hooks: SignalStoreHooks;\n} & WritableStateSource<State>;\n\nexport type SignalStoreFeatureResult = {\n  state: object;\n  props: object;\n  methods: MethodsDictionary;\n};\n\nexport type EmptyFeatureResult = { state: {}; props: {}; methods: {} };\n\nexport type SignalStoreFeature<\n  Input extends SignalStoreFeatureResult = SignalStoreFeatureResult,\n  Output extends SignalStoreFeatureResult = SignalStoreFeatureResult,\n> = (\n  store: InnerSignalStore<Input['state'], Input['props'], Input['methods']>\n) => InnerSignalStore<Output['state'], Output['props'], Output['methods']>;\n"
  },
  {
    "path": "modules/signals/src/signal-store.ts",
    "content": "import { DestroyRef, inject, Injectable, Type } from '@angular/core';\nimport { STATE_SOURCE, StateSource, WritableStateSource } from './state-source';\nimport {\n  EmptyFeatureResult,\n  InnerSignalStore,\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n  StateSignals,\n} from './signal-store-models';\nimport { OmitPrivate, Prettify } from './ts-helpers';\n\ntype ProvidedInConfig = { providedIn?: 'root' | 'platform' };\n\ntype SignalStoreConfig = ProvidedInConfig & { protectedState?: boolean };\n\ntype SignalStoreMembers<FeatureResult extends SignalStoreFeatureResult> =\n  Prettify<\n    OmitPrivate<\n      StateSignals<FeatureResult['state']> &\n        FeatureResult['props'] &\n        FeatureResult['methods']\n    >\n  >;\n\nexport function signalStore<F1 extends SignalStoreFeatureResult>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>\n): Type<\n  SignalStoreMembers<F1> & StateSource<Prettify<OmitPrivate<F1['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5 & F6,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5 & F6 & F7,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10, F11>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  F13 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12 &\n    F13,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >,\n  f13: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12,\n    F13\n  >\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  F13 extends SignalStoreFeatureResult,\n  F14 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12 &\n    F13 &\n    F14,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >,\n  f13: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12,\n    F13\n  >,\n  f14: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13,\n    F14\n  >\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  F13 extends SignalStoreFeatureResult,\n  F14 extends SignalStoreFeatureResult,\n  F15 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12 &\n    F13 &\n    F14 &\n    F15,\n>(\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >,\n  f13: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12,\n    F13\n  >,\n  f14: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13,\n    F14\n  >,\n  f15: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13 & F14,\n    F15\n  >\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\n\nexport function signalStore<F1 extends SignalStoreFeatureResult>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>\n): Type<\n  SignalStoreMembers<F1> & StateSource<Prettify<OmitPrivate<F1['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5 & F6,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5 & F6 & F7,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10, F11>\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  F13 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12 &\n    F13,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >,\n  f13: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12,\n    F13\n  >\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  F13 extends SignalStoreFeatureResult,\n  F14 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12 &\n    F13 &\n    F14,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >,\n  f13: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12,\n    F13\n  >,\n  f14: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13,\n    F14\n  >\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  F13 extends SignalStoreFeatureResult,\n  F14 extends SignalStoreFeatureResult,\n  F15 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12 &\n    F13 &\n    F14 &\n    F15,\n>(\n  config: ProvidedInConfig & { protectedState?: true },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >,\n  f13: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12,\n    F13\n  >,\n  f14: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13,\n    F14\n  >,\n  f15: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13 & F14,\n    F15\n  >\n): Type<SignalStoreMembers<R> & StateSource<Prettify<OmitPrivate<R['state']>>>>;\n\nexport function signalStore<F1 extends SignalStoreFeatureResult>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>\n): Type<\n  SignalStoreMembers<F1> &\n    WritableStateSource<Prettify<OmitPrivate<F1['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5 & F6,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5 & F6 & F7,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10, F11>\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  F13 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12 &\n    F13,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >,\n  f13: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12,\n    F13\n  >\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  F13 extends SignalStoreFeatureResult,\n  F14 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12 &\n    F13 &\n    F14,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >,\n  f13: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12,\n    F13\n  >,\n  f14: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13,\n    F14\n  >\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\nexport function signalStore<\n  F1 extends SignalStoreFeatureResult,\n  F2 extends SignalStoreFeatureResult,\n  F3 extends SignalStoreFeatureResult,\n  F4 extends SignalStoreFeatureResult,\n  F5 extends SignalStoreFeatureResult,\n  F6 extends SignalStoreFeatureResult,\n  F7 extends SignalStoreFeatureResult,\n  F8 extends SignalStoreFeatureResult,\n  F9 extends SignalStoreFeatureResult,\n  F10 extends SignalStoreFeatureResult,\n  F11 extends SignalStoreFeatureResult,\n  F12 extends SignalStoreFeatureResult,\n  F13 extends SignalStoreFeatureResult,\n  F14 extends SignalStoreFeatureResult,\n  F15 extends SignalStoreFeatureResult,\n  R extends SignalStoreFeatureResult = F1 &\n    F2 &\n    F3 &\n    F4 &\n    F5 &\n    F6 &\n    F7 &\n    F8 &\n    F9 &\n    F10 &\n    F11 &\n    F12 &\n    F13 &\n    F14 &\n    F15,\n>(\n  config: ProvidedInConfig & { protectedState: false },\n  f1: SignalStoreFeature<EmptyFeatureResult, F1>,\n  f2: SignalStoreFeature<{} & F1, F2>,\n  f3: SignalStoreFeature<F1 & F2, F3>,\n  f4: SignalStoreFeature<F1 & F2 & F3, F4>,\n  f5: SignalStoreFeature<F1 & F2 & F3 & F4, F5>,\n  f6: SignalStoreFeature<F1 & F2 & F3 & F4 & F5, F6>,\n  f7: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6, F7>,\n  f8: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7, F8>,\n  f9: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8, F9>,\n  f10: SignalStoreFeature<F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9, F10>,\n  f11: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10,\n    F11\n  >,\n  f12: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11,\n    F12\n  >,\n  f13: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12,\n    F13\n  >,\n  f14: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13,\n    F14\n  >,\n  f15: SignalStoreFeature<\n    F1 & F2 & F3 & F4 & F5 & F6 & F7 & F8 & F9 & F10 & F11 & F12 & F13 & F14,\n    F15\n  >\n): Type<\n  SignalStoreMembers<R> & WritableStateSource<Prettify<OmitPrivate<R['state']>>>\n>;\n/**\n * @description\n *\n * Creates a store by composing features.\n * Returns an injectable service that can be provided locally or globally.\n *\n * @usageNotes\n *\n * ```ts\n * import { Component, inject } from '@angular/core';\n * import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';\n *\n * export const CounterStore = signalStore(\n *   withState({ count: 0 }),\n *   withMethods((store) => ({\n *     increment(): void {\n *       patchState(store, ({ count }) => ({ count: count + 1 }));\n *     },\n *   }))\n * );\n *\n * \\@Component({\n *   // ...\n *   providers: [CounterStore],\n * })\n * export class Counter {\n *   readonly store = inject(CounterStore);\n *\n *   logCount(): void {\n *     console.log(this.store.count());\n *   }\n *\n *   increment(): void {\n *     this.store.increment();\n *   }\n * }\n * ```\n */\nexport function signalStore(\n  ...args: [SignalStoreConfig, ...SignalStoreFeature[]] | SignalStoreFeature[]\n): Type<SignalStoreMembers<any>> {\n  const signalStoreArgs = [...args];\n\n  const config =\n    typeof signalStoreArgs[0] === 'function'\n      ? {}\n      : (signalStoreArgs.shift() as SignalStoreConfig);\n  const features = signalStoreArgs as SignalStoreFeature[];\n\n  @Injectable({ providedIn: config.providedIn || null })\n  class SignalStore {\n    constructor() {\n      const innerStore = features.reduce(\n        (store, feature) => feature(store),\n        getInitialInnerStore()\n      );\n      const { stateSignals, props, methods, hooks } = innerStore;\n      const storeMembers: Record<string | symbol, unknown> = {\n        ...stateSignals,\n        ...props,\n        ...methods,\n      };\n\n      (this as any)[STATE_SOURCE] = innerStore[STATE_SOURCE];\n\n      for (const key of Reflect.ownKeys(storeMembers)) {\n        (this as any)[key] = storeMembers[key];\n      }\n\n      const { onInit, onDestroy } = hooks;\n\n      if (onInit) {\n        onInit();\n      }\n\n      if (onDestroy) {\n        inject(DestroyRef).onDestroy(onDestroy);\n      }\n    }\n  }\n\n  return SignalStore;\n}\n\nexport function getInitialInnerStore(): InnerSignalStore {\n  return {\n    [STATE_SOURCE]: {},\n    stateSignals: {},\n    props: {},\n    methods: {},\n    hooks: {},\n  };\n}\n"
  },
  {
    "path": "modules/signals/src/state-source.ts",
    "content": "import {\n  assertInInjectionContext,\n  DestroyRef,\n  inject,\n  Injector,\n  isSignal,\n  Signal,\n  untracked,\n  WritableSignal,\n} from '@angular/core';\n\nconst STATE_WATCHERS = new WeakMap<object, Array<StateWatcher<any>>>();\n\nexport const STATE_SOURCE = Symbol(\n  typeof ngDevMode !== 'undefined' && ngDevMode ? 'STATE_SOURCE' : ''\n);\n\nexport type WritableStateSource<State extends object> = {\n  [STATE_SOURCE]: { [K in keyof State]: WritableSignal<State[K]> };\n};\n\nexport type StateSource<State extends object> = {\n  [STATE_SOURCE]: { [K in keyof State]: Signal<State[K]> };\n};\n\nexport type PartialStateUpdater<State extends object> = (\n  state: State\n) => Partial<State>;\n\nexport type StateWatcher<State extends object> = (\n  state: NoInfer<State>\n) => void;\n\nexport function isWritableSignal(\n  value: unknown\n): value is WritableSignal<unknown> {\n  return (\n    isSignal(value) &&\n    'set' in value &&\n    'update' in value &&\n    typeof value.set === 'function' &&\n    typeof value.update === 'function'\n  );\n}\n\nexport function isWritableStateSource<State extends object>(\n  stateSource: StateSource<State>\n): stateSource is WritableStateSource<State> {\n  const signals: Record<string | symbol, unknown> = stateSource[STATE_SOURCE];\n  return Reflect.ownKeys(stateSource[STATE_SOURCE]).every((key) => {\n    return isWritableSignal(signals[key]);\n  });\n}\n\n/**\n * @description\n *\n * Updates the state of a SignalStore or SignalState.\n * Accepts a sequence of partial state objects and partial state updaters.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';\n *\n * export const CounterStore = signalStore(\n *   withState({ count1: 0, count2: 0 }),\n *   withMethods((store) => ({\n *     incrementFirst(): void {\n *       patchState(store, (state) => ({ count1: state.count1 + 1 }));\n *     },\n *     resetSecond(): void {\n *       patchState(store, { count2: 0 });\n *     },\n *   }))\n * );\n * ```\n */\nexport function patchState<State extends object>(\n  stateSource: WritableStateSource<State>,\n  ...updaters: Array<\n    Partial<NoInfer<State>> | PartialStateUpdater<NoInfer<State>>\n  >\n): void {\n  const currentState = untracked(() => getState(stateSource));\n  const newState = updaters.reduce(\n    (nextState: State, updater) => ({\n      ...nextState,\n      ...(typeof updater === 'function' ? updater(nextState) : updater),\n    }),\n    currentState\n  );\n\n  const signals = stateSource[STATE_SOURCE];\n  const stateKeys = Reflect.ownKeys(stateSource[STATE_SOURCE]);\n\n  for (const key of Reflect.ownKeys(newState)) {\n    if (stateKeys.includes(key)) {\n      const signalKey = key as keyof State;\n      if (currentState[signalKey] !== newState[signalKey]) {\n        signals[signalKey].set(newState[signalKey]);\n      }\n    } else if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n      console.warn(\n        `@ngrx/signals: patchState was called with an unknown state slice '${String(\n          key\n        )}'.`,\n        'Ensure that all state properties are explicitly defined in the initial state.',\n        'Updates to properties not present in the initial state will be ignored.'\n      );\n    }\n  }\n\n  notifyWatchers(stateSource);\n}\n\n/**\n * @description\n *\n * Returns a snapshot of the current state from a SignalStore or SignalState.\n * When used within a reactive context, state changes are automatically tracked.\n *\n * @usageNotes\n *\n * ```ts\n * import { Component, effect, inject } from '@angular/core';\n * import { getState, signalStore, withState } from '@ngrx/signals';\n *\n * export const CounterStore = signalStore(\n *   withState({ count1: 0, count2: 0 })\n * );\n *\n * \\@Component(...)\n * export class Counter {\n *   readonly store = inject(CounterStore);\n *\n *   constructor() {\n *     effect(() => {\n *       const state = getState(this.store);\n *       // 👇 Logs on state changes.\n *       console.log(state);\n *     });\n *   }\n * }\n * ```\n */\nexport function getState<State extends object>(\n  stateSource: StateSource<State>\n): State {\n  const signals: Record<string | symbol, Signal<unknown>> = stateSource[\n    STATE_SOURCE\n  ];\n  return Reflect.ownKeys(stateSource[STATE_SOURCE]).reduce((state, key) => {\n    const value = signals[key]();\n    return {\n      ...state,\n      [key]: value,\n    };\n  }, {} as State);\n}\n\n/**\n * @description\n *\n * Synchronously tracks state changes of a SignalStore or SignalState.\n *\n * @usageNotes\n *\n * ```ts\n * import { Component } from '@angular/core';\n * import { signalState, watchState } from '@ngrx/signals';\n *\n * \\@Component(...)\n * export class Counter {\n *   readonly state = signalState({ count1: 0, count2: 0 });\n *\n *   constructor() {\n *     // 👇 Synchronously logs every state change without debouncing.\n *     watchState(this.state, console.log);\n *   }\n * }\n * ```\n */\nexport function watchState<State extends object>(\n  stateSource: StateSource<State>,\n  watcher: StateWatcher<State>,\n  config?: { injector?: Injector }\n): { destroy(): void } {\n  if (typeof ngDevMode !== 'undefined' && ngDevMode && !config?.injector) {\n    assertInInjectionContext(watchState);\n  }\n\n  const injector = config?.injector ?? inject(Injector);\n  const destroyRef = injector.get(DestroyRef);\n\n  addWatcher(stateSource, watcher);\n  watcher(getState(stateSource));\n\n  const destroy = () => removeWatcher(stateSource, watcher);\n  destroyRef.onDestroy(destroy);\n\n  return { destroy };\n}\n\nfunction getWatchers<State extends object>(\n  stateSource: StateSource<State>\n): Array<StateWatcher<State>> {\n  return STATE_WATCHERS.get(stateSource[STATE_SOURCE]) || [];\n}\n\nfunction notifyWatchers<State extends object>(\n  stateSource: StateSource<State>\n): void {\n  const watchers = getWatchers(stateSource);\n\n  for (const watcher of watchers) {\n    const state = untracked(() => getState(stateSource));\n    watcher(state);\n  }\n}\n\nfunction addWatcher<State extends object>(\n  stateSource: StateSource<State>,\n  watcher: StateWatcher<State>\n): void {\n  const watchers = getWatchers(stateSource);\n  STATE_WATCHERS.set(stateSource[STATE_SOURCE], [...watchers, watcher]);\n}\n\nfunction removeWatcher<State extends object>(\n  stateSource: StateSource<State>,\n  watcher: StateWatcher<State>\n): void {\n  const watchers = getWatchers(stateSource);\n  STATE_WATCHERS.set(\n    stateSource[STATE_SOURCE],\n    watchers.filter((w) => w !== watcher)\n  );\n}\n"
  },
  {
    "path": "modules/signals/src/ts-helpers.ts",
    "content": "type NonRecord =\n  | Iterable<any>\n  | WeakSet<any>\n  | WeakMap<any, any>\n  | Promise<any>\n  | Date\n  | Error\n  | RegExp\n  | ArrayBuffer\n  | DataView\n  | Function;\n\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {};\n\nexport type IsRecord<T> = T extends object\n  ? T extends NonRecord\n    ? false\n    : true\n  : false;\n\nexport type IsUnknownRecord<T> = keyof T extends never\n  ? true\n  : string extends keyof T\n    ? true\n    : symbol extends keyof T\n      ? true\n      : number extends keyof T\n        ? true\n        : false;\n\nexport type IsKnownRecord<T> =\n  IsRecord<T> extends true\n    ? IsUnknownRecord<T> extends true\n      ? false\n      : true\n    : false;\n\nexport type OmitPrivate<T> = {\n  [K in keyof T as K extends `_${string}` ? never : K]: T[K];\n};\n"
  },
  {
    "path": "modules/signals/src/with-computed.ts",
    "content": "import { computed, isSignal, Signal } from '@angular/core';\nimport {\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n  StateSignals,\n} from './signal-store-models';\nimport { Prettify } from './ts-helpers';\nimport { withProps } from './with-props';\n\ntype ComputedResult<\n  ComputedDictionary extends Record<\n    string | symbol,\n    Signal<unknown> | (() => unknown)\n  >,\n> = {\n  [P in keyof ComputedDictionary]: ComputedDictionary[P] extends Signal<unknown>\n    ? ComputedDictionary[P]\n    : ComputedDictionary[P] extends () => infer V\n      ? Signal<V>\n      : never;\n};\n\n/**\n * @description\n *\n * Adds computed signals to a SignalStore.\n * Accepts a factory function that returns a dictionary of computed signals or\n * computation functions.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore, withState, withComputed } from '@ngrx/signals';\n *\n * export const CounterStore = signalStore(\n *   withState({ count: 0 }),\n *   withComputed(({ count }) => ({\n *     doubleCount: () => count() * 2,\n *   }))\n * );\n * ```\n */\nexport function withComputed<\n  Input extends SignalStoreFeatureResult,\n  ComputedDictionary extends Record<\n    string | symbol,\n    Signal<unknown> | (() => unknown)\n  >,\n>(\n  computedFactory: (\n    store: Prettify<\n      StateSignals<Input['state']> & Input['props'] & Input['methods']\n    >\n  ) => ComputedDictionary\n): SignalStoreFeature<\n  Input,\n  { state: {}; props: ComputedResult<ComputedDictionary>; methods: {} }\n> {\n  return withProps((store) => {\n    const computedResult = computedFactory(store);\n    const computedResultKeys = Reflect.ownKeys(computedResult);\n\n    return computedResultKeys.reduce((prev, key) => {\n      const signalOrComputation = computedResult[key];\n      return {\n        ...prev,\n        [key]: isSignal(signalOrComputation)\n          ? signalOrComputation\n          : computed(signalOrComputation),\n      };\n    }, {} as ComputedResult<ComputedDictionary>);\n  });\n}\n"
  },
  {
    "path": "modules/signals/src/with-feature.ts",
    "content": "import {\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n  StateSignals,\n} from './signal-store-models';\nimport { STATE_SOURCE, WritableStateSource } from './state-source';\nimport { Prettify } from './ts-helpers';\n\n/**\n * @description\n *\n * Allows passing state signals, properties, and methods from a SignalStore\n * instance to a custom feature.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore, withFeature, withMethods } from '@ngrx/signals';\n *\n * export const UserStore = signalStore(\n *   withMethods((store) => ({\n *     loadById(id: number): Promise<User> {\n *       return Promise.resolve({ id, name: 'John' });\n *     },\n *   })),\n *   withFeature(\n *     // 👇 Has full access to store members.\n *     (store) => withEntityLoader((id) => store.loadById(id))\n *   )\n * );\n * ```\n */\nexport function withFeature<\n  Input extends SignalStoreFeatureResult,\n  Output extends SignalStoreFeatureResult,\n>(\n  featureFactory: (\n    store: Prettify<\n      StateSignals<Input['state']> &\n        Input['props'] &\n        Input['methods'] &\n        WritableStateSource<Input['state']>\n    >\n  ) => SignalStoreFeature<Input, Output>\n): SignalStoreFeature<Input, Output> {\n  return (store) => {\n    const storeForFactory = {\n      [STATE_SOURCE]: store[STATE_SOURCE],\n      ...store.stateSignals,\n      ...store.props,\n      ...store.methods,\n    };\n\n    return featureFactory(storeForFactory)(store);\n  };\n}\n"
  },
  {
    "path": "modules/signals/src/with-hooks.ts",
    "content": "import { STATE_SOURCE, WritableStateSource } from './state-source';\nimport {\n  EmptyFeatureResult,\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n  StateSignals,\n} from './signal-store-models';\nimport { Prettify } from './ts-helpers';\n\ntype HookFn<Input extends SignalStoreFeatureResult> = (\n  store: Prettify<\n    StateSignals<Input['state']> &\n      Input['props'] &\n      Input['methods'] &\n      WritableStateSource<Input['state']>\n  >\n) => void;\n\ntype HooksFactory<Input extends SignalStoreFeatureResult> = (\n  store: Prettify<\n    StateSignals<Input['state']> &\n      Input['props'] &\n      Input['methods'] &\n      WritableStateSource<Input['state']>\n  >\n) => {\n  onInit?: () => void;\n  onDestroy?: () => void;\n};\n\nexport function withHooks<Input extends SignalStoreFeatureResult>(hooks: {\n  onInit?: HookFn<Input>;\n  onDestroy?: HookFn<Input>;\n}): SignalStoreFeature<Input, EmptyFeatureResult>;\nexport function withHooks<Input extends SignalStoreFeatureResult>(\n  hooks: HooksFactory<Input>\n): SignalStoreFeature<Input, EmptyFeatureResult>;\n/**\n * @description\n *\n * Adds lifecycle hooks to a SignalStore.\n * Supports an onInit hook that executes when the store is initialized.\n * Supports an onDestroy hook for when the store is destroyed.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore, withHooks, withState } from '@ngrx/signals';\n *\n * export const UserStore = signalStore(\n *   withState({ firstName: 'Jimi', lastName: 'Hendrix' }),\n *   withHooks({\n *     onInit({ firstName }) {\n *       console.log('first name on init', firstName());\n *     },\n *     onDestroy({ lastName }) {\n *       console.log('last name on destroy', lastName());\n *     },\n *   })\n * );\n * ```\n */\nexport function withHooks<Input extends SignalStoreFeatureResult>(\n  hooksOrFactory:\n    | {\n        onInit?: HookFn<Input>;\n        onDestroy?: HookFn<Input>;\n      }\n    | HooksFactory<Input>\n): SignalStoreFeature<Input, EmptyFeatureResult> {\n  return (store) => {\n    const storeMembers = {\n      [STATE_SOURCE]: store[STATE_SOURCE],\n      ...store.stateSignals,\n      ...store.props,\n      ...store.methods,\n    };\n    const hooks =\n      typeof hooksOrFactory === 'function'\n        ? hooksOrFactory(storeMembers)\n        : hooksOrFactory;\n    const mergeHooks = (currentHook?: () => void, hook?: HookFn<Input>) => {\n      return hook\n        ? () => {\n            if (currentHook) {\n              currentHook();\n            }\n\n            hook(storeMembers);\n          }\n        : currentHook;\n    };\n\n    return {\n      ...store,\n      hooks: {\n        onInit: mergeHooks(store.hooks.onInit, hooks.onInit),\n        onDestroy: mergeHooks(store.hooks.onDestroy, hooks.onDestroy),\n      },\n    };\n  };\n}\n"
  },
  {
    "path": "modules/signals/src/with-linked-state.ts",
    "content": "import { linkedSignal, WritableSignal } from '@angular/core';\nimport { toDeepSignal } from './deep-signal';\nimport { assertUniqueStoreMembers } from './signal-store-assertions';\nimport {\n  InnerSignalStore,\n  SignalsDictionary,\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n  StateSignals,\n} from './signal-store-models';\nimport { isWritableSignal, STATE_SOURCE } from './state-source';\nimport { Prettify } from './ts-helpers';\n\ntype LinkedStateResult<\n  LinkedStateInput extends Record<\n    string | symbol,\n    WritableSignal<unknown> | (() => unknown)\n  >,\n> = {\n  [K in keyof LinkedStateInput]: LinkedStateInput[K] extends WritableSignal<\n    infer V\n  >\n    ? V\n    : LinkedStateInput[K] extends () => infer V\n      ? V\n      : never;\n};\n/**\n * @description\n *\n * Adds linked state slices to a SignalStore.\n * Accepts a factory function that returns a dictionary of linked signals or\n * computation functions.\n *\n * @usageNotes\n *\n * ### Using a computation function\n *\n * ```ts\n * import { signalStore, withLinkedState, withState } from '@ngrx/signals';\n *\n * export const OptionsStore = signalStore(\n *   withState({ options: [1, 2, 3] }),\n *   withLinkedState(({ options }) => ({\n *     selectedOption: () => options()[0],\n *   }))\n * );\n * ```\n *\n * ### Using linkedSignal for advanced use cases\n *\n * ```ts\n * import { linkedSignal } from '@angular/core';\n * import { signalStore, withLinkedState, withState } from '@ngrx/signals';\n *\n * type Option = { id: number; label: string };\n *\n * export const OptionsStore = signalStore(\n *   withState({ options: [] as Option[] }),\n *   withLinkedState(({ options }) => ({\n *     selectedOption: linkedSignal<Option[], Option>({\n *       source: options,\n *       computation: (newOptions, previous) => {\n *         const option = newOptions.find((o) => o.id === previous?.value.id);\n *         return option ?? newOptions[0];\n *       },\n *     }),\n *   }))\n * )\n * ```\n */\nexport function withLinkedState<\n  State extends Record<\n    string | symbol,\n    WritableSignal<unknown> | (() => unknown)\n  >,\n  Input extends SignalStoreFeatureResult,\n>(\n  linkedStateFactory: (\n    store: Prettify<StateSignals<Input['state']> & Input['props']>\n  ) => State\n): SignalStoreFeature<\n  Input,\n  { state: LinkedStateResult<State>; props: {}; methods: {} }\n> {\n  return (store) => {\n    const linkedState = linkedStateFactory({\n      ...store.stateSignals,\n      ...store.props,\n    });\n    const stateKeys = Reflect.ownKeys(linkedState);\n    if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n      assertUniqueStoreMembers(store, stateKeys);\n    }\n    const stateSource = store[STATE_SOURCE] as SignalsDictionary;\n    const stateSignals = {} as SignalsDictionary;\n\n    for (const key of stateKeys) {\n      const signalOrComputationFn = linkedState[key];\n      stateSource[key] = isWritableSignal(signalOrComputationFn)\n        ? signalOrComputationFn\n        : linkedSignal(signalOrComputationFn);\n      stateSignals[key] = toDeepSignal(stateSource[key]);\n    }\n\n    return {\n      ...store,\n      stateSignals: { ...store.stateSignals, ...stateSignals },\n    } as InnerSignalStore<LinkedStateResult<State>>;\n  };\n}\n"
  },
  {
    "path": "modules/signals/src/with-methods.ts",
    "content": "import { STATE_SOURCE, WritableStateSource } from './state-source';\nimport { assertUniqueStoreMembers } from './signal-store-assertions';\nimport {\n  InnerSignalStore,\n  MethodsDictionary,\n  SignalsDictionary,\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n  StateSignals,\n} from './signal-store-models';\nimport { Prettify } from './ts-helpers';\n\n/**\n * @description\n *\n * Adds methods to a SignalStore.\n *\n * @usageNotes\n *\n * ```ts\n * import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';\n *\n * export const CounterStore = signalStore(\n *   withState({ count: 0 }),\n *   withMethods((store) => ({\n *     increment(): void {\n *       patchState(store, ({ count }) => ({ count: count + 1 }));\n *     },\n *     decrement(): void {\n *       patchState(store, ({ count }) => ({ count: count - 1 }));\n *     },\n *   }))\n * );\n * ```\n */\nexport function withMethods<\n  Input extends SignalStoreFeatureResult,\n  Methods extends MethodsDictionary,\n>(\n  methodsFactory: (\n    store: Prettify<\n      StateSignals<Input['state']> &\n        Input['props'] &\n        Input['methods'] &\n        WritableStateSource<Input['state']>\n    >\n  ) => Methods\n): SignalStoreFeature<Input, { state: {}; props: {}; methods: Methods }> {\n  return (store) => {\n    const methods = methodsFactory({\n      [STATE_SOURCE]: store[STATE_SOURCE],\n      ...store.stateSignals,\n      ...store.props,\n      ...store.methods,\n    });\n    if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n      assertUniqueStoreMembers(store, Reflect.ownKeys(methods));\n    }\n\n    return {\n      ...store,\n      methods: { ...store.methods, ...methods },\n    } as InnerSignalStore<Record<string, unknown>, SignalsDictionary, Methods>;\n  };\n}\n"
  },
  {
    "path": "modules/signals/src/with-props.ts",
    "content": "import { STATE_SOURCE, WritableStateSource } from './state-source';\nimport { assertUniqueStoreMembers } from './signal-store-assertions';\nimport {\n  InnerSignalStore,\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n  StateSignals,\n} from './signal-store-models';\nimport { Prettify } from './ts-helpers';\n\n/**\n * @description\n *\n * Adds custom properties to a SignalStore.\n *\n * @usageNotes\n *\n * ```ts\n * import { toObservable } from '@angular/core/rxjs-interop';\n * import { signalStore, withProps, withState } from '@ngrx/signals';\n *\n * export const TodosStore = signalStore(\n *   withState({ todos: [] as Todo[], isLoading: false }),\n *   withProps(({ isLoading }) => ({\n *     isLoading$: toObservable(isLoading),\n *   }))\n * );\n * ```\n */\nexport function withProps<\n  Input extends SignalStoreFeatureResult,\n  Props extends object,\n>(\n  propsFactory: (\n    store: Prettify<\n      StateSignals<Input['state']> &\n        Input['props'] &\n        Input['methods'] &\n        WritableStateSource<Input['state']>\n    >\n  ) => Props\n): SignalStoreFeature<Input, { state: {}; props: Props; methods: {} }> {\n  return (store) => {\n    const props = propsFactory({\n      [STATE_SOURCE]: store[STATE_SOURCE],\n      ...store.stateSignals,\n      ...store.props,\n      ...store.methods,\n    });\n    if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n      assertUniqueStoreMembers(store, Reflect.ownKeys(props));\n    }\n\n    return {\n      ...store,\n      props: { ...store.props, ...props },\n    } as InnerSignalStore<object, Props>;\n  };\n}\n"
  },
  {
    "path": "modules/signals/src/with-state.ts",
    "content": "import { Signal, signal } from '@angular/core';\nimport { toDeepSignal } from './deep-signal';\nimport { assertUniqueStoreMembers } from './signal-store-assertions';\nimport {\n  EmptyFeatureResult,\n  InnerSignalStore,\n  SignalsDictionary,\n  SignalStoreFeature,\n  SignalStoreFeatureResult,\n} from './signal-store-models';\nimport { STATE_SOURCE } from './state-source';\n\nexport function withState<State extends object>(\n  stateFactory: () => State\n): SignalStoreFeature<\n  EmptyFeatureResult,\n  { state: State; props: {}; methods: {} }\n>;\nexport function withState<State extends object>(\n  state: State\n): SignalStoreFeature<\n  EmptyFeatureResult,\n  { state: State; props: {}; methods: {} }\n>;\n/**\n * @description\n *\n * Adds state slices to a SignalStore.\n * Accepts an object or a factory function that returns the initial state.\n *\n * @usageNotes\n *\n * ```ts\n * import { signalStore, withState } from '@ngrx/signals';\n *\n * export const CounterStore = signalStore(\n *   withState({ count: 0 })\n * );\n * ```\n */\nexport function withState<State extends object>(\n  stateOrFactory: State | (() => State)\n): SignalStoreFeature<\n  SignalStoreFeatureResult,\n  { state: State; props: {}; methods: {} }\n> {\n  return (store) => {\n    const state = (\n      typeof stateOrFactory === 'function' ? stateOrFactory() : stateOrFactory\n    ) as Record<string | symbol, unknown>;\n    const stateKeys = Reflect.ownKeys(state);\n\n    if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n      assertUniqueStoreMembers(store, stateKeys);\n    }\n\n    const stateSource = store[STATE_SOURCE] as Record<\n      string | symbol,\n      Signal<unknown>\n    >;\n    const stateSignals: SignalsDictionary = {};\n\n    for (const key of stateKeys) {\n      stateSource[key] = signal(state[key]);\n      stateSignals[key] = toDeepSignal(stateSource[key]);\n    }\n\n    return {\n      ...store,\n      stateSignals: { ...store.stateSignals, ...stateSignals },\n    } as InnerSignalStore<State>;\n  };\n}\n"
  },
  {
    "path": "modules/signals/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/signals/testing/index.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/signals/testing/ng-package.json",
    "content": "{\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/signals/testing/spec/types/helpers.ts",
    "content": "export const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  target: 'ES2022',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  strict: true,\n  noImplicitAny: true,\n  paths: {\n    '@ngrx/signals': ['./modules/signals'],\n    '@ngrx/signals/testing': ['./modules/signals/testing'],\n  },\n});\n"
  },
  {
    "path": "modules/signals/testing/spec/types/uprotected.types.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './helpers';\n\ndescribe('unprotected', () => {\n  const expectSnippet = expecter(\n    (code) => `\n        import { computed, inject } from '@angular/core';\n        import { signalStore, withState, withComputed } from '@ngrx/signals';\n        import { unprotected } from '@ngrx/signals/testing';\n\n        ${code}\n      `,\n    compilerOptions()\n  );\n\n  it('replaces StateSource with WritableStateSource', () => {\n    const snippet = `\n      const CounterStore = signalStore(\n        withState({ count: 0 }),\n        withComputed(({ count }) => ({\n          doubleCount: computed(() => count() * 2),\n        })),\n      );\n\n      const store = inject(CounterStore);\n      const unprotectedStore = unprotected(store);\n    `;\n\n    expectSnippet(snippet).toInfer(\n      'unprotectedStore',\n      '{ count: Signal<number>; doubleCount: Signal<number>; [STATE_SOURCE]: { count: WritableSignal<number>; }; }'\n    );\n  });\n\n  it('does not affect the store with an unprotected state', () => {\n    const snippet = `\n      const CounterStore = signalStore(\n        { protectedState: false },\n        withState({ count: 0 }),\n      );\n\n      const store = inject(CounterStore);\n      const unprotectedStore = unprotected(store);\n    `;\n\n    expectSnippet(snippet).toInfer(\n      'unprotectedStore',\n      '{ count: Signal<number>; [STATE_SOURCE]: { count: WritableSignal<number>; }; }'\n    );\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/signals/testing/spec/unprotected.spec.ts",
    "content": "import { signal } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { patchState, signalStore, StateSource, withState } from '@ngrx/signals';\nimport { STATE_SOURCE } from '../../src/state-source';\nimport { unprotected } from '../src';\n\ndescribe('unprotected', () => {\n  it('returns writable state source', () => {\n    const CounterStore = signalStore(\n      { providedIn: 'root' },\n      withState({ count: 0 })\n    );\n\n    const counterStore = TestBed.inject(CounterStore);\n    patchState(unprotected(counterStore), { count: 1 });\n\n    expect(counterStore.count()).toBe(1);\n  });\n\n  it('throws error when provided state source is not writable', () => {\n    const readonlySource: StateSource<{ count: number }> = {\n      [STATE_SOURCE]: { count: signal(0).asReadonly() },\n    };\n\n    expect(() => unprotected(readonlySource)).toThrowError(\n      '@ngrx/signals: The provided source is not writable.'\n    );\n  });\n});\n"
  },
  {
    "path": "modules/signals/testing/src/index.ts",
    "content": "export { unprotected } from './unprotected';\n"
  },
  {
    "path": "modules/signals/testing/src/unprotected.ts",
    "content": "import {\n  isWritableStateSource,\n  Prettify,\n  StateSource,\n  WritableStateSource,\n} from '@ngrx/signals';\n\ntype UnprotectedSource<Source extends StateSource<object>> =\n  Source extends StateSource<infer State>\n    ? Prettify<\n        Omit<Source, keyof StateSource<State>> & WritableStateSource<State>\n      >\n    : never;\n\n/**\n * @description\n *\n * Allows updating the protected state of a SignalStore for testing purposes.\n *\n * @usageNotes\n *\n * ```ts\n * import { TestBed } from '@angular/core/testing';\n * import {\n *   patchState,\n *   signalStore,\n *   withState,\n *   withComputed,\n * } from '@ngrx/signals';\n * import { unprotected } from '@ngrx/signals/testing';\n *\n * const UserStore = signalStore(\n *   { providedIn: 'root' },\n *   withState({ firstName: 'Eric', lastName: 'Clapton' }),\n *   withComputed(({ firstName, lastName }) => ({\n *     fullName: () => `${firstName()} ${lastName()}`,\n *   }))\n * );\n *\n * describe('UserStore', () => {\n *   it('recomputes fullName on firstName changes', () => {\n *     const userStore = TestBed.inject(UserStore);\n *\n *     patchState(unprotected(userStore), { firstName: 'Patrick' });\n *     expect(userStore.fullName()).toBe('Patrick Clapton');\n *   });\n * });\n * ```\n */\nexport function unprotected<Source extends StateSource<object>>(\n  source: Source\n): UnprotectedSource<Source> {\n  if (isWritableStateSource(source)) {\n    return source as UnprotectedSource<Source>;\n  }\n\n  throw new Error('@ngrx/signals: The provided source is not writable.');\n}\n"
  },
  {
    "path": "modules/signals/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../../\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"downlevelIteration\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"outDir\": \"../../dist/modules/signals\",\n    \"paths\": {},\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"files\": [\"index.ts\"],\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/signals\"\n  }\n}\n"
  },
  {
    "path": "modules/signals/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/signals\",\n    \"paths\": {\n      \"@ngrx/signals/schematics-core\": [\"./schematics-core\"],\n      \"@ngrx/signals\": [\"./src\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/signals/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"node\", \"vitest/globals\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/signals/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [\n      angular(),\n      nxViteTsPaths(),\n    ],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default']\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "modules/store/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/store/README.md",
    "content": "# @ngrx/store\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/store/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: [\n      '**/dist',\n      '**/schematics-core/**/*.ts',\n    ],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@nx/enforce-module-boundaries': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/store/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['testing/**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@nx/enforce-module-boundaries': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/store/testing/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/store/index.ts",
    "content": "/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\nexport * from './public_api';\n"
  },
  {
    "path": "modules/store/migrations/13_0_0-beta/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\n\ndescribe('Store Migration 13_0_0 beta', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/store/migrations/migration.json'\n  );\n  const pkgName = 'store';\n\n  it(`should replace createFeatureSelector usages with 2 generics`, async () => {\n    const contents = `\nimport {createFeatureSelector} from '@ngrx/store'\n\n// untouched\nconst featureSelector1 = createFeatureSelector('feature1');\nconst featureSelector2 = createFeatureSelector<Feature>(feature2);\nconst featureSelector3 = createFeatureSelector<State,Feature,SomethingElse>(feature3);\nconst featureSelector4 = createFeatureSelector<fromFeat.State>('feature4');\n\n// modified\nconst featureSelector5 = createFeatureSelector<State, Feature>('feature5');\nconst featureSelector6 = createFeatureSelector<State,Feature>(feature6);\nconst featureSelector7 = createFeatureSelector<fromRoot.State, fromFeat.State>('feature7');\n`;\n\n    const expected = `\nimport {createFeatureSelector} from '@ngrx/store'\n\n// untouched\nconst featureSelector1 = createFeatureSelector('feature1');\nconst featureSelector2 = createFeatureSelector<Feature>(feature2);\nconst featureSelector3 = createFeatureSelector<State,Feature,SomethingElse>(feature3);\nconst featureSelector4 = createFeatureSelector<fromFeat.State>('feature4');\n\n// modified\nconst featureSelector5 = createFeatureSelector< Feature>('feature5');\nconst featureSelector6 = createFeatureSelector<Feature>(feature6);\nconst featureSelector7 = createFeatureSelector< fromFeat.State>('feature7');\n`;\n\n    const appTree = new UnitTestTree(Tree.empty());\n    appTree.create('./fixture.ts', contents);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const newTree = await runner.runSchematic(\n      `ngrx-${pkgName}-migration-13-beta`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('fixture.ts');\n\n    expect(file).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "modules/store/migrations/13_0_0-beta/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  RemoveChange,\n  commitChanges,\n} from '../../schematics-core';\n\nfunction updateCreateFeatureSelectorGenerics(): Rule {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const runMigration = sourceFile.statements\n        .filter(ts.isImportDeclaration)\n        .filter(\n          (importDeclaration) =>\n            importDeclaration.moduleSpecifier.getText(sourceFile) ===\n              \"'@ngrx/store'\" ||\n            importDeclaration.moduleSpecifier.getText(sourceFile) ===\n              '\"@ngrx/store\"'\n        )\n        .some((importDeclaration) => {\n          return importDeclaration.importClause?.namedBindings\n            ?.getText(sourceFile)\n            .includes('createFeatureSelector');\n        });\n\n      if (!runMigration) return;\n\n      const changes: RemoveChange[] = [];\n      ts.forEachChild(sourceFile, crawl);\n      return commitChanges(tree, sourceFile.fileName, changes);\n\n      function crawl(node: ts.Node) {\n        ts.forEachChild(node, crawl);\n\n        if (!ts.isCallExpression(node)) return;\n        if (node.typeArguments?.length !== 2) return;\n        if (!ts.isIdentifier(node.expression)) return;\n        if (node.expression.text !== 'createFeatureSelector') return;\n\n        changes.push(\n          new RemoveChange(\n            sourceFile.fileName,\n            node.typeArguments[0].pos,\n            node.typeArguments[1].pos\n          )\n        );\n      }\n    });\n  };\n}\n\nexport default function (): Rule {\n  return chain([updateCreateFeatureSelectorGenerics()]);\n}\n"
  },
  {
    "path": "modules/store/migrations/13_0_0-rc/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\n\ndescribe('Store Migration 13_0_1', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/store/migrations/migration.json'\n  );\n  const pkgName = 'store';\n\n  it(`should replace createSelector with explicit generics usages with explicit generics using Slices tuple`, async () => {\n    const contents = `\nimport {createSelector} from '@ngrx/store'\n\n// untouched\nconst selectorWithoutGenericArgs = createSelector(() => 1, a => a);\nconst selectorWithPropsWithGenericArgs = createSelector<State, number, number, number>(() => 1, (a, b) => a + b);\n\n// modified\nconst selectorWithOneSliceWithGenericArgs = createSelector<State,number,string>(() => 1, a => a);\nconst selectorWithTwoSlicesWithGenericArgs = createSelector<State,number,string,number>(() => 1, () => '2', (a, b) => a + b);\nconst selectorWithOneSliceWithGenericArgsWithWhitespace = createSelector<State, number, string>(() => 1, a => a);\nconst selectorWithTwoSlicesWithGenericArgsWithWhitespace = createSelector<State,  number, string, number>(() => 1, () => '2', (a, b) => a + b);\n`;\n\n    const expected = `\nimport {createSelector} from '@ngrx/store'\n\n// untouched\nconst selectorWithoutGenericArgs = createSelector(() => 1, a => a);\nconst selectorWithPropsWithGenericArgs = createSelector<State, number, number, number>(() => 1, (a, b) => a + b);\n\n// modified\nconst selectorWithOneSliceWithGenericArgs = createSelector<State,[number],string>(() => 1, a => a);\nconst selectorWithTwoSlicesWithGenericArgs = createSelector<State,[number,string],number>(() => 1, () => '2', (a, b) => a + b);\nconst selectorWithOneSliceWithGenericArgsWithWhitespace = createSelector<State,[number],string>(() => 1, a => a);\nconst selectorWithTwoSlicesWithGenericArgsWithWhitespace = createSelector<State,[number,string],number>(() => 1, () => '2', (a, b) => a + b);\n`;\n    const appTree = new UnitTestTree(Tree.empty());\n    appTree.create('./fixture.ts', contents);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const newTree = await runner.runSchematic(\n      `ngrx-${pkgName}-migration-13-rc`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('fixture.ts');\n\n    expect(file).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "modules/store/migrations/13_0_0-rc/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  RemoveChange,\n  commitChanges,\n  InsertChange,\n} from '../../schematics-core';\n\nfunction updateCreateSelectorGenerics(): Rule {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const runMigration = sourceFile.statements\n        .filter(ts.isImportDeclaration)\n        .filter(\n          (importDeclaration) =>\n            importDeclaration.moduleSpecifier.getText(sourceFile) ===\n              \"'@ngrx/store'\" ||\n            importDeclaration.moduleSpecifier.getText(sourceFile) ===\n              '\"@ngrx/store\"'\n        )\n        .some((importDeclaration) => {\n          return importDeclaration.importClause?.namedBindings\n            ?.getText(sourceFile)\n            .includes('createSelector');\n        });\n\n      if (!runMigration) return;\n\n      const changes: (RemoveChange | InsertChange)[] = [];\n      ts.forEachChild(sourceFile, crawl);\n      return commitChanges(tree, sourceFile.fileName, changes);\n\n      function crawl(node: ts.Node) {\n        ts.forEachChild(node, crawl);\n\n        if (!ts.isCallExpression(node)) return;\n\n        const { typeArguments } = node;\n\n        if (!typeArguments?.length) return;\n        const typeArgumentsLength = typeArguments?.length;\n        if (typeArgumentsLength < 3) return;\n        if (!ts.isIdentifier(node.expression)) return;\n        if (node.expression.text !== 'createSelector') return;\n\n        const lastTypeArgumentIndex = typeArgumentsLength - 1;\n        const slicesTypeArguments = typeArguments.slice(\n          1,\n          lastTypeArgumentIndex\n        );\n        const updatedTypeArguments = [\n          typeArguments[0],\n          slicesTypeArguments,\n          typeArguments[lastTypeArgumentIndex],\n        ];\n\n        const isSelectorWithProps =\n          node.arguments.length === typeArguments.length - 2;\n        if (isSelectorWithProps) return;\n\n        const newTypeArguments = updatedTypeArguments\n          .map((arg) =>\n            Array.isArray(arg)\n              ? `[${arg.map((a) => a.getText(sourceFile)).join(',')}]`\n              : arg.getText(sourceFile)\n          )\n          .join(',');\n\n        changes.push(\n          new RemoveChange(\n            sourceFile.fileName,\n            typeArguments.pos,\n            typeArguments?.end\n          ),\n          new InsertChange(\n            sourceFile.fileName,\n            typeArguments.pos,\n            newTypeArguments\n          )\n        );\n      }\n    });\n  };\n}\n\nexport default function (): Rule {\n  return chain([updateCreateSelectorGenerics]);\n}\n"
  },
  {
    "path": "modules/store/migrations/15_2_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\n\ndescribe('Store Migration 15_2_0', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/store/migrations/migration.json'\n  );\n  const pkgName = 'store';\n\n  it(`should replace remove the State type argument`, async () => {\n    const input = `\n    import {createFeature} from '@ngrx/store';\n    interface AppState {\n      users: State;\n    }\n    export const usersFeature = createFeature<AppState>({\n      name: 'users',\n      reducer: createReducer(initialState, /* case reducers */),\n    });\n`;\n\n    const expected = `\n    import {createFeature} from '@ngrx/store';\n    interface AppState {\n      users: State;\n    }\n    export const usersFeature = createFeature({\n      name: 'users',\n      reducer: createReducer(initialState, /* case reducers */),\n    });\n`;\n    const appTree = new UnitTestTree(Tree.empty());\n    appTree.create('./fixture.ts', input);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const newTree = await runner.runSchematic(\n      `ngrx-${pkgName}-migration-15-2-0`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('fixture.ts');\n\n    expect(file).toBe(expected);\n  });\n\n  it(`should not update createFeature when correctly used`, async () => {\n    const input = `\n    import {createFeature} from '@ngrx/store';\n    export const usersFeature = createFeature({\n      name: 'users',\n      reducer: createReducer(initialState, /* case reducers */),\n    });\n`;\n\n    const appTree = new UnitTestTree(Tree.empty());\n    appTree.create('./fixture.ts', input);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const newTree = await runner.runSchematic(\n      `ngrx-${pkgName}-migration-15-2-0`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('fixture.ts');\n\n    expect(file).toBe(input);\n  });\n});\n"
  },
  {
    "path": "modules/store/migrations/15_2_0/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  RemoveChange,\n  commitChanges,\n  InsertChange,\n} from '../../schematics-core';\n\nfunction updatecreateFeature(): Rule {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const runMigration = sourceFile.statements\n        .filter(ts.isImportDeclaration)\n        .filter(\n          (importDeclaration) =>\n            importDeclaration.moduleSpecifier.getText(sourceFile) ===\n              \"'@ngrx/store'\" ||\n            importDeclaration.moduleSpecifier.getText(sourceFile) ===\n              '\"@ngrx/store\"'\n        )\n        .some((importDeclaration) => {\n          return importDeclaration.importClause?.namedBindings\n            ?.getText(sourceFile)\n            .includes('createFeature');\n        });\n\n      if (!runMigration) return;\n\n      const changes: (RemoveChange | InsertChange)[] = [];\n      ts.forEachChild(sourceFile, crawl);\n      return commitChanges(tree, sourceFile.fileName, changes);\n\n      function crawl(node: ts.Node) {\n        ts.forEachChild(node, crawl);\n\n        if (!ts.isCallExpression(node)) return;\n\n        const { typeArguments } = node;\n\n        if (!typeArguments?.length) return;\n\n        if (!ts.isIdentifier(node.expression)) return;\n        if (node.expression.text !== 'createFeature') return;\n\n        changes.push(\n          new RemoveChange(\n            sourceFile.fileName,\n            // to include <\n            typeArguments.pos - 1,\n            // to include >\n            typeArguments.end + 1\n          )\n        );\n      }\n    });\n  };\n}\n\nexport default function (): Rule {\n  return chain([updatecreateFeature]);\n}\n"
  },
  {
    "path": "modules/store/migrations/16_0_0-beta/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\n\ndescribe('Store Migration 16_0_0-beta', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/store/migrations/migration.json'\n  );\n\n  it(`should replace getMockStore with createMockStore`, async () => {\n    const input = `\n    import { getMockStore } from '@ngrx/store';\n    import { SomethingElse } from '@ngrx/store';\n    import {getMockStore} from '@ngrx/store';\n    import {foo, getMockStore, bar} from '@ngrx/store';\n\n    const mockStore = getMockStore();\n\n    it('just a test', () => {\n      const s =getMockStore();\n    })\n`;\n\n    const expected = `\n    import {createMockStore } from '@ngrx/store';\n    import { SomethingElse } from '@ngrx/store';\n    import {createMockStore} from '@ngrx/store';\n    import {foo,createMockStore, bar} from '@ngrx/store';\n\n    const mockStore =createMockStore();\n\n    it('just a test', () => {\n      const s =createMockStore();\n    })\n`;\n    const appTree = new UnitTestTree(Tree.empty());\n    appTree.create('./fixture.ts', input);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const newTree = await runner.runSchematic(\n      `ngrx-store-migration-16-0-0-beta`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('fixture.ts');\n\n    expect(file).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "modules/store/migrations/16_0_0-beta/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  RemoveChange,\n  InsertChange,\n  commitChanges,\n} from '../../schematics-core';\n\nfunction updateGetMockStore(): Rule {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const imports = sourceFile.statements\n        .filter(ts.isImportDeclaration)\n        .filter(\n          (importDeclaration) =>\n            importDeclaration.moduleSpecifier.getText(sourceFile) ===\n              \"'@ngrx/store'\" ||\n            importDeclaration.moduleSpecifier.getText(sourceFile) ===\n              '\"@ngrx/store\"'\n        )\n        .flatMap((importDeclaration) => {\n          return importDeclaration.importClause?.namedBindings ?? [];\n        })\n        .flatMap((binding) =>\n          ts.isNamedImports(binding) ? binding.elements : []\n        )\n        .filter(\n          (element) => element.name.getText(sourceFile) === 'getMockStore'\n        );\n      if (!imports.length) return;\n\n      const changes: (InsertChange | RemoveChange)[] = [];\n      imports.forEach((binding) => {\n        changes.push(\n          new RemoveChange(sourceFile.fileName, binding.pos, binding.end)\n        );\n        changes.push(\n          new InsertChange(sourceFile.fileName, binding.pos, 'createMockStore')\n        );\n      });\n\n      ts.forEachChild(sourceFile, crawl);\n      return commitChanges(tree, sourceFile.fileName, changes);\n\n      function crawl(node: ts.Node) {\n        ts.forEachChild(node, crawl);\n\n        if (!ts.isCallExpression(node)) return;\n        if (!ts.isIdentifier(node.expression)) return;\n        if (node.expression.text !== 'getMockStore') return;\n\n        changes.push(\n          new RemoveChange(\n            sourceFile.fileName,\n            node.expression.pos,\n            node.expression.end\n          )\n        );\n        changes.push(\n          new InsertChange(\n            sourceFile.fileName,\n            node.expression.pos,\n            'createMockStore'\n          )\n        );\n      }\n    });\n  };\n}\n\nexport default function (): Rule {\n  return chain([updateGetMockStore]);\n}\n"
  },
  {
    "path": "modules/store/migrations/18_0_0-beta/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport { createWorkspace } from '@ngrx/schematics-core/testing';\nimport * as path from 'path';\nimport { tags } from '@angular-devkit/core';\n\ndescribe('Store Migration to 18.0.0-beta', () => {\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/store/migrations/migration.json'\n  );\n  const schematicRunner = new SchematicTestRunner('schematics', collectionPath);\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  const verifySchematic = async (input: string, output: string) => {\n    appTree.create('main.ts', input);\n    appTree.create(\n      'other.ts',\n      `const action: TypedAction<'[SOURCE] Event'> = { type: '[SOURCE] Event' };`\n    );\n\n    const tree = await schematicRunner.runSchematic(\n      `ngrx-store-migration-18-beta`,\n      {},\n      appTree\n    );\n\n    const actual = tree.readContent('main.ts');\n    expect(actual).toBe(output);\n\n    const other = tree.readContent('other.ts');\n    expect(other).toBe(\n      `const action: TypedAction<'[SOURCE] Event'> = { type: '[SOURCE] Event' };`\n    );\n  };\n\n  describe('replacements', () => {\n    it('should replace the import', async () => {\n      const input = tags.stripIndent`\nimport { TypedAction } from '@ngrx/store/src/models';\n\nconst action: TypedAction<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n\nexport type ActionCreatorWithOptionalProps<T> = T extends undefined\n  ? ActionCreator<string, () => TypedAction<string>>\n  : ActionCreator<\n      string,\n      (props: T & NotAllowedCheck<T & object>) => T & TypedAction<string>\n    >;\n\nclass Fixture {\n  errorHandler(input?: (payload?: any) => TypedAction<any>): Action | never {}\n}`;\n      const output = tags.stripIndent`\nimport { Action } from '@ngrx/store';\n\nconst action: Action<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n\nexport type ActionCreatorWithOptionalProps<T> = T extends undefined\n  ? ActionCreator<string, () => Action<string>>\n  : ActionCreator<\n      string,\n      (props: T & NotAllowedCheck<T & object>) => T & Action<string>\n    >;\n\nclass Fixture {\n  errorHandler(input?: (payload?: any) => Action<any>): Action | never {}\n}`;\n\n      await verifySchematic(input, output);\n    });\n\n    it('should also work with \" in imports', async () => {\n      const input = tags.stripIndent`\nimport { TypedAction } from \"@ngrx/store/src/models\";\nconst action: TypedAction<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n`;\n      const output = tags.stripIndent`\nimport { Action } from '@ngrx/store';\nconst action: Action<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n`;\n      await verifySchematic(input, output);\n    });\n\n    it('should replace if multiple imports are inside an import statement', async () => {\n      const input = tags.stripIndent`\nimport { TypedAction, ActionReducer } from '@ngrx/store/src/models';\n\nconst action: TypedAction<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n      `;\n      const output = tags.stripIndent`\nimport { ActionReducer } from '@ngrx/store/src/models';\nimport { Action } from '@ngrx/store';\n\nconst action: Action<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n      `;\n\n      await verifySchematic(input, output);\n    });\n\n    it('should add Action to existing import', async () => {\n      const input = tags.stripIndent`\nimport { TypedAction } from '@ngrx/store/src/models';\nimport { createAction } from '@ngrx/store';\n\nconst action: TypedAction<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n      `;\n      const output = tags.stripIndent`\nimport { createAction, Action } from '@ngrx/store';\n\nconst action: Action<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n      `;\n      await verifySchematic(input, output);\n    });\n\n    it('should not add Action if already exists', async () => {\n      const input = tags.stripIndent`\nimport { TypedAction } from '@ngrx/store/src/models';\nimport { Action } from '@ngrx/store';\n\nconst action: TypedAction<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n      `;\n      const output = tags.stripIndent`\nimport { Action } from '@ngrx/store';\n\nconst action: Action<'[SOURCE] Event'> = { type: '[SOURCE] Event' };\n      `;\n      await verifySchematic(input, output);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/migrations/18_0_0-beta/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Tree,\n  Rule,\n  chain,\n  SchematicContext,\n} from '@angular-devkit/schematics';\nimport {\n  Change,\n  commitChanges,\n  createReplaceChange,\n  InsertChange,\n  visitTSSourceFiles,\n} from '../../schematics-core';\nimport { createRemoveChange } from '../../schematics-core/utility/change';\n\nconst storeModelsPath = '@ngrx/store/src/models';\nconst filesWithChanges: string[] = [];\n\nexport function migrateStoreTypedAction(): Rule {\n  return (tree: Tree, ctx: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const changes: Change[] = [];\n\n      const importDeclarations = new Array<ts.ImportDeclaration>();\n      getImportDeclarations(sourceFile, importDeclarations);\n\n      const storeModelsImportsAndDeclaration = importDeclarations\n        .map((storeModelsImportDeclaration) => {\n          const storeModelsImports = getStoreModelsNamedBindings(\n            storeModelsImportDeclaration\n          );\n          if (storeModelsImports) {\n            return { storeModelsImports, storeModelsImportDeclaration };\n          } else {\n            return undefined;\n          }\n        })\n        .find(Boolean);\n\n      if (!storeModelsImportsAndDeclaration) {\n        return;\n      }\n\n      const { storeModelsImports, storeModelsImportDeclaration } =\n        storeModelsImportsAndDeclaration;\n\n      const storeImportDeclaration = importDeclarations.find(\n        (node) =>\n          node.moduleSpecifier.getText().includes('@ngrx/store') &&\n          !node.moduleSpecifier.getText().includes('@ngrx/store/')\n      );\n\n      const otherStoreModelImports = storeModelsImports.elements\n        .filter((element) => element.name.getText() !== 'TypedAction')\n        .map((element) => element.name.getText())\n        .join(', ');\n\n      // Remove `TypedAction` from @ngrx/store/src/models and leave the other imports\n      if (otherStoreModelImports) {\n        changes.push(\n          createReplaceChange(\n            sourceFile,\n            storeModelsImportDeclaration,\n            storeModelsImportDeclaration.getText(),\n            `import { ${otherStoreModelImports} } from '${storeModelsPath}';`\n          )\n        );\n      }\n      // Remove complete import because it's empty\n      else {\n        changes.push(\n          createRemoveChange(\n            sourceFile,\n            storeModelsImportDeclaration,\n            storeModelsImportDeclaration.getStart(),\n            storeModelsImportDeclaration.getEnd() + 1\n          )\n        );\n      }\n\n      let importAppendedInExistingDeclaration = false;\n      if (storeImportDeclaration?.importClause?.namedBindings) {\n        const bindings = storeImportDeclaration.importClause.namedBindings;\n        if (ts.isNamedImports(bindings)) {\n          // Add import to existing @ngrx/operators\n          const updatedImports = new Set([\n            ...bindings.elements.map((element) => element.name.getText()),\n            'Action',\n          ]);\n          const importStatement = `import { ${[...updatedImports].join(\n            ', '\n          )} } from '@ngrx/store';`;\n          changes.push(\n            createReplaceChange(\n              sourceFile,\n              storeImportDeclaration,\n              storeImportDeclaration.getText(),\n              importStatement\n            )\n          );\n          importAppendedInExistingDeclaration = true;\n        }\n      }\n\n      if (!importAppendedInExistingDeclaration) {\n        // Add new @ngrx/operators import line\n        const importStatement = `import { Action } from '@ngrx/store';`;\n        changes.push(\n          new InsertChange(\n            sourceFile.fileName,\n            storeModelsImportDeclaration.getEnd() + 1,\n            `${importStatement}\\n`\n          )\n        );\n      }\n\n      commitChanges(tree, sourceFile.fileName, changes);\n\n      if (changes.length) {\n        filesWithChanges.push(sourceFile.fileName);\n        ctx.logger.info(\n          `[@ngrx/store] ${sourceFile.fileName}: Replaced TypedAction to Action`\n        );\n      }\n    });\n  };\n}\n\nexport function migrateStoreTypedActionReferences(): Rule {\n  return (tree: Tree, _ctx: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      if (!filesWithChanges.includes(sourceFile.fileName)) {\n        return;\n      }\n      const changes: Change[] = [];\n      const typedActionIdentifiers = new Array<ts.Identifier>();\n      getTypedActionUsages(sourceFile, typedActionIdentifiers);\n\n      typedActionIdentifiers.forEach((identifier) => {\n        changes.push(\n          createReplaceChange(\n            sourceFile,\n            identifier,\n            identifier.getText(),\n            'Action'\n          )\n        );\n      });\n      commitChanges(tree, sourceFile.fileName, changes);\n    });\n  };\n}\n\nfunction getImportDeclarations(\n  node: ts.Node,\n  imports: ts.ImportDeclaration[]\n): void {\n  if (ts.isImportDeclaration(node)) {\n    imports.push(node);\n  }\n\n  ts.forEachChild(node, (childNode) =>\n    getImportDeclarations(childNode, imports)\n  );\n}\n\nfunction getTypedActionUsages(\n  node: ts.Node,\n  nodeIdentifiers: ts.Identifier[]\n): void {\n  if (ts.isIdentifier(node) && node.getText() === 'TypedAction') {\n    nodeIdentifiers.push(node);\n  }\n\n  ts.forEachChild(node, (childNode) =>\n    getTypedActionUsages(childNode, nodeIdentifiers)\n  );\n}\n\nfunction getStoreModelsNamedBindings(\n  node: ts.ImportDeclaration\n): ts.NamedImports | null {\n  const namedBindings = node?.importClause?.namedBindings;\n  if (\n    node.moduleSpecifier.getText().includes(storeModelsPath) &&\n    namedBindings &&\n    ts.isNamedImports(namedBindings)\n  ) {\n    return namedBindings;\n  }\n\n  return null;\n}\n\nexport default function (): Rule {\n  return chain([\n    migrateStoreTypedAction(),\n    migrateStoreTypedActionReferences(),\n  ]);\n}\n"
  },
  {
    "path": "modules/store/migrations/6_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport {\n  createPackageJson,\n  packagePath,\n} from '@ngrx/schematics-core/testing/create-package';\nimport {\n  upgradeVersion,\n  versionPrefixes,\n} from '@ngrx/schematics-core/testing/update';\n\nconst collectionPath = path.join(\n  process.cwd(),\n  'dist/modules/store/migrations/migration.json'\n);\n\ndescribe('Store Migration 6_0_0', () => {\n  let appTree;\n  const pkgName = 'store';\n\n  versionPrefixes.forEach((prefix) => {\n    it(`should install version ${prefix}6.0.0`, async () => {\n      appTree = new UnitTestTree(Tree.empty());\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n      const tree = createPackageJson(prefix, pkgName, appTree);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-01`,\n        {},\n        tree\n      );\n      const pkg = JSON.parse(newTree.readContent(packagePath));\n      expect(pkg.dependencies[`@ngrx/${pkgName}`]).toBe(\n        `${prefix}${upgradeVersion}`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/migrations/6_0_0/index.ts",
    "content": "import { Rule } from '@angular-devkit/schematics';\nimport { updatePackage } from '../../schematics-core';\n\nexport default function (): Rule {\n  return updatePackage('store');\n}\n"
  },
  {
    "path": "modules/store/migrations/8_0_0-beta/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createPackageJson } from '@ngrx/schematics-core/testing/create-package';\n\ndescribe('Store Migration 8_0_0 beta', () => {\n  let appTree: UnitTestTree;\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/store/migrations/migration.json'\n  );\n  const pkgName = 'store';\n  beforeEach(() => {\n    appTree = new UnitTestTree(Tree.empty());\n    appTree.create(\n      '/tsconfig.json',\n      `\n        {\n          \"include\": [**./*.ts\"]\n        }\n       `\n    );\n    createPackageJson('', pkgName, appTree);\n  });\n\n  it(`should replace the meta reducer imports`, async () => {\n    const contents = `\n      import {\n        RuntimeChecks,\n        META_REDUCERS,\n        Store,\n        META_REDUCERS,\n        StoreModule,\n        META_REDUCERS as foo,\n      } from '@ngrx/store';`;\n    const expected = `\n      import {\n        RuntimeChecks,\n        USER_PROVIDED_META_REDUCERS,\n        Store,\n        USER_PROVIDED_META_REDUCERS,\n        StoreModule,\n        USER_PROVIDED_META_REDUCERS as foo,\n      } from '@ngrx/store';`;\n\n    appTree.create('./app.module.ts', contents);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const newTree = await runner.runSchematic(\n      `ngrx-${pkgName}-migration-02`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('app.module.ts');\n\n    expect(file).toBe(expected);\n  });\n\n  it(`should replace the meta reducer assignments`, async () => {\n    const contents = `\n      @NgModule({\n        imports: [\n          CommonModule,\n          BrowserModule,\n          BrowserAnimationsModule,\n          HttpClientModule,\n          AuthModule,\n          AppRoutingModule,\n          StoreModule.forRoot(reducers),\n        ],\n        providers: [\n          {\n            provide: META_REDUCERS,\n            useValue: [fooReducer, barReducer]\n          }\n        ]\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}`;\n    const expected = `\n      @NgModule({\n        imports: [\n          CommonModule,\n          BrowserModule,\n          BrowserAnimationsModule,\n          HttpClientModule,\n          AuthModule,\n          AppRoutingModule,\n          StoreModule.forRoot(reducers),\n        ],\n        providers: [\n          {\n            provide: USER_PROVIDED_META_REDUCERS,\n            useValue: [fooReducer, barReducer]\n          }\n        ]\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}`;\n\n    appTree.create('./app.module.ts', contents);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const newTree = await runner.runSchematic(\n      `ngrx-${pkgName}-migration-02`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('app.module.ts');\n\n    expect(file).toBe(expected);\n  });\n\n  it(`should not run schematics when not using named imports`, async () => {\n    const contents = `\n      import * as store from '@ngrx/store';\n\n      @NgModule({\n        imports: [\n          CommonModule,\n          BrowserModule,\n          BrowserAnimationsModule,\n          HttpClientModule,\n          AuthModule,\n          AppRoutingModule,\n          store.StoreModule.forRoot(reducers),\n        ],\n        providers: [\n          {\n            provide: store.META_REDUCERS,\n            useValue: [fooReducer, barReducer]\n          }\n        ]\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n    appTree.create('./app.module.ts', contents);\n    const runner = new SchematicTestRunner('schematics', collectionPath);\n\n    const logs: string[] = [];\n    runner.logger.subscribe((log) => logs.push(log.message));\n\n    const newTree = await runner.runSchematic(\n      `ngrx-${pkgName}-migration-02`,\n      {},\n      appTree\n    );\n    const file = newTree.readContent('app.module.ts');\n\n    expect(file).toBe(contents);\n\n    expect(logs.length).toBe(1);\n    expect(logs[0]).toMatch(/NgRx 8 Migration: Unable to run the schematics/);\n  });\n});\n"
  },
  {
    "path": "modules/store/migrations/8_0_0-beta/index.ts",
    "content": "import * as ts from 'typescript';\nimport { tags, logging } from '@angular-devkit/core';\nimport {\n  Rule,\n  chain,\n  Tree,\n  SchematicContext,\n} from '@angular-devkit/schematics';\nimport {\n  ReplaceChange,\n  createReplaceChange,\n  visitTSSourceFiles,\n  commitChanges,\n} from '../../schematics-core';\n\nconst META_REDUCERS = 'META_REDUCERS';\n\nfunction updateMetaReducersToken(): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const createChange = (node: ts.Node) =>\n        createReplaceChange(\n          sourceFile,\n          node,\n          META_REDUCERS,\n          'USER_PROVIDED_META_REDUCERS'\n        );\n\n      const changes: ReplaceChange[] = [];\n      changes.push(\n        ...findMetaReducersImportStatements(\n          sourceFile,\n          createChange,\n          context.logger\n        )\n      );\n      changes.push(...findMetaReducersAssignment(sourceFile, createChange));\n\n      return commitChanges(tree, sourceFile.fileName, changes);\n    });\n  };\n}\n\nexport default function (): Rule {\n  return chain([updateMetaReducersToken()]);\n}\n\nfunction findMetaReducersImportStatements(\n  sourceFile: ts.SourceFile,\n  createChange: (node: ts.Node) => ReplaceChange,\n  logger: any\n) {\n  let canRunSchematics = false;\n\n  const metaReducerImports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(isNgRxStoreImport)\n    .filter((p) => {\n      canRunSchematics = Boolean(\n        p.importClause &&\n          p.importClause.namedBindings &&\n          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n          (p.importClause!.namedBindings as ts.NamedImports).elements\n      );\n      return canRunSchematics;\n    })\n    .map((p) =>\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      (p.importClause!.namedBindings! as ts.NamedImports).elements.filter(\n        isMetaReducersImportSpecifier\n      )\n    )\n    .reduce((imports, curr) => imports.concat(curr), []);\n\n  const changes = metaReducerImports.map(createChange);\n  if (!canRunSchematics && changes.length === 0) {\n    logger.info(tags.stripIndent`\n      NgRx 8 Migration: Unable to run the schematics to rename \\`META_REDUCERS\\` to \\`USER_PROVIDED_META_REDUCERS\\`\n      in file '${sourceFile.fileName}'.\n\n      For more info see https://ngrx.io/guide/migration/v8#meta_reducers-token.\n    `);\n  }\n\n  return changes;\n\n  function isNgRxStoreImport(importDeclaration: ts.ImportDeclaration) {\n    return (\n      importDeclaration.moduleSpecifier.getText(sourceFile) === \"'@ngrx/store'\"\n    );\n  }\n\n  function isMetaReducersImportSpecifier(importSpecifier: ts.ImportSpecifier) {\n    const isImport = () => importSpecifier.name.text === META_REDUCERS;\n    const isRenamedImport = () =>\n      importSpecifier.propertyName &&\n      importSpecifier.propertyName.text === META_REDUCERS;\n\n    return (\n      ts.isImportSpecifier(importSpecifier) && (isImport() || isRenamedImport())\n    );\n  }\n}\n\nfunction findMetaReducersAssignment(\n  sourceFile: ts.SourceFile,\n  createChange: (node: ts.Node) => ReplaceChange\n) {\n  const changes: ReplaceChange[] = [];\n  ts.forEachChild(sourceFile, (node) => findMetaReducers(node, changes));\n  return changes;\n\n  function findMetaReducers(node: ts.Node, changes: ReplaceChange[]) {\n    if (\n      ts.isPropertyAssignment(node) &&\n      node.initializer.getText(sourceFile) === META_REDUCERS\n    ) {\n      changes.push(createChange(node.initializer));\n    }\n\n    ts.forEachChild(node, (childNode) => findMetaReducers(childNode, changes));\n  }\n}\n"
  },
  {
    "path": "modules/store/migrations/8_0_0-rc/index.spec.ts",
    "content": "import * as path from 'node:path';\nimport { normalize } from '@angular-devkit/core';\nimport { EmptyTree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\n\ndescribe('Migration to version 8.0.0 rc', () => {\n  describe('removes the usage of the storeFreeze meta-reducer', () => {\n    /* eslint-disable */\n    const fixtures = [\n      {\n        description: 'removes the ngrx-store-freeze import',\n        input: `import { storeFreeze } from 'ngrx-store-freeze';`,\n        expected: ``,\n      },\n      {\n        description: 'removes the usage of storeFeeze',\n        input: `import { storeFreeze } from 'ngrx-store-freeze';\n          const metaReducers = environment.production ? [] : [storeFreeze]`,\n        expected: `\n          const metaReducers = environment.production ? [] : []`,\n      },\n      {\n        description:\n          'removes the usage of storeFeeze with an appending meta-reducer',\n        input: `import { storeFreeze } from 'ngrx-store-freeze';\n          const metaReducers = environment.production ? [] : [storeFreeze, foo]`,\n        expected: `\n          const metaReducers = environment.production ? [] : [foo]`,\n      },\n      {\n        description:\n          'removes the usage of storeFeeze with an prepending meta-reducer',\n        input: `import { storeFreeze } from 'ngrx-store-freeze';\n          const metaReducers = environment.production ? [] : [foo, storeFreeze]`,\n        expected: `\n          const metaReducers = environment.production ? [] : [foo]`,\n      },\n      {\n        description: 'removes the usage of storeFeeze in between meta-reducers',\n        input: `import { storeFreeze } from 'ngrx-store-freeze';\n          const metaReducers = environment.production ? [] : [foo, storeFreeze, bar]`,\n        expected: `\n          const metaReducers = environment.production ? [] : [foo, bar]`,\n      },\n    ];\n    /* eslint-enable */\n\n    const reducerPath = normalize('reducers/index.ts');\n\n    for (const { description, input, expected } of fixtures) {\n      it(description, async () => {\n        const tree = new UnitTestTree(new EmptyTree());\n        // we need a package.json, it will throw otherwise because we're trying to remove ngrx-store-freeze as a dep\n        tree.create('/package.json', JSON.stringify({}));\n        tree.create(reducerPath, input);\n\n        const schematicRunner = createSchematicsRunner();\n        await schematicRunner.runSchematic('ngrx-store-migration-03', {}, tree);\n        await schematicRunner.engine.executePostTasks().toPromise();\n\n        const actual = tree.readContent(reducerPath);\n        expect(actual).toBe(expected);\n      });\n    }\n  });\n\n  describe('StoreModule.forRoot()', () => {\n    /* eslint-disable */\n    const fixtures = [\n      {\n        description:\n          'enables strictStateImmutability and strictActionImmutability runtime checks with no store config',\n        input: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n        isStoreFreezeUsed: true,\n        expected: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS, { runtimeChecks: { strictStateImmutability: true, strictActionImmutability: true }}),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n      },\n      {\n        description:\n          'enables strictStateImmutability and strictActionImmutability runtime checks with store config',\n        input: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS, { metaReducers }),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n        isStoreFreezeUsed: true,\n        expected: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS, { metaReducers, runtimeChecks: { strictStateImmutability: true, strictActionImmutability: true } }),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n      },\n      {\n        description:\n          'enables strictStateImmutability and strictActionImmutability runtime checks with store config ending with a comma',\n        input: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS, {\n              metaReducers,\n            }),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n        isStoreFreezeUsed: true,\n        expected: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS, {\n              metaReducers, runtimeChecks: { strictStateImmutability: true, strictActionImmutability: true },\n            }),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n      },\n      {\n        description:\n          'enables strictStateImmutability and strictActionImmutability runtime checks with an empty store config',\n        input: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS, { }),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n        isStoreFreezeUsed: true,\n        expected: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS, { runtimeChecks: { strictStateImmutability: true, strictActionImmutability: true } }),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n      },\n      {\n        description:\n          'does not add runtime checks when store-freeze was not used',\n        input: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS, { metaReducers }),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n        isStoreFreezeUsed: false,\n        expected: `\n        @NgModule({\n          imports: [\n            StoreModule.forRoot(ROOT_REDUCERS, { metaReducers }),\n          ],\n          bootstrap: [AppComponent],\n        })\n        export class AppModule {}`,\n      },\n    ];\n    /* eslint-enable */\n\n    const appModulePath = normalize('app.module.ts');\n\n    for (const {\n      description,\n      input,\n      isStoreFreezeUsed,\n      expected,\n    } of fixtures) {\n      it(description, async () => {\n        const tree = new UnitTestTree(new EmptyTree());\n        // we need a package.json, it will throw otherwise because we're trying to remove ngrx-store-freeze as a dep\n        tree.create('/package.json', JSON.stringify({}));\n        if (isStoreFreezeUsed) {\n          // we need this file to \"trigger\" the runtime additions\n          tree.create(\n            'reducer.ts',\n            'import { storeFreeze } from \"ngrx-store-freeze\";'\n          );\n        }\n        tree.create(appModulePath, input);\n\n        const schematicRunner = createSchematicsRunner();\n        await schematicRunner.runSchematic('ngrx-store-migration-03', {}, tree);\n        await schematicRunner.engine.executePostTasks().toPromise();\n\n        const actual = tree.readContent(appModulePath);\n        expect(actual).toBe(expected);\n      });\n    }\n  });\n\n  describe('package.json', () => {\n    /* eslint-disable */\n    const fixtures = [\n      {\n        description: 'removes ngrx-store-freeze as a dependency',\n        input: JSON.stringify({\n          dependencies: {\n            'ngrx-store-freeze': '^1.0.0',\n          },\n        }),\n      },\n      {\n        description: 'removes ngrx-store-freeze as a dev dependency',\n        input: JSON.stringify({\n          devDependencies: {\n            'ngrx-store-freeze': '^1.0.0',\n          },\n        }),\n      },\n      {\n        description: 'does not throw when ngrx-store-freeze is not installed',\n        input: JSON.stringify({\n          dependencies: {},\n          devDependencies: {},\n        }),\n      },\n    ];\n    /* eslint-enable */\n\n    const packageJsonPath = normalize('package.json');\n\n    for (const { description, input } of fixtures) {\n      it(description, async () => {\n        const tree = new UnitTestTree(new EmptyTree());\n        tree.create(packageJsonPath, input);\n\n        const schematicRunner = createSchematicsRunner();\n        await schematicRunner.runSchematic('ngrx-store-migration-03', {}, tree);\n        await schematicRunner.engine.executePostTasks().toPromise();\n\n        const actual = tree.readContent(packageJsonPath);\n        expect(actual).not.toMatch(/ngrx-store-freeze/);\n      });\n    }\n  });\n});\n\nfunction createSchematicsRunner() {\n  const schematicRunner = new SchematicTestRunner(\n    'migrations',\n    path.join(process.cwd(), 'dist/modules/store/migrations/migration.json')\n  );\n\n  return schematicRunner;\n}\n"
  },
  {
    "path": "modules/store/migrations/8_0_0-rc/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Rule,\n  chain,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\nimport {\n  RemoveChange,\n  InsertChange,\n  visitTSSourceFiles,\n  commitChanges,\n} from '../../schematics-core';\n\nfunction replaceWithRuntimeChecks(): Rule {\n  return (tree: Tree) => {\n    // only add runtime checks when ngrx-store-freeze is used\n    const _ =\n      visitTSSourceFiles<boolean>(tree, removeUsages) &&\n      visitTSSourceFiles(tree, insertRuntimeChecks);\n  };\n}\n\nfunction removeNgRxStoreFreezePackage(): Rule {\n  return (tree: Tree) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      if (pkg[category] && pkg[category]['ngrx-store-freeze']) {\n        delete pkg[category]['ngrx-store-freeze'];\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n    return tree;\n  };\n}\n\nexport default function (): Rule {\n  return chain([removeNgRxStoreFreezePackage(), replaceWithRuntimeChecks()]);\n}\n\nfunction removeUsages(\n  sourceFile: ts.SourceFile,\n  tree: Tree,\n  ngrxStoreFreezeIsUsed?: boolean\n) {\n  if (\n    sourceFile.fileName.endsWith('.spec.ts') ||\n    sourceFile.fileName.endsWith('.test.ts')\n  ) {\n    return ngrxStoreFreezeIsUsed;\n  }\n\n  const importRemovements = findStoreFreezeImportsToRemove(sourceFile);\n  if (importRemovements.length === 0) {\n    return ngrxStoreFreezeIsUsed;\n  }\n\n  const usageReplacements = findStoreFreezeUsagesToRemove(sourceFile);\n  const changes = [...importRemovements, ...usageReplacements];\n  return commitChanges(tree, sourceFile.fileName, changes);\n}\n\nfunction insertRuntimeChecks(sourceFile: ts.SourceFile, tree: Tree) {\n  if (\n    sourceFile.fileName.endsWith('.spec.ts') ||\n    sourceFile.fileName.endsWith('.test.ts')\n  ) {\n    return;\n  }\n\n  const changes = findRuntimeCHecksToInsert(sourceFile);\n  return commitChanges(tree, sourceFile.fileName, changes);\n}\n\nfunction findStoreFreezeImportsToRemove(sourceFile: ts.SourceFile) {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(({ moduleSpecifier }) => {\n      return (\n        moduleSpecifier.getText(sourceFile) === `'ngrx-store-freeze'` ||\n        moduleSpecifier.getText(sourceFile) === `\"ngrx-store-freeze\"`\n      );\n    });\n\n  const removements = imports.map(\n    (i) =>\n      new RemoveChange(sourceFile.fileName, i.getStart(sourceFile), i.getEnd())\n  );\n  return removements;\n}\n\nfunction findStoreFreezeUsagesToRemove(sourceFile: ts.SourceFile) {\n  const changes: (RemoveChange | InsertChange)[] = [];\n  ts.forEachChild(sourceFile, crawl);\n  return changes;\n\n  function crawl(node: ts.Node) {\n    ts.forEachChild(node, crawl);\n\n    if (!ts.isArrayLiteralExpression(node)) return;\n\n    const elements = node.elements.map((elem) => elem.getText(sourceFile));\n    const elementsWithoutStoreFreeze = elements.filter(\n      (elemText) => elemText !== 'storeFreeze'\n    );\n\n    if (elements.length !== elementsWithoutStoreFreeze.length) {\n      changes.push(\n        new RemoveChange(\n          sourceFile.fileName,\n          node.getStart(sourceFile),\n          node.getEnd()\n        )\n      );\n      changes.push(\n        new InsertChange(\n          sourceFile.fileName,\n          node.getStart(sourceFile),\n          `[${elementsWithoutStoreFreeze.join(', ')}]`\n        )\n      );\n    }\n  }\n}\n\nfunction findRuntimeCHecksToInsert(sourceFile: ts.SourceFile) {\n  const changes: InsertChange[] = [];\n  ts.forEachChild(sourceFile, crawl);\n  return changes;\n\n  function crawl(node: ts.Node) {\n    ts.forEachChild(node, crawl);\n\n    if (!ts.isCallExpression(node)) return;\n\n    const expression = node.expression;\n    if (\n      !(\n        ts.isPropertyAccessExpression(expression) &&\n        expression.expression.getText(sourceFile) === 'StoreModule' &&\n        expression.name.getText(sourceFile) === 'forRoot'\n      )\n    ) {\n      return;\n    }\n\n    const runtimeChecks = `runtimeChecks: { strictStateImmutability: true, strictActionImmutability: true }`;\n\n    // covers StoreModule.forRoot(ROOT_REDUCERS)\n    if (node.arguments.length === 1) {\n      changes.push(\n        new InsertChange(\n          sourceFile.fileName,\n          node.arguments[0].getEnd(),\n          `, { ${runtimeChecks}}`\n        )\n      );\n    } else if (node.arguments.length === 2) {\n      const storeConfig = node.arguments[1];\n      if (ts.isObjectLiteralExpression(storeConfig)) {\n        // covers StoreModule.forRoot(ROOT_REDUCERS, {})\n        if (storeConfig.properties.length === 0) {\n          changes.push(\n            new InsertChange(\n              sourceFile.fileName,\n              storeConfig.getEnd() - 1,\n              `${runtimeChecks} `\n            )\n          );\n        } else {\n          // covers StoreModule.forRoot(ROOT_REDUCERS, { metaReducers })\n          const lastProperty =\n            storeConfig.properties[storeConfig.properties.length - 1];\n\n          changes.push(\n            new InsertChange(\n              sourceFile.fileName,\n              lastProperty.getEnd(),\n              `, ${runtimeChecks}`\n            )\n          );\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "modules/store/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"ngrx-store-migration-01\": {\n      \"description\": \"The road to v6\",\n      \"version\": \"5.2\",\n      \"factory\": \"./6_0_0/index\"\n    },\n    \"ngrx-store-migration-02\": {\n      \"description\": \"The road to v8 beta\",\n      \"version\": \"8-beta\",\n      \"factory\": \"./8_0_0-beta/index\"\n    },\n    \"ngrx-store-migration-03\": {\n      \"description\": \"The road to v8 RC\",\n      \"version\": \"8-rc.1\",\n      \"factory\": \"./8_0_0-rc/index\"\n    },\n    \"ngrx-store-migration-13-beta\": {\n      \"description\": \"The road to v13 beta\",\n      \"version\": \"13-beta\",\n      \"factory\": \"./13_0_0-beta/index\"\n    },\n    \"ngrx-store-migration-13-rc\": {\n      \"description\": \"The road to v13 RC\",\n      \"version\": \"13-rc.1\",\n      \"factory\": \"./13_0_0-rc/index\"\n    },\n    \"ngrx-store-migration-15-2-0\": {\n      \"description\": \"The road to v15.2\",\n      \"version\": \"15.2.0\",\n      \"factory\": \"./15_2_0/index\"\n    },\n    \"ngrx-store-migration-16-0-0-beta\": {\n      \"description\": \"The road to v16.0-beta\",\n      \"version\": \"16.0.0-beta\",\n      \"factory\": \"./16_0_0-beta/index\"\n    },\n    \"ngrx-store-migration-18-beta\": {\n      \"description\": \"As of NgRx v18, the `TypedAction` has been removed in favor of `Action`.\",\n      \"version\": \"18-beta\",\n      \"factory\": \"./18_0_0-beta/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/store/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/store\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/store/package.json",
    "content": "{\n  \"name\": \"@ngrx/store\",\n  \"version\": \"21.0.1\",\n  \"description\": \"RxJS powered Redux for Angular apps\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"RxJS\",\n    \"Angular\",\n    \"Redux\",\n    \"NgRx\",\n    \"Schematics\",\n    \"Angular CLI\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"@angular/core\": \"^21.0.0\",\n    \"rxjs\": \"^6.5.3 || ^7.5.0\"\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"sideEffects\": false,\n  \"dependencies\": {\n    \"tslib\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "modules/store/project.json",
    "content": "{\n  \"name\": \"store\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/store/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/store/tsconfig.build.json\",\n        \"project\": \"modules/store/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package store\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/store/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/store\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/store\"\n          },\n          {\n            \"command\": \"ncp dist/modules/store node_modules/@ngrx/store\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/store\"\n          }\n        ]\n      },\n      \"outputs\": [\n        \"{workspaceRoot}/dist/modules/store\",\n        \"{workspaceRoot}/node_modules/@ngrx/store\"\n      ]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/store/*/**/*.ts\",\n          \"modules/store/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/store\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/store/public_api.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/store/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Adds initial setup for state managment\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/store/schematics/ng-add/files/__statePath__/index.ts.template",
    "content": "import { isDevMode } from '@angular/core';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  createFeatureSelector,\n  createSelector,\n  MetaReducer\n} from '@ngrx/store';\n\nexport interface <%= classify(stateInterface) %> {\n\n}\n\nexport const reducers: ActionReducerMap<<%= classify(stateInterface) %>> = {\n\n};\n\n\nexport const metaReducers: MetaReducer<<%= classify(stateInterface) %>>[] = isDevMode() ? [] : [];\n"
  },
  {
    "path": "modules/store/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as RootStoreOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Store ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/store',\n    path.join(process.cwd(), 'dist/modules/store/schematics/collection.json')\n  );\n  const defaultOptions: RootStoreOptions = {\n    skipPackageJson: false,\n    skipESLintPlugin: false,\n    project: 'bar',\n    module: 'app-module',\n    minimal: false,\n  };\n\n  const projectPath = getTestProjectPath();\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/store']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/store']).toBeUndefined();\n  });\n\n  it('should create the initial store setup', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const files = tree.files;\n    expect(\n      files.indexOf(`${projectPath}/src/app/reducers/index.ts`)\n    ).toBeGreaterThanOrEqual(0);\n  });\n\n  it('should skip the initial store setup files if the minimal flag is provided', async () => {\n    const options = { ...defaultOptions, minimal: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    const files = tree.files;\n\n    expect(content).not.toMatch(\n      /import { reducers, metaReducers } from '\\.\\/reducers';/\n    );\n    expect(content).toMatch(/StoreModule.forRoot\\({}/);\n\n    expect(files.indexOf(`${projectPath}/src/app/reducers/index.ts`)).toBe(-1);\n  });\n\n  it('should import into a specified module', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(\n      /import { reducers, metaReducers } from '\\.\\/reducers';/\n    );\n  });\n\n  it('should import isDevMode correctly', async () => {\n    const options = { ...defaultOptions, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(\n      `${projectPath}/src/app/reducers/index.ts`\n    );\n    expect(content).toMatch(/import { isDevMode } from '@angular\\/core';/);\n  });\n\n  it('should fail if specified module does not exist', async () => {\n    const options = { ...defaultOptions, module: '/src/app/app-moduleXXX.ts' };\n    let thrownError: Error | null = null;\n    try {\n      await schematicRunner.runSchematic('ng-add', options, appTree);\n    } catch (err: any) {\n      thrownError = err;\n    }\n    expect(thrownError).toBeDefined();\n  });\n\n  it('should support a default root state interface name', async () => {\n    const options = { ...defaultOptions, name: 'State' };\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(\n      `${projectPath}/src/app/reducers/index.ts`\n    );\n\n    expect(content).toMatch(/export interface State {/);\n  });\n\n  it('should support a custom root state interface name', async () => {\n    const options = {\n      ...defaultOptions,\n      name: 'State',\n      stateInterface: 'AppState',\n    };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n\n    const content = tree.readContent(\n      `${projectPath}/src/app/reducers/index.ts`\n    );\n\n    expect(content).toMatch(/export interface AppState {/);\n  });\n\n  it('adds the @ngrx/eslint-plugin schematic', async () => {\n    const options = { ...defaultOptions, skipESLintPlugin: false };\n\n    appTree.create('.eslintrc.json', '{}');\n\n    await schematicRunner.runSchematic('ng-add', options, appTree);\n\n    expect(schematicRunner.tasks).toContainEqual({\n      name: 'run-schematic',\n      options: {\n        collection: '@ngrx/eslint-plugin',\n        name: 'ng-add',\n        options: {},\n      },\n    });\n  });\n\n  it('ignores the @ngrx/eslint-plugin schematic when skipped', async () => {\n    const options = { ...defaultOptions, skipESLintPlugin: true };\n\n    await schematicRunner.runSchematic('ng-add', options, appTree);\n\n    expect(schematicRunner.tasks).not.toContainEqual({\n      name: 'run-schematic',\n      options: {\n        collection: '@ngrx/eslint-plugin',\n        name: 'ng-add',\n        options: {},\n      },\n    });\n  });\n\n  describe('Store ng-add Schematic for standalone application', () => {\n    const projectPath = getTestProjectPath(undefined, {\n      name: 'bar-standalone',\n    });\n    const standaloneDefaultOptions = {\n      ...defaultOptions,\n      project: 'bar-standalone',\n    };\n\n    it('provides minimal store setup', async () => {\n      const options = { ...standaloneDefaultOptions, minimal: true };\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n\n      const content = tree.readContent(`${projectPath}/src/app/app.config.ts`);\n      const files = tree.files;\n\n      expect(content).toMatch(/provideStore\\(\\)/);\n      expect(content).not.toMatch(\n        /import { reducers, metaReducers } from '\\.\\/reducers';/\n      );\n      expect(files.indexOf(`${projectPath}/src/app/reducers/index.ts`)).toBe(\n        -1\n      );\n    });\n    it('provides full store setup', async () => {\n      const options = { ...standaloneDefaultOptions };\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n\n      const content = tree.readContent(`${projectPath}/src/app/app.config.ts`);\n      const files = tree.files;\n\n      expect(content).toMatch(/provideStore\\(reducers, \\{ metaReducers \\}\\)/);\n      expect(content).toMatch(\n        /import { reducers, metaReducers } from '\\.\\/reducers';/\n      );\n      expect(\n        files.indexOf(`${projectPath}/src/app/reducers/index.ts`)\n      ).toBeGreaterThanOrEqual(0);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/schematics/ng-add/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  Tree,\n  apply,\n  applyTemplates,\n  branchAndMerge,\n  chain,\n  mergeWith,\n  url,\n  noop,\n  move,\n  filter,\n} from '@angular-devkit/schematics';\nimport {\n  NodePackageInstallTask,\n  RunSchematicTask,\n} from '@angular-devkit/schematics/tasks';\nimport {\n  InsertChange,\n  addImportToModule,\n  buildRelativePath,\n  findModuleFromOptions,\n  getProjectPath,\n  insertImport,\n  stringUtils,\n  addPackageToPackageJson,\n  platformVersion,\n  parseName,\n} from '../../schematics-core';\nimport { Schema as RootStoreOptions } from './schema';\nimport { getProjectMainFile } from '../../schematics-core/utility/project';\nimport {\n  addFunctionalProvidersToStandaloneBootstrap,\n  callsProvidersFunction,\n} from '../../schematics-core/utility/standalone';\nimport { isStandaloneApp } from '@schematics/angular/utility/ng-ast-utils';\n\nfunction addImportToNgModule(options: RootStoreOptions): Rule {\n  return (host: Tree) => {\n    const modulePath = options.module;\n\n    if (!modulePath) {\n      return host;\n    }\n\n    if (!host.exists(modulePath)) {\n      throw new Error('Specified module does not exist');\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const storeModuleReducers = options.minimal ? `{}` : `reducers`;\n\n    const storeModuleConfig = options.minimal\n      ? `{}`\n      : `{\n      metaReducers\n    }`;\n    const storeModuleSetup = `StoreModule.forRoot(${storeModuleReducers}, ${storeModuleConfig})`;\n\n    const statePath = `/${options.path}/${options.statePath}`;\n    const relativePath = buildRelativePath(modulePath, statePath);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      storeModuleSetup,\n      relativePath\n    );\n\n    let changes = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n      storeNgModuleImport,\n    ];\n\n    if (!options.minimal) {\n      changes = changes.concat([\n        insertImport(\n          source,\n          modulePath,\n          'reducers, metaReducers',\n          relativePath\n        ),\n      ]);\n    }\n\n    const recorder = host.beginUpdate(modulePath);\n\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nfunction addNgRxStoreToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/store',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nfunction addNgRxESLintPlugin() {\n  return (host: Tree, context: SchematicContext) => {\n    const eslint = host.read('.eslintrc.json')?.toString('utf-8');\n    if (eslint) {\n      addPackageToPackageJson(\n        host,\n        'devDependencies',\n        '@ngrx/eslint-plugin',\n        platformVersion\n      );\n\n      const installTaskId = context.addTask(new NodePackageInstallTask());\n      context.addTask(\n        new RunSchematicTask('@ngrx/eslint-plugin', 'ng-add', {}),\n        [installTaskId]\n      );\n    }\n    return host;\n  };\n}\n\nfunction addStandaloneConfig(options: RootStoreOptions): Rule {\n  return (host: Tree) => {\n    const mainFile = getProjectMainFile(host, options);\n\n    if (host.exists(mainFile)) {\n      const storeProviderFn = 'provideStore';\n\n      if (callsProvidersFunction(host, mainFile, storeProviderFn)) {\n        // exit because the store config is already provided\n        return host;\n      }\n      const storeProviderOptions = options.minimal\n        ? []\n        : [\n            ts.factory.createIdentifier('reducers'),\n            ts.factory.createIdentifier('{ metaReducers }'),\n          ];\n      const patchedConfigFile = addFunctionalProvidersToStandaloneBootstrap(\n        host,\n        mainFile,\n        storeProviderFn,\n        '@ngrx/store',\n        storeProviderOptions\n      );\n\n      if (options.minimal) {\n        // no need to add imports if it is minimal\n        return host;\n      }\n\n      // insert reducers import into the patched file\n      const configFileContent = host.read(patchedConfigFile);\n      const source = ts.createSourceFile(\n        patchedConfigFile,\n        configFileContent?.toString('utf-8') || '',\n        ts.ScriptTarget.Latest,\n        true\n      );\n      const statePath = `/${options.path}/${options.statePath}`;\n      const relativePath = buildRelativePath(\n        `/${patchedConfigFile}`,\n        statePath\n      );\n\n      const recorder = host.beginUpdate(patchedConfigFile);\n\n      const change = insertImport(\n        source,\n        patchedConfigFile,\n        'reducers, metaReducers',\n        relativePath\n      );\n\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n\n      host.commitUpdate(recorder);\n\n      return host;\n    }\n    throw new SchematicsException(\n      `Main file not found for a project ${options.project}`\n    );\n  };\n}\n\nexport default function (options: RootStoreOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    const mainFile = getProjectMainFile(host, options);\n    const isStandalone = isStandaloneApp(host, mainFile);\n\n    options.path = getProjectPath(host, options);\n\n    const parsedPath = parseName(options.path, '');\n    options.path = parsedPath.path;\n\n    if (options.module && !isStandalone) {\n      options.module = findModuleFromOptions(host, {\n        name: '',\n        module: options.module,\n        path: options.path,\n      });\n    }\n\n    if (options.stateInterface && options.stateInterface !== 'State') {\n      options.stateInterface = stringUtils.classify(options.stateInterface);\n    }\n\n    const templateSource = apply(url('./files'), [\n      filter(() => (options.minimal ? false : true)),\n      applyTemplates({\n        ...stringUtils,\n        ...options,\n      }),\n      move(parsedPath.path),\n    ]);\n\n    const configOrModuleUpdate = isStandalone\n      ? addStandaloneConfig(options)\n      : addImportToNgModule(options);\n\n    return chain([\n      branchAndMerge(chain([configOrModuleUpdate, mergeWith(templateSource)])),\n      options && options.skipPackageJson ? noop() : addNgRxStoreToPackageJson(),\n      options && options.skipESLintPlugin ? noop() : addNgRxESLintPlugin(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/store/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxRootState\",\n  \"title\": \"NgRx Root State Management Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/store as dependency to package.json (e.g., --skipPackageJson).\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the state.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"default\": \"app\",\n      \"description\": \"Allows specification of the declaring module.\",\n      \"alias\": \"m\",\n      \"subtype\": \"filepath\"\n    },\n    \"statePath\": {\n      \"type\": \"string\",\n      \"default\": \"reducers\"\n    },\n    \"stateInterface\": {\n      \"type\": \"string\",\n      \"default\": \"State\",\n      \"description\": \"Specifies the interface for the state.\",\n      \"alias\": \"si\"\n    },\n    \"minimal\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Setup state management without registering initial reducers.\"\n    },\n    \"skipESLintPlugin\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not register the NgRx ESLint Plugin.\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/store/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  skipPackageJson?: boolean;\n  path?: string;\n  project?: string;\n  module?: string;\n  statePath?: string;\n  stateInterface?: string;\n  /**\n   * Setup state management without registering initial reducers.\n   */\n  minimal?: boolean;\n  skipESLintPlugin?: boolean;\n}\n"
  },
  {
    "path": "modules/store/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/store/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/store/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/store/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/store/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/store/spec/action_creator.spec.ts",
    "content": "import { createAction, props, union } from '..';\n\ndescribe('Action Creators', () => {\n  describe('createAction', () => {\n    it('should create an action', () => {\n      const foo = createAction('FOO', (foo: number) => ({ foo }));\n      const fooAction = foo(42);\n\n      expect(fooAction).toEqual({ type: 'FOO', foo: 42 });\n    });\n\n    it('should narrow the action', () => {\n      const foo = createAction('FOO', (foo: number) => ({ foo }));\n      const bar = createAction('BAR', (bar: number) => ({ bar }));\n      const both = union({ foo, bar });\n      const narrow = (action: typeof both) => {\n        if (action.type === foo.type) {\n          expect(action.foo).toEqual(42);\n        } else {\n          throw new Error('Should not get here.');\n        }\n      };\n\n      narrow(foo(42));\n    });\n\n    it('should be serializable', () => {\n      const foo = createAction('FOO', (foo: number) => ({ foo }));\n      const fooAction = foo(42);\n      const text = JSON.stringify(fooAction);\n\n      expect(JSON.parse(text)).toEqual({ type: 'FOO', foo: 42 });\n    });\n  });\n\n  describe('empty', () => {\n    it('should allow empty action', () => {\n      const foo = createAction('FOO');\n      const fooAction = foo();\n\n      expect(fooAction).toEqual({ type: 'FOO' });\n    });\n  });\n\n  describe('props', () => {\n    it('should create an action', () => {\n      const foo = createAction('FOO', props<{ foo: number }>());\n      const fooAction = foo({ foo: 42 });\n\n      expect(fooAction).toEqual({ type: 'FOO', foo: 42 });\n    });\n\n    it('should narrow the action', () => {\n      const foo = createAction('FOO', props<{ foo: number }>());\n      const bar = createAction('BAR', props<{ bar: number }>());\n      const both = union({ foo, bar });\n      const narrow = (action: typeof both) => {\n        if (action.type === foo.type) {\n          expect(action.foo).toEqual(42);\n        } else {\n          throw new Error('Should not get here.');\n        }\n      };\n\n      narrow(foo({ foo: 42 }));\n    });\n\n    it('should allow the union of types in props', () => {\n      interface A {\n        sameProp: 'A';\n      }\n      interface B {\n        sameProp: 'B';\n        extraProp: string;\n      }\n      type U = A | B;\n      const foo = createAction('FOO', props<U>());\n\n      const fooA = foo({ sameProp: 'A' });\n      const fooB = foo({ sameProp: 'B', extraProp: 'allowed' });\n\n      expect(fooA).toEqual({ type: 'FOO', sameProp: 'A' });\n      expect(fooB).toEqual({\n        type: 'FOO',\n        sameProp: 'B',\n        extraProp: 'allowed',\n      });\n    });\n\n    it('should be serializable', () => {\n      const foo = createAction('FOO', props<{ foo: number }>());\n      const fooAction = foo({ foo: 42 });\n      const text = JSON.stringify(fooAction);\n\n      expect(JSON.parse(text)).toEqual({ foo: 42, type: 'FOO' });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/action_group_creator.spec.ts",
    "content": "import { createActionGroup, emptyProps, props } from '..';\n\ndescribe('createActionGroup', () => {\n  const authApiActions = createActionGroup({\n    source: 'Auth API',\n    events: {\n      'Login Success': props<{ userId: number; token: string }>(),\n      'Login Failure': props<{ error: string }>(),\n      'Logout Success': emptyProps(),\n      'Logout Failure': (error: Error) => ({ error }),\n    },\n  });\n  const booksApiActions = createActionGroup({\n    source: 'Books API',\n    events: {\n      ' Load BOOKS  suCCess  ': emptyProps(),\n      loadBooksFailure: emptyProps(),\n    },\n  });\n\n  it('should create action name by camel casing the event name', () => {\n    expect(booksApiActions.loadBOOKSSuCCess).toBeDefined();\n    expect(booksApiActions.loadBooksFailure).toBeDefined();\n  });\n\n  it('should create action type using the \"[Source] Event\" pattern', () => {\n    expect(booksApiActions.loadBOOKSSuCCess().type).toBe(\n      '[Books API]  Load BOOKS  suCCess  '\n    );\n    expect(booksApiActions.loadBooksFailure().type).toBe(\n      '[Books API] loadBooksFailure'\n    );\n  });\n\n  it('should create action with props', () => {\n    const loginSuccess = authApiActions.loginSuccess({\n      userId: 10,\n      token: 'ngrx',\n    });\n    expect(loginSuccess).toEqual({\n      type: '[Auth API] Login Success',\n      userId: 10,\n      token: 'ngrx',\n    });\n\n    const loginFailure = authApiActions.loginFailure({\n      error: 'Login Failure!',\n    });\n    expect(loginFailure).toEqual({\n      type: '[Auth API] Login Failure',\n      error: 'Login Failure!',\n    });\n  });\n\n  it('should create action without props', () => {\n    const logoutSuccess = authApiActions.logoutSuccess();\n    expect(logoutSuccess).toEqual({ type: '[Auth API] Logout Success' });\n  });\n\n  it('should create action with props factory', () => {\n    const error = new Error('Logout Failure!');\n    const logoutFailure = authApiActions.logoutFailure(error);\n    expect(logoutFailure).toEqual({\n      type: '[Auth API] Logout Failure',\n      error,\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/edge.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { select, Store, StoreModule } from '..';\n\nimport { todoCount, todos } from './fixtures/edge_todos';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\ninterface Todo {}\n\ninterface TodoAppSchema {\n  todoCount: number;\n  todos: Todo[];\n}\n\ndescribe('ngRx Store', () => {\n  describe('basic store actions', () => {\n    let store: Store<TodoAppSchema>;\n\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot<TodoAppSchema>({ todos, todoCount } as any),\n        ],\n      });\n\n      store = TestBed.inject(Store);\n    });\n\n    it('should provide an Observable Store', () => {\n      expect(store).toBeDefined();\n    });\n\n    it('should handle re-entrancy', () =>\n      new Promise<void>((done) => {\n        let todosNextCount = 0;\n        let todosCountNextCount = 0;\n\n        store.pipe(select('todos')).subscribe((todos) => {\n          todosNextCount++;\n          store.dispatch({ type: 'SET_COUNT', payload: todos.length });\n        });\n\n        store.pipe(select('todoCount')).subscribe((count) => {\n          todosCountNextCount++;\n        });\n\n        store.dispatch({ type: 'ADD_TODO', payload: { name: 'test' } });\n        expect(todosNextCount).toBe(2);\n        expect(todosCountNextCount).toBe(2);\n\n        setTimeout(() => {\n          expect(todosNextCount).toBe(2);\n          expect(todosCountNextCount).toBe(2);\n          done();\n        }, 10);\n      }));\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/feature_creator.spec.ts",
    "content": "import {\n  createFeature,\n  createReducer,\n  createSelector,\n  Store,\n  StoreModule,\n} from '..';\nimport { TestBed } from '@angular/core/testing';\nimport { take } from 'rxjs/operators';\n\ndescribe('createFeature()', () => {\n  it('should return passed name and reducer', () => {\n    const fooName = 'foo';\n    const fooReducer = createReducer(0);\n\n    const { name, reducer } = createFeature({\n      name: fooName,\n      reducer: fooReducer,\n    });\n\n    expect(name).toBe(fooName);\n    expect(reducer).toBe(fooReducer);\n  });\n\n  it('should create a feature selector', () => {\n    const { selectFooState } = createFeature({\n      name: 'foo',\n      reducer: createReducer({ bar: '' }),\n    });\n\n    expect(selectFooState({ foo: { bar: 'baz' } })).toEqual({ bar: 'baz' });\n  });\n\n  describe('nested selectors', () => {\n    it('should create when feature state is a dictionary', () => {\n      const initialState = { alpha: 123, beta: { bar: 'baz' }, gamma: false };\n\n      const { selectAlpha, selectBeta, selectGamma } = createFeature({\n        name: 'foo',\n        reducer: createReducer(initialState),\n      });\n\n      expect(selectAlpha({ foo: initialState })).toEqual(123);\n      expect(selectBeta({ foo: initialState })).toEqual({ bar: 'baz' });\n      expect(selectGamma({ foo: initialState })).toEqual(false);\n    });\n\n    it('should return undefined when feature state is not defined', () => {\n      const { selectX } = createFeature({\n        name: 'foo',\n        reducer: createReducer({ x: 'y' }),\n      });\n\n      expect(selectX({})).toBe(undefined);\n    });\n\n    it('should not create when feature state is a primitive value', () => {\n      const feature = createFeature({ name: 'foo', reducer: createReducer(0) });\n\n      expect(Object.keys(feature)).toEqual([\n        'name',\n        'reducer',\n        'selectFooState',\n      ]);\n    });\n\n    it('should not create when feature state is null', () => {\n      const feature = createFeature({\n        name: 'foo',\n        reducer: createReducer(null),\n      });\n\n      expect(Object.keys(feature)).toEqual([\n        'name',\n        'reducer',\n        'selectFooState',\n      ]);\n    });\n\n    it('should not create when feature state is an array', () => {\n      const feature = createFeature({\n        name: 'foo',\n        reducer: createReducer([1, 2, 3]),\n      });\n\n      expect(Object.keys(feature)).toEqual([\n        'name',\n        'reducer',\n        'selectFooState',\n      ]);\n    });\n\n    it('should not create when feature state is a date object', () => {\n      const feature = createFeature({\n        name: 'foo',\n        reducer: createReducer(new Date()),\n      });\n\n      expect(Object.keys(feature)).toEqual([\n        'name',\n        'reducer',\n        'selectFooState',\n      ]);\n    });\n  });\n\n  describe('extra selectors', () => {\n    it('should create extra selectors', () => {\n      const initialState = { count1: 9, count2: 10 };\n      const counterFeature = createFeature({\n        name: 'counter',\n        reducer: createReducer(initialState),\n        extraSelectors: ({\n          selectCounterState,\n          selectCount1,\n          selectCount2,\n        }) => ({\n          selectSquaredCount2: createSelector(\n            selectCounterState,\n            ({ count2 }) => count2 * count2\n          ),\n          selectTotalCount: createSelector(\n            selectCount1,\n            selectCount2,\n            (count1, count2) => count1 + count2\n          ),\n          selectCount3: (count: number) =>\n            createSelector(\n              selectCount1,\n              selectCount2,\n              (count1, count2) => count1 + count2 + count\n            ),\n        }),\n      });\n\n      expect(counterFeature.selectCounterState({ counter: initialState })).toBe(\n        initialState\n      );\n      expect(counterFeature.selectCount1({ counter: initialState })).toBe(\n        initialState.count1\n      );\n      expect(counterFeature.selectCount2({ counter: initialState })).toBe(\n        initialState.count2\n      );\n      expect(\n        counterFeature.selectSquaredCount2({ counter: initialState })\n      ).toBe(initialState.count2 * initialState.count2);\n      expect(counterFeature.selectTotalCount({ counter: initialState })).toBe(\n        initialState.count1 + initialState.count2\n      );\n      expect(counterFeature.selectCount3(1)({ counter: initialState })).toBe(\n        initialState.count1 + initialState.count2 + 1\n      );\n      expect(Object.keys(counterFeature)).toEqual([\n        'name',\n        'reducer',\n        'selectCounterState',\n        'selectCount1',\n        'selectCount2',\n        'selectSquaredCount2',\n        'selectTotalCount',\n        'selectCount3',\n      ]);\n    });\n\n    it('should override base selectors if extra selectors have the same names', () => {\n      const initialState = { count1: 10, count2: 100 };\n      const counterFeature = createFeature({\n        name: 'counter',\n        reducer: createReducer(initialState),\n        extraSelectors: ({\n          selectCounterState,\n          selectCount1,\n          selectCount2,\n        }) => ({\n          selectCounterState: createSelector(\n            selectCounterState,\n            ({ count1, count2 }) => `ngrx-${count1}-${count2}`\n          ),\n          selectCount2: createSelector(\n            selectCount2,\n            (count2) => `ngrx-${count2}`\n          ),\n          selectTotalCount: createSelector(\n            selectCount1,\n            selectCount2,\n            (count1, count2) => count1 + count2\n          ),\n        }),\n      });\n\n      expect(counterFeature.selectCounterState({ counter: initialState })).toBe(\n        `ngrx-${initialState.count1}-${initialState.count2}`\n      );\n      expect(counterFeature.selectCount1({ counter: initialState })).toBe(\n        initialState.count1\n      );\n      expect(counterFeature.selectCount2({ counter: initialState })).toBe(\n        `ngrx-${initialState.count2}`\n      );\n      expect(counterFeature.selectTotalCount({ counter: initialState })).toBe(\n        initialState.count1 + initialState.count2\n      );\n      expect(Object.keys(counterFeature)).toEqual([\n        'name',\n        'reducer',\n        'selectCounterState',\n        'selectCount1',\n        'selectCount2',\n        'selectTotalCount',\n      ]);\n    });\n  });\n\n  it('should set up a feature state', () =>\n    new Promise<void>((done) => {\n      const initialFooState = { x: 1, y: 2, z: 3 };\n      const fooFeature = createFeature({\n        name: 'foo',\n        reducer: createReducer(initialFooState),\n      });\n\n      TestBed.configureTestingModule({\n        imports: [StoreModule.forRoot({}), StoreModule.forFeature(fooFeature)],\n      });\n\n      TestBed.inject(Store)\n        .select(fooFeature.name)\n        .pipe(take(1))\n        .subscribe((fooState) => {\n          expect(fooState).toEqual(initialFooState);\n          done();\n        });\n    }));\n});\n"
  },
  {
    "path": "modules/store/spec/fixtures/counter.ts",
    "content": "import { Action } from '../../src/models';\n\nexport const INCREMENT = 'INCREMENT';\nexport const DECREMENT = 'DECREMENT';\nexport const RESET = 'RESET';\n\nexport function counterReducer(state = 0, action: Action) {\n  switch (action.type) {\n    case INCREMENT:\n      return state + 1;\n    case DECREMENT:\n      return state - 1;\n    case RESET:\n      return 0;\n    default:\n      return state;\n  }\n}\n\nexport function counterReducer2(state = 0, action: Action) {\n  switch (action.type) {\n    case INCREMENT:\n      return state + 1;\n    case DECREMENT:\n      return state - 1;\n    case RESET:\n      return 0;\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "modules/store/spec/fixtures/edge_todos.ts",
    "content": "interface State {\n  id: string;\n  text: string;\n  completed: boolean;\n}\n\nconst todo = (state: State | undefined, action: any) => {\n  switch (action.type) {\n    case 'ADD_TODO':\n      return {\n        id: action.payload.id,\n        text: action.payload.text,\n        completed: false,\n      };\n    case 'TOGGLE_TODO':\n      if (state?.id !== action.id) {\n        return state;\n      }\n\n      return Object.assign({}, state, {\n        completed: !state?.completed,\n      });\n\n    default:\n      return state;\n  }\n};\n\nexport const todos = (state = [], action: any) => {\n  switch (action.type) {\n    case 'ADD_TODO':\n      return [...state, todo(undefined, action)];\n    case 'TOGGLE_TODO':\n      return state.map((t) => todo(t, action));\n    default:\n      return state;\n  }\n};\n\nexport const todoCount = (state = 0, action: any) => {\n  switch (action.type) {\n    case 'SET_COUNT':\n      return action.payload;\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "modules/store/spec/fixtures/todos.ts",
    "content": "export interface TodoItem {\n  id: number;\n  completed: boolean;\n  text: string;\n}\n\nexport const ADD_TODO = 'ADD_TODO';\nexport const COMPLETE_TODO = 'COMPLETE_TODO';\nexport const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';\nexport const COMPLETE_ALL_TODOS = 'COMPLETE_ALL_TODOS';\n\nlet _id = 0;\n\nexport function resetId() {\n  _id = 0;\n}\n\nexport const VisibilityFilters = {\n  SHOW_ALL: 'SHOW_ALL',\n  SHOW_COMPLETED: 'SHOW_COMPLETED',\n  SHOW_ACTIVE: 'SHOW_ACTIVE',\n};\n\nexport function visibilityFilter(\n  state = VisibilityFilters.SHOW_ALL,\n  { type, payload }: any\n) {\n  switch (type) {\n    case SET_VISIBILITY_FILTER:\n      return payload;\n    default:\n      return state;\n  }\n}\n\nexport function todos(\n  state: TodoItem[] = [],\n  { type, payload }: any\n): TodoItem[] {\n  switch (type) {\n    case ADD_TODO:\n      return [\n        ...state,\n        {\n          id: ++_id,\n          text: payload.text,\n          completed: false,\n        },\n      ];\n    case COMPLETE_ALL_TODOS:\n      return state.map((todo) => ({ ...todo, completed: true }));\n    case COMPLETE_TODO:\n      return state.map((todo) =>\n        todo.id === payload.id ? { ...todo, completed: true } : todo\n      );\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "modules/store/spec/flags.spec.ts",
    "content": "import { isNgrxMockEnvironment, setNgrxMockEnvironment } from '../src/flags';\n\ndescribe(`Store flags`, () => {\n  describe(`isNgrxMockEnvironment()`, () => {\n    it(`should return false by default`, () => {\n      expect(isNgrxMockEnvironment()).toBe(false);\n    });\n\n    it(`should return the correct flag`, () => {\n      setNgrxMockEnvironment(true);\n      expect(isNgrxMockEnvironment()).toBe(true);\n\n      setNgrxMockEnvironment(false);\n      expect(isNgrxMockEnvironment()).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/helpers.spec.ts",
    "content": "import { capitalize, uncapitalize } from '../src/helpers';\n\ndescribe('helpers', () => {\n  describe('capitalize', () => {\n    it('should capitalize the text', () => {\n      expect(capitalize('ngrx')).toEqual('Ngrx');\n    });\n\n    it('should return an empty string when the text is an empty string', () => {\n      expect(capitalize('')).toEqual('');\n    });\n  });\n\n  describe('uncapitalize', () => {\n    it('should uncapitalize the text', () => {\n      expect(uncapitalize('NGRX')).toEqual('nGRX');\n    });\n\n    it('should return an empty string when the text is an empty string', () => {\n      expect(uncapitalize('')).toEqual('');\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/integration.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  select,\n  Store,\n  StoreModule,\n  createFeatureSelector,\n  createSelector,\n} from '..';\nimport { combineLatest } from 'rxjs';\nimport { first, toArray, take, map } from 'rxjs/operators';\n\nimport { INITIAL_STATE, ReducerManager, State } from '../src/private_export';\nimport {\n  ADD_TODO,\n  COMPLETE_ALL_TODOS,\n  COMPLETE_TODO,\n  SET_VISIBILITY_FILTER,\n  todos,\n  visibilityFilter,\n  VisibilityFilters,\n  resetId,\n} from './fixtures/todos';\nimport { RouterTestingModule } from '@angular/router/testing';\nimport { NgModule } from '@angular/core';\nimport { Router } from '@angular/router';\n\ninterface Todo {\n  id: number;\n  text: string;\n  completed: boolean;\n}\n\ninterface TodoAppSchema {\n  visibilityFilter: string;\n  todos: Todo[];\n}\n\ndescribe('ngRx Integration spec', () => {\n  describe('todo integration spec', function () {\n    let store: Store<TodoAppSchema>;\n    let state: State<TodoAppSchema>;\n\n    const initialState = {\n      todos: [],\n      visibilityFilter: VisibilityFilters.SHOW_ALL,\n    };\n    const reducers: ActionReducerMap<TodoAppSchema, any> = {\n      todos: todos,\n      visibilityFilter: visibilityFilter,\n    };\n\n    beforeEach(() => {\n      resetId();\n      vi.spyOn(reducers, 'todos');\n\n      TestBed.configureTestingModule({\n        imports: [StoreModule.forRoot(reducers, { initialState })],\n      });\n\n      store = TestBed.inject(Store);\n      state = TestBed.inject(State);\n    });\n\n    it('should successfully instantiate', () => {\n      expect(store).toBeDefined();\n    });\n\n    it('should combine reducers automatically if a key/value map is provided', () =>\n      new Promise<void>((done) => {\n        const action = { type: 'Test Action' };\n        const reducer$ = TestBed.inject(ReducerManager);\n\n        reducer$.pipe(first()).subscribe((reducer: ActionReducer<any, any>) => {\n          expect(reducer).toBeDefined();\n          expect(typeof reducer === 'function').toBe(true);\n\n          reducer({ todos: [] }, action);\n\n          expect(reducers.todos).toHaveBeenCalledWith([], action);\n          done();\n        });\n      }));\n\n    it('should use a provided initial state', () => {\n      const resolvedInitialState = TestBed.inject(INITIAL_STATE);\n\n      expect(resolvedInitialState).toEqual(initialState);\n    });\n\n    it('should start with no todos and showing all filter', () => {\n      expect(state.value.todos.length).toEqual(0);\n      expect(state.value.visibilityFilter).toEqual(VisibilityFilters.SHOW_ALL);\n    });\n\n    it('should add a todo', () => {\n      store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n\n      expect(state.value.todos.length).toEqual(1);\n      expect(state.value.todos[0].text).toEqual('first todo');\n      expect(state.value.todos[0].completed).toEqual(false);\n    });\n\n    it('should add another todo', () => {\n      store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n      store.dispatch({ type: ADD_TODO, payload: { text: 'second todo' } });\n\n      expect(state.value.todos.length).toEqual(2);\n      expect(state.value.todos[1].text).toEqual('second todo');\n      expect(state.value.todos[1].completed).toEqual(false);\n    });\n\n    it('should complete the first todo', () => {\n      store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n      store.dispatch({\n        type: COMPLETE_TODO,\n        payload: { id: state.value.todos[0].id },\n      });\n\n      expect(state.value.todos[0].completed).toEqual(true);\n    });\n\n    describe('using the store.select', () => {\n      it('should use visibilityFilter to filter todos', () => {\n        store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n        store.dispatch({ type: ADD_TODO, payload: { text: 'second todo' } });\n        store.dispatch({\n          type: COMPLETE_TODO,\n          payload: { id: state.value.todos[0].id },\n        });\n\n        const filterVisibleTodos = (\n          visibilityFilter: string,\n          todos: Todo[]\n        ) => {\n          let predicate;\n          if (visibilityFilter === VisibilityFilters.SHOW_ALL) {\n            predicate = () => true;\n          } else if (visibilityFilter === VisibilityFilters.SHOW_ACTIVE) {\n            predicate = (todo: any) => !todo.completed;\n          } else {\n            predicate = (todo: any) => todo.completed;\n          }\n          return todos.filter(predicate);\n        };\n\n        let currentlyVisibleTodos: Todo[] = [];\n\n        combineLatest([store.select('visibilityFilter'), store.select('todos')])\n          .pipe(map(([filter, todos]) => filterVisibleTodos(filter, todos)))\n          .subscribe((visibleTodos) => {\n            currentlyVisibleTodos = visibleTodos;\n          });\n\n        expect(currentlyVisibleTodos.length).toBe(2);\n\n        store.dispatch({\n          type: SET_VISIBILITY_FILTER,\n          payload: VisibilityFilters.SHOW_ACTIVE,\n        });\n\n        expect(currentlyVisibleTodos.length).toBe(1);\n        expect(currentlyVisibleTodos[0].completed).toBe(false);\n\n        store.dispatch({\n          type: SET_VISIBILITY_FILTER,\n          payload: VisibilityFilters.SHOW_COMPLETED,\n        });\n\n        expect(currentlyVisibleTodos.length).toBe(1);\n        expect(currentlyVisibleTodos[0].completed).toBe(true);\n\n        store.dispatch({ type: COMPLETE_ALL_TODOS });\n\n        expect(currentlyVisibleTodos.length).toBe(2);\n        expect(currentlyVisibleTodos[0].completed).toBe(true);\n        expect(currentlyVisibleTodos[1].completed).toBe(true);\n\n        store.dispatch({\n          type: SET_VISIBILITY_FILTER,\n          payload: VisibilityFilters.SHOW_ACTIVE,\n        });\n\n        expect(currentlyVisibleTodos.length).toBe(0);\n      });\n\n      it('should use props to get a todo', () =>\n        new Promise<void>((done) => {\n          const getTodosById = createSelector(\n            (state: TodoAppSchema) => state.todos,\n            (todos: Todo[], id: number) => {\n              return todos.find((p) => p.id === id);\n            }\n          );\n\n          const todo$ = store.select(getTodosById, 2);\n          todo$.pipe(take(3), toArray()).subscribe((res) => {\n            expect(res).toEqual([\n              undefined,\n              {\n                id: 2,\n                text: 'second todo',\n                completed: false,\n              },\n              {\n                id: 2,\n                text: 'second todo',\n                completed: true,\n              },\n            ]);\n            done();\n          });\n\n          store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n          store.dispatch({ type: ADD_TODO, payload: { text: 'second todo' } });\n          store.dispatch({\n            type: COMPLETE_TODO,\n            payload: { id: 2 },\n          });\n        }));\n\n      it('should use the selector and props to get a todo', () =>\n        new Promise<void>((done) => {\n          const getTodosState = createFeatureSelector<TodoAppSchema, Todo[]>(\n            'todos'\n          );\n          const getTodos = createSelector(getTodosState, (todos) => todos);\n          const getTodosById = createSelector(\n            getTodos,\n            (state: TodoAppSchema, id: number) => id,\n            (todos, id) => todos.find((todo) => todo.id === id)\n          );\n\n          const todo$ = store.select(getTodosById, 2);\n          todo$.pipe(take(3), toArray()).subscribe((res) => {\n            expect(res).toEqual([\n              undefined,\n              { id: 2, text: 'second todo', completed: false },\n              { id: 2, text: 'second todo', completed: true },\n            ]);\n            done();\n          });\n\n          store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n          store.dispatch({ type: ADD_TODO, payload: { text: 'second todo' } });\n          store.dispatch({\n            type: COMPLETE_TODO,\n            payload: { id: 2 },\n          });\n        }));\n    });\n\n    describe('using the select operator', () => {\n      it('should use visibilityFilter to filter todos', () => {\n        store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n        store.dispatch({ type: ADD_TODO, payload: { text: 'second todo' } });\n        store.dispatch({\n          type: COMPLETE_TODO,\n          payload: { id: state.value.todos[0].id },\n        });\n\n        const filterVisibleTodos = (\n          visibilityFilter: string,\n          todos: Todo[]\n        ) => {\n          let predicate;\n          if (visibilityFilter === VisibilityFilters.SHOW_ALL) {\n            predicate = () => true;\n          } else if (visibilityFilter === VisibilityFilters.SHOW_ACTIVE) {\n            predicate = (todo: any) => !todo.completed;\n          } else {\n            predicate = (todo: any) => todo.completed;\n          }\n          return todos.filter(predicate);\n        };\n\n        let currentlyVisibleTodos: Todo[] = [];\n\n        combineLatest([\n          store.pipe(select('visibilityFilter')),\n          store.pipe(select('todos')),\n        ])\n          .pipe(map(([filter, todos]) => filterVisibleTodos(filter, todos)))\n          .subscribe((visibleTodos) => {\n            currentlyVisibleTodos = visibleTodos;\n          });\n\n        expect(currentlyVisibleTodos.length).toBe(2);\n\n        store.dispatch({\n          type: SET_VISIBILITY_FILTER,\n          payload: VisibilityFilters.SHOW_ACTIVE,\n        });\n\n        expect(currentlyVisibleTodos.length).toBe(1);\n        expect(currentlyVisibleTodos[0].completed).toBe(false);\n\n        store.dispatch({\n          type: SET_VISIBILITY_FILTER,\n          payload: VisibilityFilters.SHOW_COMPLETED,\n        });\n\n        expect(currentlyVisibleTodos.length).toBe(1);\n        expect(currentlyVisibleTodos[0].completed).toBe(true);\n\n        store.dispatch({ type: COMPLETE_ALL_TODOS });\n\n        expect(currentlyVisibleTodos.length).toBe(2);\n        expect(currentlyVisibleTodos[0].completed).toBe(true);\n        expect(currentlyVisibleTodos[1].completed).toBe(true);\n\n        store.dispatch({\n          type: SET_VISIBILITY_FILTER,\n          payload: VisibilityFilters.SHOW_ACTIVE,\n        });\n\n        expect(currentlyVisibleTodos.length).toBe(0);\n      });\n\n      it('should use the selector and props to get a todo', () =>\n        new Promise<void>((done) => {\n          const getTodosState = createFeatureSelector<TodoAppSchema, Todo[]>(\n            'todos'\n          );\n          const getTodos = createSelector(getTodosState, (todos) => todos);\n          const getTodosById = createSelector(\n            getTodos,\n            (state: TodoAppSchema, id: number) => id,\n            (todos, id) => todos.find((todo) => todo.id === id)\n          );\n\n          const todo$ = store.pipe(select(getTodosById, 2));\n          todo$.pipe(take(3), toArray()).subscribe((res) => {\n            expect(res).toEqual([\n              undefined,\n              { id: 2, text: 'second todo', completed: false },\n              { id: 2, text: 'second todo', completed: true },\n            ]);\n            done();\n          });\n\n          store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n          store.dispatch({ type: ADD_TODO, payload: { text: 'second todo' } });\n          store.dispatch({\n            type: COMPLETE_TODO,\n            payload: { id: 2 },\n          });\n        }));\n\n      it('should use the props in the projector to get a todo', () =>\n        new Promise<void>((done) => {\n          const getTodosState = createFeatureSelector<TodoAppSchema, Todo[]>(\n            'todos'\n          );\n\n          const getTodosById = createSelector(\n            getTodosState,\n            (todos: Todo[], { id }: { id: number }) =>\n              todos.find((todo) => todo.id === id)\n          );\n\n          const todo$ = store.pipe(select(getTodosById, { id: 2 }));\n          todo$.pipe(take(3), toArray()).subscribe((res) => {\n            expect(res).toEqual([\n              undefined,\n              {\n                id: 2,\n                text: 'second todo',\n                completed: false,\n              },\n              {\n                id: 2,\n                text: 'second todo',\n                completed: true,\n              },\n            ]);\n            done();\n          });\n\n          store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n          store.dispatch({ type: ADD_TODO, payload: { text: 'second todo' } });\n          store.dispatch({\n            type: COMPLETE_TODO,\n            payload: { id: 2 },\n          });\n        }));\n    });\n  });\n\n  describe('feature state', () => {\n    it('should initialize properly', () => {\n      const initialState = {\n        todos: [\n          {\n            id: 1,\n            text: 'do things',\n            completed: false,\n          },\n        ],\n        visibilityFilter: VisibilityFilters.SHOW_ALL,\n      };\n\n      const reducers: ActionReducerMap<TodoAppSchema, any> = {\n        todos: todos,\n        visibilityFilter: visibilityFilter,\n      };\n\n      const featureInitialState = [{ id: 1, completed: false, text: 'Item' }];\n\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot(reducers, { initialState }),\n          StoreModule.forFeature('items', todos, {\n            initialState: featureInitialState,\n          }),\n        ],\n      });\n\n      const store = TestBed.inject(Store);\n\n      const expected = [\n        {\n          todos: initialState.todos,\n          visibilityFilter: initialState.visibilityFilter,\n          items: featureInitialState,\n        },\n      ];\n\n      store.pipe(select((state) => state)).subscribe((state) => {\n        expect(state).toEqual(expected.shift());\n      });\n    });\n\n    it('should initialize properly with a partial state', () => {\n      const initialState = {\n        items: [{ id: 1, completed: false, text: 'Item' }],\n      };\n\n      const reducers: ActionReducerMap<TodoAppSchema, any> = {\n        todos: todos,\n        visibilityFilter: visibilityFilter,\n      };\n\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot({} as any, {\n            initialState,\n          }),\n          StoreModule.forFeature('todos', reducers),\n          StoreModule.forFeature('items', todos),\n        ],\n      });\n\n      const store = TestBed.inject(Store);\n\n      const expected = {\n        todos: {\n          todos: [],\n          visibilityFilter: VisibilityFilters.SHOW_ALL,\n        },\n        items: [{ id: 1, completed: false, text: 'Item' }],\n      };\n\n      store.pipe(select((state) => state)).subscribe((state) => {\n        expect(state).toEqual(expected);\n      });\n    });\n\n    it('throws if forRoot() is used more than once', () =>\n      new Promise<void>((done) => {\n        @NgModule({\n          imports: [StoreModule.forRoot({})],\n        })\n        class FeatureModule {}\n\n        TestBed.configureTestingModule({\n          imports: [\n            StoreModule.forRoot({}),\n            RouterTestingModule.withRoutes([]),\n          ],\n        });\n\n        const router = TestBed.inject(Router);\n\n        router.resetConfig([\n          {\n            path: 'feature-path',\n            loadChildren: () => Promise.resolve(FeatureModule),\n          },\n        ]);\n\n        router.navigateByUrl('/feature-path').catch((err: TypeError) => {\n          expect(err.message).toBe(\n            'The root Store has been provided more than once. Feature modules should provide feature states instead.'\n          );\n          done();\n        });\n      }));\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/integration_signals.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { ActionReducerMap, Store, provideStore } from '..';\n\nimport { State } from '../src/private_export';\nimport {\n  ADD_TODO,\n  COMPLETE_ALL_TODOS,\n  COMPLETE_TODO,\n  SET_VISIBILITY_FILTER,\n  todos,\n  visibilityFilter,\n  VisibilityFilters,\n  resetId,\n} from './fixtures/todos';\nimport { computed } from '@angular/core';\n\ninterface Todo {\n  id: number;\n  text: string;\n  completed: boolean;\n}\n\ninterface TodoAppSchema {\n  visibilityFilter: string;\n  todos: Todo[];\n}\n\ndescribe('NgRx and Signals Integration spec', () => {\n  let store: Store<TodoAppSchema>;\n  let state: State<TodoAppSchema>;\n\n  const initialState = {\n    todos: [],\n    visibilityFilter: VisibilityFilters.SHOW_ALL,\n  };\n  const reducers: ActionReducerMap<TodoAppSchema, any> = {\n    todos: todos,\n    visibilityFilter: visibilityFilter,\n  };\n\n  beforeEach(() => {\n    resetId();\n    vi.spyOn(reducers, 'todos');\n\n    TestBed.configureTestingModule({\n      providers: [provideStore(reducers, { initialState })],\n    });\n\n    store = TestBed.inject(Store);\n    state = TestBed.inject(State);\n  });\n\n  describe('todo integration spec', function () {\n    describe('using the store.selectSignal', () => {\n      it('should use visibilityFilter to filter todos', () => {\n        store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n        store.dispatch({ type: ADD_TODO, payload: { text: 'second todo' } });\n        store.dispatch({\n          type: COMPLETE_TODO,\n          payload: { id: state.value.todos[0].id },\n        });\n\n        const filterVisibleTodos = (\n          visibilityFilter: string,\n          todos: Todo[]\n        ) => {\n          let predicate;\n          if (visibilityFilter === VisibilityFilters.SHOW_ALL) {\n            predicate = () => true;\n          } else if (visibilityFilter === VisibilityFilters.SHOW_ACTIVE) {\n            predicate = (todo: any) => !todo.completed;\n          } else {\n            predicate = (todo: any) => todo.completed;\n          }\n          return todos.filter(predicate);\n        };\n\n        const filter = TestBed.runInInjectionContext(() =>\n          store.selectSignal((state) => state.visibilityFilter)\n        );\n        const todos = TestBed.runInInjectionContext(() =>\n          store.selectSignal((state) => state.todos)\n        );\n        const currentlyVisibleTodos = () =>\n          filterVisibleTodos(filter(), todos());\n\n        expect(currentlyVisibleTodos().length).toBe(2);\n\n        store.dispatch({\n          type: SET_VISIBILITY_FILTER,\n          payload: VisibilityFilters.SHOW_ACTIVE,\n        });\n\n        expect(currentlyVisibleTodos().length).toBe(1);\n        expect(currentlyVisibleTodos()[0].completed).toBe(false);\n\n        store.dispatch({\n          type: SET_VISIBILITY_FILTER,\n          payload: VisibilityFilters.SHOW_COMPLETED,\n        });\n\n        expect(currentlyVisibleTodos().length).toBe(1);\n        expect(currentlyVisibleTodos()[0].completed).toBe(true);\n\n        store.dispatch({ type: COMPLETE_ALL_TODOS });\n\n        expect(currentlyVisibleTodos().length).toBe(2);\n        expect(currentlyVisibleTodos()[0].completed).toBe(true);\n        expect(currentlyVisibleTodos()[1].completed).toBe(true);\n\n        store.dispatch({\n          type: SET_VISIBILITY_FILTER,\n          payload: VisibilityFilters.SHOW_ACTIVE,\n        });\n\n        expect(currentlyVisibleTodos().length).toBe(0);\n      });\n    });\n  });\n\n  describe('context integration spec', () => {\n    it('Store.selectSignal should not throw an error if used outside in the injection context', () => {\n      let error;\n\n      try {\n        store.selectSignal((state) => state.todos);\n      } catch (e) {\n        error = `${e}`;\n      }\n\n      expect(error).toBeUndefined();\n    });\n  });\n\n  describe('immutable state integration spec', () => {\n    it('Store.selectSignal should not trigger on unrelated global state changes', () => {\n      let todosTriggerCount = 0;\n\n      const todos = store.selectSignal((state) => state.todos);\n\n      const todosTriggerState = computed(() => {\n        todos();\n        return ++todosTriggerCount;\n      });\n\n      store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } });\n      expect(todosTriggerState()).toBe(1);\n\n      store.dispatch({\n        type: SET_VISIBILITY_FILTER,\n        payload: VisibilityFilters.SHOW_ACTIVE,\n      });\n      expect(todosTriggerState()).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/meta-reducers/immutability_reducer.spec.ts",
    "content": "import { immutabilityCheckMetaReducer } from '../../src/meta-reducers';\n\ndescribe('immutabilityCheckMetaReducer:', () => {\n  describe('actions:', () => {\n    it('should not throw if left untouched', () => {\n      expect(() => invokeActionReducer((state: any) => state)).not.toThrow();\n    });\n\n    it('should throw when mutating an action', () => {\n      expect(() =>\n        invokeActionReducer((state: any, action: any) => {\n          action.foo = '123';\n          return state;\n        })\n      ).toThrow();\n      expect(() =>\n        invokeActionReducer((state: any, action: any) => {\n          action.numbers.push(4);\n          return state;\n        })\n      ).toThrow();\n    });\n\n    it('should throw when mutating action outside of reducer', () => {\n      let dispatchedAction: any;\n      invokeActionReducer((state: any, action: any) => {\n        dispatchedAction = action;\n        return state;\n      });\n\n      expect(() => {\n        dispatchedAction.foo = '123';\n      }).toThrow();\n    });\n\n    it('should not throw on ivy properties (because these are ignored)', () => {\n      let dispatchedAction: any;\n      expect(() =>\n        invokeActionReducer((state: any, action: any) => {\n          dispatchedAction = action;\n          return state;\n        })\n      ).not.toThrow();\n\n      expect(() => {\n        dispatchedAction.ɵIvyProperty.value = 2;\n      }).not.toThrow();\n    });\n\n    it('should not throw when check is off', () => {\n      expect(() =>\n        invokeActionReducer((state: any, action: any) => {\n          action.foo = '123';\n          return state;\n        }, false)\n      ).not.toThrow();\n    });\n\n    function invokeActionReducer(reduce: Function, checkIsOn = true) {\n      immutabilityCheckMetaReducer((state, action) => reduce(state, action), {\n        action: () => checkIsOn,\n        state: () => false,\n      })(\n        {},\n        {\n          type: 'invoke',\n          numbers: [1, 2, 3],\n          fun: function () {},\n          ɵIvyProperty: { value: 1 },\n        }\n      );\n    }\n  });\n\n  describe('state:', () => {\n    it('should not throw if left untouched', () => {\n      expect(() =>\n        invokeStateReducer((state: any) => ({ ...state, foo: 'bar' }))\n      ).not.toThrow();\n    });\n\n    it('should throw when mutating state', () => {\n      expect(() =>\n        invokeStateReducer((state: any) => {\n          state.foo = '123';\n          return state;\n        })\n      ).toThrow();\n      expect(() =>\n        invokeStateReducer((state: any) => {\n          state.numbers.push(4);\n          return state;\n        })\n      ).toThrow();\n    });\n\n    it('should throw when mutating state outside of reducer', () => {\n      const nextState = invokeStateReducer((state: any) => state);\n      expect(() => {\n        nextState.foo = '123';\n      }).toThrow();\n    });\n\n    it('should not throw when check is off', () => {\n      expect(() =>\n        invokeStateReducer((state: any) => {\n          state.foo = '123';\n          return state;\n        }, false)\n      ).not.toThrow();\n    });\n\n    function invokeStateReducer(reduce: Function, checkIsOn = true) {\n      const reducer = immutabilityCheckMetaReducer(\n        (state, action) => {\n          if (action.type === 'init') return state;\n          return reduce(state, action);\n        },\n        {\n          state: () => checkIsOn,\n          action: () => false,\n        }\n      );\n\n      // dispatch init noop action because it's the next state that is frozen\n      const state = reducer({ numbers: [1, 2, 3] }, { type: 'init' });\n      return reducer(state, { type: 'invoke' });\n    }\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/meta-reducers/inNgZoneAssert_reducer.spec.ts",
    "content": "import * as ngCore from '@angular/core';\nimport { inNgZoneAssertMetaReducer } from '../../src/meta-reducers';\n\ndescribe('inNgZoneAssertMetaReducer:', () => {\n  it('should not throw if in NgZone', () => {\n    ngCore.NgZone.isInAngularZone = vi.fn(() => true);\n    expect(() => invokeActionReducer((state: any) => state)).not.toThrow();\n    expect(ngCore.NgZone.isInAngularZone).toHaveBeenCalled();\n  });\n\n  it('should throw when not in NgZone', () => {\n    ngCore.NgZone.isInAngularZone = vi.fn(() => false);\n    expect(() => invokeActionReducer((state: any) => state)).toThrowError(\n      `Action 'invoke' running outside NgZone. https://ngrx.io/guide/store/configuration/runtime-checks#strictactionwithinngzone`\n    );\n    expect(ngCore.NgZone.isInAngularZone).toHaveBeenCalled();\n  });\n\n  it('should not call isInAngularZone when check is off', () => {\n    ngCore.NgZone.isInAngularZone = vi.fn();\n    expect(() =>\n      invokeActionReducer((state: any) => state, false)\n    ).not.toThrow();\n    expect(ngCore.NgZone.isInAngularZone).not.toHaveBeenCalled();\n  });\n\n  function invokeActionReducer(reduce: Function, checkIsOn = true) {\n    inNgZoneAssertMetaReducer((state, action) => reduce(state, action), {\n      action: () => checkIsOn,\n    })({}, { type: 'invoke' });\n  }\n});\n"
  },
  {
    "path": "modules/store/spec/meta-reducers/serialization_reducer.spec.ts",
    "content": "import { Component } from '@angular/core';\nimport { serializationCheckMetaReducer } from '../../src/meta-reducers';\n\ndescribe('serializationCheckMetaReducer:', () => {\n  class AComponent {}\n  Object.defineProperty(AComponent, 'ɵcmp', {});\n\n  const serializables: Record<string, any> = {\n    aNumber: { value: 4 },\n    aBoolean: { value: true },\n    aString: { value: 'foobar' },\n    anArray: { value: [1, 2, 3] },\n    anObject: { value: {} },\n    aNested: { value: { aNumber: 7, anArray: ['n', 'g', 'r', 'x'] } },\n    aNull: { value: null },\n    anUndefined: { value: undefined },\n    aComponent: AComponent, // components should not throw (because these are ignored)\n  };\n\n  const unSerializables: Record<string, any> = {\n    date: { value: new Date() },\n    map: { value: new Map() },\n    set: { value: new Set() },\n    class: { value: new (class {})() },\n    function: { value: () => {} },\n  };\n\n  describe('serializable:', () => {\n    Object.keys(serializables).forEach((key) => {\n      it(`action with ${key} should not throw`, () => {\n        expect(() =>\n          invokeActionReducer({ type: 'valid', payload: serializables[key] })\n        ).not.toThrow();\n      });\n\n      it(`state with ${key} should not throw`, () => {\n        expect(() => invokeStateReducer(serializables[key])).not.toThrow();\n      });\n    });\n  });\n\n  describe('unserializable:', () => {\n    Object.keys(unSerializables).forEach((key) => {\n      it(`action with ${key} should throw`, () => {\n        expect(() =>\n          invokeActionReducer({ type: 'valid', payload: unSerializables[key] })\n        ).toThrow();\n      });\n\n      it(`state with ${key} should throw`, () => {\n        expect(() => invokeStateReducer(unSerializables[key])).toThrow();\n      });\n    });\n  });\n\n  describe('actions: ', () => {\n    it('should not throw if check is off', () => {\n      expect(() =>\n        invokeActionReducer({ type: 'valid', payload: unSerializables }, false)\n      );\n    });\n\n    it('should log the path that is not serializable', () => {\n      expect(() =>\n        invokeActionReducer({\n          type: 'valid',\n          payload: { foo: { bar: unSerializables['date'] } },\n        })\n      ).toThrowError(\n        `Detected unserializable action at \"payload.foo.bar.value\". https://ngrx.io/guide/store/configuration/runtime-checks#strictactionserializability`\n      );\n    });\n  });\n\n  describe('state: ', () => {\n    it('should not throw if check is off', () => {\n      expect(() => invokeStateReducer(unSerializables, false)).not.toThrow();\n    });\n\n    it('should log the path that is not serializable', () => {\n      expect(() =>\n        invokeStateReducer({\n          foo: { bar: unSerializables['date'] },\n        })\n      ).toThrowError(/Detected unserializable state at \"foo.bar.value\"/);\n    });\n\n    it('should throw if state is null', () => {\n      expect(() => invokeStateReducer(null)).toThrowError(\n        `Detected unserializable state at \"root\". https://ngrx.io/guide/store/configuration/runtime-checks#strictstateserializability`\n      );\n    });\n\n    it('should throw if state is undefined', () => {\n      expect(() => invokeStateReducer(undefined)).toThrowError(\n        /Detected unserializable state at \"root\"/\n      );\n    });\n  });\n\n  function invokeActionReducer(action: any, checkIsOn = true) {\n    serializationCheckMetaReducer((state) => state, {\n      action: () => checkIsOn,\n      state: () => false,\n    })(undefined, action);\n  }\n\n  function invokeStateReducer(nextState?: any, checkIsOn = true) {\n    serializationCheckMetaReducer(() => nextState, {\n      state: () => checkIsOn,\n      action: () => false,\n    })(undefined, {\n      type: 'invokeReducer',\n    });\n  }\n});\n"
  },
  {
    "path": "modules/store/spec/modules.spec.ts",
    "content": "import { InjectionToken, NgModule } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  ActionReducer,\n  ActionReducerMap,\n  combineReducers,\n  Store,\n  StoreModule,\n} from '..';\nimport { take } from 'rxjs/operators';\n\ndescribe(`Store Modules`, () => {\n  type RootState = { fruit: string };\n  type FeatureAState = number;\n  type FeatureBState = { list: number[]; index: number };\n  type State = RootState & { a: FeatureAState } & { b: FeatureBState };\n\n  let store: Store<State>;\n\n  const reducersToken = new InjectionToken<ActionReducerMap<RootState>>(\n    'Root Reducers'\n  );\n\n  const featureToken = new InjectionToken<ActionReducerMap<RootState>>(\n    'Feature Reducers'\n  );\n\n  // Trigger here is basically an action type used to trigger state update\n  const createDummyReducer =\n    <T>(def: T, trigger: string): ActionReducer<T> =>\n    (s = def, { type, payload }: any) =>\n      type === trigger ? payload : s;\n  const rootFruitReducer = createDummyReducer('apple', 'fruit');\n  const featureAReducer = createDummyReducer(5, 'a');\n  const featureBListReducer = createDummyReducer([1, 2, 3], 'bList');\n  const featureBIndexReducer = createDummyReducer(2, 'bIndex');\n  const featureBReducerMap: ActionReducerMap<FeatureBState> = {\n    list: featureBListReducer,\n    index: featureBIndexReducer,\n  };\n\n  describe(`: Config`, () => {\n    let featureAReducerFactory: any;\n    let rootReducerFactory: any;\n\n    const featureAInitial = () => ({ a: 42 });\n    const rootInitial = { fruit: 'orange' };\n\n    beforeEach(() => {\n      featureAReducerFactory = vi.fn((rm: any, initialState?: any) => {\n        return (state: any, action: any) => 4;\n      });\n      rootReducerFactory = vi.fn(combineReducers);\n\n      @NgModule({\n        imports: [\n          StoreModule.forFeature(\n            'a',\n            { a: featureAReducer },\n            {\n              initialState: featureAInitial,\n              reducerFactory: featureAReducerFactory,\n            }\n          ),\n        ],\n      })\n      class FeatureAModule {}\n\n      @NgModule({\n        imports: [\n          StoreModule.forRoot<RootState>(reducersToken, {\n            initialState: rootInitial,\n            reducerFactory: rootReducerFactory,\n          }),\n          FeatureAModule,\n        ],\n        providers: [\n          {\n            provide: reducersToken,\n            useValue: { fruit: rootFruitReducer },\n          },\n        ],\n      })\n      class RootModule {}\n\n      TestBed.configureTestingModule({\n        imports: [RootModule],\n      });\n\n      store = TestBed.inject(Store);\n    });\n\n    it(`should accept configurations`, () => {\n      expect(featureAReducerFactory).toHaveBeenCalledWith({\n        a: featureAReducer,\n      });\n\n      expect(rootReducerFactory).toHaveBeenCalledWith({\n        fruit: rootFruitReducer,\n      });\n    });\n\n    it(`should use config.reducerFactory`, () =>\n      new Promise<void>((done) => {\n        store.dispatch({ type: 'fruit', payload: 'banana' });\n        store.dispatch({ type: 'a', payload: 42 });\n\n        store.pipe(take(1)).subscribe((s: any) => {\n          expect(s).toEqual({\n            fruit: 'banana',\n            a: 4,\n          });\n          done();\n        });\n      }));\n  });\n\n  describe(`: With initial state`, () => {\n    const initialState: RootState = { fruit: 'banana' };\n    const reducerMap: ActionReducerMap<RootState> = { fruit: rootFruitReducer };\n    const noopMetaReducer = (r: Function) => (state: any, action: any) => {\n      return r(state, action);\n    };\n\n    const testWithMetaReducers = (metaReducers: any[]) => () => {\n      beforeEach(() => {\n        TestBed.configureTestingModule({\n          imports: [\n            StoreModule.forRoot(reducerMap, { initialState, metaReducers }),\n          ],\n        });\n\n        store = TestBed.inject(Store);\n      });\n\n      it('should have initial state', () =>\n        new Promise<void>((done) => {\n          store.pipe(take(1)).subscribe((s: any) => {\n            expect(s).toEqual(initialState);\n            done();\n          });\n        }));\n    };\n\n    describe(\n      'should add initial state with no meta-reducers',\n      testWithMetaReducers([])\n    );\n\n    describe(\n      'should add initial state with registered meta-reducers',\n      testWithMetaReducers([noopMetaReducer])\n    );\n  });\n\n  describe(`: Nested`, () => {\n    @NgModule({\n      imports: [StoreModule.forFeature('a', featureAReducer)],\n    })\n    class FeatureAModule {}\n\n    @NgModule({\n      imports: [StoreModule.forFeature('b', featureBReducerMap)],\n    })\n    class FeatureBModule {}\n\n    @NgModule({\n      imports: [StoreModule.forFeature('c', featureToken)],\n      providers: [\n        {\n          provide: featureToken,\n          useValue: featureBReducerMap,\n        },\n      ],\n    })\n    class FeatureCModule {}\n\n    @NgModule({\n      imports: [\n        StoreModule.forRoot<RootState>(reducersToken),\n        FeatureAModule,\n        FeatureBModule,\n        FeatureCModule,\n      ],\n      providers: [\n        {\n          provide: reducersToken,\n          useValue: { fruit: rootFruitReducer },\n        },\n      ],\n    })\n    class RootModule {}\n\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        imports: [RootModule],\n      });\n\n      store = TestBed.inject(Store);\n    });\n\n    it('should nest the child module in the root store object', () =>\n      new Promise<void>((done) => {\n        store.pipe(take(1)).subscribe((state: State) => {\n          expect(state).toEqual({\n            fruit: 'apple',\n            a: 5,\n            b: {\n              list: [1, 2, 3],\n              index: 2,\n            },\n            c: {\n              list: [1, 2, 3],\n              index: 2,\n            },\n          } as State);\n          done();\n        });\n      }));\n  });\n\n  describe(`: With slice object`, () => {\n    @NgModule({\n      imports: [\n        StoreModule.forFeature({ name: 'a', reducer: featureAReducer }),\n      ],\n    })\n    class FeatureAModule {}\n\n    @NgModule({\n      imports: [StoreModule.forRoot({}), FeatureAModule],\n    })\n    class RootModule {}\n\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        imports: [RootModule],\n      });\n\n      store = TestBed.inject(Store);\n    });\n\n    it('should set up a feature state', () =>\n      new Promise<void>((done) => {\n        store.pipe(take(1)).subscribe((state: State) => {\n          expect(state).toEqual({\n            a: 5,\n          } as State);\n          done();\n        });\n      }));\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/ngc/main.ts",
    "content": "import { Component, InjectionToken, NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { Observable } from 'rxjs';\n\nimport { combineReducers, select, Store, StoreModule } from '../../';\nimport { counterReducer, DECREMENT, INCREMENT } from '../fixtures/counter';\nimport { todos } from '../fixtures/todos';\n\n@Component({\n  selector: 'ngc-spec-child-component',\n  template: ``,\n})\nexport class NgcSpecChildComponent {}\n\n@NgModule({\n  imports: [StoreModule.forFeature('feature', { todos: todos })],\n  declarations: [NgcSpecChildComponent],\n  exports: [NgcSpecChildComponent],\n})\nexport class FeatureModule {}\n\nexport interface AppState {\n  count: number;\n}\n\nexport const reducerToken = new InjectionToken('Reducers');\n\n@Component({\n  selector: 'ngc-spec-component',\n  template: `\n    <button (click)=\"increment()\">+</button>\n    <span> Count : {{ count | async }} </span>\n    <button (click)=\"decrement()\">+</button>\n\n    <ngc-spec-child-component></ngc-spec-child-component>\n  `,\n})\nexport class NgcSpecComponent {\n  count: Observable<number>;\n  constructor(public store: Store<AppState>) {\n    this.count = store.pipe(select((state) => state.count));\n  }\n  increment() {\n    this.store.dispatch({ type: INCREMENT });\n  }\n  decrement() {\n    this.store.dispatch({ type: DECREMENT });\n  }\n}\n\n@NgModule({\n  imports: [\n    BrowserModule,\n    StoreModule.forRoot(reducerToken, {\n      initialState: { count: 0 },\n      reducerFactory: combineReducers,\n    }),\n    FeatureModule,\n  ],\n  providers: [\n    {\n      provide: reducerToken,\n      useValue: { count: counterReducer },\n    },\n  ],\n  declarations: [NgcSpecComponent],\n  bootstrap: [NgcSpecComponent],\n})\nexport class NgcSpecModule {}\n"
  },
  {
    "path": "modules/store/spec/ngc/tsconfig.ngc.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES5\",\n    \"experimentalDecorators\": true,\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"outDir\": \"./output\",\n    \"lib\": [\"es2015\", \"dom\"],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@ngrx/store\": [\"../../../store\"]\n    }\n  },\n  \"files\": [\"main.ts\"],\n  \"angularCompilerOptions\": {\n    \"genDir\": \"ngfactory\"\n  }\n}\n"
  },
  {
    "path": "modules/store/spec/reducer_creator.spec.ts",
    "content": "import { ActionType, on, createReducer, createAction, props, union } from '..';\n\ndescribe('classes/reducer', function (): void {\n  describe('base', () => {\n    const bar = createAction('[foobar] BAR', props<{ bar: number }>());\n    const foo = createAction('[foobar] FOO', props<{ foo: number }>());\n    const withDefaultParameter = createAction(\n      '[foobar] withDefaultParameter',\n      (foo = 4, bar = 7) => ({\n        foo,\n        bar,\n      })\n    );\n\n    describe('on', () => {\n      it('should support reducers with multiple actions', () => {\n        const both = union({ bar, foo });\n        const func = (state: unknown, action: typeof both) => ({});\n        const result = on(foo, bar, func);\n        expect(result.types).toContain(bar.type);\n        expect(result.types).toContain(foo.type);\n      });\n    });\n\n    describe('createReducer', () => {\n      it('should create a reducer', () => {\n        interface State {\n          foo?: number;\n          bar?: number;\n        }\n\n        const fooBarReducer = createReducer(\n          {} as State,\n          on(foo, (state, { foo }) => ({ ...state, foo })),\n          on(bar, (state, { bar }) => ({ ...state, bar })),\n          on(withDefaultParameter, (_state, { type: _, foo, bar }) => ({\n            foo,\n            bar,\n          }))\n        );\n\n        expect(typeof fooBarReducer).toEqual('function');\n\n        let state = fooBarReducer(undefined, { type: 'UNKNOWN' });\n        expect(state).toEqual({});\n\n        state = fooBarReducer(state, foo({ foo: 42 }));\n        expect(state).toEqual({ foo: 42 });\n\n        state = fooBarReducer(state, bar({ bar: 54 }));\n        expect(state).toEqual({ foo: 42, bar: 54 });\n\n        state = fooBarReducer(state, withDefaultParameter());\n        expect(state).toEqual({ foo: 4, bar: 7 });\n      });\n\n      it('should create a primitive reducer', () => {\n        const initialState = 0;\n        const setState = createAction('setState', props<{ value: number }>());\n        const resetState = createAction('resetState');\n\n        const primitiveReducer = createReducer(\n          initialState,\n          on(setState, (_state, { value }) => value),\n          on(resetState, () => initialState)\n        );\n\n        let state = primitiveReducer(undefined, { type: 'UNKNOWN' });\n        expect(state).toEqual(0);\n\n        state = primitiveReducer(state, setState({ value: 7 }));\n        expect(state).toEqual(7);\n\n        state = primitiveReducer(state, resetState);\n        expect(state).toEqual(initialState);\n      });\n\n      it('should support reducers with multiple actions', () => {\n        type State = string[];\n\n        const fooBarReducer = createReducer(\n          [] as State,\n          on(foo, bar, (state, { type }) => [...state, type])\n        );\n\n        expect(typeof fooBarReducer).toEqual('function');\n\n        let state = fooBarReducer(undefined, { type: 'UNKNOWN' });\n        expect(state).toEqual([]);\n\n        state = fooBarReducer(state, foo({ foo: 42 }));\n        expect(state).toEqual(['[foobar] FOO']);\n\n        state = fooBarReducer(state, bar({ bar: 54 }));\n        expect(state).toEqual(['[foobar] FOO', '[foobar] BAR']);\n      });\n\n      it('should support \"on\"s to have identical action types', () => {\n        const increase = createAction('[COUNTER] increase');\n\n        const counterReducer = createReducer(\n          0,\n          on(increase, (state) => state + 1),\n          on(increase, (state) => state + 1)\n        );\n\n        expect(typeof counterReducer).toEqual('function');\n\n        let state = 5;\n\n        state = counterReducer(state, increase());\n        expect(state).toEqual(7);\n      });\n\n      it('supports a union for State', () => {\n        interface StatePart1 {\n          foo?: number;\n        }\n\n        interface StatePart2 {\n          bar: number;\n        }\n\n        const fooBarReducer = createReducer<StatePart1 | StatePart2>(\n          {},\n          on(foo, (state, { foo }) => ({ ...state, foo })),\n          on(bar, (state, { bar }) => ({ ...state, bar }))\n        );\n\n        expect(typeof fooBarReducer).toEqual('function');\n      });\n\n      it('accepts custom functions with specified generics (within on calls)', () => {\n        interface State {\n          foo?: number;\n          bar?: number;\n        }\n\n        function mutableReducer<S, A>(callback: (state: S, action: A) => S) {\n          return (oldState: S, value: A) => {\n            return ((state: S) => callback(state, value))(oldState) as S;\n          };\n        }\n\n        const fooBarReducer = createReducer(\n          {} as State,\n          on(\n            foo,\n            mutableReducer<State, ActionType<typeof foo>>((state, { foo }) => ({\n              ...state,\n              foo,\n            }))\n          ),\n          on(bar, (state, { bar }) => ({ ...state, bar }))\n        );\n\n        expect(typeof fooBarReducer).toEqual('function');\n\n        let state = fooBarReducer(undefined, { type: 'UNKNOWN' });\n        expect(state).toEqual({});\n\n        state = fooBarReducer(state, foo({ foo: 42 }));\n        expect(state).toEqual({ foo: 42 });\n\n        state = fooBarReducer(state, bar({ bar: 54 }));\n        expect(state).toEqual({ foo: 42, bar: 54 });\n      });\n\n      it('accepts custom functions with inferred types (within on calls)', () => {\n        //                       baz the same prop `foo` 👇\n        const baz = createAction('[foobar] BAZ', props<{ foo: number }>());\n\n        interface State {\n          foo?: number;\n          bar?: number;\n        }\n\n        function mutableReducer<S, A>(callback: (state: S, action: A) => S) {\n          return (oldState: S, value: A) => {\n            return ((state: S) => callback(state, value))(oldState) as S;\n          };\n        }\n\n        const fooBarReducer = createReducer(\n          {} as State,\n          on(\n            foo,\n            mutableReducer((state, { foo }) => ({\n              ...state,\n              foo,\n            }))\n          ),\n          on(\n            foo,\n            baz,\n            mutableReducer((state, { foo }) => ({\n              ...state,\n              foo,\n            }))\n          ),\n          on(bar, (state, { bar }) => ({ ...state, bar })),\n          on(foo, bar, baz, (state, { type }) => ({ ...state }))\n        );\n\n        expect(typeof fooBarReducer).toEqual('function');\n\n        let state = fooBarReducer(undefined, { type: 'UNKNOWN' });\n        expect(state).toEqual({});\n\n        state = fooBarReducer(state, foo({ foo: 42 }));\n        expect(state).toEqual({ foo: 42 });\n\n        state = fooBarReducer(state, bar({ bar: 54 }));\n        expect(state).toEqual({ foo: 42, bar: 54 });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/reducer_manager.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { createReducer, ReducerManager, StoreModule } from '..';\n\ndescribe(ReducerManager.name, () => {\n  it('should provide reducers being registered in store', () => {\n    TestBed.configureTestingModule({\n      imports: [\n        StoreModule.forRoot({\n          'feature-1': createReducer(0),\n        }),\n      ],\n    });\n\n    const reducerManager = TestBed.inject(ReducerManager);\n\n    expect(Object.keys(reducerManager.currentReducers)).toContain('feature-1');\n  });\n\n  it('should provide reducers being registered at runtime', () => {\n    TestBed.configureTestingModule({\n      imports: [\n        StoreModule.forRoot({\n          'feature-1': createReducer(0),\n        }),\n      ],\n    });\n\n    const reducerManager = TestBed.inject(ReducerManager);\n\n    reducerManager.addReducer('feature-2', createReducer(0));\n\n    expect(Object.keys(reducerManager.currentReducers)).toContain('feature-1');\n    expect(Object.keys(reducerManager.currentReducers)).toContain('feature-2');\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/runtime_checks.spec.ts",
    "content": "import * as ngCore from '@angular/core';\nimport { TestBed, fakeAsync, flush } from '@angular/core/testing';\nimport { Store, StoreModule, META_REDUCERS, createAction } from '..';\nimport { createActiveRuntimeChecks } from '../src/runtime_checks';\nimport { RuntimeChecks, Action } from '../src/models';\nimport { resetRegisteredActionTypes } from '../src/globals';\n\nvi.mock('@angular/core', { spy: true });\n\ndescribe('Runtime checks:', () => {\n  describe('createActiveRuntimeChecks:', () => {\n    it('should enable immutability checks by default', () => {\n      expect(createActiveRuntimeChecks()).toEqual({\n        strictStateSerializability: false,\n        strictActionSerializability: false,\n        strictActionImmutability: true,\n        strictStateImmutability: true,\n        strictActionWithinNgZone: false,\n        strictActionTypeUniqueness: false,\n      });\n    });\n\n    it('should allow the user to override the config', () => {\n      expect(\n        createActiveRuntimeChecks({\n          strictStateSerializability: true,\n          strictActionSerializability: true,\n          strictActionImmutability: false,\n          strictStateImmutability: false,\n          strictActionWithinNgZone: true,\n          strictActionTypeUniqueness: true,\n        })\n      ).toEqual({\n        strictStateSerializability: true,\n        strictActionSerializability: true,\n        strictActionImmutability: false,\n        strictStateImmutability: false,\n        strictActionWithinNgZone: true,\n        strictActionTypeUniqueness: true,\n      });\n    });\n\n    it('should disable runtime checks in production by default', () => {\n      const spy = vi.mocked(ngCore.isDevMode).mockReturnValue(false);\n\n      expect(createActiveRuntimeChecks()).toEqual({\n        strictStateSerializability: false,\n        strictActionSerializability: false,\n        strictActionImmutability: false,\n        strictStateImmutability: false,\n        strictActionWithinNgZone: false,\n        strictActionTypeUniqueness: false,\n      });\n\n      spy.mockReset();\n      spy.mockReturnValue(true);\n    });\n\n    it('should disable runtime checks in production even if opted in to enable', () => {\n      const spy = vi.mocked(ngCore.isDevMode).mockReturnValue(false);\n\n      expect(\n        createActiveRuntimeChecks({\n          strictStateSerializability: true,\n          strictActionSerializability: true,\n          strictActionWithinNgZone: true,\n          strictActionTypeUniqueness: true,\n        })\n      ).toEqual({\n        strictStateSerializability: false,\n        strictActionSerializability: false,\n        strictActionImmutability: false,\n        strictStateImmutability: false,\n        strictActionWithinNgZone: false,\n        strictActionTypeUniqueness: false,\n      });\n\n      spy.mockReset();\n      spy.mockReturnValue(true);\n    });\n  });\n\n  describe('Registering custom meta-reducers:', () => {\n    it('should invoke internal meta reducers before user defined meta reducers', () => {\n      let logs: string[] = [];\n      function metaReducerFactory(logMessage: string) {\n        return function metaReducer(reducer: any) {\n          return function (state: any, action: any) {\n            logs.push(logMessage);\n            return reducer(state, action);\n          };\n        };\n      }\n\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot(\n            {},\n            {\n              metaReducers: [metaReducerFactory('user')],\n            }\n          ),\n        ],\n        providers: [\n          {\n            provide: META_REDUCERS,\n            useValue: metaReducerFactory('internal-single-one'),\n            multi: true,\n          },\n          {\n            provide: META_REDUCERS,\n            useValue: metaReducerFactory('internal-single-two'),\n            multi: true,\n          },\n        ],\n      });\n\n      const store = TestBed.inject(Store);\n      const expected = ['internal-single-one', 'internal-single-two', 'user'];\n\n      expect(logs).toEqual(expected);\n      logs = [];\n\n      store.dispatch({ type: 'foo' });\n      expect(logs).toEqual(expected);\n    });\n  });\n\n  describe('State Serialization:', () => {\n    const invalidAction = () => ({ type: ErrorTypes.UnserializableState });\n\n    it('should throw when enabled', fakeAsync(() => {\n      const store = setupStore({ strictStateSerializability: true });\n\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).toThrowError(/Detected unserializable state/);\n    }));\n\n    it('should not throw when disabled', fakeAsync(() => {\n      const store = setupStore({ strictStateSerializability: false });\n\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).not.toThrow();\n    }));\n  });\n\n  describe('Action Serialization:', () => {\n    const invalidAction = () => ({\n      type: ErrorTypes.UnserializableAction,\n      invalid: new Date(),\n    });\n\n    it('should throw when enabled', fakeAsync(() => {\n      const store = setupStore({ strictActionSerializability: true });\n\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).toThrowError(/Detected unserializable action/);\n    }));\n\n    it('should not throw when disabled', fakeAsync(() => {\n      const store = setupStore({ strictActionSerializability: false });\n\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).not.toThrow();\n    }));\n\n    it('should not throw for NgRx actions', fakeAsync(() => {\n      const store = setupStore({ strictActionSerializability: true });\n\n      expect(() => {\n        store.dispatch(makeNgrxAction(invalidAction()));\n        flush();\n      }).not.toThrow();\n    }));\n  });\n\n  describe('State Mutations', () => {\n    const invalidAction = () => ({\n      type: ErrorTypes.MutateState,\n    });\n\n    it('should throw when enabled', fakeAsync(() => {\n      const store = setupStore({ strictStateImmutability: true });\n\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).toThrowError(/Cannot add property/);\n    }));\n\n    it('should not throw when disabled', fakeAsync(() => {\n      const store = setupStore({ strictStateImmutability: false });\n\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).not.toThrow();\n    }));\n  });\n\n  describe('Action Mutations', () => {\n    const invalidAction = () => ({\n      type: ErrorTypes.MutateAction,\n      foo: 'foo',\n    });\n\n    it('should throw when enabled', fakeAsync(() => {\n      const store = setupStore({ strictActionImmutability: true });\n\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).toThrowError(/Cannot assign to read only property/);\n    }));\n\n    it('should not throw when disabled', fakeAsync(() => {\n      const store = setupStore({ strictActionImmutability: false });\n\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).not.toThrow();\n    }));\n\n    it('should not throw for NgRx actions', fakeAsync(() => {\n      const store = setupStore({ strictActionImmutability: true });\n\n      expect(() => {\n        store.dispatch(makeNgrxAction(invalidAction()));\n        flush();\n      }).not.toThrow();\n    }));\n  });\n\n  describe('Action in NgZone', () => {\n    const invalidAction = () => ({ type: ErrorTypes.OutOfNgZoneAction });\n\n    it('should throw when running outside ngZone', fakeAsync(() => {\n      ngCore.NgZone.isInAngularZone = vi.fn().mockReturnValue(false);\n      const store = setupStore({ strictActionWithinNgZone: true });\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).toThrowError(\n        \"Action 'Action triggered outside of NgZone' running outside NgZone. https://ngrx.io/guide/store/configuration/runtime-checks#strictactionwithinngzone\"\n      );\n    }));\n\n    it('should not throw when running in ngZone', fakeAsync(() => {\n      ngCore.NgZone.isInAngularZone = vi.fn().mockReturnValue(true);\n      const store = setupStore({ strictActionWithinNgZone: true });\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).not.toThrowError();\n\n      expect(ngCore.NgZone.isInAngularZone).toHaveBeenCalled();\n    }));\n\n    it('should not be called when disabled', fakeAsync(() => {\n      const store = setupStore({ strictActionWithinNgZone: false });\n      ngCore.NgZone.isInAngularZone = vi.fn();\n      expect(() => {\n        store.dispatch(invalidAction());\n        flush();\n      }).not.toThrow();\n\n      expect(ngCore.NgZone.isInAngularZone).not.toHaveBeenCalled();\n    }));\n  });\n});\n\ndescribe('ActionType uniqueness', () => {\n  beforeEach(() => {\n    // Clear before each test because action types are registered during tests\n    resetRegisteredActionTypes();\n  });\n\n  it('should throw when having duplicate action types', () => {\n    createAction('action 1');\n    createAction('action 1');\n\n    expect(() => {\n      setupStore({ strictActionTypeUniqueness: true });\n    }).toThrowError(/Action types are registered more than once/);\n  });\n\n  it('should not throw when having no duplicate action types', () => {\n    createAction('action 1');\n    createAction('action 2');\n\n    expect(() => {\n      setupStore({ strictActionTypeUniqueness: true });\n    }).not.toThrowError();\n  });\n\n  it('should not throw when disabled', () => {\n    createAction('action 1');\n    createAction('action 1');\n\n    expect(() => {\n      setupStore({ strictActionTypeUniqueness: false });\n    }).not.toThrowError();\n  });\n});\n\nfunction setupStore(runtimeChecks?: Partial<RuntimeChecks>): Store<any> {\n  TestBed.configureTestingModule({\n    imports: [\n      StoreModule.forRoot(\n        {\n          state: reducerWithBugs,\n        },\n        { runtimeChecks }\n      ),\n    ],\n  });\n\n  return TestBed.inject(Store);\n}\n\nenum ErrorTypes {\n  UnserializableState = 'Action type producing unserializable state',\n  UnserializableAction = 'Action type producing unserializable action',\n  MutateAction = 'Action type producing action mutation',\n  MutateState = 'Action type producing state mutation',\n  OutOfNgZoneAction = 'Action triggered outside of NgZone',\n}\n\nfunction reducerWithBugs(state: any = {}, action: any) {\n  switch (action.type) {\n    case ErrorTypes.UnserializableState:\n      return {\n        invalidSerializationState: true,\n        invalid: new Date(),\n      };\n\n    case ErrorTypes.UnserializableAction: {\n      return {\n        invalidSerializationAction: true,\n      };\n    }\n\n    case '@ngrx ' + ErrorTypes.UnserializableAction: {\n      return {\n        invalidSerializationAction: true,\n      };\n    }\n\n    case ErrorTypes.MutateAction: {\n      action.foo = 'foo';\n      return {\n        invalidMutationAction: true,\n      };\n    }\n    case '@ngrx ' + ErrorTypes.MutateAction: {\n      action.foo = 'foo';\n      return {\n        invalidMutationAction: true,\n      };\n    }\n\n    case ErrorTypes.MutateState: {\n      state.invalidMutationState = true;\n      return state;\n    }\n\n    default:\n      return state;\n  }\n}\n\nexport function makeNgrxAction(action: Action) {\n  action.type = '@ngrx ' + action.type;\n  return action;\n}\n"
  },
  {
    "path": "modules/store/spec/runtime_checks_meta_reducers.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { Store, StoreModule, USER_RUNTIME_CHECKS } from '..';\nimport * as metaReducers from '../src/meta-reducers';\n\ndescribe('USER_RUNTIME_CHECKS Token', () => {\n  it('should be possible to toggle runtime reducers via the Injection Token', () => {\n    const serializationCheckMetaReducerSpy = vi.spyOn(\n      metaReducers,\n      'serializationCheckMetaReducer'\n    );\n\n    TestBed.configureTestingModule({\n      imports: [StoreModule.forRoot({})],\n      providers: [\n        {\n          provide: USER_RUNTIME_CHECKS,\n          useValue: {\n            strictStateSerializability: true,\n          },\n        },\n      ],\n    });\n\n    const _store = TestBed.inject(Store);\n    expect(serializationCheckMetaReducerSpy).toHaveBeenCalled();\n\n    // Needs to reset or else the test fails\n    serializationCheckMetaReducerSpy.mockReset();\n  });\n\n  it('should not create a meta reducer if not desired', () => {\n    const serializationCheckMetaReducerSpy = vi.spyOn(\n      metaReducers,\n      'serializationCheckMetaReducer'\n    );\n    const inNgZoneAssertMetaReducerSpy = vi.spyOn(\n      metaReducers,\n      'inNgZoneAssertMetaReducer'\n    );\n\n    TestBed.configureTestingModule({\n      imports: [StoreModule.forRoot({})],\n      providers: [\n        {\n          provide: USER_RUNTIME_CHECKS,\n          useValue: {\n            strictStateSerializability: false,\n            strictActionWithinNgZone: false,\n          },\n        },\n      ],\n    });\n\n    const _store = TestBed.inject(Store);\n    expect(serializationCheckMetaReducerSpy).not.toHaveBeenCalled();\n    expect(inNgZoneAssertMetaReducerSpy).not.toHaveBeenCalled();\n\n    // Needs to reset or else the test fails\n    serializationCheckMetaReducerSpy.mockReset();\n    inNgZoneAssertMetaReducerSpy.mockReset();\n  });\n\n  it('should create immutability meta reducer without config', () => {\n    const serializationCheckMetaReducerSpy = vi.spyOn(\n      metaReducers,\n      'serializationCheckMetaReducer'\n    );\n    const immutabilityCheckMetaReducerSpy = vi.spyOn(\n      metaReducers,\n      'immutabilityCheckMetaReducer'\n    );\n\n    TestBed.configureTestingModule({\n      imports: [StoreModule.forRoot({})],\n      providers: [\n        {\n          provide: USER_RUNTIME_CHECKS,\n          useValue: {},\n        },\n      ],\n    });\n\n    const _store = TestBed.inject(Store);\n    expect(serializationCheckMetaReducerSpy).not.toHaveBeenCalled();\n    expect(immutabilityCheckMetaReducerSpy).toHaveBeenCalled();\n\n    // Needs to reset or else the test fails\n    serializationCheckMetaReducerSpy.mockReset();\n    immutabilityCheckMetaReducerSpy.mockReset();\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/selector.spec.ts",
    "content": "import * as ngCore from '@angular/core';\nimport { cold } from 'jasmine-marbles';\nimport {\n  createSelector,\n  createFeatureSelector,\n  defaultMemoize,\n  createSelectorFactory,\n  resultMemoize,\n  MemoizedProjection,\n} from '..';\nimport { map, distinctUntilChanged } from 'rxjs/operators';\nimport { setNgrxMockEnvironment } from '../src';\n\nimport { type Mock, vi } from 'vitest';\n\nvi.mock('@angular/core', { spy: true });\n\ndescribe('Selectors', () => {\n  let countOne: number;\n  let countTwo: number;\n  let countThree: number;\n\n  let incrementOne: Mock;\n  let incrementTwo: Mock;\n  let incrementThree: Mock;\n\n  beforeEach(() => {\n    countOne = 0;\n    countTwo = 0;\n    countThree = 0;\n\n    incrementOne = vi.fn(() => {\n      return ++countOne;\n    });\n\n    incrementTwo = vi.fn(() => {\n      return ++countTwo;\n    });\n\n    incrementThree = vi.fn(() => {\n      return ++countThree;\n    });\n  });\n\n  describe('createSelector', () => {\n    it('should deliver the value of selectors to the projection function', () => {\n      const projectFn = vi.fn();\n\n      const selector = createSelector(\n        incrementOne,\n        incrementTwo,\n        projectFn\n      )({});\n\n      expect(projectFn).toHaveBeenCalledWith(countOne, countTwo);\n    });\n\n    it('should allow an override of the selector return', () => {\n      const projectFn = vi.fn().mockReturnValue(2);\n\n      const selector = createSelector(incrementOne, incrementTwo, projectFn);\n\n      expect((selector.projector as any)()).toBe(2);\n\n      selector.setResult(5);\n\n      const result2 = selector({});\n\n      expect(result2).toBe(5);\n    });\n\n    it('should be possible to test a projector fn independent from the selectors it is composed of', () => {\n      const projectFn = vi.fn();\n      const selector = createSelector(incrementOne, incrementTwo, projectFn);\n\n      selector.projector('', '');\n\n      expect(incrementOne).not.toHaveBeenCalled();\n      expect(incrementTwo).not.toHaveBeenCalled();\n      expect(projectFn).toHaveBeenCalledWith('', '');\n    });\n\n    it('should call the projector function only when the value of a dependent selector change', () => {\n      const firstState = { first: 'state', unchanged: 'state' };\n      const secondState = { second: 'state', unchanged: 'state' };\n      const neverChangingSelector = vi.fn((state: any) => {\n        return state.unchanged;\n      });\n      const projectFn = vi.fn();\n      const selector = createSelector(neverChangingSelector, projectFn);\n\n      selector(firstState);\n      selector(secondState);\n\n      expect(projectFn).toHaveBeenCalledTimes(1);\n    });\n\n    it('should memoize the function', () => {\n      const firstState = { first: 'state' };\n      const secondState = { second: 'state' };\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        incrementOne,\n        incrementTwo,\n        incrementThree,\n        projectFn\n      );\n\n      selector(firstState);\n      selector(firstState);\n      selector(firstState);\n      selector(secondState);\n      selector(secondState);\n\n      expect(incrementOne).toHaveBeenCalledTimes(2);\n      expect(incrementTwo).toHaveBeenCalledTimes(2);\n      expect(incrementThree).toHaveBeenCalledTimes(2);\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n\n    it('should not memoize last successful projection result in case of error', () => {\n      const firstState = { ok: true };\n      const secondState = { ok: false };\n      const fail = () => {\n        throw new Error();\n      };\n      const projectorFn = vi.fn((s: any) => (s.ok ? s.ok : fail()));\n      const selectorFn = vi.fn(\n        createSelector((state: any) => state, projectorFn)\n      );\n\n      selectorFn(firstState);\n\n      expect(() => selectorFn(secondState)).toThrow(new Error());\n      expect(() => selectorFn(secondState)).toThrow(new Error());\n\n      selectorFn(firstState);\n      expect(selectorFn).toHaveBeenCalledTimes(4);\n      expect(projectorFn).toHaveBeenCalledTimes(3);\n    });\n\n    it('should allow you to release memoized arguments', () => {\n      const state = { first: 'state' };\n      const projectFn = vi.fn();\n      const selector = createSelector(incrementOne, projectFn);\n\n      selector(state);\n      selector(state);\n      selector.release();\n      selector(state);\n      selector(state);\n\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n\n    it('should recursively release ancestor selectors', () => {\n      const grandparent = createSelector(incrementOne, (a) => a);\n      const parent = createSelector(grandparent, (a) => a);\n      const child = createSelector(parent, (a) => a);\n\n      vi.spyOn(grandparent, 'release');\n      vi.spyOn(parent, 'release');\n\n      child.release();\n\n      expect(grandparent.release).toHaveBeenCalled();\n      expect(parent.release).toHaveBeenCalled();\n    });\n\n    it('should create a selector from selectors dictionary', () => {\n      interface State {\n        x: number;\n        y: string;\n      }\n\n      const selectX = (state: State) => state.x + 1;\n      const selectY = (state: State) => state.y;\n\n      const selectDictionary = createSelector({\n        s: selectX,\n        m: selectY,\n      });\n\n      expect(selectDictionary({ x: 1, y: 'ngrx' })).toEqual({\n        s: 2,\n        m: 'ngrx',\n      });\n      expect(selectDictionary({ x: 2, y: 'ngrx' })).toEqual({\n        s: 3,\n        m: 'ngrx',\n      });\n    });\n\n    it('should create a selector from empty dictionary', () => {\n      const selectDictionary = createSelector({});\n\n      expect(selectDictionary({ x: 1, y: 'ngrx' })).toEqual({});\n      expect(selectDictionary({ x: 2, y: 'store' })).toEqual({});\n    });\n  });\n\n  describe('createSelector with props', () => {\n    it('should deliver the value of selectors to the projection function', () => {\n      const projectFn = vi.fn();\n\n      const selector = createSelector(\n        incrementOne,\n        incrementTwo,\n        (state: any, props: any) => props.value,\n        projectFn\n      );\n\n      selector({}, { value: 47 });\n      expect(projectFn).toHaveBeenCalledWith(countOne, countTwo, 47, {\n        value: 47,\n      });\n    });\n\n    it('should be possible to test a projector fn independent from the selectors it is composed of', () => {\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        incrementOne,\n        incrementTwo,\n        (state: any, props: any) => {\n          fail(`Shouldn't be called`);\n          return props.value;\n        },\n        projectFn\n      );\n      selector.projector('', '', 47, 'prop');\n\n      expect(incrementOne).not.toHaveBeenCalled();\n      expect(incrementTwo).not.toHaveBeenCalled();\n      expect(projectFn).toHaveBeenCalledWith('', '', 47, 'prop');\n    });\n\n    it('should call the projector function when the state changes', () => {\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        incrementOne,\n        (state: any, props: any) => props.value,\n        projectFn\n      );\n\n      const firstSate = { first: 'state' };\n      const props = { foo: 'props' };\n      selector(firstSate, props);\n      selector(firstSate, props);\n      expect(projectFn).toHaveBeenCalledTimes(1);\n\n      const secondState = { second: 'state' };\n      selector(secondState, props);\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n\n    it('should memoize the function', () => {\n      let counter = 0;\n\n      const firstState = { first: 'state' };\n      const secondState = { second: 'state' };\n      const props = { foo: 'props' };\n\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        incrementOne,\n        incrementTwo,\n        (state: any, props: any) => {\n          counter++;\n          return props;\n        },\n        projectFn\n      );\n\n      selector(firstState, props);\n      selector(firstState, props);\n      selector(firstState, props);\n      selector(secondState, props);\n      selector(secondState, props);\n\n      expect(counter).toBe(2);\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n\n    it('should allow you to release memoized arguments', () => {\n      const state = { first: 'state' };\n      const props = { foo: 'props' };\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        incrementOne,\n        (state: any, props: any) => props,\n        projectFn\n      );\n\n      selector(state, props);\n      selector(state, props);\n      selector.release();\n      selector(state, props);\n      selector(state, props);\n\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n  });\n\n  describe('createSelector with arrays', () => {\n    it('should deliver the value of selectors to the projection function', () => {\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        [incrementOne, incrementTwo],\n        projectFn\n      )({});\n\n      expect(projectFn).toHaveBeenCalledWith(countOne, countTwo);\n    });\n\n    it('should be possible to test a projector fn independent from the selectors it is composed of', () => {\n      const projectFn = vi.fn();\n      const selector = createSelector([incrementOne, incrementTwo], projectFn);\n\n      selector.projector('', '');\n\n      expect(incrementOne).not.toHaveBeenCalled();\n      expect(incrementTwo).not.toHaveBeenCalled();\n      expect(projectFn).toHaveBeenCalledWith('', '');\n    });\n\n    it('should call the projector function only when the value of a dependent selector change', () => {\n      const firstState = { first: 'state', unchanged: 'state' };\n      const secondState = { second: 'state', unchanged: 'state' };\n      const neverChangingSelector = vi.fn((state: any) => {\n        return state.unchanged;\n      });\n      const projectFn = vi.fn();\n      const selector = createSelector([neverChangingSelector], projectFn);\n\n      selector(firstState);\n      selector(secondState);\n\n      expect(projectFn).toHaveBeenCalledTimes(1);\n    });\n\n    it('should memoize the function', () => {\n      const firstState = { first: 'state' };\n      const secondState = { second: 'state' };\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        [incrementOne, incrementTwo, incrementThree],\n        projectFn\n      );\n\n      selector(firstState);\n      selector(firstState);\n      selector(firstState);\n      selector(secondState);\n      selector(secondState);\n\n      expect(incrementOne).toHaveBeenCalledTimes(2);\n      expect(incrementTwo).toHaveBeenCalledTimes(2);\n      expect(incrementThree).toHaveBeenCalledTimes(2);\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n\n    it('should allow you to release memoized arguments', () => {\n      const state = { first: 'state' };\n      const projectFn = vi.fn();\n      const selector = createSelector([incrementOne], projectFn);\n\n      selector(state);\n      selector(state);\n      selector.release();\n      selector(state);\n      selector(state);\n\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n\n    it('should recursively release ancestor selectors', () => {\n      const grandparent = createSelector([incrementOne], (a) => a);\n      const parent = createSelector([grandparent], (a) => a);\n      const child = createSelector([parent], (a) => a);\n\n      vi.spyOn(grandparent, 'release');\n      vi.spyOn(parent, 'release');\n\n      child.release();\n\n      expect(grandparent.release).toHaveBeenCalled();\n      expect(parent.release).toHaveBeenCalled();\n    });\n  });\n\n  describe('createSelector with arrays and props', () => {\n    it('should deliver the value of selectors to the projection function', () => {\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        [incrementOne, incrementTwo, (state: any, props: any) => props.value],\n        projectFn\n      )({}, { value: 47 });\n\n      expect(projectFn).toHaveBeenCalledWith(countOne, countTwo, 47, {\n        value: 47,\n      });\n    });\n\n    it('should be possible to test a projector fn independent from the selectors it is composed of', () => {\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        [\n          incrementOne,\n          incrementTwo,\n          (state: any, props: any) => {\n            fail(`Shouldn't be called`);\n            return props.value;\n          },\n        ],\n        projectFn\n      );\n\n      selector.projector('', '', 47, 'prop');\n\n      expect(incrementOne).not.toHaveBeenCalled();\n      expect(incrementTwo).not.toHaveBeenCalled();\n      expect(projectFn).toHaveBeenCalledWith('', '', 47, 'prop');\n    });\n\n    it('should call the projector function when the state changes', () => {\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        [incrementOne, (state: any, props: any) => props.value],\n        projectFn\n      );\n\n      const firstSate = { first: 'state' };\n      const props = { foo: 'props' };\n      selector(firstSate, props);\n      selector(firstSate, props);\n      expect(projectFn).toHaveBeenCalledTimes(1);\n\n      const secondState = { second: 'state' };\n      selector(secondState, props);\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n\n    it('should memoize the function', () => {\n      let counter = 0;\n\n      const firstState = { first: 'state' };\n      const secondState = { second: 'state' };\n      const props = { foo: 'props' };\n\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        [\n          incrementOne,\n          incrementTwo,\n          (state: any, props: any) => {\n            counter++;\n            return props;\n          },\n        ],\n        projectFn\n      );\n\n      selector(firstState, props);\n      selector(firstState, props);\n      selector(firstState, props);\n      selector(secondState, props);\n      selector(secondState, props);\n\n      expect(counter).toBe(2);\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n\n    it('should allow you to release memoized arguments', () => {\n      const state = { first: 'state' };\n      const props = { foo: 'props' };\n      const projectFn = vi.fn();\n      const selector = createSelector(\n        [incrementOne, (state: any, props: any) => props],\n        projectFn\n      );\n\n      selector(state, props);\n      selector(state, props);\n      selector.release();\n      selector(state, props);\n      selector(state, props);\n\n      expect(projectFn).toHaveBeenCalledTimes(2);\n    });\n  });\n\n  describe('createFeatureSelector', () => {\n    const featureName = 'featureA';\n    let featureSelector: (state: any) => number;\n    let warnSpy: Mock;\n\n    beforeEach(() => {\n      featureSelector = createFeatureSelector<number>(featureName);\n      warnSpy = vi.spyOn(console, 'warn');\n    });\n\n    afterEach(() => warnSpy.mockReset());\n\n    it('should memoize the result', () => {\n      const firstValue = { first: 'value' };\n      const firstState = { [featureName]: firstValue };\n      const secondValue = { secondValue: 'value' };\n      const secondState = { [featureName]: secondValue };\n\n      const state$ = cold('--a--a--a--b--', { a: firstState, b: secondState });\n      const expected$ = cold('--a--------b--', {\n        a: firstValue,\n        b: secondValue,\n      });\n      const featureState$ = state$.pipe(\n        map(featureSelector),\n        distinctUntilChanged()\n      );\n\n      expect(featureState$).toBeObservable(expected$);\n    });\n\n    describe('Warning', () => {\n      describe('should not log when: ', () => {\n        it('the feature does exist', () => {\n          const ngSpy = vi.mocked(ngCore.isDevMode).mockReturnValue(true);\n          const selector = createFeatureSelector('featureA');\n\n          selector({ featureA: {} });\n\n          expect(warnSpy).not.toHaveBeenCalled();\n\n          ngSpy.mockReset();\n          ngSpy.mockReturnValue(true);\n        });\n\n        it('the feature key exist but is falsy', () => {\n          const ngSpy = vi.mocked(ngCore.isDevMode).mockReturnValue(true);\n          const selector = createFeatureSelector('featureB');\n\n          selector({ featureA: {}, featureB: undefined });\n\n          expect(warnSpy).not.toHaveBeenCalled();\n\n          ngSpy.mockReset();\n          ngSpy.mockReturnValue(true);\n        });\n\n        it('not in development mode', () => {\n          const ngSpy = vi.mocked(ngCore.isDevMode).mockReturnValue(false);\n          const selector = createFeatureSelector('featureB');\n\n          selector({ featureA: {} });\n\n          expect(warnSpy).not.toHaveBeenCalled();\n\n          ngSpy.mockReset();\n          ngSpy.mockReturnValue(true);\n        });\n      });\n\n      describe('warning will ', () => {\n        it('be logged when not in mock environment', () => {\n          const ngSpy = vi.mocked(ngCore.isDevMode).mockReturnValue(true);\n          const selector = createFeatureSelector('featureB');\n\n          selector({ featureA: {} });\n\n          expect(warnSpy).toHaveBeenCalled();\n          expect(warnSpy.mock.lastCall?.[0]).toMatch(\n            /The feature name \"featureB\" does not exist/\n          );\n\n          ngSpy.mockReset();\n        });\n\n        it('not be logged when in mock environment', () => {\n          setNgrxMockEnvironment(true);\n          const selector = createFeatureSelector('featureB');\n\n          selector({ featureA: {} });\n\n          expect(warnSpy).not.toHaveBeenCalled();\n          setNgrxMockEnvironment(false);\n        });\n      });\n    });\n  });\n\n  describe('createSelectorFactory', () => {\n    it('should return a selector creator function', () => {\n      const projectFn = vi.fn();\n      const selectorFunc = createSelectorFactory(defaultMemoize);\n\n      const selector = selectorFunc(incrementOne, incrementTwo, projectFn)({});\n\n      expect(projectFn).toHaveBeenCalledWith(countOne, countTwo);\n    });\n\n    it('should allow a custom memoization function', () => {\n      const projectFn = vi.fn();\n      const anyFn = vi.fn(() => true);\n      const equalFn = vi.fn(() => true);\n      const customMemoizer = (aFn: any = anyFn, eFn: any = equalFn) =>\n        defaultMemoize(anyFn, equalFn);\n      const customSelector = createSelectorFactory(customMemoizer);\n\n      const selector = customSelector(incrementOne, incrementTwo, projectFn);\n      selector(1);\n      selector(2);\n\n      expect(anyFn.mock.calls.length).toEqual(1);\n    });\n\n    it('should allow a custom state memoization function', () => {\n      const projectFn = vi.fn();\n      const stateFn = vi.fn();\n      const selectorFunc = createSelectorFactory(defaultMemoize, { stateFn });\n\n      const selector = selectorFunc(incrementOne, incrementTwo, projectFn)({});\n\n      expect(stateFn).toHaveBeenCalled();\n    });\n  });\n\n  describe('defaultMemoize', () => {\n    it('should allow a custom equality function', () => {\n      const anyFn = vi.fn(() => true);\n      const equalFn = vi.fn(() => true);\n      const memoizer = defaultMemoize(anyFn, equalFn);\n\n      memoizer.memoized(1, 2, 3);\n      memoizer.memoized(1, 2);\n\n      expect(anyFn.mock.calls.length).toEqual(1);\n    });\n  });\n\n  describe('resultMemoize', () => {\n    let projectionFnSpy: Mock;\n    const ARRAY = ['a', 'ab', 'b'];\n    const ARRAY_CHANGED = [...ARRAY, 'bc'];\n    const A_FILTER: { by: string } = { by: 'a' };\n    const B_FILTER: { by: string } = { by: 'b' };\n\n    let arrayMemoizer: MemoizedProjection;\n\n    // Compare a and b on equality. If a and b are Arrays then compare them\n    // on their content.\n    function isResultEqual(a: any, b: any) {\n      if (a instanceof Array) {\n        return a.length === b.length && a.every((fromA) => b.includes(fromA));\n      }\n      // Default comparison\n      return a === b;\n    }\n\n    beforeEach(() => {\n      projectionFnSpy = vi.fn((arr: string[], filter: { by: string }) =>\n        arr.filter((item) => item.startsWith(filter.by))\n      );\n\n      arrayMemoizer = resultMemoize(projectionFnSpy, isResultEqual);\n    });\n\n    it('should not rerun projector function when arguments stayed the same', () => {\n      arrayMemoizer.memoized(ARRAY, A_FILTER);\n      arrayMemoizer.memoized(ARRAY, A_FILTER);\n\n      expect(projectionFnSpy.mock.calls.length).toBe(1);\n    });\n\n    it('should rerun projector function when arguments changed', () => {\n      arrayMemoizer.memoized(ARRAY, A_FILTER);\n      arrayMemoizer.memoized(ARRAY_CHANGED, A_FILTER);\n\n      expect(projectionFnSpy.mock.calls.length).toBe(2);\n    });\n\n    it('should return the same instance of results when projector function produces the same results array', () => {\n      const result1 = arrayMemoizer.memoized(ARRAY, A_FILTER);\n      const result2 = arrayMemoizer.memoized(ARRAY, A_FILTER);\n\n      expect(result1).toBe(result2);\n    });\n\n    it('should return the same instance of results when projector function produces similar results array', () => {\n      const result1 = arrayMemoizer.memoized(ARRAY, A_FILTER);\n      const result2 = arrayMemoizer.memoized(ARRAY_CHANGED, A_FILTER);\n\n      expect(result1).toBe(result2);\n    });\n\n    it('should return the new instance of results when projector function produces different result', () => {\n      const result1 = arrayMemoizer.memoized(ARRAY, A_FILTER);\n      const result2 = arrayMemoizer.memoized(ARRAY_CHANGED, B_FILTER);\n\n      expect(result1).toBeDefined();\n      expect(result2).toBeDefined();\n      expect(result1).not.toBe(result2);\n      expect(result1).not.toEqual(result2);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/state.spec.ts",
    "content": "import { TestBed, fakeAsync, flush } from '@angular/core/testing';\nimport { INIT, Store, StoreModule, Action } from '..';\n\ndescribe('ngRx State', () => {\n  it('should call the reducer to scan over the dispatcher', () => {\n    const initialState = 123;\n    const reducer = vi.fn(() => initialState);\n\n    TestBed.configureTestingModule({\n      imports: [\n        StoreModule.forRoot(\n          { key: reducer },\n          { initialState: { key: initialState } }\n        ),\n      ],\n    });\n\n    TestBed.inject(Store);\n\n    expect(reducer).toHaveBeenCalledWith(initialState, {\n      type: INIT,\n    });\n  });\n\n  it('should fail synchronously', fakeAsync(() => {\n    function reducer(state: any, action: Action) {\n      if (action.type === 'THROW_ERROR') {\n        throw new Error('(╯°□°）╯︵ ┻━┻');\n      }\n\n      return state;\n    }\n\n    TestBed.configureTestingModule({\n      imports: [StoreModule.forRoot({ reducer })],\n    });\n\n    const store = TestBed.inject(Store);\n    expect(() => {\n      store.dispatch({ type: 'THROW_ERROR' });\n      flush();\n    }).toThrow();\n  }));\n});\n"
  },
  {
    "path": "modules/store/spec/store.spec.ts",
    "content": "import {\n  createEnvironmentInjector,\n  EnvironmentInjector,\n  InjectionToken,\n  runInInjectionContext,\n  signal,\n} from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { hot } from 'jasmine-marbles';\nimport {\n  ActionsSubject,\n  ReducerManager,\n  Store,\n  StoreModule,\n  select,\n  ReducerManagerDispatcher,\n  UPDATE,\n  ActionReducer,\n  Action,\n  createAction,\n  props,\n} from '../';\nimport { StoreConfig } from '../src/store_config';\nimport { combineReducers } from '../src/utils';\nimport {\n  counterReducer,\n  INCREMENT,\n  DECREMENT,\n  RESET,\n  counterReducer2,\n} from './fixtures/counter';\nimport { take } from 'rxjs/operators';\nimport type { Mock } from 'vitest';\n\ninterface TestAppSchema {\n  counter1: number;\n  counter2: number;\n  counter3: number;\n  counter4?: number;\n}\n\ndescribe('ngRx Store', () => {\n  let store: Store<TestAppSchema>;\n  let dispatcher: ActionsSubject;\n\n  function setup(\n    initialState: any = { counter1: 0, counter2: 1 },\n    metaReducers: any = []\n  ) {\n    const reducers = {\n      counter1: counterReducer,\n      counter2: counterReducer,\n      counter3: counterReducer,\n    };\n\n    TestBed.configureTestingModule({\n      imports: [StoreModule.forRoot(reducers, { initialState, metaReducers })],\n    });\n\n    store = TestBed.inject(Store);\n    dispatcher = TestBed.inject(ActionsSubject);\n  }\n\n  describe('initial state', () => {\n    it('should handle an initial state object', () =>\n      new Promise<void>((done) => {\n        setup();\n        testStoreValue({ counter1: 0, counter2: 1, counter3: 0 }, done);\n      }));\n\n    it('should handle an initial state function', () =>\n      new Promise<void>((done) => {\n        setup(() => ({ counter1: 0, counter2: 5 }));\n        testStoreValue({ counter1: 0, counter2: 5, counter3: 0 }, done);\n      }));\n\n    it('should keep initial state values when state is partially initialized', () =>\n      new Promise<void>((done) => {\n        TestBed.configureTestingModule({\n          imports: [\n            StoreModule.forRoot({} as any, {\n              initialState: {\n                feature1: {\n                  counter1: 1,\n                },\n                feature3: {\n                  counter3: 3,\n                },\n              },\n            }),\n            StoreModule.forFeature('feature1', { counter1: counterReducer }),\n            StoreModule.forFeature('feature2', { counter2: counterReducer }),\n            StoreModule.forFeature('feature3', { counter3: counterReducer }),\n          ],\n        });\n\n        testStoreValue(\n          {\n            feature1: { counter1: 1 },\n            feature2: { counter2: 0 },\n            feature3: { counter3: 3 },\n          },\n          done\n        );\n      }));\n\n    it('should reset to initial state when undefined (root ActionReducerMap)', () => {\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot(\n            { counter1: counterReducer },\n            { initialState: { counter1: 1 } }\n          ),\n        ],\n      });\n\n      testInitialState();\n    });\n\n    it('should reset to initial state when undefined (feature ActionReducer)', () => {\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot({}),\n          StoreModule.forFeature('counter1', counterReducer, {\n            initialState: 1,\n          }),\n        ],\n      });\n\n      testInitialState();\n    });\n\n    it('should reset to initial state when undefined (feature ActionReducerMap)', () => {\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot({}),\n          StoreModule.forFeature(\n            'feature1',\n            { counter1: counterReducer },\n            { initialState: { counter1: 1 } }\n          ),\n        ],\n      });\n\n      testInitialState('feature1');\n    });\n\n    function testInitialState(feature?: string) {\n      store = TestBed.inject(Store);\n      dispatcher = TestBed.inject(ActionsSubject);\n\n      const actionSequence = '--a--b--c--d--e--f--g';\n      const stateSequence = 'i-w-----x-----y--z---';\n      const actionValues = {\n        a: { type: INCREMENT },\n        b: { type: 'OTHER' },\n        c: { type: RESET },\n        d: { type: 'OTHER' }, // reproduces https://github.com/ngrx/platform/issues/880 because state is falsey\n        e: { type: INCREMENT },\n        f: { type: INCREMENT },\n        g: { type: 'OTHER' },\n      };\n      const counterSteps = hot(actionSequence, actionValues);\n      counterSteps.subscribe((action) => store.dispatch(action));\n\n      const counterStateWithString = feature\n        ? (store as any).select(feature, 'counter1')\n        : store.select('counter1');\n\n      const counter1Values = { i: 1, w: 2, x: 0, y: 1, z: 2 };\n\n      expect(counterStateWithString).toBeObservable(\n        hot(stateSequence, counter1Values)\n      );\n    }\n\n    function testStoreValue(expected: any, done: any) {\n      store = TestBed.inject(Store);\n\n      store.pipe(take(1)).subscribe({\n        next(val) {\n          expect(val).toEqual(expected);\n        },\n        error: done,\n        complete: done,\n      });\n    }\n  });\n\n  describe('basic store actions', () => {\n    beforeEach(() => setup());\n\n    it('should provide an Observable Store', () => {\n      expect(store).toBeDefined();\n    });\n\n    const actionSequence = '--a--b--c--d--e';\n    const actionValues = {\n      a: { type: INCREMENT },\n      b: { type: INCREMENT },\n      c: { type: DECREMENT },\n      d: { type: RESET },\n      e: { type: INCREMENT },\n    };\n\n    it('should let you select state with a key name', () => {\n      const counterSteps = hot(actionSequence, actionValues);\n\n      counterSteps.subscribe((action) => store.dispatch(action));\n\n      const counterStateWithString = store.pipe(select('counter1'));\n\n      const stateSequence = 'i-v--w--x--y--z';\n      const counter1Values = { i: 0, v: 1, w: 2, x: 1, y: 0, z: 1 };\n\n      expect(counterStateWithString).toBeObservable(\n        hot(stateSequence, counter1Values)\n      );\n    });\n\n    it('should let you select state with a selector function', () => {\n      const counterSteps = hot(actionSequence, actionValues);\n\n      counterSteps.subscribe((action) => store.dispatch(action));\n\n      const counterStateWithFunc = store.pipe(select((s) => s.counter1));\n\n      const stateSequence = 'i-v--w--x--y--z';\n      const counter1Values = { i: 0, v: 1, w: 2, x: 1, y: 0, z: 1 };\n\n      expect(counterStateWithFunc).toBeObservable(\n        hot(stateSequence, counter1Values)\n      );\n    });\n\n    it('should correctly lift itself', () => {\n      const result = store.pipe(select('counter1'));\n\n      expect(result instanceof Store).toBe(true);\n    });\n\n    it('should increment and decrement counter1', () => {\n      const counterSteps = hot(actionSequence, actionValues);\n\n      counterSteps.subscribe((action) => store.dispatch(action));\n\n      const counterState = store.pipe(select('counter1'));\n\n      const stateSequence = 'i-v--w--x--y--z';\n      const counter1Values = { i: 0, v: 1, w: 2, x: 1, y: 0, z: 1 };\n\n      expect(counterState).toBeObservable(hot(stateSequence, counter1Values));\n    });\n\n    it('should increment and decrement counter1 using the dispatcher', () => {\n      const counterSteps = hot(actionSequence, actionValues);\n\n      counterSteps.subscribe((action) => dispatcher.next(action));\n\n      const counterState = store.pipe(select('counter1'));\n\n      const stateSequence = 'i-v--w--x--y--z';\n      const counter1Values = { i: 0, v: 1, w: 2, x: 1, y: 0, z: 1 };\n\n      expect(counterState).toBeObservable(hot(stateSequence, counter1Values));\n    });\n\n    it('should increment and decrement counter2 separately', () => {\n      const counterSteps = hot(actionSequence, actionValues);\n\n      counterSteps.subscribe((action) => store.dispatch(action));\n\n      const counter1State = store.pipe(select('counter1'));\n      const counter2State = store.pipe(select('counter2'));\n\n      const stateSequence = 'i-v--w--x--y--z';\n      const counter2Values = { i: 1, v: 2, w: 3, x: 2, y: 0, z: 1 };\n\n      expect(counter2State).toBeObservable(hot(stateSequence, counter2Values));\n    });\n\n    it('should implement the observer interface forwarding actions and errors to the dispatcher', () => {\n      vi.spyOn(dispatcher, 'next').mockImplementationOnce(() => void 0);\n      vi.spyOn(dispatcher, 'error').mockImplementationOnce(() => void 0);\n\n      store.next(<any>1);\n      store.error(2);\n\n      expect(dispatcher.next).toHaveBeenCalledWith(1);\n      expect(dispatcher.error).toHaveBeenCalledWith(2);\n    });\n\n    it('should not be completable', () => {\n      const storeSubscription = store.subscribe();\n      const dispatcherSubscription = dispatcher.subscribe();\n\n      store.complete();\n      dispatcher.complete();\n\n      expect(storeSubscription.closed).toBe(false);\n      expect(dispatcherSubscription.closed).toBe(false);\n    });\n\n    it('should complete if the dispatcher is destroyed', () => {\n      const storeSubscription = store.subscribe();\n      const dispatcherSubscription = dispatcher.subscribe();\n\n      dispatcher.ngOnDestroy();\n\n      expect(dispatcherSubscription.closed).toBe(true);\n    });\n  });\n\n  describe(`add/remove reducers`, () => {\n    let addReducerSpy: Mock;\n    let removeReducerSpy: Mock;\n    let reducerManagerDispatcherSpy: Mock;\n    const key = 'counter4';\n\n    beforeEach(() => {\n      setup();\n      const reducerManager = TestBed.inject(ReducerManager);\n      const dispatcher = TestBed.inject(ReducerManagerDispatcher);\n      addReducerSpy = vi.spyOn(reducerManager, 'addReducer');\n      removeReducerSpy = vi.spyOn(reducerManager, 'removeReducer');\n      reducerManagerDispatcherSpy = vi.spyOn(dispatcher, 'next');\n    });\n\n    it(`should delegate add/remove to ReducerManager`, () => {\n      store.addReducer(key, counterReducer);\n      expect(addReducerSpy).toHaveBeenCalledWith(key, counterReducer);\n\n      store.removeReducer(key);\n      expect(removeReducerSpy).toHaveBeenCalledWith(key);\n    });\n\n    it(`should work with added / removed reducers`, () =>\n      new Promise<void>((done) => {\n        store.addReducer(key, counterReducer);\n        store.pipe(take(1)).subscribe((val) => {\n          expect(val.counter4).toBe(0);\n        });\n\n        store.removeReducer(key);\n        store.dispatch({ type: INCREMENT });\n        store.pipe(take(1)).subscribe((val) => {\n          expect(val.counter4).toBeUndefined();\n          done();\n        });\n      }));\n\n    it('should dispatch an update reducers action when a reducer is added', () => {\n      store.addReducer(key, counterReducer);\n      expect(reducerManagerDispatcherSpy).toHaveBeenCalledWith({\n        type: UPDATE,\n        features: [key],\n      });\n    });\n\n    it('should dispatch an update reducers action when a reducer is removed', () => {\n      store.removeReducer(key);\n      expect(reducerManagerDispatcherSpy).toHaveBeenCalledWith({\n        type: UPDATE,\n        features: [key],\n      });\n    });\n  });\n\n  describe('add/remove features', () => {\n    let reducerManager: ReducerManager;\n    let reducerManagerDispatcherSpy: Mock;\n\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        imports: [StoreModule.forRoot({})],\n      });\n\n      reducerManager = TestBed.inject(ReducerManager);\n      const dispatcher = TestBed.inject(ReducerManagerDispatcher);\n      reducerManagerDispatcherSpy = vi.spyOn(dispatcher, 'next');\n    });\n\n    it('should dispatch an update reducers action when a feature is added', () => {\n      reducerManager.addFeature({\n        key: 'feature1',\n        reducers: {},\n        reducerFactory: <any>combineReducers,\n      });\n\n      expect(reducerManagerDispatcherSpy).toHaveBeenCalledWith({\n        type: UPDATE,\n        features: ['feature1'],\n      });\n    });\n\n    it('should dispatch an update reducers action when multiple features are added', () => {\n      reducerManager.addFeatures([\n        {\n          key: 'feature1',\n          reducers: {},\n          reducerFactory: <any>combineReducers,\n        },\n        {\n          key: 'feature2',\n          reducers: {},\n          reducerFactory: <any>combineReducers,\n        },\n      ]);\n\n      expect(reducerManagerDispatcherSpy).toHaveBeenCalledTimes(1);\n      expect(reducerManagerDispatcherSpy).toHaveBeenCalledWith({\n        type: UPDATE,\n        features: ['feature1', 'feature2'],\n      });\n    });\n\n    it('should dispatch an update reducers action when a feature is removed', () => {\n      reducerManager.removeFeature(\n        createFeature({\n          key: 'feature1',\n        })\n      );\n\n      expect(reducerManagerDispatcherSpy).toHaveBeenCalledWith({\n        type: UPDATE,\n        features: ['feature1'],\n      });\n    });\n\n    it('should dispatch an update reducers action when multiple features are removed', () => {\n      reducerManager.removeFeatures([\n        createFeature({\n          key: 'feature1',\n        }),\n        createFeature({\n          key: 'feature2',\n        }),\n      ]);\n\n      expect(reducerManagerDispatcherSpy).toHaveBeenCalledTimes(1);\n      expect(reducerManagerDispatcherSpy).toHaveBeenCalledWith({\n        type: UPDATE,\n        features: ['feature1', 'feature2'],\n      });\n    });\n\n    function createFeature({ key }: { key: string }) {\n      return {\n        key,\n        reducers: {},\n        reducerFactory: vi.fn(),\n      };\n    }\n  });\n\n  describe('Meta Reducers', () => {\n    let metaReducerContainer: any;\n    let metaReducerSpy1: Mock;\n    let metaReducerSpy2: Mock;\n\n    beforeEach(() => {\n      metaReducerContainer = (function () {\n        function metaReducer1(reducer: ActionReducer<any, any>) {\n          return function (state: any, action: Action) {\n            return reducer(state, action);\n          };\n        }\n\n        function metaReducer2(reducer: ActionReducer<any, any>) {\n          return function (state: any, action: Action) {\n            return reducer(state, action);\n          };\n        }\n\n        return {\n          metaReducer1: metaReducer1,\n          metaReducer2: metaReducer2,\n        };\n      })();\n\n      metaReducerSpy1 = vi.spyOn(metaReducerContainer, 'metaReducer1');\n\n      metaReducerSpy2 = vi.spyOn(metaReducerContainer, 'metaReducer2');\n    });\n\n    it('should create a meta reducer for root and call it through', () => {\n      setup({}, [metaReducerContainer.metaReducer1]);\n      const action = { type: INCREMENT };\n      store.dispatch(action);\n      expect(metaReducerSpy1).toHaveBeenCalled();\n    });\n\n    it('should call two meta reducers', () => {\n      setup({}, [\n        metaReducerContainer.metaReducer1,\n        metaReducerContainer.metaReducer2,\n      ]);\n      const action = { type: INCREMENT };\n      store.dispatch(action);\n\n      expect(metaReducerSpy1).toHaveBeenCalled();\n      expect(metaReducerSpy2).toHaveBeenCalled();\n    });\n\n    it('should create a meta reducer for feature and call it with the expected reducer', () => {\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot({}),\n          StoreModule.forFeature('counter1', counterReducer, {\n            metaReducers: [metaReducerContainer.metaReducer1],\n          }),\n          StoreModule.forFeature('counter2', counterReducer2, {\n            metaReducers: [metaReducerContainer.metaReducer2],\n          }),\n        ],\n      });\n\n      const mockStore = TestBed.inject(Store);\n      const action = { type: INCREMENT };\n\n      mockStore.dispatch(action);\n\n      expect(metaReducerSpy1).toHaveBeenCalledWith(counterReducer);\n      expect(metaReducerSpy2).toHaveBeenCalledWith(counterReducer2);\n    });\n\n    it('should initial state with value', () =>\n      new Promise<void>((done) => {\n        const counterInitialState = 2;\n        TestBed.configureTestingModule({\n          imports: [\n            StoreModule.forRoot({}),\n            StoreModule.forFeature(\n              'counterState',\n              { counter: counterReducer },\n              {\n                initialState: { counter: counterInitialState },\n                metaReducers: [metaReducerContainer.metaReducer1],\n              }\n            ),\n          ],\n        });\n\n        const mockStore = TestBed.inject(Store);\n\n        mockStore.pipe(take(1)).subscribe({\n          next(val: any) {\n            expect(val['counterState'].counter).toEqual(counterInitialState);\n          },\n          error: done,\n          complete: done,\n        });\n      }));\n  });\n\n  describe('Feature config token', () => {\n    let FEATURE_CONFIG_TOKEN: InjectionToken<StoreConfig<any, any>>;\n    let FEATURE_CONFIG2_TOKEN: InjectionToken<StoreConfig<any, any>>;\n\n    beforeEach(() => {\n      FEATURE_CONFIG_TOKEN = new InjectionToken('Feature Config');\n      FEATURE_CONFIG2_TOKEN = new InjectionToken('Feature Config2');\n    });\n\n    it('should initial state with value', () =>\n      new Promise<void>((done) => {\n        const initialState = { counter1: 1 };\n        const featureKey = 'counter';\n\n        TestBed.configureTestingModule({\n          imports: [\n            StoreModule.forRoot({}),\n            StoreModule.forFeature(\n              featureKey,\n              counterReducer,\n              FEATURE_CONFIG_TOKEN\n            ),\n          ],\n          providers: [\n            {\n              provide: FEATURE_CONFIG_TOKEN,\n              useValue: { initialState: initialState },\n            },\n          ],\n        });\n\n        const mockStore = TestBed.inject(Store);\n\n        mockStore.pipe(take(1)).subscribe({\n          next(val: any) {\n            expect(val[featureKey]).toEqual(initialState);\n          },\n          error: done,\n          complete: done,\n        });\n      }));\n\n    it('should initial state with value for multi features', () =>\n      new Promise<void>((done) => {\n        const initialState = 1;\n        const initialState2 = 2;\n        const initialState3 = 3;\n        const featureKey = 'counter';\n        const featureKey2 = 'counter2';\n        const featureKey3 = 'counter3';\n\n        TestBed.configureTestingModule({\n          imports: [\n            StoreModule.forRoot({}),\n            StoreModule.forFeature(\n              featureKey,\n              counterReducer,\n              FEATURE_CONFIG_TOKEN\n            ),\n            StoreModule.forFeature(\n              featureKey2,\n              counterReducer,\n              FEATURE_CONFIG2_TOKEN\n            ),\n            StoreModule.forFeature(featureKey3, counterReducer, {\n              initialState: initialState3,\n            }),\n          ],\n          providers: [\n            {\n              provide: FEATURE_CONFIG_TOKEN,\n              useValue: { initialState: initialState },\n            },\n            {\n              provide: FEATURE_CONFIG2_TOKEN,\n              useValue: { initialState: initialState2 },\n            },\n          ],\n        });\n\n        const mockStore = TestBed.inject(Store);\n\n        mockStore.pipe(take(1)).subscribe({\n          next(val: any) {\n            expect(val[featureKey]).toEqual(initialState);\n            expect(val[featureKey2]).toEqual(initialState2);\n            expect(val[featureKey3]).toEqual(initialState3);\n          },\n          error: done,\n          complete: done,\n        });\n      }));\n\n    it('should create a meta reducer with config injection token and call it with the expected reducer', () => {\n      const metaReducerContainer = (function () {\n        function metaReducer1(reducer: ActionReducer<any, any>) {\n          return function (state: any, action: Action) {\n            return reducer(state, action);\n          };\n        }\n\n        function metaReducer2(reducer: ActionReducer<any, any>) {\n          return function (state: any, action: Action) {\n            return reducer(state, action);\n          };\n        }\n\n        return {\n          metaReducer1: metaReducer1,\n          metaReducer2: metaReducer2,\n        };\n      })();\n\n      const metaReducerSpy1 = vi.spyOn(metaReducerContainer, 'metaReducer1');\n\n      const metaReducerSpy2 = vi.spyOn(metaReducerContainer, 'metaReducer2');\n\n      TestBed.configureTestingModule({\n        imports: [\n          StoreModule.forRoot({}),\n          StoreModule.forFeature(\n            'counter1',\n            counterReducer,\n            FEATURE_CONFIG_TOKEN\n          ),\n          StoreModule.forFeature(\n            'counter2',\n            counterReducer2,\n            FEATURE_CONFIG2_TOKEN\n          ),\n        ],\n        providers: [\n          {\n            provide: FEATURE_CONFIG_TOKEN,\n            useValue: { metaReducers: [metaReducerContainer.metaReducer1] },\n          },\n          {\n            provide: FEATURE_CONFIG2_TOKEN,\n            useValue: { metaReducers: [metaReducerContainer.metaReducer2] },\n          },\n        ],\n      });\n      const mockStore = TestBed.inject(Store);\n      const action = { type: INCREMENT };\n      mockStore.dispatch(action);\n\n      expect(metaReducerSpy1).toHaveBeenCalledWith(counterReducer);\n      expect(metaReducerSpy2).toHaveBeenCalledWith(counterReducer2);\n    });\n  });\n\n  describe('Signal Dispatcher', () => {\n    const setupForSignalDispatcher = () => {\n      setup();\n      store = TestBed.inject(Store);\n\n      const inputId = signal(1);\n      const increment = createAction('INCREMENT', props<{ id: number }>());\n\n      const changeInputIdAndFlush = () => {\n        inputId.update((value) => value + 1);\n        TestBed.tick();\n      };\n\n      const stateSignal = store.selectSignal((state) => state.counter1);\n\n      return { inputId, increment, stateSignal, changeInputIdAndFlush };\n    };\n\n    it('should dispatch upon Signal change', () => {\n      const { inputId, increment, changeInputIdAndFlush, stateSignal } =\n        setupForSignalDispatcher();\n\n      expect(stateSignal()).toBe(0);\n\n      store.dispatch(() => increment({ id: inputId() }));\n      TestBed.tick();\n      expect(stateSignal()).toBe(1);\n\n      changeInputIdAndFlush();\n      expect(stateSignal()).toBe(2);\n\n      inputId.update((value) => value + 1);\n      expect(stateSignal()).toBe(2);\n\n      TestBed.tick();\n      expect(stateSignal()).toBe(3);\n\n      TestBed.tick();\n      expect(stateSignal()).toBe(3);\n    });\n\n    it('should stop dispatching once the effect is destroyed', () => {\n      const { increment, changeInputIdAndFlush, stateSignal, inputId } =\n        setupForSignalDispatcher();\n\n      const ref = store.dispatch(() => increment({ id: inputId() }));\n      TestBed.tick();\n\n      ref.destroy();\n      changeInputIdAndFlush();\n      expect(stateSignal()).toBe(1);\n    });\n\n    it('should use the injectionContext of the caller if available', () => {\n      const { increment, changeInputIdAndFlush, stateSignal, inputId } =\n        setupForSignalDispatcher();\n\n      const callerContext = createEnvironmentInjector(\n        [],\n        TestBed.inject(EnvironmentInjector)\n      );\n      runInInjectionContext(callerContext, () =>\n        store.dispatch(() => increment({ id: inputId() }))\n      );\n\n      TestBed.tick();\n      expect(stateSignal()).toBe(1);\n\n      callerContext.destroy();\n      changeInputIdAndFlush();\n      expect(stateSignal()).toBe(1);\n    });\n\n    it('should allow to override the injectionContext of the caller', () => {\n      const { increment, changeInputIdAndFlush, stateSignal, inputId } =\n        setupForSignalDispatcher();\n\n      const environmentInjector = TestBed.inject(EnvironmentInjector);\n      const callerContext = createEnvironmentInjector([], environmentInjector);\n      runInInjectionContext(callerContext, () =>\n        store.dispatch(() => increment({ id: inputId() }), {\n          injector: environmentInjector,\n        })\n      );\n\n      TestBed.tick();\n      expect(stateSignal()).toBe(1);\n\n      callerContext.destroy();\n      changeInputIdAndFlush();\n      expect(stateSignal()).toBe(2);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/store_pipes.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { Store, provideStore } from '..';\nimport { Component, inject, Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'test', standalone: true })\nexport class TestPipe implements PipeTransform {\n  store = inject(Store);\n\n  transform(s: number) {\n    this.store.select('count');\n    return s * 2;\n  }\n}\n\n@Component({\n  selector: 'test-component',\n  standalone: true,\n  imports: [TestPipe],\n  template: `{{ 3 | test }}`,\n})\nexport class TestComponent {}\n\ndescribe('NgRx Store Integration', () => {\n  describe('with pipes', () => {\n    beforeEach(() => {\n      TestBed.configureTestingModule({\n        imports: [TestComponent],\n        providers: [\n          provideStore({ count: () => 2 }, { initialState: { count: 2 } }),\n        ],\n      });\n    });\n\n    it('should not throw an error', () => {\n      const component = TestBed.createComponent(TestComponent);\n\n      component.detectChanges();\n\n      expect(component.nativeElement.textContent).toBe('6');\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/spec/types/action_creator.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('createAction()', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import {createAction, props, union} from '@ngrx/store';\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  describe('with props', () => {\n    it('should enforce ctor parameters', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<{ foo: number }>());\n        const fooAction = foo({ foo: '42' });\n      `).toFail(/'string' is not assignable to type 'number'/);\n    });\n\n    it('should enforce action property types', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<{ foo: number }>());\n        const fooAction = foo({ foo: 42 });\n        const value: string = fooAction.foo;\n      `).toFail(/'number' is not assignable to type 'string'/);\n    });\n\n    it('should enforce action property names', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<{ foo: number }>());\n        const fooAction = foo({ foo: 42 });\n        const value = fooAction.bar;\n      `).toFail(/'bar' does not exist on type/);\n    });\n\n    it('should not allow type property', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<{ type: number }>());\n      `).toFail(\n        /Type '{ type: number; }' does not satisfy the constraint '\"action creator props cannot have a property named `type`\"'/\n      );\n    });\n\n    it('should not allow arrays', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<[]>());\n      `).toFail(\n        /Type '\\[]' does not satisfy the constraint '\"action creator props cannot be an array\"'/\n      );\n    });\n\n    it('should not allow empty objects', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<{}>());\n      `).toFail(\n        /Type '{}' does not satisfy the constraint '\"action creator props cannot be an empty object\"'/\n      );\n    });\n\n    it('should not allow strings', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<string>());\n      `).toFail(\n        /Type 'string' does not satisfy the constraint '\"action creator props cannot be a primitive value\"'/\n      );\n    });\n\n    it('should not allow numbers', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<number>());\n      `).toFail(\n        /Type 'number' does not satisfy the constraint '\"action creator props cannot be a primitive value\"'/\n      );\n    });\n\n    it('should not allow bigints', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<bigint>());\n      `).toFail(\n        /Type 'bigint' does not satisfy the constraint '\"action creator props cannot be a primitive value\"'/\n      );\n    });\n\n    it('should not allow booleans', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<boolean>());\n      `).toFail(\n        /Type 'boolean' does not satisfy the constraint '\"action creator props cannot be a primitive value\"'/\n      );\n    });\n\n    it('should not allow symbols', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<symbol>());\n      `).toFail(\n        /Type 'symbol' does not satisfy the constraint '\"action creator props cannot be a primitive value\"'/\n      );\n    });\n\n    it('should not allow null', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<null>());\n      `).toFail(\n        /Type 'ActionCreatorProps<null>' is not assignable to type '\"action creator cannot return an array\"'/\n      );\n    });\n\n    it('should not allow undefined', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<undefined>());\n      `).toFail(\n        /Type 'ActionCreatorProps<undefined>' is not assignable to type '\"action creator cannot return an array\"'/\n      );\n    });\n  });\n\n  describe('with function', () => {\n    it('should enforce ctor parameters', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', (foo: number) => ({ foo }));\n        const fooAction = foo('42');\n      `).toFail(/not assignable to parameter of type 'number'/);\n    });\n\n    it('should enforce action property types', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', (foo: number) => ({ foo }));\n        const fooAction = foo(42);\n        const value: string = fooAction.foo;\n      `).toFail(/'number' is not assignable to type 'string'/);\n    });\n\n    it('should enforce action property names', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', (foo: number) => ({ foo }));\n        const fooAction = foo(42);\n        const value = fooAction.bar;\n      `).toFail(/'bar' does not exist on type/);\n    });\n\n    it('should not allow type property', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', (type: string) => ({type}));\n      `).toFail(\n        /Type '\\{ type: string; \\}' is not assignable to type '\"action creator cannot return an object with a property named `type`\"'/\n      );\n    });\n\n    it('should allow default parameters', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', (bar = 3) => ({bar}));\n      `).toSucceed();\n    });\n\n    it('should not allow arrays', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', () => [ ]);\n      `).toFail(\n        /Type 'any\\[\\]' is not assignable to type '\"action creator cannot return an array\"'/\n      );\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/store/spec/types/action_group_creator.spec.ts",
    "content": "import { Expect, expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('createActionGroup', () => {\n  const snippetFactory = (code: string): string => `\n    import { createActionGroup, emptyProps, props } from '@ngrx/store';\n\n    ${code}\n  `;\n\n  function testWith(expectSnippet: (code: string) => Expect): void {\n    it('should create action group', () => {\n      const snippet = expectSnippet(`\n        const authApiActions = createActionGroup({\n          source: 'Auth API',\n          events: {\n            'Login Success': props<{ userId: number; token: string }>(),\n            'Login Failure': props<{ error: string }>(),\n            'Logout Success': emptyProps(),\n            'Logout Failure': (error: Error) => ({ error }),\n          },\n        });\n\n        let loginSuccess: typeof authApiActions.loginSuccess;\n        let loginFailure: typeof authApiActions.loginFailure;\n        let logoutSuccess: typeof authApiActions.logoutSuccess;\n        let logoutFailure: typeof authApiActions.logoutFailure;\n      `);\n\n      snippet.toInfer(\n        'loginSuccess',\n        `ActionCreator<\n          \"[Auth API] Login Success\",\n          (props: { userId: number; token: string; }) =>\n            { userId: number; token: string; } & Action<\"[Auth API] Login Success\">\n        >`\n      );\n      snippet.toInfer(\n        'loginFailure',\n        `ActionCreator<\n          \"[Auth API] Login Failure\",\n          (props: { error: string; }) =>\n            { error: string; } & Action<\"[Auth API] Login Failure\">\n        >`\n      );\n      snippet.toInfer(\n        'logoutSuccess',\n        `ActionCreator<\n          \"[Auth API] Logout Success\",\n          () => Action<\"[Auth API] Logout Success\">\n        >`\n      );\n      snippet.toInfer(\n        'logoutFailure',\n        `FunctionWithParametersType<\n          [error: Error],\n          { error: Error; } & Action<\"[Auth API] Logout Failure\">\n        > & Action<\"[Auth API] Logout Failure\">`\n      );\n    });\n\n    describe('source', () => {\n      it('should fail when source is not a string literal type', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API' as string,\n            events: {},\n          });\n        `).toFail(/source must be a string literal type/);\n      });\n    });\n\n    describe('events', () => {\n      it('should infer events dictionary', () => {\n        expectSnippet(`\n          const authApiActions = createActionGroup({\n            source: 'Auth API',\n            events: {\n              'Login Success': props<{ token: string; }>,\n              'Login Failure': (message: string) => ({ message }),\n            },\n          });\n        `).toInfer(\n          'authApiActions',\n          \"ActionGroup<\\\"Auth API\\\", { 'Login Success': () => ActionCreatorProps<{ token: string; }>; 'Login Failure': (message: string) => { message: string; }; }>\"\n        );\n      });\n\n      it('should infer events defined as an empty object', () => {\n        expectSnippet(`\n          const authApiActions = createActionGroup({\n            source: 'Auth API',\n            events: {},\n          });\n        `).toInfer('authApiActions', 'ActionGroup<\"Auth API\", {}>');\n      });\n    });\n\n    describe('event name', () => {\n      it('should create action name by camel casing the event name', () => {\n        const snippet = expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              ' Load BOOKS  suCCess  ': emptyProps(),\n              loadBooksFailure: emptyProps(),\n            },\n          });\n\n          let loadBooksSuccess: typeof booksApiActions.loadBOOKSSuCCess;\n          let loadBooksFailure: typeof booksApiActions.loadBooksFailure;\n        `);\n\n        snippet.toInfer(\n          'loadBooksSuccess',\n          `ActionCreator<\n            \"[Books API]  Load BOOKS  suCCess  \",\n            () => Action<\"[Books API]  Load BOOKS  suCCess  \">\n          >`\n        );\n        snippet.toInfer(\n          'loadBooksFailure',\n          `ActionCreator<\n            \"[Books API] loadBooksFailure\",\n            () => Action<\"[Books API] loadBooksFailure\">\n          >`\n        );\n      });\n\n      it('should fail when event name is not a string literal type', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              ['Load Books Success' as string]: emptyProps()\n            },\n          });\n        `).toFail(/event name must be a string literal type/);\n      });\n\n      it('should fail when two event names are mapped to the same action name', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              '  Load Books  success ': emptyProps(),\n              'load Books Success': props<{ books: string[] }>(),\n            }\n          });\n        `).toFail(/loadBooksSuccess action is already defined/);\n      });\n    });\n\n    describe('props', () => {\n      it('should infer when props are typed as union', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': props<{ books: string[]; total: number } | { books: symbol[] }>(),\n            },\n          });\n\n          let loadBooksSuccess: typeof booksApiActions.loadBooksSuccess;\n        `).toInfer(\n          'loadBooksSuccess',\n          'ActionCreator<\"[Books API] Load Books Success\", (props: { books: string[]; total: number; } | { books: symbol[]; }) => ({ books: string[]; total: number; } | { books: symbol[]; }) & Action<\"[Books API] Load Books Success\">>'\n        );\n      });\n\n      it('should infer when props are typed as intersection', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': props<{ books: string[] } & { total: number }>(),\n            },\n          });\n\n          let loadBooksSuccess: typeof booksApiActions.loadBooksSuccess;\n        `).toInfer(\n          'loadBooksSuccess',\n          'ActionCreator<\"[Books API] Load Books Success\", (props: { books: string[]; } & { total: number; }) => { books: string[]; } & { total: number; } & Action<\"[Books API] Load Books Success\">>'\n        );\n      });\n\n      it('should fail when props contain a type property', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': props<{ books: string[]; type: any }>(),\n            },\n          });\n        `).toFail(\n          /action creator cannot return an object with a property named `type`/\n        );\n      });\n\n      it('should fail when props are an array', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': props<string[]>(),\n            },\n          });\n        `).toFail(/action creator cannot return an array/);\n      });\n\n      it('should fail when props are an empty object', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': props<{}>(),\n            },\n          });\n        `).toFail(/action creator cannot return an empty object/);\n      });\n\n      it('should fail when props are a primitive value', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': props<string>(),\n            },\n          });\n        `).toFail(/action creator props cannot be a primitive value/);\n      });\n    });\n\n    describe('props factory', () => {\n      it('should fail when props factory returns an object with type property', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': (books: string[]) => ({ books, type: 'T' }),\n            },\n          });\n        `).toFail(\n          /action creator cannot return an object with a property named `type`/\n        );\n      });\n\n      it('should fail when props factory returns an array', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': (books: string[]) => books,\n            },\n          });\n        `).toFail(/action creator cannot return an array/);\n      });\n\n      it('should fail when props factory returns an empty object', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': () => ({}),\n            },\n          });\n        `).toFail(/action creator cannot return an empty object/);\n      });\n\n      it('should fail when props factory returns a primitive value', () => {\n        expectSnippet(`\n          const booksApiActions = createActionGroup({\n            source: 'Books API',\n            events: {\n              'Load Books Success': () => '',\n            },\n          });\n        `).toFail(/Type '\\(\\) => string' is not assignable to type 'never'/);\n      });\n    });\n  }\n\n  describe('strict mode', () => {\n    const expectSnippet = expecter(snippetFactory, {\n      ...compilerOptions(),\n      strict: true,\n    });\n\n    testWith(expectSnippet);\n  });\n\n  describe('non-strict mode', () => {\n    const expectSnippet = expecter(snippetFactory, {\n      ...compilerOptions(),\n      strict: false,\n    });\n\n    testWith(expectSnippet);\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/store/spec/types/feature_creator.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('createFeature()', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import {\n        ActionReducer,\n        createAction,\n        createFeature,\n        createReducer,\n        createSelector,\n        on,\n        props,\n        Selector,\n        Store,\n        StoreModule,\n      } from '@ngrx/store';\n\n      ${code}\n    `,\n    { ...compilerOptions(), strict: true }\n  );\n\n  it('should create', () => {\n    const snippet = expectSnippet(`\n      const search = createAction(\n        '[Products Page] Search',\n        props<{ query: string }>()\n      );\n      const loadProductsSuccess = createAction(\n        '[Products API] Load Products Success',\n        props<{ products: string[] }>()\n      );\n\n      interface State {\n        products: string[] | null;\n        query: string;\n      }\n\n      const initialState: State = {\n        products: null,\n        query: '',\n      };\n\n      const productsFeature = createFeature({\n        name: 'products',\n        reducer: createReducer(\n          initialState,\n          on(search, (state, { query }) => ({ ...state, query })),\n          on(loadProductsSuccess, (state, { products }) => ({\n            ...state,\n            products,\n          }))\n        ),\n      });\n\n      let {\n        name,\n        reducer,\n        selectProductsState,\n        selectProducts,\n        selectQuery,\n      } = productsFeature;\n\n      let productsFeatureKeys: keyof typeof productsFeature;\n    `);\n\n    snippet.toInfer('name', '\"products\"');\n    snippet.toInfer('reducer', 'ActionReducer<State, Action<string>>');\n    snippet.toInfer(\n      'selectProductsState',\n      'MemoizedSelector<Record<string, any>, State, (featureState: State) => State>'\n    );\n    snippet.toInfer(\n      'selectProducts',\n      'MemoizedSelector<Record<string, any>, string[] | null, (featureState: State) => string[] | null>'\n    );\n    snippet.toInfer(\n      'selectQuery',\n      'MemoizedSelector<Record<string, any>, string, (featureState: State) => string>'\n    );\n    snippet.toInfer(\n      'productsFeatureKeys',\n      '\"selectProductsState\" | \"selectQuery\" | \"selectProducts\" | keyof FeatureConfig<\"products\", State>'\n    );\n  });\n\n  it('should create a feature when reducer is created outside', () => {\n    const snippet = expectSnippet(`\n      const counterReducer = createReducer({ count: 0 });\n      const counterFeature = createFeature({\n        name: 'counter',\n        reducer: counterReducer,\n      });\n\n      const {\n        name,\n        reducer,\n        selectCounterState,\n        selectCount,\n      } = counterFeature;\n    `);\n\n    snippet.toInfer('name', '\"counter\"');\n    snippet.toInfer(\n      'reducer',\n      'ActionReducer<{ count: number; }, Action<string>>'\n    );\n    snippet.toInfer(\n      'selectCounterState',\n      'MemoizedSelector<Record<string, any>, { count: number; }, (featureState: { count: number; }) => { count: number; }>'\n    );\n    snippet.toInfer(\n      'selectCount',\n      'MemoizedSelector<Record<string, any>, number, (featureState: { count: number; }) => number>'\n    );\n  });\n\n  it('should allow use with StoreModule.forFeature', () => {\n    expectSnippet(`\n      const counterFeature = createFeature({\n        name: 'counter',\n        reducer: createReducer(0),\n      });\n\n      StoreModule.forFeature(counterFeature);\n    `).toSucceed();\n  });\n\n  it('should allow use with untyped store.select', () => {\n    const snippet = expectSnippet(`\n      const { selectCounterState, selectCount } = createFeature({\n        name: 'counter',\n        reducer: createReducer({ count: 0 }),\n      });\n\n      let store!: Store;\n      const counterState$ = store.select(selectCounterState);\n      const count$ = store.select(selectCount);\n    `);\n\n    snippet.toInfer('counterState$', 'Observable<{ count: number; }>');\n    snippet.toInfer('count$', 'Observable<number>');\n  });\n\n  it('should allow use with typed store.select', () => {\n    const snippet = expectSnippet(`\n      const { selectCounterState } = createFeature({\n        name: 'counter',\n        reducer: createReducer(0),\n      });\n\n      let store!: Store<{ counter: number }>;\n      const counterState$ = store.select(selectCounterState);\n    `);\n\n    snippet.toInfer('counterState$', 'Observable<number>');\n  });\n\n  it('should fail when feature state contains optional properties', () => {\n    expectSnippet(`\n      interface State {\n        movies: string[];\n        activeProductId?: number;\n      }\n\n      const initialState: State = { movies: [], activeProductId: undefined };\n\n      const counterFeature = createFeature({\n        name: 'movies',\n        reducer: createReducer(initialState),\n      });\n    `).toFail(/optional properties are not allowed in the feature state/);\n  });\n\n  describe('nested selectors', () => {\n    it('should not create with feature state as a primitive value', () => {\n      expectSnippet(`\n        const feature = createFeature({\n          name: 'primitive',\n          reducer: createReducer('text'),\n        });\n\n        let featureKeys: keyof typeof feature;\n      `).toInfer(\n        'featureKeys',\n        '\"selectPrimitiveState\" | keyof FeatureConfig<\"primitive\", string>'\n      );\n    });\n\n    it('should not create with feature state as an array', () => {\n      expectSnippet(`\n        const feature = createFeature({\n          name: 'array',\n          reducer: createReducer([1, 2, 3]),\n        });\n\n        let featureKeys: keyof typeof feature;\n      `).toInfer(\n        'featureKeys',\n        '\"selectArrayState\" | keyof FeatureConfig<\"array\", number[]>'\n      );\n    });\n\n    it('should not create with feature state as a date object', () => {\n      expectSnippet(`\n        const feature = createFeature({\n          name: 'date',\n          reducer: createReducer(new Date()),\n        });\n\n        let featureKeys: keyof typeof feature;\n      `).toInfer(\n        'featureKeys',\n        '\"selectDateState\" | keyof FeatureConfig<\"date\", Date>'\n      );\n    });\n  });\n\n  describe('extra selectors', () => {\n    it('should create extra selectors', () => {\n      const snippet = expectSnippet(`\n        const increment = createAction('increment');\n\n        interface State {\n          count: number;\n        }\n        const initialState: State = {\n          count: 0,\n        };\n\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: createReducer(\n            initialState,\n            on(increment, (state) => ({ count: state.count + 1 }))\n          ),\n          extraSelectors: ({ selectCounterState, selectCount }) => ({\n            selectCounterState2: createSelector(\n              selectCounterState,\n              (state) => state\n            ),\n            selectCountPlus1: createSelector(\n              selectCount,\n              (count) => count + 1\n            ),\n            selectCountPlusNum: (num: number) =>\n              createSelector(selectCount, (count) => count + num),\n          }),\n        });\n\n        const {\n          name,\n          reducer,\n          selectCounterState,\n          selectCount,\n          selectCounterState2,\n          selectCountPlus1,\n          selectCountPlusNum,\n        } = counterFeature;\n        let counterFeatureKeys: keyof typeof counterFeature;\n      `);\n\n      snippet.toInfer('name', '\"counter\"');\n      snippet.toInfer('reducer', 'ActionReducer<State, Action<string>>');\n      snippet.toInfer(\n        'selectCounterState',\n        'MemoizedSelector<Record<string, any>, State, (featureState: State) => State>'\n      );\n      snippet.toInfer(\n        'selectCount',\n        'MemoizedSelector<Record<string, any>, number, (featureState: State) => number>'\n      );\n      snippet.toInfer(\n        'selectCounterState2',\n        'MemoizedSelector<Record<string, any>, State, (s1: State) => State>'\n      );\n      snippet.toInfer(\n        'selectCountPlus1',\n        'MemoizedSelector<Record<string, any>, number, (s1: number) => number>'\n      );\n      snippet.toInfer(\n        'selectCountPlusNum',\n        '(num: number) => MemoizedSelector<Record<string, any>, number, (s1: number) => number>'\n      );\n      snippet.toInfer(\n        'counterFeatureKeys',\n        '\"name\" | \"reducer\" | \"selectCounterState\" | \"selectCount\" | \"selectCounterState2\" | \"selectCountPlus1\" | \"selectCountPlusNum\"'\n      );\n    });\n\n    it('should create extra selectors when reducer is created outside', () => {\n      const snippet = expectSnippet(`\n        const counterReducer = createReducer({ count: 0 });\n\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: counterReducer,\n          extraSelectors: ({ selectCounterState, selectCount }) => ({\n            selectSquaredCount: createSelector(\n              selectCounterState,\n              selectCount,\n              ({ count }, c) => count * c\n            ),\n          }),\n        });\n\n        const {\n          name,\n          reducer,\n          selectCounterState,\n          selectCount,\n          selectSquaredCount,\n        } = counterFeature;\n        let counterFeatureKeys: keyof typeof counterFeature;\n      `);\n\n      snippet.toInfer('name', '\"counter\"');\n      snippet.toInfer(\n        'reducer',\n        'ActionReducer<{ count: number; }, Action<string>>'\n      );\n      snippet.toInfer(\n        'selectCounterState',\n        'MemoizedSelector<Record<string, any>, { count: number; }, (featureState: { count: number; }) => { count: number; }>'\n      );\n      snippet.toInfer(\n        'selectCount',\n        'MemoizedSelector<Record<string, any>, number, (featureState: { count: number; }) => number>'\n      );\n      snippet.toInfer(\n        'selectSquaredCount',\n        'MemoizedSelector<Record<string, any>, number, (s1: { count: number; }, s2: number) => number>'\n      );\n      snippet.toInfer(\n        'counterFeatureKeys',\n        '\"name\" | \"reducer\" | \"selectCounterState\" | \"selectCount\" | \"selectSquaredCount\"'\n      );\n    });\n\n    it('should override base selectors if extra selectors have the same names', () => {\n      const snippet = expectSnippet(`\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: createReducer({ count1: 0, count2: 0 }),\n          extraSelectors: ({ selectCounterState, selectCount1, selectCount2 }) => ({\n            selectCounterState: createSelector(\n              selectCount1,\n              selectCount2,\n              (count3, count4) => ({ count3, count4 })\n            ),\n            selectCount1: createSelector(selectCount1, (count) => count + ''),\n            selectCount10: createSelector(selectCount2, (count) => count + 1),\n          }),\n        });\n\n        const {\n          selectCounterState,\n          selectCount1,\n          selectCount2,\n          selectCount10,\n        } = counterFeature;\n        let counterFeatureKeys: keyof typeof counterFeature;\n      `);\n\n      snippet.toInfer(\n        'selectCounterState',\n        'MemoizedSelector<Record<string, any>, { count3: number; count4: number; }, (s1: number, s2: number) => { count3: number; count4: number; }>'\n      );\n      snippet.toInfer(\n        'selectCount1',\n        'MemoizedSelector<Record<string, any>, string, (s1: number) => string>'\n      );\n      snippet.toInfer(\n        'selectCount2',\n        'MemoizedSelector<Record<string, any>, number, (featureState: { count1: number; count2: number; }) => number>'\n      );\n      snippet.toInfer(\n        'selectCount10',\n        'MemoizedSelector<Record<string, any>, number, (s1: number) => number>'\n      );\n      snippet.toInfer(\n        'counterFeatureKeys',\n        '\"name\" | \"reducer\" | \"selectCounterState\" | \"selectCount1\" | \"selectCount2\" | \"selectCount10\"'\n      );\n    });\n\n    it('should not break the feature object when extra selector names are not string literals', () => {\n      const snippet = expectSnippet(`\n        const untypedSelectors: Record<string, Selector<Record<string, any>, unknown>> = {};\n\n        const counterFeature1 = createFeature({\n          name: 'counter1',\n          reducer: createReducer(0),\n          extraSelectors: ({ selectCounter1State }) => ({\n            ['selectInvalid' as string]: createSelector(\n              selectCounter1State,\n              (count) => count\n            ),\n            selectSquaredCount: createSelector(\n              selectCounter1State,\n              (count) => count * count\n            ),\n            ...untypedSelectors,\n          }),\n        });\n\n        const counterFeature2 = createFeature({\n          name: 'counter2',\n          reducer: createReducer(0),\n          extraSelectors: () => untypedSelectors,\n        });\n\n        const { selectCounter1State } = counterFeature1;\n        const { selectCounter2State } = counterFeature2;\n\n        let counterFeature1Keys: keyof typeof counterFeature1;\n        let counterFeature2Keys: keyof typeof counterFeature2;\n      `);\n\n      snippet.toInfer(\n        'selectCounter1State',\n        'MemoizedSelector<Record<string, any>, number, (featureState: number) => number>'\n      );\n      snippet.toInfer(\n        'selectCounter2State',\n        'MemoizedSelector<Record<string, any>, number, (featureState: number) => number>'\n      );\n      snippet.toInfer(\n        'counterFeature1Keys',\n        '\"selectCounter1State\" | keyof FeatureConfig<\"counter1\", number>'\n      );\n      snippet.toInfer(\n        'counterFeature2Keys',\n        '\"selectCounter2State\" | keyof FeatureConfig<\"counter2\", number>'\n      );\n    });\n\n    it('should not break the feature object when extra selectors are an empty object', () => {\n      const snippet = expectSnippet(`\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: createReducer(0),\n          extraSelectors: () => ({}),\n        });\n\n        const { selectCounterState } = counterFeature;\n        let counterFeatureKeys: keyof typeof counterFeature;\n      `);\n\n      snippet.toInfer(\n        'selectCounterState',\n        'MemoizedSelector<Record<string, any>, number, (featureState: number) => number>'\n      );\n      snippet.toInfer(\n        'counterFeatureKeys',\n        '\"name\" | \"reducer\" | \"selectCounterState\"'\n      );\n    });\n\n    it('should create a feature when extra selectors dictionary is typed as a type', () => {\n      const snippet = expectSnippet(`\n        type ExtraSelectors = {\n          selectCountStr: Selector<Record<string, any>, string>;\n          selectCountPlusNum: (num: number) => Selector<Record<string, any>, number>;\n        }\n\n        function getExtraSelectors(\n          selectCount: Selector<Record<string, any>, number>\n        ): ExtraSelectors {\n          return {\n            selectCountStr: createSelector(\n              selectCount,\n              (count) => count + ''\n            ),\n            selectCountPlusNum: (num: number) =>\n              createSelector(selectCount, (count) => count + num)\n          };\n        }\n\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: createReducer(0),\n          extraSelectors: ({ selectCounterState }) =>\n            getExtraSelectors(selectCounterState),\n        });\n\n        const { selectCountStr, selectCountPlusNum } = counterFeature;\n        let counterFeatureKeys: keyof typeof counterFeature;\n      `);\n\n      snippet.toInfer(\n        'selectCountStr',\n        'Selector<Record<string, any>, string>'\n      );\n      snippet.toInfer(\n        'selectCountPlusNum',\n        '(num: number) => Selector<Record<string, any>, number>'\n      );\n      snippet.toInfer(\n        'counterFeatureKeys',\n        '\"name\" | \"reducer\" | \"selectCounterState\" | keyof ExtraSelectors'\n      );\n    });\n\n    // This is known behavior.\n    // Record<string, Selector> is not compatible with interface of selectors.\n    it('should fail when extra selectors dictionary is typed as an interface', () => {\n      expectSnippet(`\n        interface ExtraSelectors {\n          selectSquaredCount: Selector<Record<string, any>, number>;\n        }\n\n        function getExtraSelectors(\n          selectCount: Selector<Record<string, any>, number>\n        ): ExtraSelectors {\n          return {\n            selectSquaredCount: createSelector(\n              selectCount,\n              (count) => count * count\n            ),\n          };\n        }\n\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: createReducer(0),\n          extraSelectors: ({ selectCounterState }) =>\n            getExtraSelectors(selectCounterState),\n        });\n      `).toFail(\n        /Index signature for type 'string' is missing in type 'ExtraSelectors'./\n      );\n    });\n\n    it('should fail when extra selectors result is not a dictionary of selectors', () => {\n      expectSnippet(`\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: createReducer(0),\n          extraSelectors: ({ selectCounterState }) => ({\n            selectSquaredCount: createSelector(\n              selectCounterState,\n              (count) => count * count\n            ),\n            x: 1,\n          }),\n        });\n      `).toFail();\n\n      expectSnippet(`\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: createReducer(0),\n          extraSelectors: () => 'ngrx',\n        });\n      `).toFail();\n    });\n\n    it('should fail when feature state contains optional properties', () => {\n      expectSnippet(`\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: createReducer({} as { count?: number; }),\n          extraSelectors: () => ({}),\n        });\n      `).toFail();\n\n      expectSnippet(`\n        interface State {\n          count?: number;\n        }\n        const initialState: State = {};\n\n        const counterFeature = createFeature({\n          name: 'counter',\n          reducer: createReducer(initialState),\n          extraSelectors: () => ({}),\n        });\n      `).toFail();\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/store/spec/types/reducer_creator.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('createReducer()', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import {createAction, createReducer, on, props, ActionCreator, ActionReducer} from '@ngrx/store';\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  describe('createReducer()', () => {\n    it('should support objects', () => {\n      expectSnippet(`\n        interface State { name: string };\n        const initialState: State = { name: 'sarah' };\n\n        const setAction = createAction('set', props<{ value: State }>());\n        const resetAction = createAction('reset');\n\n        const reducer = createReducer(\n          initialState,\n          on(setAction, (_, { value }) => value),\n          on(resetAction, () => initialState),\n        );\n      `).toInfer('reducer', 'ActionReducer<State, Action<string>>');\n    });\n\n    it('should support arrays', () => {\n      expectSnippet(`\n        const initialState: string[] = [];\n\n        const setAction = createAction('set', props<{ value: string[] }>());\n        const resetAction = createAction('reset');\n\n        const reducer = createReducer(\n          initialState,\n          on(setAction, (_, { value }) => value),\n          on(resetAction, () => initialState),\n        );\n      `).toInfer('reducer', 'ActionReducer<string[], Action<string>>');\n    });\n\n    it('should support primitive types', () => {\n      expectSnippet(`\n        const initialState: number = 0;\n\n        const setAction = createAction('set', props<{ value: number }>());\n        const resetAction = createAction('reset');\n\n        const reducer = createReducer(\n          initialState,\n          on(setAction, (_, { value }) => value),\n          on(resetAction, () => initialState),\n        );\n      `).toInfer('reducer', 'ActionReducer<number, Action<string>>');\n    });\n\n    it('should support a generic reducer factory', () => {\n      expectSnippet(`\n        const creator = null as unknown as ActionCreator;\n\n        export function createGenericReducer<TState extends object>(initialState: TState): ActionReducer<TState> {\n          const reducer = createReducer(\n            initialState,\n            on(creator, (state, action) => {\n              return state ;\n            })\n          );\n\n          return reducer;\n        }\n      `).toSucceed();\n    });\n  });\n\n  describe('on()', () => {\n    it('should enforce action property types', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<{ foo: number }>());\n        on(foo, (state, action) => { const foo: string = action.foo; return state; });\n      `).toFail(/'number' is not assignable to type 'string'/);\n    });\n\n    it('should enforce action property names', () => {\n      expectSnippet(`\n        const foo = createAction('FOO', props<{ foo: number }>());\n        on(foo, (state, action) => { const bar: string = action.bar; return state; });\n      `).toFail(/'bar' does not exist on type/);\n    });\n\n    it('should infer the typed based on state and actions type with action used in on function', () => {\n      expectSnippet(`\n        interface State { name: string };\n        const foo = createAction('FOO', props<{ foo: string }>());\n        const onFn = on(foo, (state: State, action) => ({  name: action.foo }));\n      `).toInfer(\n        'onFn',\n        `\n      ReducerTypes<{\n        name: string;\n    }, [ActionCreator<\"FOO\", (props: {\n        foo: string;\n    }) => {\n        foo: string;\n    } & Action<\"FOO\">>]>\n    `\n      );\n    });\n\n    it('should infer the typed based on state and actions type without action', () => {\n      expectSnippet(`\n        interface State { name: string };\n        const foo = createAction('FOO');\n        const onFn = on(foo, (state: State) => ({ name: 'some value' }));\n      `).toInfer(\n        'onFn',\n        `\n      ReducerTypes<{\n        name: string;\n    }, [ActionCreator<\"FOO\", () => Action<\"FOO\">>]>\n    `\n      );\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/store/spec/types/select.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('select()', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import { Store, select, createSelector, createFeatureSelector } from '@ngrx/store';\n\n      interface State { foo: { bar: { baz: [] } } };\n      const store = {} as Store<State>;\n      const fooSelector = createFeatureSelector<State, State['foo']>('foo')\n      const barSelector = createSelector(fooSelector, s => s.bar)\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  describe('as property', () => {\n    describe('with strings', () => {\n      it('should enforce that properties exists on state (root)', () => {\n        expectSnippet(`const selector = store.select('mia');`).toFail(\n          /Argument of type '\"mia\"' is not assignable to parameter of type '\"foo\"'/\n        );\n      });\n\n      it('should enforce that properties exists on state (nested)', () => {\n        expectSnippet(\n          `const selector = store.select('foo', 'bar', 'mia');`\n        ).toFail(\n          /Argument of type '\"mia\"' is not assignable to parameter of type '\"baz\"'/\n        );\n      });\n\n      it('should infer correctly (root)', () => {\n        expectSnippet(`const selector = store.select('foo');`).toInfer(\n          'selector',\n          'Observable<{ bar: { baz: []; }; }>'\n        );\n      });\n\n      it('should infer correctly (nested)', () => {\n        expectSnippet(`const selector = store.select('foo', 'bar');`).toInfer(\n          'selector',\n          'Observable<{ baz: []; }>'\n        );\n      });\n    });\n\n    describe('with functions', () => {\n      it('should enforce that properties exists on state (root)', () => {\n        expectSnippet(`const selector = store.select(s => s.mia);`).toFail(\n          /Property 'mia' does not exist on type 'State'/\n        );\n      });\n\n      it('should enforce that properties exists on state (nested)', () => {\n        expectSnippet(\n          `const selector = store.select(s => s.foo.bar.mia);`\n        ).toFail(/Property 'mia' does not exist on type '\\{ baz: \\[\\]; \\}'/);\n      });\n\n      it('should infer correctly (root)', () => {\n        expectSnippet(`const selector = store.select(s => s.foo);`).toInfer(\n          'selector',\n          'Observable<{ bar: { baz: []; }; }>'\n        );\n      });\n\n      it('should infer correctly (nested)', () => {\n        expectSnippet(`const selector = store.select(s => s.foo.bar);`).toInfer(\n          'selector',\n          'Observable<{ baz: []; }>'\n        );\n      });\n    });\n\n    describe('with selectors', () => {\n      it('should infer correctly', () => {\n        expectSnippet(`const selector = store.select(fooSelector);`).toInfer(\n          'selector',\n          'Observable<{ bar: { baz: []; }; }>'\n        );\n\n        expectSnippet(`const selector = store.select(barSelector);`).toInfer(\n          'selector',\n          'Observable<{ baz: []; }>'\n        );\n      });\n    });\n  });\n\n  describe('as operator', () => {\n    describe('with strings', () => {\n      it('should enforce that properties exists on state (root)', () => {\n        expectSnippet(`const selector = store.pipe(select('mia'));`).toFail(\n          /Argument of type '\"mia\"' is not assignable to parameter of type '\"foo\"'/\n        );\n      });\n\n      it('should enforce that properties exists on state (nested)', () => {\n        expectSnippet(\n          `const selector = store.pipe(select('foo', 'bar', 'mia'));`\n        ).toFail(\n          /Argument of type '\"mia\"' is not assignable to parameter of type '\"baz\"'/\n        );\n      });\n\n      it('should infer correctly (root)', () => {\n        expectSnippet(`const selector = store.pipe(select('foo'));`).toInfer(\n          'selector',\n          'Observable<{ bar: { baz: []; }; }>'\n        );\n      });\n\n      it('should infer correctly (nested)', () => {\n        expectSnippet(\n          `const selector = store.pipe(select('foo', 'bar'));`\n        ).toInfer('selector', 'Observable<{ baz: []; }>');\n      });\n    });\n\n    describe('with functions', () => {\n      it('should enforce that properties exists on state (root)', () => {\n        expectSnippet(\n          `const selector = store.pipe(select(s => s.mia));`\n        ).toFail(/Property 'mia' does not exist on type 'State'/);\n      });\n\n      it('should enforce that properties exists on state (nested)', () => {\n        expectSnippet(\n          `const selector = store.pipe(select(s => s.foo.bar.mia));`\n        ).toFail(/Property 'mia' does not exist on type '\\{ baz: \\[\\]; \\}'/);\n      });\n\n      it('should infer correctly (root)', () => {\n        expectSnippet(\n          `const selector = store.pipe(select(s => s.foo));`\n        ).toInfer('selector', 'Observable<{ bar: { baz: []; }; }>');\n      });\n\n      it('should infer correctly (nested)', () => {\n        expectSnippet(\n          `const selector = store.pipe(select(s => s.foo.bar));`\n        ).toInfer('selector', 'Observable<{ baz: []; }>');\n      });\n    });\n\n    describe('with selectors', () => {\n      it('should infer correctly', () => {\n        expectSnippet(\n          `const selector = store.pipe(select(fooSelector));`\n        ).toInfer('selector', 'Observable<{ bar: { baz: []; }; }>');\n\n        expectSnippet(\n          `const selector = store.pipe(select(barSelector));`\n        ).toInfer('selector', 'Observable<{ baz: []; }>');\n      });\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/store/spec/types/select_signal.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('Store.selectSignal()', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import { Store, createSelector, createFeatureSelector } from '@ngrx/store';\n\n      interface State { foo: { bar: { baz: [] } } };\n      const store = {} as Store<State>;\n      const fooSelector = createFeatureSelector<State, State['foo']>('foo')\n      const barSelector = createSelector(fooSelector, s => s.bar)\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  describe('as property', () => {\n    describe('with functions', () => {\n      it('should enforce that properties exists on state (root)', () => {\n        expectSnippet(\n          `const selector = store.selectSignal(s => s.mia);`\n        ).toFail(/Property 'mia' does not exist on type 'State'/);\n      });\n\n      it('should enforce that properties exists on state (nested)', () => {\n        expectSnippet(\n          `const selector = store.selectSignal(s => s.foo.bar.mia);`\n        ).toFail(/Property 'mia' does not exist on type '\\{ baz: \\[\\]; \\}'/);\n      });\n\n      it('should infer correctly (root)', () => {\n        expectSnippet(\n          `const selector = store.selectSignal(s => s.foo);`\n        ).toInfer('selector', 'Signal<{ bar: { baz: []; }; }>');\n      });\n\n      it('should infer correctly (nested)', () => {\n        expectSnippet(\n          `const selector = store.selectSignal(s => s.foo.bar);`\n        ).toInfer('selector', 'Signal<{ baz: []; }>');\n      });\n\n      it('should infer correctly (with options)', () => {\n        expectSnippet(\n          `const selector = store.selectSignal(s => s.foo, {equal: (a, b) => a === b});`\n        ).toInfer('selector', 'Signal<{ bar: { baz: []; }; }>');\n      });\n    });\n\n    describe('with selectors', () => {\n      it('should infer correctly', () => {\n        expectSnippet(\n          `const selector = store.selectSignal(fooSelector);`\n        ).toInfer('selector', 'Signal<{ bar: { baz: []; }; }>');\n\n        expectSnippet(\n          `const selector = store.selectSignal(barSelector);`\n        ).toInfer('selector', 'Signal<{ baz: []; }>');\n      });\n\n      it('should infer correctly (with options)', () => {\n        expectSnippet(\n          `const selector = store.selectSignal(fooSelector, {equal: (a,b) => a === b});`\n        ).toInfer('selector', 'Signal<{ bar: { baz: []; }; }>');\n\n        expectSnippet(\n          `const selector = store.selectSignal(barSelector);`\n        ).toInfer('selector', 'Signal<{ baz: []; }>');\n      });\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/store/spec/types/selector.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('createSelector()', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import { createSelector } from '@ngrx/store';\n      import { MemoizedSelector, DefaultProjectorFn } from '@ngrx/store';\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  describe('projector', () => {\n    it('should require correct arguments by default', () => {\n      expectSnippet(`\n        const selectTest = createSelector(\n            () => 'one',\n            () => 2,\n            (one, two) => 3\n        );\n        selectTest.projector();\n      `).toFail(/Expected 2 arguments, but got 0./);\n    });\n    it('should not require parameters for existing explicitly loosely typed selectors', () => {\n      expectSnippet(`\n        const selectTest: MemoizedSelector<\n          unknown,\n          number,\n          DefaultProjectorFn<number>\n        > = createSelector(\n          () => 'one',\n          () => 2,\n          (one, two) => 3\n        );\n        selectTest.projector();\n      `).toSucceed();\n    });\n  }, 8_000);\n\n  it('should create a selector from selectors dictionary', () => {\n    expectSnippet(`\n      const selectDictionary = createSelector({\n        s: (state: { x: string }) => state.x,\n        m: (state: { y: number }) => state.y,\n      });\n    `).toInfer(\n      'selectDictionary',\n      'MemoizedSelector<{ x: string; } & { y: number; }, { s: string; m: number; }, never>'\n    );\n  });\n\n  it('should create a selector from empty dictionary', () => {\n    expectSnippet(`\n      const selectDictionary = createSelector({});\n    `).toInfer('selectDictionary', 'MemoizedSelector<unknown, {}, never>');\n  });\n});\n\ndescribe('createSelector() with props', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import { createSelector } from '@ngrx/store';\n      import { MemoizedSelectorWithProps, DefaultProjectorFn } from '@ngrx/store';\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  describe('projector', () => {\n    it('should require correct arguments by default', () => {\n      expectSnippet(`\n        const selectTest = createSelector(\n            () => 'one',\n            () => 2,\n            (one, two, props) => 3\n        );\n        selectTest.projector();\n      `).toFail(/Expected 3 arguments, but got 0./);\n    });\n    it('should not require parameters for existing explicitly loosely typed selectors', () => {\n      expectSnippet(`\n        const selectTest: MemoizedSelectorWithProps<\n          unknown,\n          number,\n          any,\n          DefaultProjectorFn<number>\n        > = createSelector(\n          () => 'one',\n          () => 2,\n          (one, two, props) => 3\n        );\n        selectTest.projector();\n      `).toSucceed();\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/store/spec/types/store.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('Store', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import { Store, createAction, props } from '@ngrx/store';\n      import { inject, signal } from '@angular/core';\n\n      const load = createAction('load');\n      const incrementer = createAction('increment', props<{value: number}>());\n\n      const value = signal(1);\n\n      const store = inject(Store);\n      const fooAction = createAction('foo')\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  describe('compilation fails', () => {\n    const assertCompilationFailure = (code: string) =>\n      expectSnippet(code).toFail(\n        /is not assignable to type '\"Action creator is not allowed to be dispatched. Did you forget to call it/\n      );\n\n    it('does not allow dispatching action creators without props', () => {\n      assertCompilationFailure('store.dispatch(load);');\n    });\n\n    it('does not allow dispatching action creators with props', () => {\n      assertCompilationFailure('store.dispatch(incrementer);');\n    });\n  });\n\n  describe('compilation succeeds', () => {\n    const assertCompilationSuccess = (code: string) =>\n      expectSnippet(code).toSucceed();\n\n    it('allows dispatching actions without props', () => {\n      assertCompilationSuccess('store.dispatch(load());');\n    });\n\n    it('allows dispatching actions with props', () => {\n      assertCompilationSuccess('store.dispatch(incrementer({ value: 1 }));');\n    });\n\n    it('allows dispatching a function returning an action without props', () => {\n      assertCompilationSuccess('store.dispatch(() => load());');\n    });\n\n    it('allows dispatching a function returning an action with props ', () => {\n      assertCompilationSuccess(\n        'store.dispatch(() => incrementer({ value: value() }));'\n      );\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/store/spec/types/store_module.spec.ts",
    "content": "import { expecter } from 'ts-snippet';\nimport { compilerOptions } from './utils';\n\ndescribe('StoreModule', () => {\n  const expectSnippet = expecter(\n    (code) => `\n      import { StoreModule, ActionReducerMap, Action, createReducer } from '@ngrx/store';\n\n      interface State {\n        featureA: object;\n        featureB: object;\n      }\n\n      const reducers: ActionReducerMap<State, Action> = {} as ActionReducerMap<\n        State,\n        Action\n      >;\n\n      function metaReducer(reducer) {\n        return (state, action) => {\n          return reducer(state, action);\n        };\n      }\n\n      ${code}\n    `,\n    compilerOptions()\n  );\n\n  describe('StoreModule.forRoot()', () => {\n    it('accepts initialState and metaReducers with matching State', () => {\n      expectSnippet(`\n        StoreModule.forRoot(reducers, {\n          initialState: { featureA: {} },\n          metaReducers: [metaReducer]\n        });\n      `).toSucceed();\n    });\n\n    it(\"throws when initial state don't with store state\", () => {\n      expectSnippet(`\n        StoreModule.forRoot(reducers, {\n          initialState: { notExisting: 3 },\n          metaReducers: [metaReducer]\n        });\n      `).toFail(\n        /Object literal may only specify known properties, and 'notExisting' does not exist in type 'Partial<State> | TypeId<Partial<State>>/\n      );\n    });\n  });\n\n  describe('StoreModule.forFeature()', () => {\n    it('accepts initialState and metaReducers with matching State', () => {\n      expectSnippet(`\n        StoreModule.forFeature('feature', reducers, {\n          initialState: { featureA: {} },\n          metaReducers: [metaReducer]\n        });\n      `).toSucceed();\n    });\n\n    it(\"throws when initial state don't with store state\", () => {\n      expectSnippet(`\n        StoreModule.forFeature('feature', reducers, {\n          initialState: { notExisting: 3 },\n          metaReducers: [metaReducer]\n        });\n      `).toFail(\n        /Object literal may only specify known properties, and 'notExisting' does not exist in type 'Partial<State> | TypeId<Partial<State>>/\n      );\n    });\n\n    it('throws when store config is passed along with slice object', () => {\n      expectSnippet(`\n        StoreModule.forFeature(\n          { name: 'feature', reducer: createReducer(0) },\n          { initialState: 100, metaReducers: [metaReducer] }\n        );\n      `).toFail(/No overload matches this call/);\n    });\n  });\n}, 8_000);\n"
  },
  {
    "path": "modules/store/spec/types/utils.ts",
    "content": "export const compilerOptions = () => ({\n  module: 'preserve',\n  moduleResolution: 'bundler',\n  target: 'ES2022',\n  baseUrl: '.',\n  experimentalDecorators: true,\n  paths: {\n    '@ngrx/store': ['./modules/store'],\n  },\n});\n"
  },
  {
    "path": "modules/store/spec/utils.spec.ts",
    "content": "import { omit, createFeatureReducerFactory } from '../src/utils';\nimport { combineReducers, compose } from '..';\n\ndescribe(`Store utils`, () => {\n  describe(`combineReducers()`, () => {\n    const state1 = { x: '' };\n    const state2 = { y: '' };\n    const reducer1 = (state = state1, action: any): typeof state1 =>\n      action.type === 'state1' ? { ...state, x: action.payload } : state;\n    const reducer2 = (state = state2, action: any): typeof state2 =>\n      action.type === 'state2' ? { ...state, y: action.payload } : state;\n    const reducers = { reducer1, reducer2, extraneous: true };\n    const initialState = { reducer1: { x: 'foo' }, reducer2: { y: 'bar' } };\n\n    let combination: any;\n\n    beforeEach(() => {\n      combination = combineReducers(reducers, initialState);\n    });\n\n    it(`should ignore extraneous keys`, () => {\n      expect(combination(undefined, { type: '' }).extraneous).toBeUndefined();\n    });\n\n    it(`should create a function that accepts state and action and returns combined state object`, () => {\n      const updateAction1 = { type: 'state1', payload: 'baz' };\n      expect(combination(initialState, updateAction1)).toEqual({\n        ...initialState,\n        reducer1: { x: updateAction1.payload },\n      });\n    });\n\n    it(`should handle initialState`, () => {\n      expect(combination(undefined, { type: '' })).toEqual(initialState);\n    });\n\n    it(`should return original state if nothing changed`, () => {\n      expect(combination(initialState, { type: '' })).toBe(initialState);\n    });\n  });\n\n  describe(`omit()`, () => {\n    let originalObj: { x: string; y: string; z?: string };\n\n    beforeEach(() => {\n      originalObj = { x: 'foo', y: 'bar' };\n    });\n\n    it(`should omit a key passed`, () => {\n      expect(omit(originalObj, 'x')).toEqual({ y: 'bar' });\n    });\n\n    it(`should not modify the original object`, () => {\n      expect(omit(originalObj, 'y')).not.toBe(originalObj);\n    });\n  });\n\n  describe(`compose()`, () => {\n    const cube = (n: number) => Math.pow(n, 3);\n    const multiplyByFive = (n: number) => n * 5;\n    const addTwo = (n: number) => n + 2;\n\n    it(`should compose functions`, () => {\n      const add2AndMultiply5 = compose(multiplyByFive, addTwo);\n      const add2AndMultiply5Cubed = compose(cube, add2AndMultiply5);\n\n      expect(add2AndMultiply5(1)).toBe(15);\n      expect(add2AndMultiply5Cubed(2)).toBe(8000);\n    });\n\n    it(`should act as identity if no functions passed`, () => {\n      const id = compose();\n      expect(id(1)).toBe(1);\n    });\n  });\n\n  describe('createFeatureReducerFactory()', () => {\n    it('should compose a reducer factory from the provided meta reducers', () => {\n      const metaReducer = vi.fn((red) => (s: any, a: any) => red(s, a));\n      const reducer = (state: any, action: any) => state;\n\n      const featureReducerFactory = createFeatureReducerFactory([metaReducer]);\n      const initialState = 1;\n      const featureReducer = featureReducerFactory(reducer, initialState);\n\n      const state = featureReducer(undefined, <any>undefined);\n\n      expect(metaReducer).toHaveBeenCalled();\n      expect(state).toBe(initialState);\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store/src/action_creator.ts",
    "content": "import {\n  Creator,\n  ActionCreator,\n  Action,\n  FunctionWithParametersType,\n  NotAllowedCheck,\n  ActionCreatorProps,\n  NotAllowedInPropsCheck,\n} from './models';\nimport { REGISTERED_ACTION_TYPES } from './globals';\n\n// Action creators taken from ts-action library and modified a bit to better\n// fit current NgRx usage. Thank you Nicholas Jamieson (@cartant).\n\n/**\n * @description\n * Creates a configured `Creator` function that, when called, returns an object in the shape of the\n * `Action` interface with no additional metadata.\n *\n * @param type Describes the action that will be dispatched\n *\n * @usageNotes\n *\n * Declaring an action creator:\n *\n * ```ts\n * export const increment = createAction('[Counter] Increment');\n * ```\n *\n * Dispatching an action:\n *\n * ```ts\n * store.dispatch(increment());\n * ```\n *\n * Referencing the action in a reducer:\n *\n * ```ts\n * on(CounterActions.increment, (state) => ({ ...state, count: state.count + 1 }))\n * ```\n *\n * Referencing the action in an effect:\n * ```ts\n * effectName$ = createEffect(\n *   () => this.actions$.pipe(\n *     ofType(CounterActions.increment),\n *     // ...\n *   )\n * );\n * ```\n */\nexport function createAction<T extends string>(\n  type: T\n): ActionCreator<T, () => Action<T>>;\n/**\n * @description\n * Creates a configured `Creator` function that, when called, returns an object in the shape of the\n * `Action` interface with metadata provided by the `props` or `emptyProps` functions.\n *\n * @param type Describes the action that will be dispatched\n *\n * @usageNotes\n *\n * Declaring an action creator:\n *\n * ```ts\n * export const loginSuccess = createAction(\n *   '[Auth/API] Login Success',\n *   props<{ user: User }>()\n * );\n * ```\n *\n * Dispatching an action:\n *\n * ```ts\n * store.dispatch(loginSuccess({ user: newUser }));\n * ```\n *\n * Referencing the action in a reducer:\n *\n * ```ts\n * on(AuthApiActions.loginSuccess, (state, { user }) => ({ ...state, user }))\n * ```\n *\n * Referencing the action in an effect:\n * ```ts\n * effectName$ = createEffect(\n *   () => this.actions$.pipe(\n *     ofType(AuthApiActions.loginSuccess),\n *     // ...\n *   )\n * );\n * ```\n */\nexport function createAction<T extends string, P extends object>(\n  type: T,\n  config: ActionCreatorProps<P> & NotAllowedCheck<P>\n): ActionCreator<T, (props: P & NotAllowedCheck<P>) => P & Action<T>>;\nexport function createAction<\n  T extends string,\n  P extends any[],\n  R extends object,\n>(\n  type: T,\n  creator: Creator<P, R & NotAllowedCheck<R>>\n): FunctionWithParametersType<P, R & Action<T>> & Action<T>;\n/**\n * @description\n * Creates a configured `Creator` function that, when called, returns an object in the shape of the `Action` interface.\n *\n * Action creators reduce the explicitness of class-based action creators.\n *\n * @param type Describes the action that will be dispatched\n * @param config Additional metadata needed for the handling of the action.  See {@link createAction#usage-notes Usage Notes}.\n *\n * @usageNotes\n *\n * **Declaring an action creator**\n *\n * Without additional metadata:\n * ```ts\n * export const increment = createAction('[Counter] Increment');\n * ```\n * With additional metadata:\n * ```ts\n * export const loginSuccess = createAction(\n *   '[Auth/API] Login Success',\n *   props<{ user: User }>()\n * );\n * ```\n * With a function:\n * ```ts\n * export const loginSuccess = createAction(\n *   '[Auth/API] Login Success',\n *   (response: Response) => response.user\n * );\n * ```\n *\n * **Dispatching an action**\n *\n * Without additional metadata:\n * ```ts\n * store.dispatch(increment());\n * ```\n * With additional metadata:\n * ```ts\n * store.dispatch(loginSuccess({ user: newUser }));\n * ```\n *\n * **Referencing an action in a reducer**\n *\n * Using a switch statement:\n * ```ts\n * switch (action.type) {\n *   // ...\n *   case AuthApiActions.loginSuccess.type: {\n *     return {\n *       ...state,\n *       user: action.user\n *     };\n *   }\n * }\n * ```\n * Using a reducer creator:\n * ```ts\n * on(AuthApiActions.loginSuccess, (state, { user }) => ({ ...state, user }))\n * ```\n *\n *  **Referencing an action in an effect**\n * ```ts\n * effectName$ = createEffect(\n *   () => this.actions$.pipe(\n *     ofType(AuthApiActions.loginSuccess),\n *     // ...\n *   )\n * );\n * ```\n */\nexport function createAction<T extends string, C extends Creator>(\n  type: T,\n  config?: { _as: 'props' } | C\n): ActionCreator<T> {\n  REGISTERED_ACTION_TYPES[type] = (REGISTERED_ACTION_TYPES[type] || 0) + 1;\n\n  if (typeof config === 'function') {\n    return defineType(type, (...args: any[]) => ({\n      ...config(...args),\n      type,\n    }));\n  }\n  const as = config ? config._as : 'empty';\n  switch (as) {\n    case 'empty':\n      return defineType(type, () => ({ type }));\n    case 'props':\n      return defineType(type, (props: object) => ({\n        ...props,\n        type,\n      }));\n    default:\n      throw new Error('Unexpected config.');\n  }\n}\n\nexport function props<\n  P extends SafeProps,\n  SafeProps = NotAllowedInPropsCheck<P>,\n>(): ActionCreatorProps<P> {\n  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n  return { _as: 'props', _p: undefined! };\n}\n\nexport function union<\n  C extends { [key: string]: ActionCreator<string, Creator> },\n>(creators: C): ReturnType<C[keyof C]> {\n  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n  return undefined!;\n}\n\nfunction defineType<T extends string>(\n  type: T,\n  creator: Creator\n): ActionCreator<T> {\n  return Object.defineProperty(creator, 'type', {\n    value: type,\n    writable: false,\n  }) as ActionCreator<T>;\n}\n"
  },
  {
    "path": "modules/store/src/action_group_creator.ts",
    "content": "import { createAction, props } from './action_creator';\nimport {\n  ActionCreator,\n  ActionCreatorProps,\n  Creator,\n  FunctionWithParametersType,\n  NotAllowedCheck,\n  Action,\n} from './models';\nimport { capitalize, uncapitalize } from './helpers';\n\ntype Join<\n  Str extends string,\n  Separator extends string = ' ',\n> = Str extends `${infer First}${Separator}${infer Rest}`\n  ? Join<`${First}${Rest}`, Separator>\n  : Str;\n\ntype CapitalizeWords<Str extends string> =\n  Str extends `${infer First} ${infer Rest}`\n    ? `${Capitalize<First>} ${CapitalizeWords<Rest>}`\n    : Capitalize<Str>;\n\ntype StringLiteralCheck<\n  Str extends string,\n  Name extends string,\n> = string extends Str ? `${Name} must be a string literal type` : unknown;\n\ntype UniqueEventNameCheck<EventNames extends string, EventName extends string> =\n  ActionName<EventName> extends ActionName<Exclude<EventNames, EventName>>\n    ? `${ActionName<EventName>} action is already defined`\n    : unknown;\n\ntype NotAllowedEventPropsCheck<\n  PropsCreator extends ActionCreatorProps<unknown> | Creator,\n> =\n  PropsCreator extends ActionCreatorProps<infer Props>\n    ? Props extends void\n      ? unknown\n      : NotAllowedCheck<Props & object>\n    : PropsCreator extends Creator<any, infer Result>\n      ? NotAllowedCheck<Result>\n      : unknown;\n\ntype EventCreator<\n  PropsCreator extends ActionCreatorProps<unknown> | Creator,\n  Type extends string,\n> =\n  PropsCreator extends ActionCreatorProps<infer Props>\n    ? void extends Props\n      ? ActionCreator<Type, () => Action<Type>>\n      : ActionCreator<\n          Type,\n          (\n            props: Props & NotAllowedCheck<Props & object>\n          ) => Props & Action<Type>\n        >\n    : PropsCreator extends Creator<infer Props, infer Result>\n      ? FunctionWithParametersType<\n          Props,\n          Result & NotAllowedCheck<Result> & Action<Type>\n        > &\n          Action<Type>\n      : never;\n\ntype ActionName<EventName extends string> = Uncapitalize<\n  Join<CapitalizeWords<EventName>>\n>;\n\ninterface ActionGroupConfig<\n  Source extends string,\n  Events extends Record<string, ActionCreatorProps<unknown> | Creator>,\n> {\n  source: Source & StringLiteralCheck<Source, 'source'>;\n  events: Events & {\n    [EventName in keyof Events]: StringLiteralCheck<\n      EventName & string,\n      'event name'\n    > &\n      UniqueEventNameCheck<keyof Events & string, EventName & string> &\n      NotAllowedEventPropsCheck<Events[EventName]>;\n  };\n}\n\ntype ActionGroup<\n  Source extends string,\n  Events extends Record<string, ActionCreatorProps<unknown> | Creator>,\n> = {\n  [EventName in keyof Events as ActionName<EventName & string>]: EventCreator<\n    Events[EventName],\n    `[${Source}] ${EventName & string}`\n  >;\n};\n\n/**\n * @description\n * A function that creates a group of action creators with the same source.\n *\n * @param config An object that contains a source and dictionary of events.\n * An event is a key-value pair of an event name and event props.\n * @returns A dictionary of action creators.\n * The name of each action creator is created by camel casing the event name.\n * The type of each action is created using the \"[Source] Event Name\" pattern.\n *\n * @usageNotes\n *\n * ```ts\n * const authApiActions = createActionGroup({\n *   source: 'Auth API',\n *   events: {\n *     // defining events with payload using the `props` function\n *     'Login Success': props<{ userId: number; token: string }>(),\n *     'Login Failure': props<{ error: string }>(),\n *\n *     // defining an event without payload using the `emptyProps` function\n *     'Logout Success': emptyProps(),\n *\n *     // defining an event with payload using the props factory\n *     'Logout Failure': (error: Error) => ({ error }),\n *   },\n * });\n *\n * // action type: \"[Auth API] Login Success\"\n * authApiActions.loginSuccess({ userId: 10, token: 'ngrx' });\n *\n * // action type: \"[Auth API] Login Failure\"\n * authApiActions.loginFailure({ error: 'Login Failure!' });\n *\n * // action type: \"[Auth API] Logout Success\"\n * authApiActions.logoutSuccess();\n *\n * // action type: \"[Auth API] Logout Failure\";\n * authApiActions.logoutFailure(new Error('Logout Failure!'));\n * ```\n */\nexport function createActionGroup<\n  Source extends string,\n  Events extends Record<string, ActionCreatorProps<unknown> | Creator>,\n>(config: ActionGroupConfig<Source, Events>): ActionGroup<Source, Events> {\n  const { source, events } = config;\n\n  return Object.keys(events).reduce(\n    (actionGroup, eventName) => ({\n      ...actionGroup,\n      [toActionName(eventName)]: createAction(\n        toActionType(source, eventName),\n        (events as any)[eventName]\n      ),\n    }),\n    {} as ActionGroup<Source, Events>\n  );\n}\n\nexport function emptyProps(): ActionCreatorProps<void> {\n  return props();\n}\n\nfunction toActionName<EventName extends string>(\n  eventName: EventName\n): ActionName<EventName> {\n  return eventName\n    .trim()\n    .split(' ')\n    .map((word, i) => (i === 0 ? uncapitalize(word) : capitalize(word)))\n    .join('') as ActionName<EventName>;\n}\n\nfunction toActionType<Source extends string, EventName extends string>(\n  source: Source,\n  eventName: EventName\n): `[${Source}] ${EventName}` {\n  return `[${source}] ${eventName}`;\n}\n"
  },
  {
    "path": "modules/store/src/actions_subject.ts",
    "content": "import { Injectable, OnDestroy, Provider } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\n\nimport { Action } from './models';\n\nexport const INIT = '@ngrx/store/init' as const;\n\n@Injectable()\nexport class ActionsSubject\n  extends BehaviorSubject<Action>\n  implements OnDestroy\n{\n  constructor() {\n    super({ type: INIT });\n  }\n\n  override next(action: Action): void {\n    if (typeof action === 'function') {\n      throw new TypeError(`\n        Dispatch expected an object, instead it received a function.\n        If you're using the createAction function, make sure to invoke the function\n        before dispatching the action. For example, someAction should be someAction().`);\n    } else if (typeof action === 'undefined') {\n      throw new TypeError(`Actions must be objects`);\n    } else if (typeof action.type === 'undefined') {\n      throw new TypeError(`Actions must have a type property`);\n    }\n    super.next(action);\n  }\n\n  override complete() {\n    /* noop */\n  }\n\n  ngOnDestroy() {\n    super.complete();\n  }\n}\n\nexport const ACTIONS_SUBJECT_PROVIDERS: Provider[] = [ActionsSubject];\n"
  },
  {
    "path": "modules/store/src/feature_creator.ts",
    "content": "import { capitalize } from './helpers';\nimport { ActionReducer, Prettify, Primitive, Selector } from './models';\nimport { isPlainObject } from './meta-reducers/utils';\nimport {\n  createFeatureSelector,\n  createSelector,\n  MemoizedSelector,\n} from './selector';\n\nexport interface FeatureConfig<FeatureName extends string, FeatureState> {\n  name: FeatureName;\n  reducer: ActionReducer<FeatureState>;\n}\n\ntype Feature<FeatureName extends string, FeatureState> = FeatureConfig<\n  FeatureName,\n  FeatureState\n> &\n  BaseSelectors<FeatureName, FeatureState>;\n\ntype FeatureWithExtraSelectors<\n  FeatureName extends string,\n  FeatureState,\n  ExtraSelectors extends SelectorsDictionary,\n> = string extends keyof ExtraSelectors\n  ? Feature<FeatureName, FeatureState>\n  : Omit<Feature<FeatureName, FeatureState>, keyof ExtraSelectors> &\n      ExtraSelectors;\n\ntype FeatureSelector<FeatureName extends string, FeatureState> = {\n  [K in FeatureName as `select${Capitalize<K>}State`]: MemoizedSelector<\n    Record<string, any>,\n    FeatureState,\n    (featureState: FeatureState) => FeatureState\n  >;\n};\n\ntype NestedSelectors<FeatureState> = FeatureState extends\n  | Primitive\n  | unknown[]\n  | Date\n  ? {}\n  : {\n      [K in keyof FeatureState &\n        string as `select${Capitalize<K>}`]: MemoizedSelector<\n        Record<string, any>,\n        FeatureState[K],\n        (featureState: FeatureState) => FeatureState[K]\n      >;\n    };\n\ntype BaseSelectors<FeatureName extends string, FeatureState> = FeatureSelector<\n  FeatureName,\n  FeatureState\n> &\n  NestedSelectors<FeatureState>;\n\ntype SelectorsDictionary = Record<\n  string,\n  | Selector<Record<string, any>, unknown>\n  | ((...args: any[]) => Selector<Record<string, any>, unknown>)\n>;\n\ntype ExtraSelectorsFactory<\n  FeatureName extends string,\n  FeatureState,\n  ExtraSelectors extends SelectorsDictionary,\n> = (baseSelectors: BaseSelectors<FeatureName, FeatureState>) => ExtraSelectors;\n\ntype NotAllowedFeatureStateCheck<FeatureState> =\n  FeatureState extends Required<FeatureState>\n    ? unknown\n    : 'optional properties are not allowed in the feature state';\n\n/**\n * Creates a feature object with extra selectors.\n *\n * @param featureConfig An object that contains a feature name, a feature\n * reducer, and extra selectors factory.\n * @returns An object that contains a feature name, a feature reducer,\n * a feature selector, a selector for each feature state property, and\n * extra selectors.\n */\nexport function createFeature<\n  FeatureName extends string,\n  FeatureState,\n  ExtraSelectors extends SelectorsDictionary,\n>(\n  featureConfig: FeatureConfig<FeatureName, FeatureState> & {\n    extraSelectors: ExtraSelectorsFactory<\n      FeatureName,\n      FeatureState,\n      ExtraSelectors\n    >;\n  } & NotAllowedFeatureStateCheck<FeatureState>\n): Prettify<\n  FeatureWithExtraSelectors<FeatureName, FeatureState, ExtraSelectors>\n>;\n/**\n * Creates a feature object.\n *\n * @param featureConfig An object that contains a feature name and a feature\n * reducer.\n * @returns An object that contains a feature name, a feature reducer,\n * a feature selector, and a selector for each feature state property.\n */\nexport function createFeature<FeatureName extends string, FeatureState>(\n  featureConfig: FeatureConfig<FeatureName, FeatureState> &\n    NotAllowedFeatureStateCheck<FeatureState>\n): Prettify<Feature<FeatureName, FeatureState>>;\n/**\n * @description\n * A function that accepts a feature name and a feature reducer, and creates\n * a feature selector and a selector for each feature state property.\n * This function also provides the ability to add extra selectors to\n * the feature object.\n *\n * @param featureConfig An object that contains a feature name and a feature\n * reducer as required, and extra selectors factory as an optional argument.\n * @returns An object that contains a feature name, a feature reducer,\n * a feature selector, a selector for each feature state property, and extra\n * selectors.\n *\n * @usageNotes\n *\n * ```ts\n * interface ProductsState {\n *   products: Product[];\n *   selectedId: string | null;\n * }\n *\n * const initialState: ProductsState = {\n *   products: [],\n *   selectedId: null,\n * };\n *\n * const productsFeature = createFeature({\n *   name: 'products',\n *   reducer: createReducer(\n *     initialState,\n *     on(ProductsApiActions.loadSuccess, (state, { products }) => ({\n *       ...state,\n *       products,\n *     })),\n *   ),\n * });\n *\n * const {\n *   name,\n *   reducer,\n *   // feature selector\n *   selectProductsState, // type: MemoizedSelector<Record<string, any>, ProductsState>\n *   // feature state properties selectors\n *   selectProducts, // type: MemoizedSelector<Record<string, any>, Product[]>\n *   selectSelectedId, // type: MemoizedSelector<Record<string, any>, string | null>\n * } = productsFeature;\n * ```\n *\n * **Creating Feature with Extra Selectors**\n *\n * ```ts\n * type CallState = 'init' | 'loading' | 'loaded' | { error: string };\n *\n * interface State extends EntityState<Product> {\n *   callState: CallState;\n * }\n *\n * const adapter = createEntityAdapter<Product>();\n * const initialState: State = adapter.getInitialState({\n *   callState: 'init',\n * });\n *\n * export const productsFeature = createFeature({\n *   name: 'products',\n *   reducer: createReducer(initialState),\n *   extraSelectors: ({ selectProductsState, selectCallState }) => ({\n *     ...adapter.getSelectors(selectProductsState),\n *     ...getCallStateSelectors(selectCallState)\n *   }),\n * });\n *\n * const {\n *   name,\n *   reducer,\n *   // feature selector\n *   selectProductsState,\n *   // feature state properties selectors\n *   selectIds,\n *   selectEntities,\n *   selectCallState,\n *   // selectors returned by `adapter.getSelectors`\n *   selectAll,\n *   selectTotal,\n *   // selectors returned by `getCallStateSelectors`\n *   selectIsLoading,\n *   selectIsLoaded,\n *   selectError,\n * } = productsFeature;\n * ```\n */\nexport function createFeature<\n  FeatureName extends string,\n  FeatureState,\n  ExtraSelectors extends SelectorsDictionary,\n>(\n  featureConfig: FeatureConfig<FeatureName, FeatureState> & {\n    extraSelectors?: ExtraSelectorsFactory<\n      FeatureName,\n      FeatureState,\n      ExtraSelectors\n    >;\n  }\n): Feature<FeatureName, FeatureState> & ExtraSelectors {\n  const {\n    name,\n    reducer,\n    extraSelectors: extraSelectorsFactory,\n  } = featureConfig;\n\n  const featureSelector = createFeatureSelector<FeatureState>(name);\n  const nestedSelectors = createNestedSelectors(featureSelector, reducer);\n  const baseSelectors = {\n    [`select${capitalize(name)}State`]: featureSelector,\n    ...nestedSelectors,\n  } as BaseSelectors<FeatureName, FeatureState>;\n  const extraSelectors = extraSelectorsFactory\n    ? extraSelectorsFactory(baseSelectors)\n    : {};\n\n  return {\n    name,\n    reducer,\n    ...baseSelectors,\n    ...extraSelectors,\n  } as Feature<FeatureName, FeatureState> & ExtraSelectors;\n}\n\nfunction createNestedSelectors<FeatureState>(\n  featureSelector: MemoizedSelector<Record<string, any>, FeatureState>,\n  reducer: ActionReducer<FeatureState>\n): NestedSelectors<FeatureState> {\n  const initialState = getInitialState(reducer);\n  const nestedKeys = (\n    isPlainObject(initialState) ? Object.keys(initialState) : []\n  ) as Array<keyof FeatureState & string>;\n\n  return nestedKeys.reduce(\n    (nestedSelectors, nestedKey) => ({\n      ...nestedSelectors,\n      [`select${capitalize(nestedKey)}`]: createSelector(\n        featureSelector,\n        (parentState) => parentState?.[nestedKey]\n      ),\n    }),\n    {} as NestedSelectors<FeatureState>\n  );\n}\n\nfunction getInitialState<FeatureState>(\n  reducer: ActionReducer<FeatureState>\n): FeatureState {\n  return reducer(undefined, { type: '@ngrx/feature/init' });\n}\n"
  },
  {
    "path": "modules/store/src/flags.ts",
    "content": "let _ngrxMockEnvironment = false;\nexport function setNgrxMockEnvironment(value: boolean): void {\n  _ngrxMockEnvironment = value;\n}\nexport function isNgrxMockEnvironment(): boolean {\n  return _ngrxMockEnvironment;\n}\n"
  },
  {
    "path": "modules/store/src/globals.ts",
    "content": "export const REGISTERED_ACTION_TYPES: { [actionType: string]: number } = {};\n\nexport function resetRegisteredActionTypes() {\n  for (const key of Object.keys(REGISTERED_ACTION_TYPES)) {\n    delete REGISTERED_ACTION_TYPES[key];\n  }\n}\n"
  },
  {
    "path": "modules/store/src/helpers.ts",
    "content": "export function capitalize<T extends string>(text: T): Capitalize<T> {\n  return (text.charAt(0).toUpperCase() + text.substring(1)) as Capitalize<T>;\n}\n\nexport function uncapitalize<T extends string>(text: T): Uncapitalize<T> {\n  return (text.charAt(0).toLowerCase() + text.substring(1)) as Uncapitalize<T>;\n}\n\nexport function assertDefined<T>(\n  value: T | null | undefined,\n  name: string\n): asserts value is T {\n  if (value === null || value === undefined) {\n    throw new Error(`${name} must be defined.`);\n  }\n}\n"
  },
  {
    "path": "modules/store/src/index.ts",
    "content": "export {\n  Action,\n  ActionCreator,\n  ActionCreatorProps,\n  ActionReducer,\n  ActionReducerFactory,\n  ActionReducerMap,\n  ActionType,\n  Creator,\n  FunctionWithParametersType,\n  MetaReducer,\n  NotAllowedCheck,\n  RuntimeChecks,\n  SelectSignalOptions,\n  Selector,\n  SelectorWithProps,\n} from './models';\nexport { createAction, props, union } from './action_creator';\nexport { createActionGroup, emptyProps } from './action_group_creator';\nexport { Store, select } from './store';\nexport { combineReducers, compose, createReducerFactory } from './utils';\nexport { ActionsSubject, INIT } from './actions_subject';\nexport { createFeature, FeatureConfig } from './feature_creator';\nexport { setNgrxMockEnvironment, isNgrxMockEnvironment } from './flags';\nexport {\n  ReducerManager,\n  ReducerObservable,\n  ReducerManagerDispatcher,\n  UPDATE,\n} from './reducer_manager';\nexport { ScannedActionsSubject } from './scanned_actions_subject';\nexport {\n  createSelector,\n  createSelectorFactory,\n  createFeatureSelector,\n  defaultMemoize,\n  defaultStateFn,\n  MemoizeFn,\n  MemoizedProjection,\n  MemoizedSelector,\n  MemoizedSelectorWithProps,\n  resultMemoize,\n  DefaultProjectorFn,\n} from './selector';\nexport { State, StateObservable, reduceState } from './state';\nexport {\n  INITIAL_STATE,\n  REDUCER_FACTORY,\n  INITIAL_REDUCERS,\n  STORE_FEATURES,\n  META_REDUCERS,\n  FEATURE_REDUCERS,\n  USER_PROVIDED_META_REDUCERS,\n  USER_RUNTIME_CHECKS,\n  ACTIVE_RUNTIME_CHECKS,\n  FEATURE_STATE_PROVIDER,\n  ROOT_STORE_PROVIDER,\n} from './tokens';\nexport {\n  StoreModule,\n  StoreRootModule,\n  StoreFeatureModule,\n} from './store_module';\nexport { RootStoreConfig, StoreConfig, FeatureSlice } from './store_config';\nexport { provideStore, provideState } from './provide_store';\nexport { ReducerTypes, on, createReducer } from './reducer_creator';\n"
  },
  {
    "path": "modules/store/src/meta-reducers/immutability_reducer.ts",
    "content": "import { ActionReducer, Action } from '../models';\nimport { isFunction, hasOwnProperty, isObjectLike } from './utils';\n\nexport function immutabilityCheckMetaReducer(\n  reducer: ActionReducer<any, any>,\n  checks: { action: (action: Action) => boolean; state: () => boolean }\n): ActionReducer<any, any> {\n  return function (state, action) {\n    const act = checks.action(action) ? freeze(action) : action;\n\n    const nextState = reducer(state, act);\n\n    return checks.state() ? freeze(nextState) : nextState;\n  };\n}\n\nfunction freeze(target: any) {\n  Object.freeze(target);\n\n  const targetIsFunction = isFunction(target);\n\n  Object.getOwnPropertyNames(target).forEach((prop) => {\n    // Ignore Ivy properties, ref: https://github.com/ngrx/platform/issues/2109#issuecomment-582689060\n    if (prop.startsWith('ɵ')) {\n      return;\n    }\n\n    if (\n      hasOwnProperty(target, prop) &&\n      (targetIsFunction\n        ? prop !== 'caller' && prop !== 'callee' && prop !== 'arguments'\n        : true)\n    ) {\n      const propValue = target[prop];\n\n      if (\n        (isObjectLike(propValue) || isFunction(propValue)) &&\n        !Object.isFrozen(propValue)\n      ) {\n        freeze(propValue);\n      }\n    }\n  });\n\n  return target;\n}\n"
  },
  {
    "path": "modules/store/src/meta-reducers/inNgZoneAssert_reducer.ts",
    "content": "import * as ngCore from '@angular/core';\nimport { Action, ActionReducer } from '../models';\nimport { RUNTIME_CHECK_URL } from './utils';\n\nexport function inNgZoneAssertMetaReducer(\n  reducer: ActionReducer<any, Action>,\n  checks: { action: (action: Action) => boolean }\n) {\n  return function (state: any, action: Action) {\n    if (checks.action(action) && !ngCore.NgZone.isInAngularZone()) {\n      throw new Error(\n        `Action '${action.type}' running outside NgZone. ${RUNTIME_CHECK_URL}#strictactionwithinngzone`\n      );\n    }\n    return reducer(state, action);\n  };\n}\n"
  },
  {
    "path": "modules/store/src/meta-reducers/index.ts",
    "content": "export { immutabilityCheckMetaReducer } from './immutability_reducer';\nexport { serializationCheckMetaReducer } from './serialization_reducer';\nexport { inNgZoneAssertMetaReducer } from './inNgZoneAssert_reducer';\n"
  },
  {
    "path": "modules/store/src/meta-reducers/serialization_reducer.ts",
    "content": "import { ActionReducer, Action } from '../models';\nimport {\n  isPlainObject,\n  isUndefined,\n  isNull,\n  isNumber,\n  isBoolean,\n  isString,\n  isArray,\n  RUNTIME_CHECK_URL,\n  isComponent,\n} from './utils';\n\nexport function serializationCheckMetaReducer(\n  reducer: ActionReducer<any, any>,\n  checks: { action: (action: Action) => boolean; state: () => boolean }\n): ActionReducer<any, any> {\n  return function (state, action) {\n    if (checks.action(action)) {\n      const unserializableAction = getUnserializable(action);\n      throwIfUnserializable(unserializableAction, 'action');\n    }\n\n    const nextState = reducer(state, action);\n\n    if (checks.state()) {\n      const unserializableState = getUnserializable(nextState);\n      throwIfUnserializable(unserializableState, 'state');\n    }\n\n    return nextState;\n  };\n}\n\nfunction getUnserializable(\n  target?: any,\n  path: string[] = []\n): false | { path: string[]; value: any } {\n  // Guard against undefined and null, e.g. a reducer that returns undefined\n  if ((isUndefined(target) || isNull(target)) && path.length === 0) {\n    return {\n      path: ['root'],\n      value: target,\n    };\n  }\n\n  const keys = Object.keys(target);\n  return keys.reduce<false | { path: string[]; value: any }>((result, key) => {\n    if (result) {\n      return result;\n    }\n\n    const value = (target as any)[key];\n\n    // Ignore Ivy components\n    if (isComponent(value)) {\n      return result;\n    }\n\n    if (\n      isUndefined(value) ||\n      isNull(value) ||\n      isNumber(value) ||\n      isBoolean(value) ||\n      isString(value) ||\n      isArray(value)\n    ) {\n      return false;\n    }\n\n    if (isPlainObject(value)) {\n      return getUnserializable(value, [...path, key]);\n    }\n\n    return {\n      path: [...path, key],\n      value,\n    };\n  }, false);\n}\n\nfunction throwIfUnserializable(\n  unserializable: false | { path: string[]; value: any },\n  context: 'state' | 'action'\n) {\n  if (unserializable === false) {\n    return;\n  }\n\n  const unserializablePath = unserializable.path.join('.');\n  const error: any = new Error(\n    `Detected unserializable ${context} at \"${unserializablePath}\". ${RUNTIME_CHECK_URL}#strict${context}serializability`\n  );\n  error.value = unserializable.value;\n  error.unserializablePath = unserializablePath;\n  throw error;\n}\n"
  },
  {
    "path": "modules/store/src/meta-reducers/utils.ts",
    "content": "export const RUNTIME_CHECK_URL =\n  'https://ngrx.io/guide/store/configuration/runtime-checks';\n\nexport function isUndefined(target: any): target is undefined {\n  return target === undefined;\n}\n\nexport function isNull(target: any): target is null {\n  return target === null;\n}\n\nexport function isArray(target: any): target is Array<any> {\n  return Array.isArray(target);\n}\n\nexport function isString(target: any): target is string {\n  return typeof target === 'string';\n}\n\nexport function isBoolean(target: any): target is boolean {\n  return typeof target === 'boolean';\n}\n\nexport function isNumber(target: any): target is number {\n  return typeof target === 'number';\n}\n\nexport function isObjectLike(target: any): target is object {\n  return typeof target === 'object' && target !== null;\n}\n\nexport function isObject(target: any): target is object {\n  return isObjectLike(target) && !isArray(target);\n}\n\nexport function isPlainObject(target: any): target is object {\n  if (!isObject(target)) {\n    return false;\n  }\n\n  const targetPrototype = Object.getPrototypeOf(target);\n  return targetPrototype === Object.prototype || targetPrototype === null;\n}\n\nexport function isFunction(target: any): target is () => void {\n  return typeof target === 'function';\n}\n\nexport function isComponent(target: any) {\n  return isFunction(target) && target.hasOwnProperty('ɵcmp');\n}\n\nexport function hasOwnProperty(target: object, propertyName: string): boolean {\n  return Object.prototype.hasOwnProperty.call(target, propertyName);\n}\n"
  },
  {
    "path": "modules/store/src/models.ts",
    "content": "import { type ValueEqualityFn } from '@angular/core';\n\nexport interface Action<Type extends string = string> {\n  type: Type;\n}\n\nexport type ActionType<A> =\n  A extends ActionCreator<infer T, infer C>\n    ? ReturnType<C> & { type: T }\n    : never;\n\nexport type TypeId<T> = () => T;\n\nexport type InitialState<T> = Partial<T> | TypeId<Partial<T>> | void;\n\n/**\n * A function that takes an `Action` and a `State`, and returns a `State`.\n * See `createReducer`.\n */\nexport interface ActionReducer<T, V extends Action = Action> {\n  (state: T | undefined, action: V): T;\n}\n\nexport type ActionReducerMap<T, V extends Action = Action> = {\n  [p in keyof T]: ActionReducer<T[p], V>;\n};\n\nexport interface ActionReducerFactory<T, V extends Action = Action> {\n  (\n    reducerMap: ActionReducerMap<T, V>,\n    initialState?: InitialState<T>\n  ): ActionReducer<T, V>;\n}\n\nexport type MetaReducer<T = any, V extends Action = Action> = (\n  reducer: ActionReducer<T, V>\n) => ActionReducer<T, V>;\n\nexport interface StoreFeature<T, V extends Action = Action> {\n  key: string;\n  reducers: ActionReducerMap<T, V> | ActionReducer<T, V>;\n  reducerFactory: ActionReducerFactory<T, V>;\n  initialState?: InitialState<T>;\n  metaReducers?: MetaReducer<T, V>[];\n}\n\nexport type Selector<T, V> = (state: T) => V;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport type SelectorWithProps<State, Props, Result> = (\n  state: State,\n  props: Props\n) => Result;\n\nexport const arraysAreNotAllowedMsg = 'action creator cannot return an array';\ntype ArraysAreNotAllowed = typeof arraysAreNotAllowedMsg;\n\nexport const typePropertyIsNotAllowedMsg =\n  'action creator cannot return an object with a property named `type`';\ntype TypePropertyIsNotAllowed = typeof typePropertyIsNotAllowedMsg;\n\nexport const emptyObjectsAreNotAllowedMsg =\n  'action creator cannot return an empty object';\ntype EmptyObjectsAreNotAllowed = typeof emptyObjectsAreNotAllowedMsg;\n\nexport const arraysAreNotAllowedInProps =\n  'action creator props cannot be an array';\ntype ArraysAreNotAllowedInProps = typeof arraysAreNotAllowedInProps;\n\nexport const typePropertyIsNotAllowedInProps =\n  'action creator props cannot have a property named `type`';\ntype TypePropertyIsNotAllowedInProps = typeof typePropertyIsNotAllowedInProps;\n\nexport const emptyObjectsAreNotAllowedInProps =\n  'action creator props cannot be an empty object';\ntype EmptyObjectsAreNotAllowedInProps = typeof emptyObjectsAreNotAllowedInProps;\n\nexport const primitivesAreNotAllowedInProps =\n  'action creator props cannot be a primitive value';\ntype PrimitivesAreNotAllowedInProps = typeof primitivesAreNotAllowedInProps;\n\nexport type CreatorsNotAllowedCheck<T> = T extends ActionCreator\n  ? 'Action creator is not allowed to be dispatched. Did you forget to call it?'\n  : unknown;\n\n/**\n * A function that returns an object in the shape of the `Action` interface.  Configured using `createAction`.\n */\nexport type Creator<\n  P extends any[] = any[],\n  R extends object = object,\n> = FunctionWithParametersType<P, R>;\n\nexport type Primitive =\n  | string\n  | number\n  | bigint\n  | boolean\n  | symbol\n  | null\n  | undefined;\n\nexport type NotAllowedCheck<T extends object> = T extends any[]\n  ? ArraysAreNotAllowed\n  : T extends { type: any }\n    ? TypePropertyIsNotAllowed\n    : keyof T extends never\n      ? EmptyObjectsAreNotAllowed\n      : unknown;\n\nexport type NotAllowedInPropsCheck<T> = T extends object\n  ? T extends any[]\n    ? ArraysAreNotAllowedInProps\n    : T extends { type: any }\n      ? TypePropertyIsNotAllowedInProps\n      : keyof T extends never\n        ? EmptyObjectsAreNotAllowedInProps\n        : unknown\n  : T extends Primitive\n    ? PrimitivesAreNotAllowedInProps\n    : never;\n\n/**\n * See `Creator`.\n */\nexport type ActionCreator<\n  T extends string = string,\n  C extends Creator = Creator,\n> = C & Action<T>;\n\nexport interface ActionCreatorProps<T> {\n  _as: 'props';\n  _p: T;\n}\n\nexport type FunctionWithParametersType<P extends unknown[], R = void> = (\n  ...args: P\n) => R;\n\nexport interface RuntimeChecks {\n  /**\n   * Verifies if the state is serializable\n   */\n  strictStateSerializability: boolean;\n  /**\n   * Verifies if the actions are serializable. Please note, you may not need to set it to `true` unless you are storing/replaying actions using external resources, for example `localStorage`.\n   */\n  strictActionSerializability: boolean;\n  /**\n   * Verifies that the state isn't mutated\n   */\n  strictStateImmutability: boolean;\n  /**\n   * Verifies that actions aren't mutated\n   */\n  strictActionImmutability: boolean;\n\n  /**\n   * Verifies that actions are dispatched within NgZone\n   */\n  strictActionWithinNgZone: boolean;\n\n  /**\n   * Verifies that action types are not registered more than once\n   */\n  strictActionTypeUniqueness?: boolean;\n}\n\nexport interface SelectSignalOptions<T> {\n  /**\n   *  A comparison function which defines equality for select results.\n   */\n  equal?: ValueEqualityFn<T>;\n}\n\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {};\n"
  },
  {
    "path": "modules/store/src/private_export.ts",
    "content": "export { ActionsSubject } from './actions_subject';\nexport { ReducerManager, ReducerObservable } from './reducer_manager';\nexport { ScannedActionsSubject } from './scanned_actions_subject';\nexport { State, StateObservable, reduceState } from './state';\nexport {\n  INITIAL_STATE,\n  REDUCER_FACTORY,\n  INITIAL_REDUCERS,\n  STORE_FEATURES,\n} from './tokens';\nexport { StoreRootModule, StoreFeatureModule } from './store_module';\n"
  },
  {
    "path": "modules/store/src/provide_store.ts",
    "content": "import {\n  EnvironmentProviders,\n  Inject,\n  inject,\n  InjectionToken,\n  makeEnvironmentProviders,\n  provideEnvironmentInitializer,\n  Provider,\n} from '@angular/core';\nimport {\n  Action,\n  ActionReducer,\n  ActionReducerMap,\n  StoreFeature,\n} from './models';\nimport { combineReducers, createReducerFactory } from './utils';\nimport {\n  _ACTION_TYPE_UNIQUENESS_CHECK,\n  _FEATURE_CONFIGS,\n  _FEATURE_REDUCERS,\n  _FEATURE_REDUCERS_TOKEN,\n  _INITIAL_REDUCERS,\n  _INITIAL_STATE,\n  _REDUCER_FACTORY,\n  _RESOLVED_META_REDUCERS,\n  _ROOT_STORE_GUARD,\n  _STORE_FEATURES,\n  _STORE_REDUCERS,\n  FEATURE_REDUCERS,\n  FEATURE_STATE_PROVIDER,\n  INITIAL_REDUCERS,\n  INITIAL_STATE,\n  META_REDUCERS,\n  REDUCER_FACTORY,\n  ROOT_STORE_PROVIDER,\n  STORE_FEATURES,\n  USER_PROVIDED_META_REDUCERS,\n} from './tokens';\nimport { ACTIONS_SUBJECT_PROVIDERS, ActionsSubject } from './actions_subject';\nimport {\n  REDUCER_MANAGER_PROVIDERS,\n  ReducerManager,\n  ReducerObservable,\n} from './reducer_manager';\nimport {\n  SCANNED_ACTIONS_SUBJECT_PROVIDERS,\n  ScannedActionsSubject,\n} from './scanned_actions_subject';\nimport { STATE_PROVIDERS } from './state';\nimport { Store, STORE_PROVIDERS } from './store';\nimport {\n  checkForActionTypeUniqueness,\n  provideRuntimeChecks,\n} from './runtime_checks';\nimport {\n  _concatMetaReducers,\n  _createFeatureReducers,\n  _createFeatureStore,\n  _createStoreReducers,\n  _initialStateFactory,\n  _provideForRootGuard,\n  FeatureSlice,\n  RootStoreConfig,\n  StoreConfig,\n} from './store_config';\n\nexport function provideState<T, V extends Action = Action>(\n  featureName: string,\n  reducers: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>,\n  config?: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>>\n): EnvironmentProviders;\nexport function provideState<T, V extends Action = Action>(\n  featureName: string,\n  reducer: ActionReducer<T, V> | InjectionToken<ActionReducer<T, V>>,\n  config?: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>>\n): EnvironmentProviders;\nexport function provideState<T, V extends Action = Action>(\n  slice: FeatureSlice<T, V>\n): EnvironmentProviders;\n/**\n * Provides additional slices of state in the Store.\n * These providers cannot be used at the component level.\n *\n * @usageNotes\n *\n * ### Providing Store Features\n *\n * ```ts\n * const booksRoutes: Route[] = [\n *   {\n *     path: '',\n *     providers: [provideState('books', booksReducer)],\n *     children: [\n *       { path: '', component: BookListComponent },\n *       { path: ':id', component: BookDetailsComponent },\n *     ],\n *   },\n * ];\n * ```\n */\nexport function provideState<T, V extends Action = Action>(\n  featureNameOrSlice: string | FeatureSlice<T, V>,\n  reducers?:\n    | ActionReducerMap<T, V>\n    | InjectionToken<ActionReducerMap<T, V>>\n    | ActionReducer<T, V>\n    | InjectionToken<ActionReducer<T, V>>,\n  config: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>> = {}\n): EnvironmentProviders {\n  return makeEnvironmentProviders([\n    ..._provideState(featureNameOrSlice, reducers, config),\n    ENVIRONMENT_STATE_PROVIDER,\n  ]);\n}\n\nexport function _provideStore<T, V extends Action = Action>(\n  reducers:\n    | ActionReducerMap<T, V>\n    | InjectionToken<ActionReducerMap<T, V>>\n    | Record<string, never> = {},\n  config: RootStoreConfig<T, V> = {}\n): Provider[] {\n  return [\n    {\n      provide: _ROOT_STORE_GUARD,\n      useFactory: _provideForRootGuard,\n    },\n    { provide: _INITIAL_STATE, useValue: config.initialState },\n    {\n      provide: INITIAL_STATE,\n      useFactory: _initialStateFactory,\n      deps: [_INITIAL_STATE],\n    },\n    { provide: _INITIAL_REDUCERS, useValue: reducers },\n    {\n      provide: _STORE_REDUCERS,\n      useExisting:\n        reducers instanceof InjectionToken ? reducers : _INITIAL_REDUCERS,\n    },\n    {\n      provide: INITIAL_REDUCERS,\n      deps: [_INITIAL_REDUCERS, [new Inject(_STORE_REDUCERS)]],\n      useFactory: _createStoreReducers,\n    },\n    {\n      provide: USER_PROVIDED_META_REDUCERS,\n      useValue: config.metaReducers ? config.metaReducers : [],\n    },\n    {\n      provide: _RESOLVED_META_REDUCERS,\n      deps: [META_REDUCERS, USER_PROVIDED_META_REDUCERS],\n      useFactory: _concatMetaReducers,\n    },\n    {\n      provide: _REDUCER_FACTORY,\n      useValue: config.reducerFactory ? config.reducerFactory : combineReducers,\n    },\n    {\n      provide: REDUCER_FACTORY,\n      deps: [_REDUCER_FACTORY, _RESOLVED_META_REDUCERS],\n      useFactory: createReducerFactory,\n    },\n    ACTIONS_SUBJECT_PROVIDERS,\n    REDUCER_MANAGER_PROVIDERS,\n    SCANNED_ACTIONS_SUBJECT_PROVIDERS,\n    STATE_PROVIDERS,\n    STORE_PROVIDERS,\n    provideRuntimeChecks(config.runtimeChecks),\n    checkForActionTypeUniqueness(),\n  ];\n}\n\nfunction rootStoreProviderFactory(): void {\n  inject(ActionsSubject);\n  inject(ReducerObservable);\n  inject(ScannedActionsSubject);\n  inject(Store);\n  inject(_ROOT_STORE_GUARD, { optional: true });\n  inject(_ACTION_TYPE_UNIQUENESS_CHECK, { optional: true });\n}\n\n/**\n * Environment Initializer used in the root\n * providers to initialize the Store\n */\nconst ENVIRONMENT_STORE_PROVIDER: Array<Provider | EnvironmentProviders> = [\n  { provide: ROOT_STORE_PROVIDER, useFactory: rootStoreProviderFactory },\n  provideEnvironmentInitializer(() => inject(ROOT_STORE_PROVIDER)),\n];\n\n/**\n * Provides the global Store providers and initializes\n * the Store.\n * These providers cannot be used at the component level.\n *\n * @usageNotes\n *\n * ### Providing the Global Store\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n *   providers: [provideStore()],\n * });\n * ```\n */\nexport function provideStore<T, V extends Action = Action>(\n  reducers?: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>,\n  config?: RootStoreConfig<T, V>\n): EnvironmentProviders {\n  return makeEnvironmentProviders([\n    ..._provideStore(reducers, config),\n    ENVIRONMENT_STORE_PROVIDER,\n  ]);\n}\n\nfunction featureStateProviderFactory(): void {\n  inject(ROOT_STORE_PROVIDER);\n  const features = inject<StoreFeature<any, any>[]>(_STORE_FEATURES);\n  const featureReducers = inject<ActionReducerMap<any>[]>(FEATURE_REDUCERS);\n  const reducerManager = inject(ReducerManager);\n  inject(_ACTION_TYPE_UNIQUENESS_CHECK, { optional: true });\n\n  const feats = features.map((feature, index) => {\n    const featureReducerCollection = featureReducers.shift();\n    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n    const reducers = featureReducerCollection! /*TODO(#823)*/[index];\n\n    return {\n      ...feature,\n      reducers,\n      initialState: _initialStateFactory(feature.initialState),\n    };\n  });\n\n  reducerManager.addFeatures(feats);\n}\n\n/**\n * Environment Initializer used in the feature\n * providers to register state features\n */\nconst ENVIRONMENT_STATE_PROVIDER: Array<Provider | EnvironmentProviders> = [\n  {\n    provide: FEATURE_STATE_PROVIDER,\n    useFactory: featureStateProviderFactory,\n  },\n  provideEnvironmentInitializer(() => inject(FEATURE_STATE_PROVIDER)),\n];\n\nexport function _provideState<T, V extends Action = Action>(\n  featureNameOrSlice: string | FeatureSlice<T, V>,\n  reducers?:\n    | ActionReducerMap<T, V>\n    | InjectionToken<ActionReducerMap<T, V>>\n    | ActionReducer<T, V>\n    | InjectionToken<ActionReducer<T, V>>,\n  config: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>> = {}\n): Provider[] {\n  return [\n    {\n      provide: _FEATURE_CONFIGS,\n      multi: true,\n      useValue: featureNameOrSlice instanceof Object ? {} : config,\n    },\n    {\n      provide: STORE_FEATURES,\n      multi: true,\n      useValue: {\n        key:\n          featureNameOrSlice instanceof Object\n            ? featureNameOrSlice.name\n            : featureNameOrSlice,\n        reducerFactory:\n          !(config instanceof InjectionToken) && config.reducerFactory\n            ? config.reducerFactory\n            : combineReducers,\n        metaReducers:\n          !(config instanceof InjectionToken) && config.metaReducers\n            ? config.metaReducers\n            : [],\n        initialState:\n          !(config instanceof InjectionToken) && config.initialState\n            ? config.initialState\n            : undefined,\n      },\n    },\n    {\n      provide: _STORE_FEATURES,\n      deps: [_FEATURE_CONFIGS, STORE_FEATURES],\n      useFactory: _createFeatureStore,\n    },\n    {\n      provide: _FEATURE_REDUCERS,\n      multi: true,\n      useValue:\n        featureNameOrSlice instanceof Object\n          ? featureNameOrSlice.reducer\n          : reducers,\n    },\n    {\n      provide: _FEATURE_REDUCERS_TOKEN,\n      multi: true,\n      useExisting:\n        reducers instanceof InjectionToken ? reducers : _FEATURE_REDUCERS,\n    },\n    {\n      provide: FEATURE_REDUCERS,\n      multi: true,\n      deps: [_FEATURE_REDUCERS, [new Inject(_FEATURE_REDUCERS_TOKEN)]],\n      useFactory: _createFeatureReducers,\n    },\n    checkForActionTypeUniqueness(),\n  ];\n}\n"
  },
  {
    "path": "modules/store/src/reducer_creator.ts",
    "content": "import { ActionCreator, ActionReducer, ActionType, Action } from './models';\n\n// Goes over the array of ActionCreators, pulls the action type out of each one\n// and returns the array of these action types.\ntype ExtractActionTypes<Creators extends readonly ActionCreator[]> = {\n  [Key in keyof Creators]: Creators[Key] extends ActionCreator<infer T>\n    ? T\n    : never;\n};\n\n/**\n * Return type of the `on` fn.\n * Contains the action reducer coupled to one or more action types.\n */\nexport interface ReducerTypes<\n  State,\n  Creators extends readonly ActionCreator[],\n> {\n  reducer: OnReducer<State, Creators>;\n  types: ExtractActionTypes<Creators>;\n}\n\n/**\n *  Specialized Reducer that is aware of the Action type it needs to handle\n */\nexport interface OnReducer<\n  // State type that is being passed from consumer of `on` fn, e.g. from `createReducer` factory\n  State,\n  Creators extends readonly ActionCreator[],\n  // Inferred type from within OnReducer function if `State` is unknown\n  InferredState = State,\n  // Resulting state would be either a State or if State is unknown then the inferred state from the function itself\n  ResultState = unknown extends State ? InferredState : State,\n> {\n  (\n    // if State is unknown then set the InferredState type\n    state: unknown extends State ? InferredState : State,\n    action: ActionType<Creators[number]>\n  ): ResultState;\n}\n\n/**\n * @description\n * Associates actions with a given state change function.\n * A state change function must be provided as the last parameter.\n *\n * @param args `ActionCreator`'s followed by a state change function.\n *\n * @returns an association of action types with a state change function.\n *\n * @usageNotes\n * ```ts\n * on(AuthApiActions.loginSuccess, (state, { user }) => ({ ...state, user }))\n * ```\n */\nexport function on<\n  // State type that is being passed from `createReducer` when created within that factory function\n  State,\n  // Action creators\n  Creators extends readonly ActionCreator[],\n  // Inferred type from within OnReducer function if `State` is unknown. This is typically the case when `on` function\n  // is created outside of `createReducer` and state type is either explicitly set OR inferred by return type.\n  // For example: `const onFn = on(action, (state: State, {prop}) => ({ ...state, name: prop }));`\n  InferredState = State,\n>(\n  ...args: [\n    ...creators: Creators,\n    reducer: OnReducer<\n      State extends infer S ? S : never,\n      Creators,\n      InferredState\n    >,\n  ]\n): ReducerTypes<unknown extends State ? InferredState : State, Creators> {\n  const reducer = args.pop() as unknown as OnReducer<\n    unknown extends State ? InferredState : State,\n    Creators\n  >;\n  const types = (args as unknown as Creators).map(\n    (creator) => creator.type\n  ) as unknown as ExtractActionTypes<Creators>;\n  return { reducer, types };\n}\n\n/**\n * @description\n * Creates a reducer function to handle state transitions.\n *\n * Reducer creators reduce the explicitness of reducer functions with switch statements.\n *\n * @param initialState Provides a state value if the current state is `undefined`, as it is initially.\n * @param ons Associations between actions and state changes.\n * @returns A reducer function.\n *\n * @usageNotes\n *\n * - Must be used with `ActionCreator`'s (returned by `createAction`). Cannot be used with class-based action creators.\n * - The returned `ActionReducer` does not require being wrapped with another function.\n *\n * **Declaring a reducer creator**\n *\n * ```ts\n * export const reducer = createReducer(\n *   initialState,\n *   on(\n *     featureActions.actionOne,\n *     featureActions.actionTwo,\n *     (state, { updatedValue }) => ({ ...state, prop: updatedValue })\n *   ),\n *   on(featureActions.actionThree, () => initialState);\n * );\n * ```\n */\nexport function createReducer<\n  S,\n  A extends Action = Action,\n  // Additional generic for the return type is introduced to enable correct\n  // type inference when `createReducer` is used within `createFeature`.\n  // For more info see: https://github.com/microsoft/TypeScript/issues/52114\n  R extends ActionReducer<S, A> = ActionReducer<S, A>,\n>(initialState: S, ...ons: ReducerTypes<S, readonly ActionCreator[]>[]): R {\n  const map = new Map<string, OnReducer<S, ActionCreator[]>>();\n  for (const on of ons) {\n    for (const type of on.types) {\n      const existingReducer = map.get(type);\n      if (existingReducer) {\n        const newReducer: typeof existingReducer = (state, action) =>\n          on.reducer(existingReducer(state, action), action);\n        map.set(type, newReducer);\n      } else {\n        map.set(type, on.reducer);\n      }\n    }\n  }\n\n  return function (state: S = initialState, action: A): S {\n    const reducer = map.get(action.type);\n    return reducer ? reducer(state, action) : state;\n  } as R;\n}\n"
  },
  {
    "path": "modules/store/src/reducer_manager.ts",
    "content": "import { Inject, Injectable, OnDestroy, Provider } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\nimport { ActionsSubject } from './actions_subject';\nimport {\n  Action,\n  ActionReducer,\n  ActionReducerFactory,\n  ActionReducerMap,\n  StoreFeature,\n} from './models';\nimport { INITIAL_REDUCERS, INITIAL_STATE, REDUCER_FACTORY } from './tokens';\nimport {\n  createFeatureReducerFactory,\n  createReducerFactory,\n  omit,\n} from './utils';\n\nexport abstract class ReducerObservable extends Observable<\n  ActionReducer<any, any>\n> {}\nexport abstract class ReducerManagerDispatcher extends ActionsSubject {}\nexport const UPDATE = '@ngrx/store/update-reducers' as const;\n\n@Injectable()\nexport class ReducerManager\n  extends BehaviorSubject<ActionReducer<any, any>>\n  implements OnDestroy\n{\n  get currentReducers(): ActionReducerMap<any, any> {\n    return this.reducers;\n  }\n\n  constructor(\n    private dispatcher: ReducerManagerDispatcher,\n    @Inject(INITIAL_STATE) private initialState: any,\n    @Inject(INITIAL_REDUCERS) private reducers: ActionReducerMap<any, any>,\n    @Inject(REDUCER_FACTORY)\n    private reducerFactory: ActionReducerFactory<any, any>\n  ) {\n    super(reducerFactory(reducers, initialState));\n  }\n\n  addFeature(feature: StoreFeature<any, any>) {\n    this.addFeatures([feature]);\n  }\n\n  addFeatures(features: StoreFeature<any, any>[]) {\n    const reducers = features.reduce(\n      (\n        reducerDict,\n        { reducers, reducerFactory, metaReducers, initialState, key }\n      ) => {\n        const reducer =\n          typeof reducers === 'function'\n            ? createFeatureReducerFactory(metaReducers)(reducers, initialState)\n            : createReducerFactory(reducerFactory, metaReducers)(\n                reducers,\n                initialState\n              );\n\n        reducerDict[key] = reducer;\n        return reducerDict;\n      },\n      {} as { [key: string]: ActionReducer<any, any> }\n    );\n\n    this.addReducers(reducers);\n  }\n\n  removeFeature(feature: StoreFeature<any, any>) {\n    this.removeFeatures([feature]);\n  }\n\n  removeFeatures(features: StoreFeature<any, any>[]) {\n    this.removeReducers(features.map((p) => p.key));\n  }\n\n  addReducer(key: string, reducer: ActionReducer<any, any>) {\n    this.addReducers({ [key]: reducer });\n  }\n\n  addReducers(reducers: { [key: string]: ActionReducer<any, any> }) {\n    this.reducers = { ...this.reducers, ...reducers };\n    this.updateReducers(Object.keys(reducers));\n  }\n\n  removeReducer(featureKey: string) {\n    this.removeReducers([featureKey]);\n  }\n\n  removeReducers(featureKeys: string[]) {\n    featureKeys.forEach((key) => {\n      this.reducers = omit(this.reducers, key) /*TODO(#823)*/ as any;\n    });\n    this.updateReducers(featureKeys);\n  }\n\n  private updateReducers(featureKeys: string[]) {\n    this.next(this.reducerFactory(this.reducers, this.initialState));\n    this.dispatcher.next(<Action>{\n      type: UPDATE,\n      features: featureKeys,\n    });\n  }\n\n  ngOnDestroy() {\n    this.complete();\n  }\n}\n\nexport const REDUCER_MANAGER_PROVIDERS: Provider[] = [\n  ReducerManager,\n  { provide: ReducerObservable, useExisting: ReducerManager },\n  { provide: ReducerManagerDispatcher, useExisting: ActionsSubject },\n];\n"
  },
  {
    "path": "modules/store/src/runtime_checks.ts",
    "content": "import { isDevMode, Provider } from '@angular/core';\nimport {\n  serializationCheckMetaReducer,\n  immutabilityCheckMetaReducer,\n  inNgZoneAssertMetaReducer,\n} from './meta-reducers';\nimport { RuntimeChecks, MetaReducer, Action } from './models';\nimport {\n  _USER_RUNTIME_CHECKS,\n  ACTIVE_RUNTIME_CHECKS,\n  META_REDUCERS,\n  USER_RUNTIME_CHECKS,\n  _ACTION_TYPE_UNIQUENESS_CHECK,\n} from './tokens';\nimport { REGISTERED_ACTION_TYPES } from './globals';\nimport { RUNTIME_CHECK_URL } from './meta-reducers/utils';\n\nexport function createActiveRuntimeChecks(\n  runtimeChecks?: Partial<RuntimeChecks>\n): RuntimeChecks {\n  if (isDevMode()) {\n    return {\n      strictStateSerializability: false,\n      strictActionSerializability: false,\n      strictStateImmutability: true,\n      strictActionImmutability: true,\n      strictActionWithinNgZone: false,\n      strictActionTypeUniqueness: false,\n      ...runtimeChecks,\n    };\n  }\n\n  return {\n    strictStateSerializability: false,\n    strictActionSerializability: false,\n    strictStateImmutability: false,\n    strictActionImmutability: false,\n    strictActionWithinNgZone: false,\n    strictActionTypeUniqueness: false,\n  };\n}\n\nexport function createSerializationCheckMetaReducer({\n  strictActionSerializability,\n  strictStateSerializability,\n}: RuntimeChecks): MetaReducer {\n  return (reducer) =>\n    strictActionSerializability || strictStateSerializability\n      ? serializationCheckMetaReducer(reducer, {\n          action: (action) =>\n            strictActionSerializability && !ignoreNgrxAction(action),\n          state: () => strictStateSerializability,\n        })\n      : reducer;\n}\n\nexport function createImmutabilityCheckMetaReducer({\n  strictActionImmutability,\n  strictStateImmutability,\n}: RuntimeChecks): MetaReducer {\n  return (reducer) =>\n    strictActionImmutability || strictStateImmutability\n      ? immutabilityCheckMetaReducer(reducer, {\n          action: (action) =>\n            strictActionImmutability && !ignoreNgrxAction(action),\n          state: () => strictStateImmutability,\n        })\n      : reducer;\n}\n\nfunction ignoreNgrxAction(action: Action) {\n  return action.type.startsWith('@ngrx');\n}\n\nexport function createInNgZoneCheckMetaReducer({\n  strictActionWithinNgZone,\n}: RuntimeChecks): MetaReducer {\n  return (reducer) =>\n    strictActionWithinNgZone\n      ? inNgZoneAssertMetaReducer(reducer, {\n          action: (action) =>\n            strictActionWithinNgZone && !ignoreNgrxAction(action),\n        })\n      : reducer;\n}\n\nexport function provideRuntimeChecks(\n  runtimeChecks?: Partial<RuntimeChecks>\n): Provider[] {\n  return [\n    {\n      provide: _USER_RUNTIME_CHECKS,\n      useValue: runtimeChecks,\n    },\n    {\n      provide: USER_RUNTIME_CHECKS,\n      useFactory: _runtimeChecksFactory,\n      deps: [_USER_RUNTIME_CHECKS],\n    },\n    {\n      provide: ACTIVE_RUNTIME_CHECKS,\n      deps: [USER_RUNTIME_CHECKS],\n      useFactory: createActiveRuntimeChecks,\n    },\n    {\n      provide: META_REDUCERS,\n      multi: true,\n      deps: [ACTIVE_RUNTIME_CHECKS],\n      useFactory: createImmutabilityCheckMetaReducer,\n    },\n    {\n      provide: META_REDUCERS,\n      multi: true,\n      deps: [ACTIVE_RUNTIME_CHECKS],\n      useFactory: createSerializationCheckMetaReducer,\n    },\n    {\n      provide: META_REDUCERS,\n      multi: true,\n      deps: [ACTIVE_RUNTIME_CHECKS],\n      useFactory: createInNgZoneCheckMetaReducer,\n    },\n  ];\n}\n\nexport function checkForActionTypeUniqueness(): Provider[] {\n  return [\n    {\n      provide: _ACTION_TYPE_UNIQUENESS_CHECK,\n      multi: true,\n      deps: [ACTIVE_RUNTIME_CHECKS],\n      useFactory: _actionTypeUniquenessCheck,\n    },\n  ];\n}\n\nexport function _runtimeChecksFactory(\n  runtimeChecks: RuntimeChecks\n): RuntimeChecks {\n  return runtimeChecks;\n}\n\nexport function _actionTypeUniquenessCheck(config: RuntimeChecks): void {\n  if (!config.strictActionTypeUniqueness) {\n    return;\n  }\n\n  const duplicates = Object.entries(REGISTERED_ACTION_TYPES)\n    .filter(([, registrations]) => registrations > 1)\n    .map(([type]) => type);\n\n  if (duplicates.length) {\n    throw new Error(\n      `Action types are registered more than once, ${duplicates\n        .map((type) => `\"${type}\"`)\n        .join(', ')}. ${RUNTIME_CHECK_URL}#strictactiontypeuniqueness`\n    );\n  }\n}\n"
  },
  {
    "path": "modules/store/src/scanned_actions_subject.ts",
    "content": "import { Injectable, OnDestroy, Provider } from '@angular/core';\nimport { Subject } from 'rxjs';\n\nimport { Action } from './models';\n\n@Injectable()\nexport class ScannedActionsSubject\n  extends Subject<Action>\n  implements OnDestroy\n{\n  ngOnDestroy() {\n    this.complete();\n  }\n}\n\nexport const SCANNED_ACTIONS_SUBJECT_PROVIDERS: Provider[] = [\n  ScannedActionsSubject,\n];\n"
  },
  {
    "path": "modules/store/src/selector.ts",
    "content": "import { Selector, SelectorWithProps } from './models';\nimport { isDevMode } from '@angular/core';\nimport { isNgrxMockEnvironment } from './flags';\n\nexport type AnyFn = (...args: any[]) => any;\n\nexport type MemoizedProjection = {\n  memoized: AnyFn;\n  reset: () => void;\n  setResult: (result?: any) => void;\n  clearResult: () => void;\n};\n\nexport type MemoizeFn = (t: AnyFn) => MemoizedProjection;\n\nexport type ComparatorFn = (a: any, b: any) => boolean;\n\nexport type DefaultProjectorFn<T> = (...args: any[]) => T;\n\nexport interface MemoizedSelector<\n  State,\n  Result,\n  ProjectorFn = DefaultProjectorFn<Result>,\n> extends Selector<State, Result> {\n  release(): void;\n  projector: ProjectorFn;\n  setResult: (result?: Result) => void;\n  clearResult: () => void;\n}\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see the {@link https://ngrx.io/guide/migration/v12#ngrxstore migration guide}\n */\nexport interface MemoizedSelectorWithProps<\n  State,\n  Props,\n  Result,\n  ProjectorFn = DefaultProjectorFn<Result>,\n> extends SelectorWithProps<State, Props, Result> {\n  release(): void;\n  projector: ProjectorFn;\n  setResult: (result?: Result) => void;\n  clearResult: () => void;\n}\n\nexport function isEqualCheck(a: any, b: any): boolean {\n  return a === b;\n}\n\nfunction isArgumentsChanged(\n  args: IArguments,\n  lastArguments: IArguments,\n  comparator: ComparatorFn\n) {\n  for (let i = 0; i < args.length; i++) {\n    if (!comparator(args[i], lastArguments[i])) {\n      return true;\n    }\n  }\n  return false;\n}\n\nexport function resultMemoize(\n  projectionFn: AnyFn,\n  isResultEqual: ComparatorFn\n) {\n  return defaultMemoize(projectionFn, isEqualCheck, isResultEqual);\n}\n\nexport function defaultMemoize(\n  projectionFn: AnyFn,\n  isArgumentsEqual = isEqualCheck,\n  isResultEqual = isEqualCheck\n): MemoizedProjection {\n  let lastArguments: null | IArguments = null;\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  let lastResult: any = null;\n  let overrideResult: any;\n\n  function reset() {\n    lastArguments = null;\n    lastResult = null;\n  }\n\n  function setResult(result: any = undefined) {\n    overrideResult = { result };\n  }\n\n  function clearResult() {\n    overrideResult = undefined;\n  }\n\n  /* eslint-disable prefer-rest-params, prefer-spread */\n\n  // disabled because of the use of `arguments`\n  function memoized(): any {\n    if (overrideResult !== undefined) {\n      return overrideResult.result;\n    }\n\n    if (!lastArguments) {\n      lastResult = projectionFn.apply(null, arguments as any);\n      lastArguments = arguments;\n      return lastResult;\n    }\n\n    if (!isArgumentsChanged(arguments, lastArguments, isArgumentsEqual)) {\n      return lastResult;\n    }\n\n    const newResult = projectionFn.apply(null, arguments as any);\n    lastArguments = arguments;\n\n    if (isResultEqual(lastResult, newResult)) {\n      return lastResult;\n    }\n\n    lastResult = newResult;\n\n    return newResult;\n  }\n\n  return { memoized, reset, setResult, clearResult };\n}\n\nexport function createSelector<State, S1, Result>(\n  s1: Selector<State, S1>,\n  projector: (s1: S1) => Result\n): MemoizedSelector<State, Result, typeof projector>;\nexport function createSelector<State, S1, S2, Result>(\n  s1: Selector<State, S1>,\n  s2: Selector<State, S2>,\n  projector: (s1: S1, s2: S2) => Result\n): MemoizedSelector<State, Result, typeof projector>;\nexport function createSelector<State, S1, S2, S3, Result>(\n  s1: Selector<State, S1>,\n  s2: Selector<State, S2>,\n  s3: Selector<State, S3>,\n  projector: (s1: S1, s2: S2, s3: S3) => Result\n): MemoizedSelector<State, Result, typeof projector>;\nexport function createSelector<State, S1, S2, S3, S4, Result>(\n  s1: Selector<State, S1>,\n  s2: Selector<State, S2>,\n  s3: Selector<State, S3>,\n  s4: Selector<State, S4>,\n  projector: (s1: S1, s2: S2, s3: S3, s4: S4) => Result\n): MemoizedSelector<State, Result, typeof projector>;\nexport function createSelector<State, S1, S2, S3, S4, S5, Result>(\n  s1: Selector<State, S1>,\n  s2: Selector<State, S2>,\n  s3: Selector<State, S3>,\n  s4: Selector<State, S4>,\n  s5: Selector<State, S5>,\n  projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5) => Result\n): MemoizedSelector<State, Result, typeof projector>;\nexport function createSelector<State, S1, S2, S3, S4, S5, S6, Result>(\n  s1: Selector<State, S1>,\n  s2: Selector<State, S2>,\n  s3: Selector<State, S3>,\n  s4: Selector<State, S4>,\n  s5: Selector<State, S5>,\n  s6: Selector<State, S6>,\n  projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6) => Result\n): MemoizedSelector<State, Result, typeof projector>;\nexport function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(\n  s1: Selector<State, S1>,\n  s2: Selector<State, S2>,\n  s3: Selector<State, S3>,\n  s4: Selector<State, S4>,\n  s5: Selector<State, S5>,\n  s6: Selector<State, S6>,\n  s7: Selector<State, S7>,\n  projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7) => Result\n): MemoizedSelector<State, Result, typeof projector>;\nexport function createSelector<State, S1, S2, S3, S4, S5, S6, S7, S8, Result>(\n  s1: Selector<State, S1>,\n  s2: Selector<State, S2>,\n  s3: Selector<State, S3>,\n  s4: Selector<State, S4>,\n  s5: Selector<State, S5>,\n  s6: Selector<State, S6>,\n  s7: Selector<State, S7>,\n  s8: Selector<State, S8>,\n  projector: (\n    s1: S1,\n    s2: S2,\n    s3: S3,\n    s4: S4,\n    s5: S5,\n    s6: S6,\n    s7: S7,\n    s8: S8\n  ) => Result\n): MemoizedSelector<State, Result, typeof projector>;\n\nexport function createSelector<\n  Selectors extends Record<string, Selector<State, unknown>>,\n  State = Selectors extends Record<string, Selector<infer S, unknown>>\n    ? S\n    : never,\n  Result extends Record<string, unknown> = {\n    [Key in keyof Selectors]: Selectors[Key] extends Selector<State, infer R>\n      ? R\n      : never;\n  },\n>(selectors: Selectors): MemoizedSelector<State, Result, never>;\n\nexport function createSelector<State, Slices extends unknown[], Result>(\n  ...args: [...slices: Selector<State, unknown>[], projector: unknown] &\n    [\n      ...slices: { [i in keyof Slices]: Selector<State, Slices[i]> },\n      projector: (...s: Slices) => Result,\n    ]\n): MemoizedSelector<State, Result, (...s: Slices) => Result>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, Result>(\n  s1: SelectorWithProps<State, Props, S1>,\n  projector: (s1: S1, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, Result>(\n  s1: SelectorWithProps<State, Props, S1>,\n  s2: SelectorWithProps<State, Props, S2>,\n  projector: (s1: S1, s2: S2, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, S3, Result>(\n  s1: SelectorWithProps<State, Props, S1>,\n  s2: SelectorWithProps<State, Props, S2>,\n  s3: SelectorWithProps<State, Props, S3>,\n  projector: (s1: S1, s2: S2, s3: S3, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, S3, S4, Result>(\n  s1: SelectorWithProps<State, Props, S1>,\n  s2: SelectorWithProps<State, Props, S2>,\n  s3: SelectorWithProps<State, Props, S3>,\n  s4: SelectorWithProps<State, Props, S4>,\n  projector: (s1: S1, s2: S2, s3: S3, s4: S4, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, S3, S4, S5, Result>(\n  s1: SelectorWithProps<State, Props, S1>,\n  s2: SelectorWithProps<State, Props, S2>,\n  s3: SelectorWithProps<State, Props, S3>,\n  s4: SelectorWithProps<State, Props, S4>,\n  s5: SelectorWithProps<State, Props, S5>,\n  projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, S3, S4, S5, S6, Result>(\n  s1: SelectorWithProps<State, Props, S1>,\n  s2: SelectorWithProps<State, Props, S2>,\n  s3: SelectorWithProps<State, Props, S3>,\n  s4: SelectorWithProps<State, Props, S4>,\n  s5: SelectorWithProps<State, Props, S5>,\n  s6: SelectorWithProps<State, Props, S6>,\n  projector: (\n    s1: S1,\n    s2: S2,\n    s3: S3,\n    s4: S4,\n    s5: S5,\n    s6: S6,\n    props: Props\n  ) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<\n  State,\n  Props,\n  S1,\n  S2,\n  S3,\n  S4,\n  S5,\n  S6,\n  S7,\n  Result,\n>(\n  s1: SelectorWithProps<State, Props, S1>,\n  s2: SelectorWithProps<State, Props, S2>,\n  s3: SelectorWithProps<State, Props, S3>,\n  s4: SelectorWithProps<State, Props, S4>,\n  s5: SelectorWithProps<State, Props, S5>,\n  s6: SelectorWithProps<State, Props, S6>,\n  s7: SelectorWithProps<State, Props, S7>,\n  projector: (\n    s1: S1,\n    s2: S2,\n    s3: S3,\n    s4: S4,\n    s5: S5,\n    s6: S6,\n    s7: S7,\n    props: Props\n  ) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<\n  State,\n  Props,\n  S1,\n  S2,\n  S3,\n  S4,\n  S5,\n  S6,\n  S7,\n  S8,\n  Result,\n>(\n  s1: SelectorWithProps<State, Props, S1>,\n  s2: SelectorWithProps<State, Props, S2>,\n  s3: SelectorWithProps<State, Props, S3>,\n  s4: SelectorWithProps<State, Props, S4>,\n  s5: SelectorWithProps<State, Props, S5>,\n  s6: SelectorWithProps<State, Props, S6>,\n  s7: SelectorWithProps<State, Props, S7>,\n  s8: SelectorWithProps<State, Props, S8>,\n  projector: (\n    s1: S1,\n    s2: S2,\n    s3: S3,\n    s4: S4,\n    s5: S5,\n    s6: S6,\n    s7: S7,\n    s8: S8,\n    props: Props\n  ) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\nexport function createSelector<State, Slices extends unknown[], Result>(\n  selectors: Selector<State, unknown>[] &\n    [...{ [i in keyof Slices]: Selector<State, Slices[i]> }],\n  projector: (...s: Slices) => Result\n): MemoizedSelector<State, Result, (...s: Slices) => Result>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, Result>(\n  selectors: [SelectorWithProps<State, Props, S1>],\n  projector: (s1: S1, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, Result>(\n  selectors: [\n    SelectorWithProps<State, Props, S1>,\n    SelectorWithProps<State, Props, S2>,\n  ],\n  projector: (s1: S1, s2: S2, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, S3, Result>(\n  selectors: [\n    SelectorWithProps<State, Props, S1>,\n    SelectorWithProps<State, Props, S2>,\n    SelectorWithProps<State, Props, S3>,\n  ],\n  projector: (s1: S1, s2: S2, s3: S3, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, S3, S4, Result>(\n  selectors: [\n    SelectorWithProps<State, Props, S1>,\n    SelectorWithProps<State, Props, S2>,\n    SelectorWithProps<State, Props, S3>,\n    SelectorWithProps<State, Props, S4>,\n  ],\n  projector: (s1: S1, s2: S2, s3: S3, s4: S4, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, S3, S4, S5, Result>(\n  selectors: [\n    SelectorWithProps<State, Props, S1>,\n    SelectorWithProps<State, Props, S2>,\n    SelectorWithProps<State, Props, S3>,\n    SelectorWithProps<State, Props, S4>,\n    SelectorWithProps<State, Props, S5>,\n  ],\n  projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, props: Props) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<State, Props, S1, S2, S3, S4, S5, S6, Result>(\n  selectors: [\n    SelectorWithProps<State, Props, S1>,\n    SelectorWithProps<State, Props, S2>,\n    SelectorWithProps<State, Props, S3>,\n    SelectorWithProps<State, Props, S4>,\n    SelectorWithProps<State, Props, S5>,\n    SelectorWithProps<State, Props, S6>,\n  ],\n  projector: (\n    s1: S1,\n    s2: S2,\n    s3: S3,\n    s4: S4,\n    s5: S5,\n    s6: S6,\n    props: Props\n  ) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<\n  State,\n  Props,\n  S1,\n  S2,\n  S3,\n  S4,\n  S5,\n  S6,\n  S7,\n  Result,\n>(\n  selectors: [\n    SelectorWithProps<State, Props, S1>,\n    SelectorWithProps<State, Props, S2>,\n    SelectorWithProps<State, Props, S3>,\n    SelectorWithProps<State, Props, S4>,\n    SelectorWithProps<State, Props, S5>,\n    SelectorWithProps<State, Props, S6>,\n    SelectorWithProps<State, Props, S7>,\n  ],\n  projector: (\n    s1: S1,\n    s2: S2,\n    s3: S3,\n    s4: S4,\n    s5: S5,\n    s6: S6,\n    s7: S7,\n    props: Props\n  ) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelector<\n  State,\n  Props,\n  S1,\n  S2,\n  S3,\n  S4,\n  S5,\n  S6,\n  S7,\n  S8,\n  Result,\n>(\n  selectors: [\n    SelectorWithProps<State, Props, S1>,\n    SelectorWithProps<State, Props, S2>,\n    SelectorWithProps<State, Props, S3>,\n    SelectorWithProps<State, Props, S4>,\n    SelectorWithProps<State, Props, S5>,\n    SelectorWithProps<State, Props, S6>,\n    SelectorWithProps<State, Props, S7>,\n    SelectorWithProps<State, Props, S8>,\n  ],\n  projector: (\n    s1: S1,\n    s2: S2,\n    s3: S3,\n    s4: S4,\n    s5: S5,\n    s6: S6,\n    s7: S7,\n    s8: S8,\n    props: Props\n  ) => Result\n): MemoizedSelectorWithProps<State, Props, Result, typeof projector>;\n\nexport function createSelector(\n  ...input: any[]\n): MemoizedSelector<any, any> | MemoizedSelectorWithProps<any, any, any> {\n  return createSelectorFactory(defaultMemoize)(...input);\n}\n\nexport function defaultStateFn(\n  state: any,\n  selectors: Selector<any, any>[] | SelectorWithProps<any, any, any>[],\n  props: any,\n  memoizedProjector: MemoizedProjection\n): any {\n  if (props === undefined) {\n    const args = (<Selector<any, any>[]>selectors).map((fn) => fn(state));\n    return memoizedProjector.memoized.apply(null, args);\n  }\n\n  const args = (<SelectorWithProps<any, any, any>[]>selectors).map((fn) =>\n    fn(state, props)\n  );\n  return memoizedProjector.memoized.apply(null, [...args, props]);\n}\n\nexport type SelectorFactoryConfig<T = any, V = any> = {\n  stateFn: (\n    state: T,\n    selectors: Selector<any, any>[],\n    props: any,\n    memoizedProjector: MemoizedProjection\n  ) => V;\n};\n\nexport function createSelectorFactory<T = any, V = any>(\n  memoize: MemoizeFn\n): (...input: any[]) => MemoizedSelector<T, V>;\nexport function createSelectorFactory<T = any, V = any>(\n  memoize: MemoizeFn,\n  options: SelectorFactoryConfig<T, V>\n): (...input: any[]) => MemoizedSelector<T, V>;\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelectorFactory<T = any, Props = any, V = any>(\n  memoize: MemoizeFn\n): (...input: any[]) => MemoizedSelectorWithProps<T, Props, V>;\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function createSelectorFactory<T = any, Props = any, V = any>(\n  memoize: MemoizeFn,\n  options: SelectorFactoryConfig<T, V>\n): (...input: any[]) => MemoizedSelectorWithProps<T, Props, V>;\n/**\n *\n * @param memoize The function used to memoize selectors\n * @param options Config Object that may include a `stateFn` function defining how to return the selector's value, given the entire `Store`'s state, parent `Selector`s, `Props`, and a `MemoizedProjection`\n *\n * @usageNotes\n *\n * **Creating a Selector Factory Where Array Order Does Not Matter**\n *\n * ```ts\n * function removeMatch(arr: string[], target: string): string[] {\n *   const matchIndex = arr.indexOf(target);\n *   return [...arr.slice(0, matchIndex), ...arr.slice(matchIndex + 1)];\n * }\n *\n * function orderDoesNotMatterComparer(a: any, b: any): boolean {\n *   if (!Array.isArray(a) || !Array.isArray(b)) {\n *     return a === b;\n *   }\n *   if (a.length !== b.length) {\n *     return false;\n *   }\n *   let tempB = [...b];\n *   function reduceToDetermineIfArraysContainSameContents(\n *     previousCallResult: boolean,\n *     arrayMember: any\n *   ): boolean {\n *     if (previousCallResult === false) {\n *       return false;\n *     }\n *     if (tempB.includes(arrayMember)) {\n *       tempB = removeMatch(tempB, arrayMember);\n *       return true;\n *     }\n *     return false;\n *   }\n *   return a.reduce(reduceToDetermineIfArraysContainSameContents, true);\n * }\n *\n * export const createOrderDoesNotMatterSelector = createSelectorFactory(\n *   (projectionFun) => defaultMemoize(\n *     projectionFun,\n *     orderDoesNotMatterComparer,\n *     orderDoesNotMatterComparer\n *   )\n * );\n * ```\n *\n * **Creating an Alternative Memoization Strategy**\n *\n * ```ts\n * function serialize(x: any): string {\n *   return JSON.stringify(x);\n * }\n *\n * export const createFullHistorySelector = createSelectorFactory(\n *  (projectionFunction) => {\n *    const cache = {};\n *\n *    function memoized() {\n *      const serializedArguments = serialize(...arguments);\n *       if (cache[serializedArguments] != null) {\n *         cache[serializedArguments] = projectionFunction.apply(null, arguments);\n *       }\n *       return cache[serializedArguments];\n *     }\n *     return {\n *       memoized,\n *       reset: () => {},\n *       setResult: () => {},\n *       clearResult: () => {},\n *     };\n *   }\n * );\n * ```\n */\nexport function createSelectorFactory(\n  memoize: MemoizeFn,\n  options: SelectorFactoryConfig<any, any> = {\n    stateFn: defaultStateFn,\n  }\n) {\n  return function (\n    ...input: any[]\n  ): MemoizedSelector<any, any> | MemoizedSelectorWithProps<any, any, any> {\n    let args = input;\n    if (Array.isArray(args[0])) {\n      const [head, ...tail] = args;\n      args = [...head, ...tail];\n    } else if (args.length === 1 && isSelectorsDictionary(args[0])) {\n      args = extractArgsFromSelectorsDictionary(args[0]);\n    }\n\n    const selectors = args.slice(0, args.length - 1);\n    const projector = args[args.length - 1];\n    const memoizedSelectors = selectors.filter(\n      (selector: any) =>\n        selector.release && typeof selector.release === 'function'\n    );\n\n    const memoizedProjector = memoize(function (...selectors: any[]) {\n      return projector.apply(null, selectors);\n    });\n\n    const memoizedState = defaultMemoize(function (state: any, props: any) {\n      return options.stateFn.apply(null, [\n        state,\n        selectors,\n        props,\n        memoizedProjector,\n      ]);\n    });\n\n    function release() {\n      memoizedState.reset();\n      memoizedProjector.reset();\n\n      memoizedSelectors.forEach((selector) => selector.release());\n    }\n\n    return Object.assign(memoizedState.memoized, {\n      release,\n      projector: memoizedProjector.memoized,\n      setResult: memoizedState.setResult,\n      clearResult: memoizedState.clearResult,\n    });\n  };\n}\n\nexport function createFeatureSelector<T>(\n  featureName: string\n): MemoizedSelector<object, T>;\n/**\n * @deprecated  Feature selectors with a root state are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/3179 Github Issue}\n */\nexport function createFeatureSelector<T, V>(\n  featureName: keyof T\n): MemoizedSelector<T, V>;\nexport function createFeatureSelector(\n  featureName: any\n): MemoizedSelector<any, any> {\n  return createSelector(\n    (state: any) => {\n      const featureState = state[featureName];\n      if (!isNgrxMockEnvironment() && isDevMode() && !(featureName in state)) {\n        console.warn(\n          `@ngrx/store: The feature name \"${featureName}\" does ` +\n            'not exist in the state, therefore createFeatureSelector ' +\n            'cannot access it.  Be sure it is imported in a loaded module ' +\n            `using StoreModule.forRoot('${featureName}', ...) or ` +\n            `StoreModule.forFeature('${featureName}', ...).  If the default ` +\n            'state is intended to be undefined, as is the case with router ' +\n            'state, this development-only warning message can be ignored.'\n        );\n      }\n      return featureState;\n    },\n    (featureState: any) => featureState\n  );\n}\n\nfunction isSelectorsDictionary(\n  selectors: unknown\n): selectors is Record<string, Selector<unknown, unknown>> {\n  return (\n    !!selectors &&\n    typeof selectors === 'object' &&\n    Object.values(selectors).every((selector) => typeof selector === 'function')\n  );\n}\n\nfunction extractArgsFromSelectorsDictionary(\n  selectorsDictionary: Record<string, Selector<unknown, unknown>>\n): [\n  ...selectors: Selector<unknown, unknown>[],\n  projector: (...selectorResults: unknown[]) => unknown,\n] {\n  const selectors = Object.values(selectorsDictionary);\n  const resultKeys = Object.keys(selectorsDictionary);\n  const projector = (...selectorResults: unknown[]) =>\n    resultKeys.reduce(\n      (result, key, index) => ({\n        ...result,\n        [key]: selectorResults[index],\n      }),\n      {}\n    );\n\n  return [...selectors, projector];\n}\n"
  },
  {
    "path": "modules/store/src/state.ts",
    "content": "import { Inject, Injectable, OnDestroy, Provider, Signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport {\n  BehaviorSubject,\n  Observable,\n  queueScheduler,\n  Subscription,\n} from 'rxjs';\nimport { observeOn, scan, withLatestFrom } from 'rxjs/operators';\n\nimport { ActionsSubject, INIT } from './actions_subject';\nimport { Action, ActionReducer } from './models';\nimport { ReducerObservable } from './reducer_manager';\nimport { ScannedActionsSubject } from './scanned_actions_subject';\nimport { INITIAL_STATE } from './tokens';\n\nexport abstract class StateObservable extends Observable<any> {\n  /**\n   * @internal\n   */\n  abstract readonly state: Signal<any>;\n}\n\n@Injectable()\nexport class State<T> extends BehaviorSubject<any> implements OnDestroy {\n  static readonly INIT = INIT;\n\n  private stateSubscription: Subscription;\n\n  /**\n   * @internal\n   */\n  public state: Signal<T>;\n\n  constructor(\n    actions$: ActionsSubject,\n    reducer$: ReducerObservable,\n    scannedActions: ScannedActionsSubject,\n    @Inject(INITIAL_STATE) initialState: any\n  ) {\n    super(initialState);\n\n    const actionsOnQueue$: Observable<Action> = actions$.pipe(\n      observeOn(queueScheduler)\n    );\n    const withLatestReducer$: Observable<[Action, ActionReducer<any, Action>]> =\n      actionsOnQueue$.pipe(withLatestFrom(reducer$));\n\n    const seed: StateActionPair<T> = { state: initialState };\n    const stateAndAction$: Observable<{\n      state: any;\n      action?: Action;\n    }> = withLatestReducer$.pipe(\n      scan<[Action, ActionReducer<T, Action>], StateActionPair<T>>(\n        reduceState,\n        seed\n      )\n    );\n\n    this.stateSubscription = stateAndAction$.subscribe(({ state, action }) => {\n      this.next(state);\n      scannedActions.next(action as Action);\n    });\n\n    this.state = toSignal(this, { manualCleanup: true, requireSync: true });\n  }\n\n  ngOnDestroy() {\n    this.stateSubscription.unsubscribe();\n    this.complete();\n  }\n}\n\nexport type StateActionPair<T, V extends Action = Action> = {\n  state: T | undefined;\n  action?: V;\n};\nexport function reduceState<T, V extends Action = Action>(\n  stateActionPair: StateActionPair<T, V> = { state: undefined },\n  [action, reducer]: [V, ActionReducer<T, V>]\n): StateActionPair<T, V> {\n  const { state } = stateActionPair;\n  return { state: reducer(state, action), action };\n}\n\nexport const STATE_PROVIDERS: Provider[] = [\n  State,\n  { provide: StateObservable, useExisting: State },\n];\n"
  },
  {
    "path": "modules/store/src/store.ts",
    "content": "// disabled because we have lowercase generics for `select`\nimport {\n  computed,\n  effect,\n  EffectRef,\n  inject,\n  Injectable,\n  Injector,\n  Provider,\n  Signal,\n  untracked,\n} from '@angular/core';\nimport { Observable, Observer, Operator } from 'rxjs';\nimport { distinctUntilChanged, map, pluck } from 'rxjs/operators';\n\nimport { ActionsSubject } from './actions_subject';\nimport {\n  Action,\n  ActionReducer,\n  CreatorsNotAllowedCheck,\n  SelectSignalOptions,\n} from './models';\nimport { ReducerManager } from './reducer_manager';\nimport { StateObservable } from './state';\nimport { assertDefined } from './helpers';\n\n@Injectable()\n/**\n * @description\n * Store is an injectable service that provides reactive state management and a public API for dispatching actions.\n *\n * @usageNotes\n *\n * In a component:\n *\n * ```ts\n * import { Component, inject } from '@angular/core';\n * import { Store } from '@ngrx/store';\n *\n * @Component({\n *  selector: 'app-my-component',\n *  template: `\n *    <div>{{ count() }}</div>\n *    <button (click)=\"increment()\">Increment</button>\n *  `\n * })\n * export class MyComponent {\n *   private store = inject(Store);\n *\n *   count = this.store.selectSignal(state => state.count);\n *\n *   increment() {\n *     this.store.dispatch({ type: 'INCREMENT' });\n *   }\n * }\n * ```\n *\n */\nexport class Store<T = object>\n  extends Observable<T>\n  implements Observer<Action>\n{\n  /**\n   * @internal\n   */\n  readonly state: Signal<T>;\n\n  constructor(\n    state$: StateObservable,\n    private actionsObserver: ActionsSubject,\n    private reducerManager: ReducerManager,\n    private injector?: Injector\n  ) {\n    super();\n\n    this.source = state$;\n    this.state = state$.state;\n  }\n\n  select<K>(mapFn: (state: T) => K): Observable<K>;\n  /**\n   * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n   */\n  select<K, Props = any>(\n    mapFn: (state: T, props: Props) => K,\n    props: Props\n  ): Observable<K>;\n  /**\n   * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n   */\n  select<a extends keyof T>(key: a): Observable<T[a]>;\n  /**\n   * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n   */\n  select<a extends keyof T, b extends keyof T[a]>(\n    key1: a,\n    key2: b\n  ): Observable<T[a][b]>;\n  /**\n   * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n   */\n  select<a extends keyof T, b extends keyof T[a], c extends keyof T[a][b]>(\n    key1: a,\n    key2: b,\n    key3: c\n  ): Observable<T[a][b][c]>;\n  /**\n   * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n   */\n  select<\n    a extends keyof T,\n    b extends keyof T[a],\n    c extends keyof T[a][b],\n    d extends keyof T[a][b][c],\n  >(key1: a, key2: b, key3: c, key4: d): Observable<T[a][b][c][d]>;\n  /**\n   * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n   */\n  select<\n    a extends keyof T,\n    b extends keyof T[a],\n    c extends keyof T[a][b],\n    d extends keyof T[a][b][c],\n    e extends keyof T[a][b][c][d],\n  >(key1: a, key2: b, key3: c, key4: d, key5: e): Observable<T[a][b][c][d][e]>;\n  /**\n   * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n   */\n  select<\n    a extends keyof T,\n    b extends keyof T[a],\n    c extends keyof T[a][b],\n    d extends keyof T[a][b][c],\n    e extends keyof T[a][b][c][d],\n    f extends keyof T[a][b][c][d][e],\n  >(\n    key1: a,\n    key2: b,\n    key3: c,\n    key4: d,\n    key5: e,\n    key6: f\n  ): Observable<T[a][b][c][d][e][f]>;\n  /**\n   * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n   */\n  select<\n    a extends keyof T,\n    b extends keyof T[a],\n    c extends keyof T[a][b],\n    d extends keyof T[a][b][c],\n    e extends keyof T[a][b][c][d],\n    f extends keyof T[a][b][c][d][e],\n    K = any,\n  >(\n    key1: a,\n    key2: b,\n    key3: c,\n    key4: d,\n    key5: e,\n    key6: f,\n    ...paths: string[]\n  ): Observable<K>;\n  /**\n   * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n   */\n  select<Props = any, K = any>(\n    pathOrMapFn: ((state: T, props?: Props) => K) | string,\n    ...paths: string[]\n  ): Observable<any> {\n    return (select as any).call(null, pathOrMapFn, ...paths)(this);\n  }\n\n  /**\n   * Returns a signal of the provided selector.\n   *\n   * @param selector selector function\n   * @param options select signal options\n   * @returns Signal of the state selected by the provided selector\n   * @usageNotes\n   *\n   * ```ts\n   * const count = this.store.selectSignal(state => state.count);\n   * ```\n   *\n   * Or with a selector created by @ngrx/store!createSelector:function\n   *\n   * ```ts\n   * const selectCount = createSelector(\n   *  (state: State) => state.count,\n   * );\n   *\n   * const count = this.store.selectSignal(selectCount);\n   * ```\n   */\n  selectSignal<K>(\n    selector: (state: T) => K,\n    options?: SelectSignalOptions<K>\n  ): Signal<K> {\n    return computed(() => selector(this.state()), options);\n  }\n\n  override lift<R>(operator: Operator<T, R>): Store<R> {\n    const store = new Store<R>(this, this.actionsObserver, this.reducerManager);\n    store.operator = operator;\n\n    return store;\n  }\n\n  dispatch<V extends Action>(action: V & CreatorsNotAllowedCheck<V>): void;\n  dispatch<V extends () => Action>(\n    dispatchFn: V & CreatorsNotAllowedCheck<V>,\n    config?: {\n      injector: Injector;\n    }\n  ): EffectRef;\n  dispatch<V extends Action | (() => Action)>(\n    actionOrDispatchFn: V,\n    config?: { injector?: Injector }\n  ): EffectRef | void {\n    if (typeof actionOrDispatchFn === 'function') {\n      return this.processDispatchFn(actionOrDispatchFn, config);\n    }\n    this.actionsObserver.next(actionOrDispatchFn);\n  }\n\n  next(action: Action) {\n    this.actionsObserver.next(action);\n  }\n\n  error(err: any) {\n    this.actionsObserver.error(err);\n  }\n\n  complete() {\n    this.actionsObserver.complete();\n  }\n\n  addReducer<State, Actions extends Action = Action>(\n    key: string,\n    reducer: ActionReducer<State, Actions>\n  ) {\n    this.reducerManager.addReducer(key, reducer);\n  }\n\n  removeReducer<Key extends Extract<keyof T, string>>(key: Key) {\n    this.reducerManager.removeReducer(key);\n  }\n\n  private processDispatchFn(\n    dispatchFn: () => Action,\n    config?: { injector?: Injector }\n  ) {\n    assertDefined(this.injector, 'Store Injector');\n    const effectInjector =\n      config?.injector ?? getCallerInjector() ?? this.injector;\n\n    return effect(\n      () => {\n        const action = dispatchFn();\n        untracked(() => this.dispatch(action));\n      },\n      { injector: effectInjector }\n    );\n  }\n}\n\nexport const STORE_PROVIDERS: Provider[] = [Store];\n\nexport function select<T, K>(\n  mapFn: (state: T) => K\n): (source$: Observable<T>) => Observable<K>;\n/**\n * @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}\n */\nexport function select<T, Props, K>(\n  mapFn: (state: T, props: Props) => K,\n  props: Props\n): (source$: Observable<T>) => Observable<K>;\nexport function select<T, a extends keyof T>(\n  key: a\n): (source$: Observable<T>) => Observable<T[a]>;\nexport function select<T, a extends keyof T, b extends keyof T[a]>(\n  key1: a,\n  key2: b\n): (source$: Observable<T>) => Observable<T[a][b]>;\nexport function select<\n  T,\n  a extends keyof T,\n  b extends keyof T[a],\n  c extends keyof T[a][b],\n>(\n  key1: a,\n  key2: b,\n  key3: c\n): (source$: Observable<T>) => Observable<T[a][b][c]>;\nexport function select<\n  T,\n  a extends keyof T,\n  b extends keyof T[a],\n  c extends keyof T[a][b],\n  d extends keyof T[a][b][c],\n>(\n  key1: a,\n  key2: b,\n  key3: c,\n  key4: d\n): (source$: Observable<T>) => Observable<T[a][b][c][d]>;\nexport function select<\n  T,\n  a extends keyof T,\n  b extends keyof T[a],\n  c extends keyof T[a][b],\n  d extends keyof T[a][b][c],\n  e extends keyof T[a][b][c][d],\n>(\n  key1: a,\n  key2: b,\n  key3: c,\n  key4: d,\n  key5: e\n): (source$: Observable<T>) => Observable<T[a][b][c][d][e]>;\nexport function select<\n  T,\n  a extends keyof T,\n  b extends keyof T[a],\n  c extends keyof T[a][b],\n  d extends keyof T[a][b][c],\n  e extends keyof T[a][b][c][d],\n  f extends keyof T[a][b][c][d][e],\n>(\n  key1: a,\n  key2: b,\n  key3: c,\n  key4: d,\n  key5: e,\n  key6: f\n): (source$: Observable<T>) => Observable<T[a][b][c][d][e][f]>;\nexport function select<\n  T,\n  a extends keyof T,\n  b extends keyof T[a],\n  c extends keyof T[a][b],\n  d extends keyof T[a][b][c],\n  e extends keyof T[a][b][c][d],\n  f extends keyof T[a][b][c][d][e],\n  K = any,\n>(\n  key1: a,\n  key2: b,\n  key3: c,\n  key4: d,\n  key5: e,\n  key6: f,\n  ...paths: string[]\n): (source$: Observable<T>) => Observable<K>;\nexport function select<T, Props, K>(\n  pathOrMapFn: ((state: T, props?: Props) => any) | string,\n  propsOrPath?: Props | string,\n  ...paths: string[]\n) {\n  return function selectOperator(source$: Observable<T>): Observable<K> {\n    let mapped$: Observable<any>;\n\n    if (typeof pathOrMapFn === 'string') {\n      const pathSlices = [<string>propsOrPath, ...paths].filter(Boolean);\n      mapped$ = source$.pipe(pluck(pathOrMapFn, ...pathSlices));\n    } else if (typeof pathOrMapFn === 'function') {\n      mapped$ = source$.pipe(\n        map((source) => pathOrMapFn(source, <Props>propsOrPath))\n      );\n    } else {\n      throw new TypeError(\n        `Unexpected type '${typeof pathOrMapFn}' in select operator,` +\n          ` expected 'string' or 'function'`\n      );\n    }\n\n    return mapped$.pipe(distinctUntilChanged());\n  };\n}\n\nfunction getCallerInjector() {\n  try {\n    return inject(Injector);\n  } catch (_) {\n    return undefined;\n  }\n}\n"
  },
  {
    "path": "modules/store/src/store_config.ts",
    "content": "import { inject, InjectionToken } from '@angular/core';\nimport {\n  Action,\n  ActionReducer,\n  ActionReducerMap,\n  ActionReducerFactory,\n  StoreFeature,\n  InitialState,\n  MetaReducer,\n  RuntimeChecks,\n} from './models';\nimport { combineReducers } from './utils';\nimport { Store } from './store';\n\nexport interface StoreConfig<T, V extends Action = Action> {\n  initialState?: InitialState<T>;\n  reducerFactory?: ActionReducerFactory<T, V>;\n  metaReducers?: MetaReducer<{ [P in keyof T]: T[P] }, V>[];\n}\n\nexport interface RootStoreConfig<T, V extends Action = Action>\n  extends StoreConfig<T, V> {\n  runtimeChecks?: Partial<RuntimeChecks>;\n}\n\n/**\n * An object with the name and the reducer for the feature.\n */\nexport interface FeatureSlice<T, V extends Action = Action> {\n  name: string;\n  reducer: ActionReducer<T, V>;\n}\n\nexport function _createStoreReducers<T, V extends Action = Action>(\n  reducers: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>\n): ActionReducerMap<T, V> {\n  return reducers instanceof InjectionToken ? inject(reducers) : reducers;\n}\n\nexport function _createFeatureStore<T, V extends Action = Action>(\n  configs: StoreConfig<T, V>[] | InjectionToken<StoreConfig<T, V>>[],\n  featureStores: StoreFeature<T, V>[]\n) {\n  return featureStores.map((feat, index) => {\n    if (configs[index] instanceof InjectionToken) {\n      const conf = inject(configs[index] as InjectionToken<StoreConfig<T, V>>);\n      return {\n        key: feat.key,\n        reducerFactory: conf.reducerFactory\n          ? conf.reducerFactory\n          : combineReducers,\n        metaReducers: conf.metaReducers ? conf.metaReducers : [],\n        initialState: conf.initialState,\n      };\n    }\n    return feat;\n  });\n}\n\nexport function _createFeatureReducers<T, V extends Action = Action>(\n  reducerCollection: Array<\n    ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>\n  >\n): ActionReducerMap<T, V>[] {\n  return reducerCollection.map((reducer) => {\n    return reducer instanceof InjectionToken ? inject(reducer) : reducer;\n  });\n}\n\nexport function _initialStateFactory(initialState: any): any {\n  if (typeof initialState === 'function') {\n    return initialState();\n  }\n\n  return initialState;\n}\n\nexport function _concatMetaReducers(\n  metaReducers: MetaReducer[],\n  userProvidedMetaReducers: MetaReducer[]\n): MetaReducer[] {\n  return metaReducers.concat(userProvidedMetaReducers);\n}\n\nexport function _provideForRootGuard(): unknown {\n  const store = inject(Store, { optional: true, skipSelf: true });\n  if (store) {\n    throw new TypeError(\n      `The root Store has been provided more than once. Feature modules should provide feature states instead.`\n    );\n  }\n  return 'guarded';\n}\n"
  },
  {
    "path": "modules/store/src/store_module.ts",
    "content": "import {\n  Inject,\n  InjectionToken,\n  ModuleWithProviders,\n  NgModule,\n  OnDestroy,\n  Optional,\n} from '@angular/core';\nimport {\n  Action,\n  ActionReducer,\n  ActionReducerMap,\n  StoreFeature,\n} from './models';\nimport {\n  _ACTION_TYPE_UNIQUENESS_CHECK,\n  _ROOT_STORE_GUARD,\n  _STORE_FEATURES,\n  FEATURE_REDUCERS,\n} from './tokens';\nimport { ActionsSubject } from './actions_subject';\nimport { ReducerManager, ReducerObservable } from './reducer_manager';\nimport { ScannedActionsSubject } from './scanned_actions_subject';\nimport { Store } from './store';\nimport {\n  _initialStateFactory,\n  FeatureSlice,\n  RootStoreConfig,\n  StoreConfig,\n} from './store_config';\nimport { _provideState, _provideStore } from './provide_store';\n\n@NgModule({})\nexport class StoreRootModule {\n  constructor(\n    actions$: ActionsSubject,\n    reducer$: ReducerObservable,\n    scannedActions$: ScannedActionsSubject,\n    store: Store<any>,\n    @Optional()\n    @Inject(_ROOT_STORE_GUARD)\n    guard: any,\n    @Optional()\n    @Inject(_ACTION_TYPE_UNIQUENESS_CHECK)\n    actionCheck: any\n  ) {}\n}\n\n@NgModule({})\nexport class StoreFeatureModule implements OnDestroy {\n  constructor(\n    @Inject(_STORE_FEATURES) private features: StoreFeature<any, any>[],\n    @Inject(FEATURE_REDUCERS) private featureReducers: ActionReducerMap<any>[],\n    private reducerManager: ReducerManager,\n    root: StoreRootModule,\n    @Optional()\n    @Inject(_ACTION_TYPE_UNIQUENESS_CHECK)\n    actionCheck: any\n  ) {\n    const feats = features.map((feature, index) => {\n      const featureReducerCollection = featureReducers.shift();\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      const reducers = featureReducerCollection! /*TODO(#823)*/[index];\n\n      return {\n        ...feature,\n        reducers,\n        initialState: _initialStateFactory(feature.initialState),\n      };\n    });\n\n    reducerManager.addFeatures(feats);\n  }\n\n  // eslint-disable-next-line @angular-eslint/contextual-lifecycle\n  ngOnDestroy() {\n    this.reducerManager.removeFeatures(this.features);\n  }\n}\n\n@NgModule({})\nexport class StoreModule {\n  static forRoot<T, V extends Action = Action>(\n    reducers?: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>,\n    config?: RootStoreConfig<T, V>\n  ): ModuleWithProviders<StoreRootModule> {\n    return {\n      ngModule: StoreRootModule,\n      providers: [..._provideStore(reducers, config)],\n    };\n  }\n\n  static forFeature<T, V extends Action = Action>(\n    featureName: string,\n    reducers: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>,\n    config?: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>>\n  ): ModuleWithProviders<StoreFeatureModule>;\n  static forFeature<T, V extends Action = Action>(\n    featureName: string,\n    reducer: ActionReducer<T, V> | InjectionToken<ActionReducer<T, V>>,\n    config?: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>>\n  ): ModuleWithProviders<StoreFeatureModule>;\n  static forFeature<T, V extends Action = Action>(\n    slice: FeatureSlice<T, V>\n  ): ModuleWithProviders<StoreFeatureModule>;\n  static forFeature<T, V extends Action = Action>(\n    featureNameOrSlice: string | FeatureSlice<T, V>,\n    reducers?:\n      | ActionReducerMap<T, V>\n      | InjectionToken<ActionReducerMap<T, V>>\n      | ActionReducer<T, V>\n      | InjectionToken<ActionReducer<T, V>>,\n    config: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>> = {}\n  ): ModuleWithProviders<StoreFeatureModule> {\n    return {\n      ngModule: StoreFeatureModule,\n      providers: [..._provideState(featureNameOrSlice, reducers, config)],\n    };\n  }\n}\n"
  },
  {
    "path": "modules/store/src/tokens.ts",
    "content": "import { InjectionToken } from '@angular/core';\nimport { RuntimeChecks, MetaReducer } from './models';\n\nexport const _ROOT_STORE_GUARD = new InjectionToken<void>(\n  '@ngrx/store Internal Root Guard'\n);\nexport const _INITIAL_STATE = new InjectionToken(\n  '@ngrx/store Internal Initial State'\n);\nexport const INITIAL_STATE = new InjectionToken('@ngrx/store Initial State');\nexport const REDUCER_FACTORY = new InjectionToken(\n  '@ngrx/store Reducer Factory'\n);\nexport const _REDUCER_FACTORY = new InjectionToken(\n  '@ngrx/store Internal Reducer Factory Provider'\n);\nexport const INITIAL_REDUCERS = new InjectionToken(\n  '@ngrx/store Initial Reducers'\n);\nexport const _INITIAL_REDUCERS = new InjectionToken(\n  '@ngrx/store Internal Initial Reducers'\n);\nexport const STORE_FEATURES = new InjectionToken('@ngrx/store Store Features');\nexport const _STORE_REDUCERS = new InjectionToken(\n  '@ngrx/store Internal Store Reducers'\n);\nexport const _FEATURE_REDUCERS = new InjectionToken(\n  '@ngrx/store Internal Feature Reducers'\n);\n\nexport const _FEATURE_CONFIGS = new InjectionToken(\n  '@ngrx/store Internal Feature Configs'\n);\n\nexport const _STORE_FEATURES = new InjectionToken(\n  '@ngrx/store Internal Store Features'\n);\n\nexport const _FEATURE_REDUCERS_TOKEN = new InjectionToken(\n  '@ngrx/store Internal Feature Reducers Token'\n);\nexport const FEATURE_REDUCERS = new InjectionToken(\n  '@ngrx/store Feature Reducers'\n);\n\n/**\n * User-defined meta reducers from StoreModule.forRoot()\n */\nexport const USER_PROVIDED_META_REDUCERS = new InjectionToken<MetaReducer[]>(\n  '@ngrx/store User Provided Meta Reducers'\n);\n\n/**\n * Meta reducers defined either internally by @ngrx/store or by library authors\n */\nexport const META_REDUCERS = new InjectionToken<MetaReducer[]>(\n  '@ngrx/store Meta Reducers'\n);\n\n/**\n * Concats the user provided meta reducers and the meta reducers provided on the multi\n * injection token\n */\nexport const _RESOLVED_META_REDUCERS = new InjectionToken<MetaReducer>(\n  '@ngrx/store Internal Resolved Meta Reducers'\n);\n\n/**\n * Runtime checks defined by the user via an InjectionToken\n * Defaults to `_USER_RUNTIME_CHECKS`\n */\nexport const USER_RUNTIME_CHECKS = new InjectionToken<RuntimeChecks>(\n  '@ngrx/store User Runtime Checks Config'\n);\n\n/**\n * Runtime checks defined by the user via forRoot()\n */\nexport const _USER_RUNTIME_CHECKS = new InjectionToken<RuntimeChecks>(\n  '@ngrx/store Internal User Runtime Checks Config'\n);\n\n/**\n * Runtime checks currently in use\n */\nexport const ACTIVE_RUNTIME_CHECKS = new InjectionToken<RuntimeChecks>(\n  '@ngrx/store Internal Runtime Checks'\n);\n\nexport const _ACTION_TYPE_UNIQUENESS_CHECK = new InjectionToken<void>(\n  '@ngrx/store Check if Action types are unique'\n);\n\n/**\n * InjectionToken that registers the global Store.\n * Mainly used to provide a hook that can be injected\n * to ensure the root state is loaded before something\n * that depends on it.\n */\nexport const ROOT_STORE_PROVIDER = new InjectionToken<void>(\n  '@ngrx/store Root Store Provider'\n);\n\n/**\n * InjectionToken that registers feature states.\n * Mainly used to provide a hook that can be injected\n * to ensure feature state is loaded before something\n * that depends on it.\n */\nexport const FEATURE_STATE_PROVIDER = new InjectionToken<void>(\n  '@ngrx/store Feature State Provider'\n);\n"
  },
  {
    "path": "modules/store/src/utils.ts",
    "content": "import {\n  Action,\n  ActionReducer,\n  ActionReducerFactory,\n  ActionReducerMap,\n  MetaReducer,\n  InitialState,\n} from './models';\n\nexport function combineReducers<T, V extends Action = Action>(\n  reducers: ActionReducerMap<T, V>,\n  initialState?: Partial<T>\n): ActionReducer<T, V>;\n/**\n * @description\n * Combines reducers for individual features into a single reducer.\n *\n * You can use this function to delegate handling of state transitions to multiple reducers, each acting on their\n * own sub-state within the root state.\n *\n * @param reducers An object mapping keys of the root state to their corresponding feature reducer.\n * @param initialState Provides a state value if the current state is `undefined`, as it is initially.\n * @returns A reducer function.\n *\n * @usageNotes\n *\n * **Example combining two feature reducers into one \"root\" reducer**\n *\n * ```ts\n * export const reducer = combineReducers({\n *   featureA: featureAReducer,\n *   featureB: featureBReducer\n * });\n * ```\n *\n * You can also override the initial states of the sub-features:\n * ```ts\n * export const reducer = combineReducers({\n *   featureA: featureAReducer,\n *   featureB: featureBReducer\n * }, {\n *   featureA: { counterA: 13 },\n *   featureB: { counterB: 37 }\n * });\n * ```\n */\nexport function combineReducers(\n  reducers: any,\n  initialState: any = {}\n): ActionReducer<any, Action> {\n  const reducerKeys = Object.keys(reducers);\n  const finalReducers: any = {};\n\n  for (let i = 0; i < reducerKeys.length; i++) {\n    const key = reducerKeys[i];\n    if (typeof reducers[key] === 'function') {\n      finalReducers[key] = reducers[key];\n    }\n  }\n\n  const finalReducerKeys = Object.keys(finalReducers);\n\n  return function combination(state, action) {\n    state = state === undefined ? initialState : state;\n    let hasChanged = false;\n    const nextState: any = {};\n    for (let i = 0; i < finalReducerKeys.length; i++) {\n      const key = finalReducerKeys[i];\n      const reducer: any = finalReducers[key];\n      const previousStateForKey = state[key];\n      const nextStateForKey = reducer(previousStateForKey, action);\n\n      nextState[key] = nextStateForKey;\n      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;\n    }\n    return hasChanged ? nextState : state;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function compose<A>(): (i: A) => A;\nexport function compose<A, B>(b: (i: A) => B): (i: A) => B;\nexport function compose<A, B, C>(c: (i: B) => C, b: (i: A) => B): (i: A) => C;\nexport function compose<A, B, C, D>(\n  d: (i: C) => D,\n  c: (i: B) => C,\n  b: (i: A) => B\n): (i: A) => D;\nexport function compose<A, B, C, D, E>(\n  e: (i: D) => E,\n  d: (i: C) => D,\n  c: (i: B) => C,\n  b: (i: A) => B\n): (i: A) => E;\nexport function compose<A, B, C, D, E, F>(\n  f: (i: E) => F,\n  e: (i: D) => E,\n  d: (i: C) => D,\n  c: (i: B) => C,\n  b: (i: A) => B\n): (i: A) => F;\nexport function compose<A = any, F = any>(...functions: any[]): (i: A) => F;\nexport function compose(...functions: any[]) {\n  return function (arg: any) {\n    if (functions.length === 0) {\n      return arg;\n    }\n\n    const last = functions[functions.length - 1];\n    const rest = functions.slice(0, -1);\n\n    return rest.reduceRight((composed, fn) => fn(composed), last(arg));\n  };\n}\n\nexport function createReducerFactory<T, V extends Action = Action>(\n  reducerFactory: ActionReducerFactory<T, V>,\n  metaReducers?: MetaReducer<T, V>[]\n): ActionReducerFactory<T, V> {\n  if (Array.isArray(metaReducers) && metaReducers.length > 0) {\n    (reducerFactory as any) = compose.apply(null, [\n      ...metaReducers,\n      reducerFactory,\n    ]);\n  }\n\n  return (reducers: ActionReducerMap<T, V>, initialState?: InitialState<T>) => {\n    const reducer = reducerFactory(reducers);\n    return (state: T | undefined, action: V) => {\n      state = state === undefined ? (initialState as T) : state;\n      return reducer(state, action);\n    };\n  };\n}\n\nexport function createFeatureReducerFactory<T, V extends Action = Action>(\n  metaReducers?: MetaReducer<T, V>[]\n): (reducer: ActionReducer<T, V>, initialState?: T) => ActionReducer<T, V> {\n  const reducerFactory =\n    Array.isArray(metaReducers) && metaReducers.length > 0\n      ? compose<ActionReducer<T, V>>(...metaReducers)\n      : (r: ActionReducer<T, V>) => r;\n\n  return (reducer: ActionReducer<T, V>, initialState?: T) => {\n    reducer = reducerFactory(reducer);\n\n    return (state: T | undefined, action: V) => {\n      state = state === undefined ? initialState : state;\n      return reducer(state, action);\n    };\n  };\n}\n"
  },
  {
    "path": "modules/store/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/store/testing/index.ts",
    "content": "export * from './src/public_api';\n"
  },
  {
    "path": "modules/store/testing/ng-package.json",
    "content": "{}\n"
  },
  {
    "path": "modules/store/testing/spec/mock_store.spec.ts",
    "content": "import { TestBed, ComponentFixture } from '@angular/core/testing';\nimport { skip, take } from 'rxjs/operators';\nimport {\n  createMockStore,\n  MockReducerManager,\n  MockState,\n  MockStore,\n  provideMockStore,\n} from '../index';\nimport {\n  Store,\n  createSelector,\n  select,\n  MemoizedSelector,\n  createFeatureSelector,\n  isNgrxMockEnvironment,\n  INITIAL_STATE,\n  ActionsSubject,\n  INIT,\n  StateObservable,\n  ReducerManager,\n} from '@ngrx/store';\nimport { INCREMENT } from '../../spec/fixtures/counter';\nimport { Component, Injector } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { By } from '@angular/platform-browser';\nimport { AsyncPipe } from '@angular/common';\n\ninterface TestAppSchema {\n  counter1: number;\n  counter2: number;\n  counter3: number;\n  counter4?: number;\n}\n\ndescribe('Mock Store with TestBed', () => {\n  let mockStore: MockStore<TestAppSchema>;\n  const initialState = { counter1: 0, counter2: 1, counter4: 3 };\n  const stringSelector = 'counter4';\n  const memoizedSelector = createSelector(\n    () => initialState,\n    (state) => state.counter4\n  );\n  const selectorWithPropMocked = createSelector(\n    () => initialState,\n    (state: typeof initialState, add: number) => state.counter4 + add\n  );\n\n  const selectorWithProp = createSelector(\n    () => initialState,\n    (state: typeof initialState, add: number) => state.counter4 + add\n  );\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        provideMockStore({\n          initialState,\n          selectors: [\n            { selector: stringSelector, value: 87 },\n            { selector: memoizedSelector, value: 98 },\n            { selector: selectorWithPropMocked, value: 99 },\n          ],\n        }),\n      ],\n    });\n\n    mockStore = TestBed.inject(MockStore);\n  });\n\n  afterEach(() => {\n    memoizedSelector.release();\n    selectorWithProp.release();\n    selectorWithPropMocked.release();\n    mockStore.resetSelectors();\n  });\n\n  it('should set NgrxMockEnvironment to true', () => {\n    expect(isNgrxMockEnvironment()).toBe(true);\n  });\n\n  it('should provide the same instance with Store and MockStore', () => {\n    const fromStore = TestBed.inject(Store);\n    const fromMockStore = TestBed.inject(MockStore);\n    expect(fromStore).toBe(fromMockStore);\n  });\n\n  it('should set the initial state to a mocked one', () =>\n    new Promise<void>((done, fail) => {\n      const fixedState = {\n        counter1: 17,\n        counter2: 11,\n        counter3: 25,\n      };\n      mockStore.setState(fixedState);\n      mockStore.pipe(take(1)).subscribe({\n        next(val) {\n          expect(val).toEqual(fixedState);\n        },\n        error: fail,\n        complete: done,\n      });\n    }));\n\n  it('should allow tracing dispatched actions', () => {\n    const action = { type: INCREMENT };\n    mockStore.scannedActions$\n      .pipe(skip(1))\n      .subscribe((scannedAction) => expect(scannedAction).toEqual(action));\n    mockStore.dispatch(action);\n  });\n\n  it('should allow mocking of store.select with string selector using provideMockStore', () => {\n    const expectedValue = 87;\n\n    mockStore\n      .select(stringSelector)\n      .subscribe((result) => expect(result).toBe(expectedValue));\n  });\n\n  it('should allow mocking of store.select with a memoized selector using provideMockStore', () => {\n    const expectedValue = 98;\n\n    mockStore\n      .select(memoizedSelector)\n      .subscribe((result) => expect(result).toBe(expectedValue));\n  });\n\n  it('should allow mocking of store.pipe(select()) with a memoized selector using provideMockStore', () => {\n    const expectedValue = 98;\n\n    mockStore\n      .pipe(select(memoizedSelector))\n      .subscribe((result) => expect(result).toBe(expectedValue));\n  });\n\n  it('should allow mocking of store.select with a memoized selector with Prop using provideMockStore', () => {\n    const expectedValue = 99;\n\n    mockStore\n      .select(selectorWithPropMocked, 100)\n      .subscribe((result) => expect(result).toBe(expectedValue));\n  });\n\n  it('should allow mocking of store.pipe(select()) with a memoized selector with Prop using provideMockStore', () => {\n    const expectedValue = 99;\n\n    mockStore\n      .pipe(select(selectorWithPropMocked, 200))\n      .subscribe((result) => expect(result).toBe(expectedValue));\n  });\n\n  it('should allow mocking of store.select with string selector using overrideSelector', () => {\n    const mockValue = 5;\n\n    mockStore.overrideSelector('counter1', mockValue);\n\n    mockStore\n      .select('counter1')\n      .subscribe((result) => expect(result).toBe(mockValue));\n  });\n\n  it('should allow mocking of store.select with a memoized selector using overrideSelector', () => {\n    const mockValue = 5;\n    const selector = createSelector(\n      () => initialState,\n      (state) => state.counter1\n    );\n\n    mockStore.overrideSelector(selector, mockValue);\n\n    mockStore\n      .select(selector)\n      .subscribe((result) => expect(result).toBe(mockValue));\n  });\n\n  it('should allow mocking of store.pipe(select()) with a memoized selector using overrideSelector', () => {\n    const mockValue = 5;\n    const selector = createSelector(\n      () => initialState,\n      (state) => state.counter2\n    );\n\n    mockStore.overrideSelector(selector, mockValue);\n\n    mockStore\n      .pipe(select(selector))\n      .subscribe((result) => expect(result).toBe(mockValue));\n  });\n\n  it('should allow mocking of store.select with a memoized selector with Prop using overrideSelector', () => {\n    const mockValue = 100;\n\n    mockStore.overrideSelector(selectorWithProp, mockValue);\n\n    mockStore\n      .select(selectorWithProp, 200)\n      .subscribe((result) => expect(result).toBe(mockValue));\n  });\n\n  it('should allow mocking of store.pipe(select()) with a memoized selector with Prop using overrideSelector', () => {\n    const mockValue = 1000;\n\n    mockStore.overrideSelector(selectorWithProp, mockValue);\n\n    mockStore\n      .pipe(select(selectorWithProp, 200))\n      .subscribe((result) => expect(result).toBe(mockValue));\n  });\n\n  it('should pass through unmocked selectors with Props using store.pipe(select())', () => {\n    const selectorWithProp = createSelector(\n      () => initialState,\n      (state: typeof initialState, add: number) => state.counter4 + add\n    );\n\n    mockStore\n      .pipe(select(selectorWithProp, 6))\n      .subscribe((result) => expect(result).toBe(9));\n  });\n\n  it('should pass through unmocked selectors with Props using store.select', () => {\n    const selectorWithProp = createSelector(\n      () => initialState,\n      (state: typeof initialState, add: number) => state.counter4 + add\n    );\n\n    (mockStore as Store<{}>)\n      .select(selectorWithProp, 7)\n      .subscribe((result) => expect(result).toBe(10));\n  });\n\n  it('should pass through unmocked selectors', () => {\n    const mockValue = 5;\n    const selector = createSelector(\n      () => initialState,\n      (state) => state.counter1\n    );\n    const selector2 = createSelector(\n      () => initialState,\n      (state) => state.counter2\n    );\n    const selector3 = createSelector(\n      selector,\n      selector2,\n      (sel1, sel2) => sel1 + sel2\n    );\n\n    mockStore.overrideSelector(selector, mockValue);\n\n    mockStore\n      .pipe(select(selector2))\n      .subscribe((result) => expect(result).toBe(1));\n    mockStore\n      .pipe(select(selector3))\n      .subscribe((result) => expect(result).toBe(6));\n  });\n\n  it('should allow you reset mocked selectors', () => {\n    const mockValue = 5;\n    const selector = createSelector(\n      () => initialState,\n      (state) => state.counter1\n    );\n    const selector2 = createSelector(\n      () => initialState,\n      (state) => state.counter2\n    );\n    const selector3 = createSelector(\n      selector,\n      selector2,\n      (sel1, sel2) => sel1 + sel2\n    );\n\n    mockStore\n      .pipe(select(selector3))\n      .subscribe((result) => expect(result).toBe(1));\n\n    mockStore.overrideSelector(selector, mockValue);\n    mockStore.overrideSelector(selector2, mockValue);\n    selector3.release();\n\n    mockStore\n      .pipe(select(selector3))\n      .subscribe((result) => expect(result).toBe(10));\n\n    mockStore.resetSelectors();\n    selector3.release();\n\n    mockStore\n      .pipe(select(selector3))\n      .subscribe((result) => expect(result).toBe(1));\n  });\n});\n\ndescribe('Mock Store with Injector', () => {\n  const initialState = { counter: 0 } as const;\n  const mockSelector = { selector: 'counter', value: 10 } as const;\n\n  describe('Injector.create', () => {\n    let injector: Injector;\n\n    beforeEach(() => {\n      injector = Injector.create({\n        providers: [\n          provideMockStore({ initialState, selectors: [mockSelector] }),\n        ],\n      });\n    });\n\n    it('should set NgrxMockEnvironment to true', () => {\n      expect(isNgrxMockEnvironment()).toBe(true);\n    });\n\n    it('should provide Store', () =>\n      new Promise<void>((done) => {\n        const store: Store<typeof initialState> = injector.get(Store);\n\n        store.pipe(take(1)).subscribe((state) => {\n          expect(state).toBe(initialState);\n          done();\n        });\n      }));\n\n    it('should provide MockStore', () =>\n      new Promise<void>((done) => {\n        const mockStore: MockStore<typeof initialState> =\n          injector.get(MockStore);\n\n        mockStore.pipe(take(1)).subscribe((state) => {\n          expect(state).toBe(initialState);\n          done();\n        });\n      }));\n\n    it('should provide the same instance for Store and MockStore', () => {\n      const store: Store<typeof initialState> = injector.get(Store);\n      const mockStore: MockStore<typeof initialState> = injector.get(MockStore);\n\n      expect(store).toBe(mockStore);\n    });\n\n    it('should use a mock selector', () =>\n      new Promise<void>((done) => {\n        const mockStore: MockStore<typeof initialState> =\n          injector.get(MockStore);\n\n        mockStore\n          .select(mockSelector.selector)\n          .pipe(take(1))\n          .subscribe((selectedValue) => {\n            expect(selectedValue).toBe(mockSelector.value);\n            done();\n          });\n      }));\n\n    it('should provide INITIAL_STATE', () => {\n      const providedInitialState = injector.get(INITIAL_STATE);\n\n      expect(providedInitialState).toBe(initialState);\n    });\n\n    it('should provide ActionsSubject', () =>\n      new Promise<void>((done) => {\n        const actionsSubject = injector.get(ActionsSubject);\n\n        actionsSubject.pipe(take(1)).subscribe((action) => {\n          expect(action.type).toBe(INIT);\n          done();\n        });\n      }));\n\n    it('should provide MockState', () =>\n      new Promise<void>((done) => {\n        const mockState: MockState<typeof initialState> =\n          injector.get(MockState);\n\n        mockState.pipe(take(1)).subscribe((state) => {\n          expect(state).toEqual({});\n          done();\n        });\n      }));\n\n    it('should provide StateObservable', () =>\n      new Promise<void>((done) => {\n        const stateObservable = injector.get(StateObservable);\n\n        stateObservable.pipe(take(1)).subscribe((state) => {\n          expect(state).toEqual({});\n          done();\n        });\n      }));\n\n    it('should provide the same instance for MockState and StateObservable', () => {\n      const mockState: MockState<typeof initialState> = injector.get(MockState);\n      const stateObservable: StateObservable = injector.get(StateObservable);\n\n      expect(mockState).toBe(stateObservable);\n    });\n\n    it('should provide ReducerManager', () => {\n      const reducerManager = injector.get(ReducerManager);\n\n      expect(reducerManager.addFeature).toBeInstanceOf(Function);\n      expect(reducerManager.addFeatures).toBeInstanceOf(Function);\n    });\n\n    it('should provide MockReducerManager', () => {\n      const mockReducerManager = injector.get(MockReducerManager);\n\n      expect(mockReducerManager.addFeature).toBeInstanceOf(Function);\n      expect(mockReducerManager.addFeatures).toBeInstanceOf(Function);\n    });\n\n    it('should provide the same instance for ReducerManager and MockReducerManager', () => {\n      const reducerManager = injector.get(ReducerManager);\n      const mockReducerManager = injector.get(MockReducerManager);\n\n      expect(reducerManager).toBe(mockReducerManager);\n    });\n  });\n\n  describe('createMockStore', () => {\n    let mockStore: MockStore<typeof initialState>;\n\n    beforeEach(() => {\n      mockStore = createMockStore({ initialState, selectors: [mockSelector] });\n    });\n\n    it('should create MockStore', () =>\n      new Promise<void>((done) => {\n        mockStore.pipe(take(1)).subscribe((state) => {\n          expect(state).toBe(initialState);\n          done();\n        });\n      }));\n\n    it('should use a mock selector', () =>\n      new Promise<void>((done) => {\n        mockStore\n          .select(mockSelector.selector)\n          .pipe(take(1))\n          .subscribe((selectedValue) => {\n            expect(selectedValue).toBe(mockSelector.value);\n            done();\n          });\n      }));\n  });\n});\n\ndescribe('Refreshing state', () => {\n  type TodoState = {\n    items: { name: string; done: boolean }[];\n  };\n  const selectTodosState = createFeatureSelector<TodoState>('todos');\n  const todos = createSelector(selectTodosState, (todos) => todos.items);\n  const getTodoItems = (elSelector: string) =>\n    fixture.debugElement.queryAll(By.css(elSelector));\n  let mockStore: MockStore<TodoState>;\n  let mockSelector: MemoizedSelector<TodoState, any[]>;\n  const initialTodos = [{ name: 'aaa', done: false }];\n  let fixture: ComponentFixture<TodosComponent>;\n\n  @Component({\n    selector: 'ngrx-app-todos',\n    template: `\n      <ul>\n        @for (todo of todos | async; track todo.name) {\n          <li>\n            {{ todo.name }} <input type=\"checkbox\" [checked]=\"todo.done\" />\n          </li>\n        }\n\n        @for (todo of todosSelect | async; track todo.name) {\n          <p>{{ todo.name }} <input type=\"checkbox\" [checked]=\"todo.done\" /></p>\n        }\n      </ul>\n    `,\n    imports: [AsyncPipe],\n  })\n  class TodosComponent {\n    todos: Observable<any[]> = this.store.pipe(select(todos));\n    todosSelect: Observable<any[]> = this.store.select(todos);\n\n    constructor(private store: Store<{}>) {}\n  }\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [provideMockStore()],\n    }).compileComponents();\n\n    mockStore = TestBed.inject(MockStore);\n    mockSelector = mockStore.overrideSelector(todos, initialTodos);\n\n    fixture = TestBed.createComponent(TodosComponent);\n    fixture.detectChanges();\n  });\n\n  it('should work with store and select operator', () => {\n    const newTodos = [{ name: 'bbb', done: true }];\n    mockSelector.setResult(newTodos);\n    mockStore.refreshState();\n\n    fixture.detectChanges();\n\n    expect(getTodoItems('li').length).toBe(1);\n    expect(getTodoItems('li')[0].nativeElement.textContent.trim()).toBe('bbb');\n  });\n\n  it('should work with store.select method', () => {\n    const newTodos = [{ name: 'bbb', done: true }];\n    mockSelector.setResult(newTodos);\n    mockStore.refreshState();\n\n    fixture.detectChanges();\n\n    expect(getTodoItems('p').length).toBe(1);\n    expect(getTodoItems('p')[0].nativeElement.textContent.trim()).toBe('bbb');\n  });\n\n  it('should work with overrideSelector twice', () => {\n    const newTodos = [{ name: 'bbb', done: true }];\n    mockStore.overrideSelector(todos, newTodos);\n    mockStore.refreshState();\n\n    fixture.detectChanges();\n\n    expect(getTodoItems('li').length).toBe(1);\n    expect(getTodoItems('li')[0].nativeElement.textContent.trim()).toBe('bbb');\n  });\n});\n"
  },
  {
    "path": "modules/store/testing/src/mock_reducer_manager.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\nimport { ActionReducer } from '@ngrx/store';\n\n@Injectable()\nexport class MockReducerManager extends BehaviorSubject<\n  ActionReducer<any, any>\n> {\n  constructor() {\n    super(() => undefined);\n  }\n\n  addFeature(feature: any) {\n    /* noop */\n  }\n\n  addFeatures(feature: any) {\n    /* noop */\n  }\n\n  removeFeature(feature: any) {\n    /* noop */\n  }\n\n  removeFeatures(features: any) {\n    /* noop */\n  }\n\n  addReducer(key: any, reducer: any) {\n    /* noop */\n  }\n\n  addReducers(reducers: any) {\n    /* noop */\n  }\n\n  removeReducer(featureKey: any) {\n    /* noop */\n  }\n\n  removeReducers(featureKeys: any) {\n    /* noop */\n  }\n}\n"
  },
  {
    "path": "modules/store/testing/src/mock_selector.ts",
    "content": "import { MemoizedSelector, MemoizedSelectorWithProps } from '@ngrx/store';\n\nexport interface MockSelector {\n  selector:\n    | string\n    | MemoizedSelector<any, any>\n    | MemoizedSelectorWithProps<any, any, any>;\n  value: any;\n}\n"
  },
  {
    "path": "modules/store/testing/src/mock_state.ts",
    "content": "import { Injectable, Signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { BehaviorSubject } from 'rxjs';\n\n@Injectable()\nexport class MockState<T> extends BehaviorSubject<T> {\n  /**\n   * @internal\n   */\n  readonly state: Signal<T>;\n\n  constructor() {\n    super(<T>{});\n\n    this.state = toSignal(this, { manualCleanup: true, requireSync: true });\n  }\n}\n"
  },
  {
    "path": "modules/store/testing/src/mock_store.ts",
    "content": "import { Inject, Injectable } from '@angular/core';\nimport { Observable, BehaviorSubject } from 'rxjs';\nimport {\n  Action,\n  ActionsSubject,\n  INITIAL_STATE,\n  ReducerManager,\n  Store,\n  createSelector,\n  MemoizedSelectorWithProps,\n  MemoizedSelector,\n} from '@ngrx/store';\nimport { MockState } from './mock_state';\nimport { MockSelector } from './mock_selector';\nimport { MOCK_SELECTORS } from './tokens';\n\ntype OnlyMemoized<T, Result> = T extends string | MemoizedSelector<any, any>\n  ? MemoizedSelector<any, Result>\n  : T extends MemoizedSelectorWithProps<any, any, any>\n    ? MemoizedSelectorWithProps<any, any, Result>\n    : never;\n\ntype Memoized<Result> =\n  | MemoizedSelector<any, Result>\n  | MemoizedSelectorWithProps<any, any, Result>;\n\n@Injectable()\nexport class MockStore<T = object> extends Store<T> {\n  private readonly selectors = new Map<Memoized<any> | string, any>();\n\n  readonly scannedActions$: Observable<Action>;\n  private lastState?: T;\n\n  constructor(\n    private state$: MockState<T>,\n    actionsObserver: ActionsSubject,\n    reducerManager: ReducerManager,\n    @Inject(INITIAL_STATE) private initialState: T,\n    @Inject(MOCK_SELECTORS) mockSelectors: MockSelector[] = []\n  ) {\n    super(state$, actionsObserver, reducerManager);\n    this.resetSelectors();\n    this.setState(this.initialState);\n    this.scannedActions$ = actionsObserver.asObservable();\n    for (const mockSelector of mockSelectors) {\n      this.overrideSelector(mockSelector.selector, mockSelector.value);\n    }\n  }\n\n  setState(nextState: T): void {\n    this.state$.next(nextState);\n    this.lastState = nextState;\n  }\n\n  overrideSelector<\n    Selector extends Memoized<Result>,\n    Value extends Result,\n    Result = Selector extends MemoizedSelector<any, infer T>\n      ? T\n      : Selector extends MemoizedSelectorWithProps<any, any, infer U>\n        ? U\n        : Value,\n  >(\n    selector: Selector | string,\n    value: Value\n  ): OnlyMemoized<typeof selector, Result> {\n    this.selectors.set(selector, value);\n\n    const resultSelector: Memoized<Result> =\n      typeof selector === 'string'\n        ? createSelector(\n            () => {},\n            (): Result => value\n          )\n        : selector;\n\n    resultSelector.setResult(value);\n\n    return resultSelector as OnlyMemoized<typeof selector, Result>;\n  }\n\n  resetSelectors() {\n    for (const selector of this.selectors.keys()) {\n      if (typeof selector !== 'string') {\n        selector.release();\n        selector.clearResult();\n      }\n    }\n\n    this.selectors.clear();\n  }\n\n  override select(selector: any, prop?: any) {\n    if (typeof selector === 'string' && this.selectors.has(selector)) {\n      return new BehaviorSubject<any>(\n        this.selectors.get(selector)\n      ).asObservable();\n    }\n\n    return super.select(selector, prop);\n  }\n\n  override addReducer() {\n    /* noop */\n  }\n\n  override removeReducer() {\n    /* noop */\n  }\n\n  /**\n   * Refreshes the existing state.\n   */\n  refreshState() {\n    if (this.lastState) this.setState({ ...this.lastState });\n  }\n}\n"
  },
  {
    "path": "modules/store/testing/src/public_api.ts",
    "content": "export * from './testing';\n"
  },
  {
    "path": "modules/store/testing/src/testing.ts",
    "content": "import {\n  ExistingProvider,\n  FactoryProvider,\n  Injector,\n  ValueProvider,\n} from '@angular/core';\nimport { MockState } from './mock_state';\nimport {\n  ActionsSubject,\n  INITIAL_STATE,\n  ReducerManager,\n  StateObservable,\n  Store,\n  setNgrxMockEnvironment,\n} from '@ngrx/store';\nimport { MockStore } from './mock_store';\nimport { MockReducerManager } from './mock_reducer_manager';\nimport { MockSelector } from './mock_selector';\nimport { MOCK_SELECTORS } from './tokens';\n\nexport interface MockStoreConfig<T> {\n  initialState?: T;\n  selectors?: MockSelector[];\n}\n\n/**\n * @description\n * Creates mock store providers.\n *\n * @param config `MockStoreConfig<T>` to provide the values for `INITIAL_STATE` and `MOCK_SELECTORS` tokens.\n * By default, `initialState` and `selectors` are not defined.\n * @returns Mock store providers that can be used with both `TestBed.configureTestingModule` and `Injector.create`.\n *\n * @usageNotes\n *\n * **With `TestBed.configureTestingModule`**\n *\n * ```typescript\n * describe('Books Component', () => {\n *   let store: MockStore;\n *\n *   beforeEach(() => {\n *     TestBed.configureTestingModule({\n *       providers: [\n *         provideMockStore({\n *           initialState: { books: { entities: [] } },\n *           selectors: [\n *             { selector: selectAllBooks, value: ['Book 1', 'Book 2'] },\n *             { selector: selectVisibleBooks, value: ['Book 1'] },\n *           ],\n *         }),\n *       ],\n *     });\n *\n *     store = TestBed.inject(MockStore);\n *   });\n * });\n * ```\n *\n * **With `Injector.create`**\n *\n * ```typescript\n * describe('Counter Component', () => {\n *   let injector: Injector;\n *   let store: MockStore;\n *\n *   beforeEach(() => {\n *     injector = Injector.create({\n *       providers: [\n *         provideMockStore({ initialState: { counter: 0 } }),\n *       ],\n *     });\n *     store = injector.get(MockStore);\n *   });\n * });\n * ```\n */\nexport function provideMockStore<T = any>(\n  config: MockStoreConfig<T> = {}\n): (ValueProvider | ExistingProvider | FactoryProvider)[] {\n  setNgrxMockEnvironment(true);\n  return [\n    {\n      provide: ActionsSubject,\n      useFactory: () => new ActionsSubject(),\n      deps: [],\n    },\n    { provide: MockState, useFactory: () => new MockState<T>(), deps: [] },\n    {\n      provide: MockReducerManager,\n      useFactory: () => new MockReducerManager(),\n      deps: [],\n    },\n    { provide: INITIAL_STATE, useValue: config.initialState || {} },\n    { provide: MOCK_SELECTORS, useValue: config.selectors },\n    { provide: StateObservable, useExisting: MockState },\n    { provide: ReducerManager, useExisting: MockReducerManager },\n    {\n      provide: MockStore,\n      useFactory: mockStoreFactory,\n      deps: [\n        MockState,\n        ActionsSubject,\n        ReducerManager,\n        INITIAL_STATE,\n        MOCK_SELECTORS,\n      ],\n    },\n    { provide: Store, useExisting: MockStore },\n  ];\n}\n\nfunction mockStoreFactory<T>(\n  mockState: MockState<T>,\n  actionsSubject: ActionsSubject,\n  reducerManager: ReducerManager,\n  initialState: T,\n  mockSelectors: MockSelector[]\n): MockStore<T> {\n  return new MockStore(\n    mockState,\n    actionsSubject,\n    reducerManager,\n    initialState,\n    mockSelectors\n  );\n}\n\n/**\n * @description\n * Creates mock store with all necessary dependencies outside of the `TestBed`.\n *\n * @param config `MockStoreConfig<T>` to provide the values for `INITIAL_STATE` and `MOCK_SELECTORS` tokens.\n * By default, `initialState` and `selectors` are not defined.\n * @returns `MockStore<T>`\n *\n * @usageNotes\n *\n * ```typescript\n * describe('Books Effects', () => {\n *   let store: MockStore;\n *\n *   beforeEach(() => {\n *     store = createMockStore({\n *       initialState: { books: { entities: ['Book 1', 'Book 2', 'Book 3'] } },\n *       selectors: [\n *         { selector: selectAllBooks, value: ['Book 1', 'Book 2'] },\n *         { selector: selectVisibleBooks, value: ['Book 1'] },\n *       ],\n *     });\n *   });\n * });\n * ```\n */\nexport function createMockStore<T>(\n  config: MockStoreConfig<T> = {}\n): MockStore<T> {\n  const injector = Injector.create({ providers: provideMockStore(config) });\n  return injector.get(MockStore);\n}\n\nexport { MockReducerManager } from './mock_reducer_manager';\nexport { MockState } from './mock_state';\nexport { MockStore } from './mock_store';\nexport { MockSelector } from './mock_selector';\n"
  },
  {
    "path": "modules/store/testing/src/tokens.ts",
    "content": "import { InjectionToken } from '@angular/core';\n\nexport const MOCK_SELECTORS = new InjectionToken('@ngrx/store Mock Selectors');\n"
  },
  {
    "path": "modules/store/testing/tsconfig.build.json",
    "content": "{\n  \"extends\": \"../tsconfig.build\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@ngrx/store\": [\"../../dist/packages/store\"]\n    }\n  },\n  \"files\": [\"index.ts\"],\n  \"angularCompilerOptions\": {}\n}\n"
  },
  {
    "path": "modules/store/testing/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.spec\",\n  \"files\": [\"index.ts\"]\n}\n"
  },
  {
    "path": "modules/store/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../../\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"downlevelIteration\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"outDir\": \"../../dist/modules/store\",\n    \"paths\": {},\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"files\": [\"public_api.ts\"],\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/store\"\n  }\n}\n"
  },
  {
    "path": "modules/store/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/store\",\n    \"paths\": {\n      \"@ngrx/store/schematics-core\": [\"./schematics-core\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/store/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"node\", \"vitest\", \"vitest/globals\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/store/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [angular(), nxViteTsPaths()],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default'],\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "modules/store-devtools/CHANGELOG.md",
    "content": "# Change Log\n\nSee [CHANGELOG.md](https://github.com/ngrx/platform/blob/main/CHANGELOG.md)\n"
  },
  {
    "path": "modules/store-devtools/README.md",
    "content": "# @ngrx/store-devtools\n\nThe sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n"
  },
  {
    "path": "modules/store-devtools/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: [\n      '**/dist',\n      '**/schematics-core/**/*.ts',\n      '**/vite.config.*.timestamp*',\n      '**/vitest.config.*.timestamp*',\n    ],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['modules/store-devtools/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/store-devtools/index.ts",
    "content": "/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\nexport * from './public_api';\n"
  },
  {
    "path": "modules/store-devtools/migrations/17_0_0-beta/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { createPackageJson } from '@ngrx/schematics-core/testing/create-package';\nimport { waitForAsync } from '@angular/core/testing';\n\ndescribe('DevTools Migration 17_0_0-beta', () => {\n  let appTree: UnitTestTree;\n  const collectionPath = path.join(\n    process.cwd(),\n    'dist/modules/store-devtools/migrations/migration.json'\n  );\n  const pkgName = 'store-devtools';\n  const migrationname = `ngrx-${pkgName}-migration-17-0-0-beta`;\n\n  beforeEach(() => {\n    appTree = new UnitTestTree(Tree.empty());\n    appTree.create(\n      '/tsconfig.json',\n      `\n        {\n          \"include\": [**./*.ts\"]\n        }\n       `\n    );\n    createPackageJson('', pkgName, appTree);\n  });\n\n  describe('StoreDevtoolsModule.instrument', () => {\n    it(`should add connectInZone to config properties (previous property ends with a comma)`, waitForAsync(async () => {\n      const input = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument({\n            name: 'DevTools Name',\n          }),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument({\n            name: 'DevTools Name',\n          connectInZone: true}),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`should add connectInZone to config properties (previous property doesn't end with a comma)`, waitForAsync(async () => {\n      const input = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument({\n            name: 'DevTools Name'\n          }),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument({\n            name: 'DevTools Name'\n          , connectInZone: true}),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`should add connectInZone to empty config properties`, waitForAsync(async () => {\n      const input = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument({\n          }),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument({\n          connectInZone: true}),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`should add connectInZone to empty config`, waitForAsync(async () => {\n      const input = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument(),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument({connectInZone: true}),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`renames connectOutsideZone to connectInZone and inverts its value`, waitForAsync(async () => {\n      const input = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument({\n            connectOutsideZone: true,\n          }),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n      const expected = `\n      import { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n      @NgModule({\n        imports: [\n          StoreDevtoolsModule.instrument({\n            connectInZone: false,\n          }),\n        ],\n        bootstrap: [AppComponent],\n      })\n      export class AppModule {}\n    `;\n\n      appTree.create('./app.module.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('app.module.ts');\n\n      expect(file).toBe(expected);\n    }));\n  });\n\n  describe('bootstrapApplication', () => {\n    it(`should add connectInZone to config properties (previous property ends with a comma)`, waitForAsync(async () => {\n      const input = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools({\n            maxAge: 25,\n            logOnly: !isDevMode(),\n          }),\n        ],\n      });\n    `;\n      const expected = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools({\n            maxAge: 25,\n            logOnly: !isDevMode(),\n          connectInZone: true}),\n        ],\n      });\n    `;\n\n      appTree.create('./main.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('main.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`should add connectInZone to config properties (previous property doesn't end with a comma)`, waitForAsync(async () => {\n      const input = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools({\n            maxAge: 25,\n            logOnly: !isDevMode()\n          }),\n        ],\n      });\n    `;\n      const expected = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools({\n            maxAge: 25,\n            logOnly: !isDevMode()\n          , connectInZone: true}),\n        ],\n      });\n    `;\n\n      appTree.create('./main.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('main.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`should add connectInZone to empty config properties`, waitForAsync(async () => {\n      const input = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools({ }),\n        ],\n      });\n    `;\n      const expected = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools({ connectInZone: true}),\n        ],\n      });\n    `;\n\n      appTree.create('./main.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('main.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`should add connectInZone to empty config`, waitForAsync(async () => {\n      const input = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools(),\n        ],\n      });\n    `;\n      const expected = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools({connectInZone: true}),\n        ],\n      });\n    `;\n\n      appTree.create('./main.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('main.ts');\n\n      expect(file).toBe(expected);\n    }));\n\n    it(`renames connectOutsideZone to connectInZone and inverts its value`, waitForAsync(async () => {\n      const input = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools({ connectOutsideZone: someValue }),\n        ],\n      });\n    `;\n      const expected = `\n      import { provideStoreDevtools } from '@ngrx/store-devtools';\n\n      bootstrapApplication(AppComponent, {\n        providers: [\n          provideStoreDevtools({ connectInZone: !someValue }),\n        ],\n      });\n    `;\n\n      appTree.create('./main.ts', input);\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n\n      const newTree = await runner.runSchematic(migrationname, {}, appTree);\n      const file = newTree.readContent('main.ts');\n\n      expect(file).toBe(expected);\n    }));\n  });\n});\n"
  },
  {
    "path": "modules/store-devtools/migrations/17_0_0-beta/index.ts",
    "content": "import * as ts from 'typescript';\nimport { Rule, chain, Tree } from '@angular-devkit/schematics';\nimport {\n  visitTSSourceFiles,\n  commitChanges,\n  InsertChange,\n  Change,\n  createReplaceChange,\n} from '../..//schematics-core';\n\nfunction migrate() {\n  return (tree: Tree) => {\n    visitTSSourceFiles(tree, (sourceFile) => {\n      const devtoolsImports = sourceFile.statements\n        .filter(ts.isImportDeclaration)\n        .filter(({ moduleSpecifier }) =>\n          moduleSpecifier.getText(sourceFile).includes('@ngrx/store-devtools')\n        );\n\n      if (devtoolsImports.length === 0) {\n        return;\n      }\n\n      const changes = [...findAndUpdateConfigs(sourceFile)];\n\n      commitChanges(tree, sourceFile.fileName, changes);\n    });\n  };\n}\n\nfunction findAndUpdateConfigs(sourceFile: ts.SourceFile) {\n  const changes: Change[] = [];\n  ts.forEachChild(sourceFile, (node) => find(node, changes));\n  return changes;\n\n  function find(node: ts.Node, changes: Change[]) {\n    if (\n      ts.isPropertyAccessExpression(node) &&\n      node.name.text === 'instrument' &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === 'StoreDevtoolsModule' &&\n      ts.isCallExpression(node.parent)\n    ) {\n      if (node.parent.arguments.length) {\n        const [devtoolsConfig] = node.parent.arguments;\n\n        if (ts.isObjectLiteralExpression(devtoolsConfig)) {\n          updateConfig(sourceFile, devtoolsConfig, (change) =>\n            changes.push(change)\n          );\n        }\n      } else {\n        createDevtoolsConfig(sourceFile, node.parent, (change) =>\n          changes.push(change)\n        );\n      }\n    }\n\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === 'provideStoreDevtools'\n    ) {\n      if (node.arguments.length) {\n        const [devtoolsConfig] = node.arguments;\n\n        if (ts.isObjectLiteralExpression(devtoolsConfig)) {\n          updateConfig(sourceFile, devtoolsConfig, (change) =>\n            changes.push(change)\n          );\n        }\n      } else {\n        createDevtoolsConfig(sourceFile, node, (change) =>\n          changes.push(change)\n        );\n      }\n    }\n\n    ts.forEachChild(node, (childNode) => find(childNode, changes));\n  }\n}\n\nfunction updateConfig(\n  sourceFile: ts.SourceFile,\n  devtoolsConfig: ts.ObjectLiteralExpression,\n  addChange: (changes: Change) => number\n) {\n  const connectOutsideZoneProperty = devtoolsConfig.properties.find(\n    (p) =>\n      ts.isPropertyAssignment(p) &&\n      ts.isIdentifier(p.name) &&\n      p.name.text === 'connectOutsideZone'\n  );\n\n  if (!connectOutsideZoneProperty) {\n    addConnectInZoneProperty();\n  } else if (ts.isPropertyAssignment(connectOutsideZoneProperty)) {\n    replaceConnectOutsideZoneConfig(connectOutsideZoneProperty);\n  }\n\n  function addConnectInZoneProperty() {\n    const configText = devtoolsConfig.getText(sourceFile);\n    const comma =\n      !devtoolsConfig.properties.length ||\n      configText\n        .substring(0, configText.length - 1)\n        .trim()\n        .endsWith(',')\n        ? ''\n        : ',';\n\n    addChange(\n      new InsertChange(\n        sourceFile.fileName,\n        devtoolsConfig.getEnd() - 1,\n        `${comma} connectInZone: true`.trim()\n      )\n    );\n  }\n\n  function replaceConnectOutsideZoneConfig(\n    connectOutsideZone: ts.PropertyAssignment\n  ) {\n    const currentValue = connectOutsideZone.initializer\n      .getText(sourceFile)\n      .trim();\n    addChange(\n      createReplaceChange(\n        sourceFile,\n        connectOutsideZone.name,\n        'connectOutsideZone',\n        'connectInZone'\n      )\n    );\n    addChange(\n      createReplaceChange(\n        sourceFile,\n        connectOutsideZone.initializer,\n        currentValue,\n        currentValue === 'true'\n          ? 'false'\n          : currentValue === 'false'\n            ? 'true'\n            : `!${currentValue}`\n      )\n    );\n  }\n}\n\nfunction createDevtoolsConfig(\n  sourceFile: ts.SourceFile,\n  callExpression: ts.CallExpression,\n  addChange: (...items: Change[]) => number\n) {\n  addChange(\n    new InsertChange(\n      sourceFile.fileName,\n      callExpression.getEnd() - 1,\n      `{connectInZone: true}`\n    )\n  );\n}\n\nexport default function (): Rule {\n  return chain([migrate()]);\n}\n"
  },
  {
    "path": "modules/store-devtools/migrations/6_0_0/index.spec.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\nimport {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport {\n  createPackageJson,\n  packagePath,\n} from '@ngrx/schematics-core/testing/create-package';\nimport {\n  upgradeVersion,\n  versionPrefixes,\n} from '@ngrx/schematics-core/testing/update';\n\nconst collectionPath = path.join(\n  process.cwd(),\n  'dist/modules/store-devtools/migrations/migration.json'\n);\n\ndescribe('Store Devtools Migration 6_0_0', () => {\n  let appTree;\n  const pkgName = 'store-devtools';\n\n  versionPrefixes.forEach((prefix) => {\n    it(`should install version ${prefix}6.0.0`, async () => {\n      appTree = new UnitTestTree(Tree.empty());\n      const runner = new SchematicTestRunner('schematics', collectionPath);\n      const tree = createPackageJson(prefix, pkgName, appTree);\n\n      const newTree = await runner.runSchematic(\n        `ngrx-${pkgName}-migration-01`,\n        {},\n        tree\n      );\n      const pkg = JSON.parse(newTree.readContent(packagePath));\n      expect(pkg.dependencies[`@ngrx/${pkgName}`]).toBe(\n        `${prefix}${upgradeVersion}`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store-devtools/migrations/6_0_0/index.ts",
    "content": "import { Rule } from '@angular-devkit/schematics';\nimport { updatePackage } from '../../schematics-core';\n\nexport default function (): Rule {\n  return updatePackage('store-devtools');\n}\n"
  },
  {
    "path": "modules/store-devtools/migrations/migration.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n  \"schematics\": {\n    \"ngrx-store-devtools-migration-01\": {\n      \"description\": \"The road to v6\",\n      \"version\": \"5.2\",\n      \"factory\": \"./6_0_0/index\"\n    },\n    \"ngrx-store-devtools-migration-17-0-0-beta\": {\n      \"description\": \"The road to v17-beta.1\",\n      \"version\": \"17.0.0-beta\",\n      \"factory\": \"./17_0_0-beta/index\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/modules/store-devtools\",\n  \"assets\": [\"migrations/**/*.json\", \"schematics/**/*.json\", \"**/files/**/*\"],\n  \"lib\": {\n    \"entryFile\": \"index.ts\"\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/package.json",
    "content": "{\n  \"name\": \"@ngrx/store-devtools\",\n  \"version\": \"21.0.1\",\n  \"description\": \"Developer tools for @ngrx/store\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ngrx/platform.git\"\n  },\n  \"keywords\": [\n    \"RxJS\",\n    \"Angular\",\n    \"Redux\",\n    \"Store\",\n    \"@ngrx/store\"\n  ],\n  \"author\": \"NgRx\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ngrx/platform/issues\"\n  },\n  \"homepage\": \"https://github.com/ngrx/platform#readme\",\n  \"peerDependencies\": {\n    \"@angular/core\": \"^21.0.0\",\n    \"@ngrx/store\": \"21.0.1\",\n    \"rxjs\": \"^6.5.3 || ^7.5.0\"\n  },\n  \"schematics\": \"./schematics/collection.json\",\n  \"ng-update\": {\n    \"packageGroupName\": \"@ngrx/store\",\n    \"packageGroup\": [\n      \"@ngrx/store\",\n      \"@ngrx/effects\",\n      \"@ngrx/entity\",\n      \"@ngrx/router-store\",\n      \"@ngrx/data\",\n      \"@ngrx/schematics\",\n      \"@ngrx/store-devtools\",\n      \"@ngrx/component-store\",\n      \"@ngrx/component\",\n      \"@ngrx/eslint-plugin\",\n      \"@ngrx/operators\",\n      \"@ngrx/signals\"\n    ],\n    \"migrations\": \"./migrations/migration.json\"\n  },\n  \"sideEffects\": false,\n  \"dependencies\": {\n    \"tslib\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/project.json",
    "content": "{\n  \"name\": \"store-devtools\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"library\",\n  \"sourceRoot\": \"modules/store-devtools/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"generators\": {\n    \"@angular-eslint/schematics:application\": {\n      \"setParserOptionsProject\": true\n    },\n    \"@angular-eslint/schematics:library\": {\n      \"setParserOptionsProject\": true\n    }\n  },\n  \"targets\": {\n    \"build-package\": {\n      \"executor\": \"@angular-devkit/build-angular:ng-packagr\",\n      \"options\": {\n        \"tsConfig\": \"modules/store-devtools/tsconfig.build.json\",\n        \"project\": \"modules/store-devtools/ng-package.json\"\n      }\n    },\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"parallel\": false,\n        \"commands\": [\n          {\n            \"command\": \"nx build-package store-devtools\"\n          },\n          {\n            \"command\": \"pnpm exec tsc -p modules/store-devtools/tsconfig.schematics.json\"\n          },\n          {\n            \"command\": \"pnpm exec rimraf node_modules/@ngrx/store-devtools\"\n          },\n          {\n            \"command\": \"pnpm exec mkdirp node_modules/@ngrx/store-devtools\"\n          },\n          {\n            \"command\": \"ncp dist/modules/store-devtools node_modules/@ngrx/store-devtools\"\n          },\n          {\n            \"command\": \"cpy LICENSE dist/modules/store-devtools\"\n          }\n        ]\n      },\n      \"outputs\": [\n        \"{workspaceRoot}/dist/modules/store-devtools\",\n        \"{workspaceRoot}/node_modules/@ngrx/store-devtools\"\n      ]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"modules/store-devtools/*/**/*.ts\",\n          \"modules/store-devtools/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"{workspaceRoot}/coverage/modules/store-devtools\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/public_api.ts",
    "content": "export * from './src/index';\n"
  },
  {
    "path": "modules/store-devtools/schematics/collection.json",
    "content": "{\n  \"schematics\": {\n    \"ng-add\": {\n      \"aliases\": [\"init\"],\n      \"factory\": \"./ng-add\",\n      \"schema\": \"./ng-add/schema.json\",\n      \"description\": \"Adds initial setup for store-devtools\"\n    }\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics/ng-add/__snapshots__/index.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Store-Devtools ng-add Schematic > Store Devtools ng-add Schematic for standalone application > provides initial setup 1`] = `\n\"import { ApplicationConfig, provideBrowserGlobalErrorListeners, isDevMode } from '@angular/core';\nimport { provideStoreDevtools } from '@ngrx/store-devtools';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideBrowserGlobalErrorListeners(),\n    provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() })\n]\n};\n\"\n`;\n"
  },
  {
    "path": "modules/store-devtools/schematics/ng-add/index.spec.ts",
    "content": "import {\n  SchematicTestRunner,\n  UnitTestTree,\n} from '@angular-devkit/schematics/testing';\nimport * as path from 'path';\nimport { Schema as StoreDevtoolsOptions } from './schema';\nimport {\n  getTestProjectPath,\n  createWorkspace,\n} from '@ngrx/schematics-core/testing';\n\ndescribe('Store-Devtools ng-add Schematic', () => {\n  const schematicRunner = new SchematicTestRunner(\n    '@ngrx/store-devtools',\n    path.join(\n      process.cwd(),\n      'dist/modules/store-devtools/schematics/collection.json'\n    )\n  );\n  const defaultOptions: StoreDevtoolsOptions = {\n    skipPackageJson: false,\n    project: 'bar',\n    module: 'app-module',\n  };\n\n  const projectPath = getTestProjectPath();\n\n  let appTree: UnitTestTree;\n\n  beforeEach(async () => {\n    appTree = await createWorkspace(schematicRunner, appTree);\n  });\n\n  it('should update package.json', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/store-devtools']).toBeDefined();\n  });\n\n  it('should skip package.json update', async () => {\n    const options = { ...defaultOptions, skipPackageJson: true };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const packageJson = JSON.parse(tree.readContent('/package.json'));\n\n    expect(packageJson.dependencies['@ngrx/store-devtools']).toBeUndefined();\n  });\n\n  it('should be provided by default', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(\n      /import { StoreDevtoolsModule } from '@ngrx\\/store-devtools';/\n    );\n    expect(content).toMatch(\n      /StoreDevtoolsModule.instrument\\({ maxAge: 25, logOnly: !isDevMode\\(\\) }\\)/\n    );\n  });\n\n  it('should import into a specified module', async () => {\n    const options = { ...defaultOptions };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(\n      /import { StoreDevtoolsModule } from '@ngrx\\/store-devtools';/\n    );\n  });\n\n  it('should import isDevMode correctly', async () => {\n    const options = { ...defaultOptions, module: 'app-module.ts' };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(\n      /import { NgModule, provideBrowserGlobalErrorListeners, isDevMode } from '@angular\\/core';/\n    );\n  });\n\n  it('should fail if specified module does not exist', async () => {\n    const options = { ...defaultOptions, module: '/src/app/app-moduleXXX.ts' };\n    let thrownError: Error | null = null;\n    try {\n      await schematicRunner.runSchematic('ng-add', options, appTree);\n    } catch (err: any) {\n      thrownError = err;\n    }\n    expect(thrownError).toBeDefined();\n  });\n\n  it('should fail if negative maxAges', async () => {\n    const options = { ...defaultOptions, maxAge: -4 };\n\n    let thrownError: Error | null = null;\n    try {\n      await schematicRunner.runSchematic('ng-add', options, appTree);\n    } catch (err: any) {\n      thrownError = err;\n    }\n    expect(thrownError).toBeDefined();\n  });\n\n  it('should fail if maxAge of 1', async () => {\n    const options = { ...defaultOptions, maxAge: -4 };\n\n    let thrownError: Error | null = null;\n    try {\n      await schematicRunner.runSchematic('ng-add', options, appTree);\n    } catch (err: any) {\n      thrownError = err;\n    }\n    expect(thrownError).toBeDefined();\n  });\n\n  it('should support a custom maxAge', async () => {\n    const options = {\n      ...defaultOptions,\n      name: 'State',\n      maxAge: 5,\n    };\n\n    const tree = await schematicRunner.runSchematic('ng-add', options, appTree);\n    const content = tree.readContent(`${projectPath}/src/app/app-module.ts`);\n    expect(content).toMatch(/maxAge: 5/);\n  });\n\n  describe('Store Devtools ng-add Schematic for standalone application', () => {\n    const projectPath = getTestProjectPath(undefined, {\n      name: 'bar-standalone',\n    });\n\n    const standaloneDefaultOptions = {\n      ...defaultOptions,\n      project: 'bar-standalone',\n    };\n\n    it('provides initial setup', async () => {\n      const options = { ...standaloneDefaultOptions };\n      const tree = await schematicRunner.runSchematic(\n        'ng-add',\n        options,\n        appTree\n      );\n\n      const content = tree.readContent(`${projectPath}/src/app/app.config.ts`);\n\n      expect(content).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store-devtools/schematics/ng-add/index.ts",
    "content": "import * as ts from 'typescript';\nimport {\n  Rule,\n  SchematicContext,\n  SchematicsException,\n  Tree,\n  branchAndMerge,\n  chain,\n  noop,\n} from '@angular-devkit/schematics';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\nimport {\n  InsertChange,\n  addImportToModule,\n  findModuleFromOptions,\n  getProjectPath,\n  insertImport,\n  addPackageToPackageJson,\n  platformVersion,\n  parseName,\n} from '../../schematics-core';\nimport { Schema as StoreDevtoolsOptions } from './schema';\nimport {\n  addFunctionalProvidersToStandaloneBootstrap,\n  callsProvidersFunction,\n} from '../../schematics-core/utility/standalone';\nimport { getProjectMainFile } from '../../schematics-core/utility/project';\nimport { isStandaloneApp } from '@schematics/angular/utility/ng-ast-utils';\n\nfunction addImportToNgModule(options: StoreDevtoolsOptions): Rule {\n  return (host: Tree) => {\n    const modulePath = options.module;\n\n    if (!modulePath) {\n      return host;\n    }\n\n    if (!host.exists(modulePath)) {\n      throw new Error('Specified module does not exist');\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const [instrumentNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreDevtoolsModule.instrument({ maxAge: ${options.maxAge}, logOnly: !isDevMode() })`,\n      modulePath\n    );\n\n    const changes = [\n      insertImport(\n        source,\n        modulePath,\n        'StoreDevtoolsModule',\n        '@ngrx/store-devtools'\n      ),\n      insertImport(source, modulePath, 'isDevMode', '@angular/core'),\n      instrumentNgModuleImport,\n    ];\n    const recorder = host.beginUpdate(modulePath);\n\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nfunction addNgRxStoreDevToolsToPackageJson() {\n  return (host: Tree, context: SchematicContext) => {\n    addPackageToPackageJson(\n      host,\n      'dependencies',\n      '@ngrx/store-devtools',\n      platformVersion\n    );\n    context.addTask(new NodePackageInstallTask());\n    return host;\n  };\n}\n\nfunction addStandaloneConfig(options: StoreDevtoolsOptions): Rule {\n  return (host: Tree) => {\n    const mainFile = getProjectMainFile(host, options);\n\n    if (host.exists(mainFile)) {\n      const providerFn = 'provideStoreDevtools';\n\n      if (callsProvidersFunction(host, mainFile, providerFn)) {\n        // exit because the store config is already provided\n        return host;\n      }\n\n      const providerOptions = [\n        ts.factory.createIdentifier(\n          `{ maxAge: ${options.maxAge}, logOnly: !isDevMode() }`\n        ),\n      ];\n\n      const patchedConfigFile = addFunctionalProvidersToStandaloneBootstrap(\n        host,\n        mainFile,\n        providerFn,\n        '@ngrx/store-devtools',\n        providerOptions\n      );\n\n      // insert reducers import into the patched file\n      const configFileContent = host.read(patchedConfigFile);\n      const source = ts.createSourceFile(\n        patchedConfigFile,\n        configFileContent?.toString('utf-8') || '',\n        ts.ScriptTarget.Latest,\n        true\n      );\n\n      const recorder = host.beginUpdate(patchedConfigFile);\n\n      const changes = [\n        insertImport(source, patchedConfigFile, 'isDevMode', '@angular/core'),\n      ];\n\n      for (const change of changes) {\n        if (change instanceof InsertChange) {\n          recorder.insertLeft(change.pos, change.toAdd);\n        }\n      }\n\n      host.commitUpdate(recorder);\n\n      return host;\n    }\n\n    throw new SchematicsException(\n      `Main file not found for a project ${options.project}`\n    );\n  };\n}\n\nexport default function (options: StoreDevtoolsOptions): Rule {\n  return (host: Tree, context: SchematicContext) => {\n    const mainFile = getProjectMainFile(host, options);\n    const isStandalone = isStandaloneApp(host, mainFile);\n\n    options.path = getProjectPath(host, options);\n\n    if (options.module && !isStandalone) {\n      options.module = findModuleFromOptions(host, {\n        name: '',\n        module: options.module,\n        path: options.path,\n      });\n    }\n\n    const parsedPath = parseName(options.path, '');\n    options.path = parsedPath.path;\n\n    if (options.maxAge && (options.maxAge < 0 || options.maxAge === 1)) {\n      throw new SchematicsException(\n        `maxAge should be an integer greater than 1.`\n      );\n    }\n\n    const configOrModuleUpdate = isStandalone\n      ? addStandaloneConfig(options)\n      : addImportToNgModule(options);\n\n    return chain([\n      branchAndMerge(chain([configOrModuleUpdate])),\n      options && options.skipPackageJson\n        ? noop()\n        : addNgRxStoreDevToolsToPackageJson(),\n    ])(host, context);\n  };\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics/ng-add/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"SchematicsNgRxRootState\",\n  \"title\": \"NgRx Root State Management Options Schema\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"skipPackageJson\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Do not add @ngrx/store as dependency to package.json (e.g., --skipPackageJson).\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"format\": \"path\",\n      \"description\": \"The path to create the state.\",\n      \"visible\": false,\n      \"$default\": {\n        \"$source\": \"workingDirectory\"\n      }\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the project.\",\n      \"aliases\": [\"p\"]\n    },\n    \"module\": {\n      \"type\": \"string\",\n      \"default\": \"app\",\n      \"description\": \"Allows specification of the declaring module.\",\n      \"alias\": \"m\",\n      \"subtype\": \"filepath\"\n    },\n    \"maxAge\": {\n      \"type\": \"number\",\n      \"default\": 25,\n      \"description\": \"number (>1) | 0 - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. 0 is infinite. Default is 25 for performance reasons.\"\n    },\n    \"autoPause\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"boolean - pauses recording actions and state changes when the extension window is not open.\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics/ng-add/schema.ts",
    "content": "export interface Schema {\n  skipPackageJson?: boolean;\n  path?: string;\n  project?: string;\n  module?: string;\n  maxAge?: number;\n  autoPause?: boolean;\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': 'off',\n        '@angular-eslint/component-selector': 'off',\n        'no-prototype-builtins': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/index.ts",
    "content": "import {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n} from './utility/strings';\n\nexport {\n  findNodes,\n  getSourceNodes,\n  getDecoratorMetadata,\n  getContentOfKeyLiteral,\n  insertAfterLastOccurrence,\n  insertImport,\n  addBootstrapToModule,\n  addDeclarationToModule,\n  addExportToModule,\n  addImportToModule,\n  addProviderToComponent,\n  addProviderToModule,\n  replaceImport,\n  containsProperty,\n} from './utility/ast-utils';\n\nexport {\n  Host,\n  Change,\n  NoopChange,\n  InsertChange,\n  RemoveChange,\n  ReplaceChange,\n  createReplaceChange,\n  createChangeRecorder,\n  commitChanges,\n} from './utility/change';\n\nexport { AppConfig, getWorkspace, getWorkspacePath } from './utility/config';\n\nexport { findComponentFromOptions } from './utility/find-component';\n\nexport {\n  findModule,\n  findModuleFromOptions,\n  buildRelativePath,\n  ModuleOptions,\n} from './utility/find-module';\n\nexport { findPropertyInAstObject } from './utility/json-utilts';\n\nexport {\n  addReducerToState,\n  addReducerToStateInterface,\n  addReducerImportToNgModule,\n  addReducerToActionReducerMap,\n  omit,\n  getPrefix,\n} from './utility/ngrx-utils';\n\nexport { getProjectPath, getProject, isLib } from './utility/project';\n\nexport const stringUtils = {\n  dasherize,\n  decamelize,\n  camelize,\n  classify,\n  underscore,\n  group,\n  capitalize,\n  featurePath,\n  pluralize,\n};\n\nexport { updatePackage } from './utility/update';\n\nexport { parseName } from './utility/parse-name';\n\nexport { addPackageToPackageJson } from './utility/package';\n\nexport { platformVersion } from './utility/libs-version';\n\nexport {\n  visitTSSourceFiles,\n  visitNgModuleImports,\n  visitNgModuleExports,\n  visitComponents,\n  visitDecorator,\n  visitNgModules,\n  visitTemplates,\n} from './utility/visitors';\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/tsconfig.lib.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/schematics-score\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"es2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"test-setup.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/ast-utils.ts",
    "content": "/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport * as ts from 'typescript';\nimport {\n  Change,\n  InsertChange,\n  NoopChange,\n  createReplaceChange,\n  ReplaceChange,\n  RemoveChange,\n  createRemoveChange,\n} from './change';\nimport { Path } from '@angular-devkit/core';\n\n/**\n * Find all nodes from the AST in the subtree of node of SyntaxKind kind.\n * @param node\n * @param kind\n * @param max The maximum number of items to return.\n * @return all nodes of kind, or [] if none is found\n */\nexport function findNodes(\n  node: ts.Node,\n  kind: ts.SyntaxKind,\n  max = Infinity\n): ts.Node[] {\n  if (!node || max == 0) {\n    return [];\n  }\n\n  const arr: ts.Node[] = [];\n  if (node.kind === kind) {\n    arr.push(node);\n    max--;\n  }\n  if (max > 0) {\n    for (const child of node.getChildren()) {\n      findNodes(child, kind, max).forEach((node) => {\n        if (max > 0) {\n          arr.push(node);\n        }\n        max--;\n      });\n\n      if (max <= 0) {\n        break;\n      }\n    }\n  }\n\n  return arr;\n}\n\n/**\n * Get all the nodes from a source.\n * @param sourceFile The source file object.\n * @returns {Observable<ts.Node>} An observable of all the nodes in the source.\n */\nexport function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {\n  const nodes: ts.Node[] = [sourceFile];\n  const result = [];\n\n  while (nodes.length > 0) {\n    const node = nodes.shift();\n\n    if (node) {\n      result.push(node);\n      if (node.getChildCount(sourceFile) >= 0) {\n        nodes.unshift(...node.getChildren());\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Helper for sorting nodes.\n * @return function to sort nodes in increasing order of position in sourceFile\n */\nfunction nodesByPosition(first: ts.Node, second: ts.Node): number {\n  return first.pos - second.pos;\n}\n\n/**\n * Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`\n * or after the last of occurence of `syntaxKind` if the last occurence is a sub child\n * of ts.SyntaxKind[nodes[i].kind] and save the changes in file.\n *\n * @param nodes insert after the last occurence of nodes\n * @param toInsert string to insert\n * @param file file to insert changes into\n * @param fallbackPos position to insert if toInsert happens to be the first occurence\n * @param syntaxKind the ts.SyntaxKind of the subchildren to insert after\n * @return Change instance\n * @throw Error if toInsert is first occurence but fall back is not set\n */\nexport function insertAfterLastOccurrence(\n  nodes: ts.Node[],\n  toInsert: string,\n  file: string,\n  fallbackPos: number,\n  syntaxKind?: ts.SyntaxKind\n): Change {\n  let lastItem = nodes.sort(nodesByPosition).pop();\n  if (!lastItem) {\n    throw new Error();\n  }\n  if (syntaxKind) {\n    lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();\n  }\n  if (!lastItem && fallbackPos == undefined) {\n    throw new Error(\n      `tried to insert ${toInsert} as first occurence with no fallback position`\n    );\n  }\n  const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;\n\n  return new InsertChange(file, lastItemPosition, toInsert);\n}\n\nexport function getContentOfKeyLiteral(\n  _source: ts.SourceFile,\n  node: ts.Node\n): string | null {\n  if (node.kind == ts.SyntaxKind.Identifier) {\n    return (node as ts.Identifier).text;\n  } else if (node.kind == ts.SyntaxKind.StringLiteral) {\n    return (node as ts.StringLiteral).text;\n  } else {\n    return null;\n  }\n}\n\nfunction _angularImportsFromNode(\n  node: ts.ImportDeclaration,\n  _sourceFile: ts.SourceFile\n): { [name: string]: string } {\n  const ms = node.moduleSpecifier;\n  let modulePath: string;\n  switch (ms.kind) {\n    case ts.SyntaxKind.StringLiteral:\n      modulePath = (ms as ts.StringLiteral).text;\n      break;\n    default:\n      return {};\n  }\n\n  if (!modulePath.startsWith('@angular/')) {\n    return {};\n  }\n\n  if (node.importClause) {\n    if (node.importClause.name) {\n      // This is of the form `import Name from 'path'`. Ignore.\n      return {};\n    } else if (node.importClause.namedBindings) {\n      const nb = node.importClause.namedBindings;\n      if (nb.kind == ts.SyntaxKind.NamespaceImport) {\n        // This is of the form `import * as name from 'path'`. Return `name.`.\n        return {\n          [(nb as ts.NamespaceImport).name.text + '.']: modulePath,\n        };\n      } else {\n        // This is of the form `import {a,b,c} from 'path'`\n        const namedImports = nb as ts.NamedImports;\n\n        return namedImports.elements\n          .map((is: ts.ImportSpecifier) =>\n            is.propertyName ? is.propertyName.text : is.name.text\n          )\n          .reduce((acc: { [name: string]: string }, curr: string) => {\n            acc[curr] = modulePath;\n\n            return acc;\n          }, {});\n      }\n    }\n\n    return {};\n  } else {\n    // This is of the form `import 'path';`. Nothing to do.\n    return {};\n  }\n}\n\nexport function getDecoratorMetadata(\n  source: ts.SourceFile,\n  identifier: string,\n  module: string\n): ts.Node[] {\n  const angularImports: { [name: string]: string } = findNodes(\n    source,\n    ts.SyntaxKind.ImportDeclaration\n  )\n    .map((node) =>\n      _angularImportsFromNode(node as ts.ImportDeclaration, source)\n    )\n    .reduce(\n      (\n        acc: { [name: string]: string },\n        current: { [name: string]: string }\n      ) => {\n        for (const key of Object.keys(current)) {\n          acc[key] = current[key];\n        }\n\n        return acc;\n      },\n      {}\n    );\n\n  return getSourceNodes(source)\n    .filter((node) => {\n      return (\n        node.kind == ts.SyntaxKind.Decorator &&\n        (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression\n      );\n    })\n    .map((node) => (node as ts.Decorator).expression as ts.CallExpression)\n    .filter((expr) => {\n      if (expr.expression.kind == ts.SyntaxKind.Identifier) {\n        const id = expr.expression as ts.Identifier;\n\n        return (\n          id.getFullText(source) == identifier &&\n          angularImports[id.getFullText(source)] === module\n        );\n      } else if (\n        expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression\n      ) {\n        // This covers foo.NgModule when importing * as foo.\n        const paExpr = expr.expression as ts.PropertyAccessExpression;\n        // If the left expression is not an identifier, just give up at that point.\n        if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {\n          return false;\n        }\n\n        const id = paExpr.name.text;\n        const moduleId = (paExpr.expression as ts.Identifier).getText(source);\n\n        return id === identifier && angularImports[moduleId + '.'] === module;\n      }\n\n      return false;\n    })\n    .filter(\n      (expr) =>\n        expr.arguments[0] &&\n        expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression\n    )\n    .map((expr) => expr.arguments[0] as ts.ObjectLiteralExpression);\n}\n\nfunction _addSymbolToNgModuleMetadata(\n  source: ts.SourceFile,\n  ngModulePath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      ngModulePath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      ngModulePath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No app module found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n\n    const effectsModule = nodeArray.find(\n      (node) =>\n        (node.getText().includes('EffectsModule.forRoot') &&\n          symbolName.includes('EffectsModule.forRoot')) ||\n        (node.getText().includes('EffectsModule.forFeature') &&\n          symbolName.includes('EffectsModule.forFeature'))\n    );\n\n    if (effectsModule && symbolName.includes('EffectsModule')) {\n      const effectsArgs = (effectsModule as any).arguments.shift();\n\n      if (\n        effectsArgs &&\n        effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression\n      ) {\n        const effectsElements = (effectsArgs as ts.ArrayLiteralExpression)\n          .elements;\n        const [, effectsSymbol] = (<any>symbolName).match(/\\[(.*)\\]/);\n\n        let epos;\n        if (effectsElements.length === 0) {\n          epos = effectsArgs.getStart() + 1;\n          return [new InsertChange(ngModulePath, epos, effectsSymbol)];\n        } else {\n          const lastEffect = effectsElements[\n            effectsElements.length - 1\n          ] as ts.Expression;\n          epos = lastEffect.getEnd();\n          // Get the indentation of the last element, if any.\n          const text: any = lastEffect.getFullText(source);\n\n          let effectInsert: string;\n          if (text.match('^\\r?\\r?\\n')) {\n            effectInsert = `,${text.match(/^\\r?\\n\\s+/)[0]}${effectsSymbol}`;\n          } else {\n            effectInsert = `, ${effectsSymbol}`;\n          }\n\n          return [new InsertChange(ngModulePath, epos, effectInsert)];\n        }\n      } else {\n        return [];\n      }\n    }\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(ngModulePath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    ngModulePath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\nfunction _addSymbolToComponentMetadata(\n  source: ts.SourceFile,\n  componentPath: string,\n  metadataField: string,\n  symbolName: string,\n  importPath: string\n): Change[] {\n  const nodes = getDecoratorMetadata(source, 'Component', '@angular/core');\n  let node: any = nodes[0]; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Find the decorator declaration.\n  if (!node) {\n    return [];\n  }\n\n  // Get all the children property assignment of object literals.\n  const matchingProperties: ts.ObjectLiteralElement[] = (\n    node as ts.ObjectLiteralExpression\n  ).properties\n    .filter((prop) => prop.kind == ts.SyntaxKind.PropertyAssignment)\n    // Filter out every fields that's not \"metadataField\". Also handles string literals\n    // (but not expressions).\n    .filter((prop: any) => {\n      const name = prop.name;\n      switch (name.kind) {\n        case ts.SyntaxKind.Identifier:\n          return (name as ts.Identifier).getText(source) == metadataField;\n        case ts.SyntaxKind.StringLiteral:\n          return (name as ts.StringLiteral).text == metadataField;\n      }\n\n      return false;\n    });\n\n  // Get the last node of the array literal.\n  if (!matchingProperties) {\n    return [];\n  }\n  if (matchingProperties.length == 0) {\n    // We haven't found the field in the metadata declaration. Insert a new field.\n    const expr = node as ts.ObjectLiteralExpression;\n    let position: number;\n    let toInsert: string;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      const matches = text.match(/^\\r?\\n\\s*/);\n      if (matches.length > 0) {\n        toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n    const newMetadataProperty = new InsertChange(\n      componentPath,\n      position,\n      toInsert\n    );\n    const newMetadataImport = insertImport(\n      source,\n      componentPath,\n      symbolName.replace(/\\..*$/, ''),\n      importPath\n    );\n\n    return [newMetadataProperty, newMetadataImport];\n  }\n\n  const assignment = matchingProperties[0] as ts.PropertyAssignment;\n\n  // If it's not an array, nothing we can do really.\n  if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {\n    return [];\n  }\n\n  const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;\n  if (arrLiteral.elements.length == 0) {\n    // Forward the property.\n    node = arrLiteral;\n  } else {\n    node = arrLiteral.elements;\n  }\n\n  if (!node) {\n    console.log(\n      'No component found. Please add your new class to your component.'\n    );\n\n    return [];\n  }\n\n  if (Array.isArray(node)) {\n    const nodeArray = node as {} as Array<ts.Node>;\n    const symbolsArray = nodeArray.map((node) => node.getText());\n    if (symbolsArray.includes(symbolName)) {\n      return [];\n    }\n\n    node = node[node.length - 1];\n  }\n\n  let toInsert: string;\n  let position = node.getEnd();\n  if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {\n    // We haven't found the field in the metadata declaration. Insert a new\n    // field.\n    const expr = node as ts.ObjectLiteralExpression;\n    if (expr.properties.length == 0) {\n      position = expr.getEnd() - 1;\n      toInsert = `  ${metadataField}: [${symbolName}]\\n`;\n    } else {\n      node = expr.properties[expr.properties.length - 1];\n      position = node.getEnd();\n      // Get the indentation of the last element, if any.\n      const text = node.getFullText(source);\n      if (text.match('^\\r?\\r?\\n')) {\n        toInsert = `,${\n          text.match(/^\\r?\\n\\s+/)[0]\n        }${metadataField}: [${symbolName}]`;\n      } else {\n        toInsert = `, ${metadataField}: [${symbolName}]`;\n      }\n    }\n  } else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {\n    // We found the field but it's empty. Insert it just before the `]`.\n    position--;\n    toInsert = `${symbolName}`;\n  } else {\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    if (text.match(/^\\r?\\n/)) {\n      toInsert = `,${text.match(/^\\r?\\n(\\r?)\\s+/)[0]}${symbolName}`;\n    } else {\n      toInsert = `, ${symbolName}`;\n    }\n  }\n  const insert = new InsertChange(componentPath, position, toInsert);\n  const importInsert: Change = insertImport(\n    source,\n    componentPath,\n    symbolName.replace(/\\..*$/, ''),\n    importPath\n  );\n\n  return [insert, importInsert];\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addDeclarationToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'declarations',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a declaration (component, pipe, directive)\n * into NgModule declarations. It also imports the component.\n */\nexport function addImportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'imports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into NgModule. It also imports it.\n */\nexport function addProviderToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert a provider into Component. It also imports it.\n */\nexport function addProviderToComponent(\n  source: ts.SourceFile,\n  componentPath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToComponentMetadata(\n    source,\n    componentPath,\n    'providers',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addExportToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'exports',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Custom function to insert an export into NgModule. It also imports it.\n */\nexport function addBootstrapToModule(\n  source: ts.SourceFile,\n  modulePath: string,\n  classifiedName: string,\n  importPath: string\n): Change[] {\n  return _addSymbolToNgModuleMetadata(\n    source,\n    modulePath,\n    'bootstrap',\n    classifiedName,\n    importPath\n  );\n}\n\n/**\n * Add Import `import { symbolName } from fileName` if the import doesn't exit\n * already. Assumes fileToEdit can be resolved and accessed.\n * @param fileToEdit (file we want to add import to)\n * @param symbolName (item to import)\n * @param fileName (path to the file)\n * @param isDefault (if true, import follows style for importing default exports)\n * @return Change\n */\n\nexport function insertImport(\n  source: ts.SourceFile,\n  fileToEdit: string,\n  symbolName: string,\n  fileName: string,\n  isDefault = false\n): Change {\n  const rootNode = source;\n  const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);\n\n  // get nodes that map to import statements from the file fileName\n  const relevantImports = allImports.filter((node) => {\n    // StringLiteral of the ImportDeclaration is the import file (fileName in this case).\n    const importFiles = node\n      .getChildren()\n      .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)\n      .map((n) => (n as ts.StringLiteral).text);\n\n    return importFiles.filter((file) => file === fileName).length === 1;\n  });\n\n  if (relevantImports.length > 0) {\n    let importsAsterisk = false;\n    // imports from import file\n    const imports: ts.Node[] = [];\n    relevantImports.forEach((n) => {\n      Array.prototype.push.apply(\n        imports,\n        findNodes(n, ts.SyntaxKind.Identifier)\n      );\n      if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {\n        importsAsterisk = true;\n      }\n    });\n\n    // if imports * from fileName, don't add symbolName\n    if (importsAsterisk) {\n      return new NoopChange();\n    }\n\n    const importTextNodes = imports.filter(\n      (n) => (n as ts.Identifier).text === symbolName\n    );\n\n    // insert import if it's not there\n    if (importTextNodes.length === 0) {\n      const fallbackPos =\n        findNodes(\n          relevantImports[0],\n          ts.SyntaxKind.CloseBraceToken\n        )[0].getStart() ||\n        findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();\n\n      return insertAfterLastOccurrence(\n        imports,\n        `, ${symbolName}`,\n        fileToEdit,\n        fallbackPos\n      );\n    }\n\n    return new NoopChange();\n  }\n\n  // no such import declaration exists\n  const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(\n    (n) => n.getText() === 'use strict'\n  );\n  let fallbackPos = 0;\n  if (useStrict.length > 0) {\n    fallbackPos = useStrict[0].end;\n  }\n  const open = isDefault ? '' : '{ ';\n  const close = isDefault ? '' : ' }';\n  // if there are no imports or 'use strict' statement, insert import at beginning of file\n  const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;\n  const separator = insertAtBeginning ? '' : ';\\n';\n  const toInsert =\n    `${separator}import ${open}${symbolName}${close}` +\n    ` from '${fileName}'${insertAtBeginning ? ';\\n' : ''}`;\n\n  return insertAfterLastOccurrence(\n    allImports,\n    toInsert,\n    fileToEdit,\n    fallbackPos,\n    ts.SyntaxKind.StringLiteral\n  );\n}\n\nexport function replaceImport(\n  sourceFile: ts.SourceFile,\n  path: Path,\n  importFrom: string,\n  importAsIs: string,\n  importToBe: string\n): (ReplaceChange | RemoveChange)[] {\n  const imports = sourceFile.statements\n    .filter(ts.isImportDeclaration)\n    .filter(\n      ({ moduleSpecifier }) =>\n        moduleSpecifier.getText(sourceFile) === `'${importFrom}'` ||\n        moduleSpecifier.getText(sourceFile) === `\"${importFrom}\"`\n    );\n\n  if (imports.length === 0) {\n    return [];\n  }\n\n  const importText = (specifier: ts.ImportSpecifier) => {\n    if (specifier.name.text) {\n      return specifier.name.text;\n    }\n\n    // if import is renamed\n    if (specifier.propertyName && specifier.propertyName.text) {\n      return specifier.propertyName.text;\n    }\n\n    return '';\n  };\n\n  const changes = imports.map((p) => {\n    const namedImports = p?.importClause?.namedBindings as ts.NamedImports;\n    if (!namedImports) {\n      return [];\n    }\n\n    const importSpecifiers = namedImports.elements;\n    const isAlreadyImported = importSpecifiers\n      .map(importText)\n      .includes(importToBe);\n\n    const importChanges = importSpecifiers.map((specifier, index) => {\n      const text = importText(specifier);\n\n      // import is not the one we're looking for, can be skipped\n      if (text !== importAsIs) {\n        return undefined;\n      }\n\n      // identifier has not been imported, simply replace the old text with the new text\n      if (!isAlreadyImported) {\n        return createReplaceChange(\n          sourceFile,\n          specifier,\n          importAsIs,\n          importToBe\n        );\n      }\n\n      const nextIdentifier = importSpecifiers[index + 1];\n      // identifer is not the last, also clean up the comma\n      if (nextIdentifier) {\n        return createRemoveChange(\n          sourceFile,\n          specifier,\n          specifier.getStart(sourceFile),\n          nextIdentifier.getStart(sourceFile)\n        );\n      }\n\n      // there are no imports following, just remove it\n      return createRemoveChange(\n        sourceFile,\n        specifier,\n        specifier.getStart(sourceFile),\n        specifier.getEnd()\n      );\n    });\n\n    return importChanges.filter(Boolean) as (ReplaceChange | RemoveChange)[];\n  });\n\n  return changes.reduce((imports, curr) => imports.concat(curr), []);\n}\n\nexport function containsProperty(\n  objectLiteral: ts.ObjectLiteralExpression,\n  propertyName: string\n) {\n  return (\n    objectLiteral &&\n    objectLiteral.properties.some(\n      (prop) =>\n        ts.isPropertyAssignment(prop) &&\n        ts.isIdentifier(prop.name) &&\n        prop.name.text === propertyName\n    )\n  );\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/change.ts",
    "content": "import * as ts from 'typescript';\nimport { Tree, UpdateRecorder } from '@angular-devkit/schematics';\nimport { Path } from '@angular-devkit/core';\n\n/* istanbul ignore file */\n/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nexport interface Host {\n  write(path: string, content: string): Promise<void>;\n  read(path: string): Promise<string>;\n}\n\nexport interface Change {\n  apply(host: Host): Promise<void>;\n\n  // The file this change should be applied to. Some changes might not apply to\n  // a file (maybe the config).\n  readonly path: string | null;\n\n  // The order this change should be applied. Normally the position inside the file.\n  // Changes are applied from the bottom of a file to the top.\n  readonly order: number;\n\n  // The description of this change. This will be outputted in a dry or verbose run.\n  readonly description: string;\n}\n\n/**\n * An operation that does nothing.\n */\nexport class NoopChange implements Change {\n  description = 'No operation.';\n  order = Infinity;\n  path = null;\n  apply() {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Will add text to the source code.\n */\nexport class InsertChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public toAdd: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;\n    this.order = pos;\n  }\n\n  /**\n   * This method does not insert spaces if there is none in the original string.\n   */\n  apply(host: Host) {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos);\n\n      return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will remove text from the source code.\n */\nexport class RemoveChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public end: number\n  ) {\n    if (pos < 0 || end < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Removed text in position ${pos} to ${end} of ${path}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.end);\n\n      // TODO: throw error if toRemove doesn't match removed string.\n      return host.write(this.path, `${prefix}${suffix}`);\n    });\n  }\n}\n\n/**\n * Will replace text from the source code.\n */\nexport class ReplaceChange implements Change {\n  order: number;\n  description: string;\n\n  constructor(\n    public path: string,\n    public pos: number,\n    public oldText: string,\n    public newText: string\n  ) {\n    if (pos < 0) {\n      throw new Error('Negative positions are invalid');\n    }\n    this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;\n    this.order = pos;\n  }\n\n  apply(host: Host): Promise<void> {\n    return host.read(this.path).then((content) => {\n      const prefix = content.substring(0, this.pos);\n      const suffix = content.substring(this.pos + this.oldText.length);\n      const text = content.substring(this.pos, this.pos + this.oldText.length);\n\n      if (text !== this.oldText) {\n        return Promise.reject(\n          new Error(`Invalid replace: \"${text}\" != \"${this.oldText}\".`)\n        );\n      }\n\n      // TODO: throw error if oldText doesn't match removed string.\n      return host.write(this.path, `${prefix}${this.newText}${suffix}`);\n    });\n  }\n}\n\nexport function createReplaceChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  oldText: string,\n  newText: string\n): ReplaceChange {\n  return new ReplaceChange(\n    sourceFile.fileName,\n    node.getStart(sourceFile),\n    oldText,\n    newText\n  );\n}\n\nexport function createRemoveChange(\n  sourceFile: ts.SourceFile,\n  node: ts.Node,\n  from = node.getStart(sourceFile),\n  to = node.getEnd()\n): RemoveChange {\n  return new RemoveChange(sourceFile.fileName, from, to);\n}\n\nexport function createChangeRecorder(\n  tree: Tree,\n  path: string,\n  changes: Change[]\n): UpdateRecorder {\n  const recorder = tree.beginUpdate(path);\n  for (const change of changes) {\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    } else if (change instanceof RemoveChange) {\n      recorder.remove(change.pos, change.end - change.pos);\n    } else if (change instanceof ReplaceChange) {\n      recorder.remove(change.pos, change.oldText.length);\n      recorder.insertLeft(change.pos, change.newText);\n    }\n  }\n  return recorder;\n}\n\nexport function commitChanges(tree: Tree, path: string, changes: Change[]) {\n  if (changes.length === 0) {\n    return false;\n  }\n\n  const recorder = createChangeRecorder(tree, path, changes);\n  tree.commitUpdate(recorder);\n  return true;\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/config.ts",
    "content": "import { SchematicsException, Tree } from '@angular-devkit/schematics';\n\n// The interfaces below are generated from the Angular CLI configuration schema\n// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json\nexport interface AppConfig {\n  /**\n   * Name of the app.\n   */\n  name?: string;\n  /**\n   * Directory where app files are placed.\n   */\n  appRoot?: string;\n  /**\n   * The root directory of the app.\n   */\n  root?: string;\n  /**\n   * The output directory for build results.\n   */\n  outDir?: string;\n  /**\n   * List of application assets.\n   */\n  assets?: (\n    | string\n    | {\n        /**\n         * The pattern to match.\n         */\n        glob?: string;\n        /**\n         * The dir to search within.\n         */\n        input?: string;\n        /**\n         * The output path (relative to the outDir).\n         */\n        output?: string;\n      }\n  )[];\n  /**\n   * URL where files will be deployed.\n   */\n  deployUrl?: string;\n  /**\n   * Base url for the application being built.\n   */\n  baseHref?: string;\n  /**\n   * The runtime platform of the app.\n   */\n  platform?: 'browser' | 'server';\n  /**\n   * The name of the start HTML file.\n   */\n  index?: string;\n  /**\n   * The name of the main entry-point file.\n   */\n  main?: string;\n  /**\n   * The name of the polyfills file.\n   */\n  polyfills?: string;\n  /**\n   * The name of the test entry-point file.\n   */\n  test?: string;\n  /**\n   * The name of the TypeScript configuration file.\n   */\n  tsconfig?: string;\n  /**\n   * The name of the TypeScript configuration file for unit tests.\n   */\n  testTsconfig?: string;\n  /**\n   * The prefix to apply to generated selectors.\n   */\n  prefix?: string;\n  /**\n   * Experimental support for a service worker from @angular/service-worker.\n   */\n  serviceWorker?: boolean;\n  /**\n   * Global styles to be included in the build.\n   */\n  styles?: (\n    | string\n    | {\n        input?: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Options to pass to style preprocessors\n   */\n  stylePreprocessorOptions?: {\n    /**\n     * Paths to include. Paths will be resolved to project root.\n     */\n    includePaths?: string[];\n  };\n  /**\n   * Global scripts to be included in the build.\n   */\n  scripts?: (\n    | string\n    | {\n        input: string;\n        [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n      }\n  )[];\n  /**\n   * Source file for environment config.\n   */\n  environmentSource?: string;\n  /**\n   * Name and corresponding file for environment config.\n   */\n  environments?: {\n    [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  };\n  appShell?: {\n    app: string;\n    route: string;\n  };\n}\n\nexport function getWorkspacePath(host: Tree): string {\n  const possibleFiles = ['/angular.json', '/.angular.json', '/workspace.json'];\n  const path = possibleFiles.filter((path) => host.exists(path))[0];\n\n  return path;\n}\n\nexport function getWorkspace(host: Tree) {\n  const path = getWorkspacePath(host);\n  const configBuffer = host.read(path);\n  if (configBuffer === null) {\n    throw new SchematicsException(`Could not find (${path})`);\n  }\n  const config = configBuffer.toString();\n\n  return JSON.parse(config);\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/find-component.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ComponentOptions {\n  component?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the component referred by a set of options passed to the schematics.\n */\nexport function findComponentFromOptions(\n  host: Tree,\n  options: ComponentOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.component) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findComponent(host, pathToCheck));\n  } else {\n    const componentPath = normalize(\n      '/' + options.path + '/' + options.component\n    );\n    const componentBaseName = normalize(componentPath).split('/').pop();\n\n    if (host.exists(componentPath)) {\n      return normalize(componentPath);\n    } else if (host.exists(componentPath + '.ts')) {\n      return normalize(componentPath + '.ts');\n    } else if (host.exists(componentPath + '.component.ts')) {\n      return normalize(componentPath + '.component.ts');\n    } else if (\n      host.exists(componentPath + '/' + componentBaseName + '.component.ts')\n    ) {\n      return normalize(\n        componentPath + '/' + componentBaseName + '.component.ts'\n      );\n    } else {\n      throw new Error(\n        `Specified component path ${componentPath} does not exist`\n      );\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" component to a generated file's path.\n */\nexport function findComponent(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const componentRe = /\\.component\\.ts$/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter((p) => componentRe.test(p));\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one component matches. Use skip-import option to skip importing ' +\n          'the component store into the closest component.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an Component. Use the skip-import ' +\n      'option to skip importing in Component.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/find-module.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nimport {\n  Path,\n  join,\n  normalize,\n  relative,\n  strings,\n  basename,\n  extname,\n  dirname,\n} from '@angular-devkit/core';\nimport { DirEntry, Tree } from '@angular-devkit/schematics';\n\nexport interface ModuleOptions {\n  module?: string;\n  name: string;\n  flat?: boolean;\n  path?: string;\n  skipImport?: boolean;\n}\n\n/**\n * Find the module referred by a set of options passed to the schematics.\n */\nexport function findModuleFromOptions(\n  host: Tree,\n  options: ModuleOptions\n): Path | undefined {\n  if (options.hasOwnProperty('skipImport') && options.skipImport) {\n    return undefined;\n  }\n\n  if (!options.module) {\n    const pathToCheck =\n      (options.path || '') +\n      (options.flat ? '' : '/' + strings.dasherize(options.name));\n\n    return normalize(findModule(host, pathToCheck));\n  } else {\n    const modulePath = normalize('/' + options.path + '/' + options.module);\n    const moduleBaseName = normalize(modulePath).split('/').pop();\n\n    if (host.exists(modulePath)) {\n      return normalize(modulePath);\n    } else if (host.exists(modulePath + '.ts')) {\n      return normalize(modulePath + '.ts');\n    } else if (host.exists(modulePath + '.module.ts')) {\n      return normalize(modulePath + '.module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '.module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '.module.ts');\n    } else if (host.exists(modulePath + '-module.ts')) {\n      return normalize(modulePath + '-module.ts');\n    } else if (host.exists(modulePath + '/' + moduleBaseName + '-module.ts')) {\n      return normalize(modulePath + '/' + moduleBaseName + '-module.ts');\n    } else {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n  }\n}\n\n/**\n * Function to find the \"closest\" module to a generated file's path.\n */\nexport function findModule(host: Tree, generateDir: string): Path {\n  let dir: DirEntry | null = host.getDir('/' + generateDir);\n\n  const moduleRe = /(\\.|-)module\\.ts$/;\n  const routingModuleRe = /-routing(\\.|-)module\\.ts/;\n\n  while (dir) {\n    const matches = dir.subfiles.filter(\n      (p) => moduleRe.test(p) && !routingModuleRe.test(p)\n    );\n\n    if (matches.length == 1) {\n      return join(dir.path, matches[0]);\n    } else if (matches.length > 1) {\n      throw new Error(\n        'More than one module matches. Use skip-import option to skip importing ' +\n          'the component into the closest module.'\n      );\n    }\n\n    dir = dir.parent;\n  }\n\n  throw new Error(\n    'Could not find an NgModule. Use the skip-import ' +\n      'option to skip importing in NgModule.'\n  );\n}\n\n/**\n * Build a relative path from one file path to another file path.\n */\nexport function buildRelativePath(from: string, to: string): string {\n  const {\n    path: fromPath,\n    filename: fromFileName,\n    directory: fromDirectory,\n  } = parsePath(from);\n  const {\n    path: toPath,\n    filename: toFileName,\n    directory: toDirectory,\n  } = parsePath(to);\n  const relativePath = relative(fromDirectory, toDirectory);\n  const fixedRelativePath = relativePath.startsWith('.')\n    ? relativePath\n    : `./${relativePath}`;\n\n  return !toFileName || toFileName === 'index.ts'\n    ? fixedRelativePath\n    : `${\n        fixedRelativePath.endsWith('/')\n          ? fixedRelativePath\n          : fixedRelativePath + '/'\n      }${convertToTypeScriptFileName(toFileName)}`;\n}\n\nfunction parsePath(path: string) {\n  const pathNormalized = normalize(path) as Path;\n  const filename = extname(pathNormalized) ? basename(pathNormalized) : '';\n  const directory = filename ? dirname(pathNormalized) : pathNormalized;\n  return {\n    path: pathNormalized,\n    filename,\n    directory,\n  };\n}\n/**\n * Strips the typescript extension and clears index filenames\n * foo.ts -> foo\n * index.ts -> empty\n */\nfunction convertToTypeScriptFileName(filename: string | undefined) {\n  return filename ? filename.replace(/(\\.ts)|(index\\.ts)$/, '') : '';\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/json-utilts.ts",
    "content": "// https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/json-utils.ts\nexport function findPropertyInAstObject(\n  node: any,\n  propertyName: string\n): any | null {\n  let maybeNode: any | null = null;\n  for (const property of node.properties) {\n    if (property.key.value == propertyName) {\n      maybeNode = property.value;\n    }\n  }\n\n  return maybeNode;\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/libs-version.ts",
    "content": "export const platformVersion = '^21.0.1';\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/ngrx-utils.ts",
    "content": "import * as ts from 'typescript';\nimport * as stringUtils from './strings';\nimport { InsertChange, Change, NoopChange } from './change';\nimport { Tree, SchematicsException, Rule } from '@angular-devkit/schematics';\nimport { normalize } from '@angular-devkit/core';\nimport { buildRelativePath } from './find-module';\nimport { addImportToModule, insertImport } from './ast-utils';\n\nexport function addReducerToState(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.reducers) {\n      return host;\n    }\n\n    const reducersPath = normalize(`/${options.path}/${options.reducers}`);\n\n    if (!host.exists(reducersPath)) {\n      throw new Error(`Specified reducers path ${reducersPath} does not exist`);\n    }\n\n    const text = host.read(reducersPath);\n    if (text === null) {\n      throw new SchematicsException(`File ${reducersPath} does not exist.`);\n    }\n\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      reducersPath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n\n    const relativePath = buildRelativePath(reducersPath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      reducersPath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n\n    const stateInterfaceInsert = addReducerToStateInterface(\n      source,\n      reducersPath,\n      options\n    );\n    const reducerMapInsert = addReducerToActionReducerMap(\n      source,\n      reducersPath,\n      options\n    );\n\n    const changes = [reducerImport, stateInterfaceInsert, reducerMapInsert];\n    const recorder = host.beginUpdate(reducersPath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\n/**\n * Insert the reducer into the first defined top level interface\n */\nexport function addReducerToStateInterface(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  const stateInterface = source.statements.find(\n    (stm) => stm.kind === ts.SyntaxKind.InterfaceDeclaration\n  );\n  let node = stateInterface as ts.Statement;\n\n  if (!node) {\n    return new NoopChange();\n  }\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.State;`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.members.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.members[expr.members.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `${matches[1]}${keyInsert}\\n`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Insert the reducer into the ActionReducerMap\n */\nexport function addReducerToActionReducerMap(\n  source: ts.SourceFile,\n  reducersPath: string,\n  options: { name: string; plural: boolean }\n): Change {\n  let initializer: any;\n  const actionReducerMap: any = source.statements\n    .filter((stm) => stm.kind === ts.SyntaxKind.VariableStatement)\n    .filter((stm: any) => !!stm.declarationList)\n    .map((stm: any) => {\n      const {\n        declarations,\n      }: {\n        declarations: ts.SyntaxKind.VariableDeclarationList[];\n      } = stm.declarationList;\n      const variable: any = declarations.find(\n        (decl: any) => decl.kind === ts.SyntaxKind.VariableDeclaration\n      );\n      const type = variable ? variable.type : {};\n\n      return { initializer: variable.initializer, type };\n    })\n    .filter((initWithType) => initWithType.type !== undefined)\n    .find(({ type }) => type.typeName.text === 'ActionReducerMap');\n\n  if (!actionReducerMap || !actionReducerMap.initializer) {\n    return new NoopChange();\n  }\n\n  let node = actionReducerMap.initializer;\n\n  const state = options.plural\n    ? stringUtils.pluralize(options.name)\n    : stringUtils.camelize(options.name);\n\n  const keyInsert = `[from${stringUtils.classify(\n    options.name\n  )}.${stringUtils.camelize(state)}FeatureKey]: from${stringUtils.classify(\n    options.name\n  )}.reducer,`;\n  const expr = node as any;\n  let position;\n  let toInsert;\n\n  if (expr.properties.length === 0) {\n    position = expr.getEnd() - 1;\n    toInsert = `  ${keyInsert}\\n`;\n  } else {\n    node = expr.properties[expr.properties.length - 1];\n    position = node.getEnd() + 1;\n    // Get the indentation of the last element, if any.\n    const text = node.getFullText(source);\n    const matches = text.match(/^\\r?\\n+(\\s*)/);\n\n    if (matches && matches.length > 0) {\n      toInsert = `\\n${matches[1]}${keyInsert}`;\n    } else {\n      toInsert = `\\n${keyInsert}`;\n    }\n  }\n\n  return new InsertChange(reducersPath, position, toInsert);\n}\n\n/**\n * Add reducer feature to NgModule\n */\nexport function addReducerImportToNgModule(options: any): Rule {\n  return (host: Tree) => {\n    if (!options.module) {\n      return host;\n    }\n\n    const modulePath = options.module;\n    if (!host.exists(options.module)) {\n      throw new Error(`Specified module path ${modulePath} does not exist`);\n    }\n\n    const text = host.read(modulePath);\n    if (text === null) {\n      throw new SchematicsException(`File ${modulePath} does not exist.`);\n    }\n    const sourceText = text.toString('utf-8');\n\n    const source = ts.createSourceFile(\n      modulePath,\n      sourceText,\n      ts.ScriptTarget.Latest,\n      true\n    );\n\n    const commonImports = [\n      insertImport(source, modulePath, 'StoreModule', '@ngrx/store'),\n    ];\n\n    const reducerPath =\n      `/${options.path}/` +\n      (options.flat ? '' : stringUtils.dasherize(options.name) + '/') +\n      (options.group ? 'reducers/' : '') +\n      stringUtils.dasherize(options.name) +\n      '.reducer';\n    const relativePath = buildRelativePath(modulePath, reducerPath);\n    const reducerImport = insertImport(\n      source,\n      modulePath,\n      `* as from${stringUtils.classify(options.name)}`,\n      relativePath,\n      true\n    );\n    const state = options.plural\n      ? stringUtils.pluralize(options.name)\n      : stringUtils.camelize(options.name);\n    const [storeNgModuleImport] = addImportToModule(\n      source,\n      modulePath,\n      `StoreModule.forFeature(from${stringUtils.classify(\n        options.name\n      )}.${state}FeatureKey, from${stringUtils.classify(\n        options.name\n      )}.reducer)`,\n      relativePath\n    );\n    const changes = [...commonImports, reducerImport, storeNgModuleImport];\n    const recorder = host.beginUpdate(modulePath);\n    for (const change of changes) {\n      if (change instanceof InsertChange) {\n        recorder.insertLeft(change.pos, change.toAdd);\n      }\n    }\n    host.commitUpdate(recorder);\n\n    return host;\n  };\n}\n\nexport function omit<T extends { [key: string]: any }>(\n  object: T,\n  keyToRemove: keyof T\n): Partial<T> {\n  return Object.keys(object)\n    .filter((key) => key !== keyToRemove)\n    .reduce((result, key) => Object.assign(result, { [key]: object[key] }), {});\n}\n\nexport function getPrefix(options: { prefix?: string }) {\n  return stringUtils.camelize(options.prefix || 'load');\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/package.ts",
    "content": "import { Tree } from '@angular-devkit/schematics';\n\n/**\n * Adds a package to the package.json\n */\nexport function addPackageToPackageJson(\n  host: Tree,\n  type: string,\n  pkg: string,\n  version: string\n): Tree {\n  if (host.exists('package.json')) {\n    const sourceText = host.read('package.json')?.toString('utf-8') ?? '{}';\n    const json = JSON.parse(sourceText);\n    if (!json[type]) {\n      json[type] = {};\n    }\n\n    if (!json[type][pkg]) {\n      json[type][pkg] = version;\n    }\n\n    host.overwrite('package.json', JSON.stringify(json, null, 2));\n  }\n\n  return host;\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/parse-name.ts",
    "content": "import { Path, basename, dirname, normalize } from '@angular-devkit/core';\n\nexport interface Location {\n  name: string;\n  path: Path;\n}\n\nexport function parseName(path: string, name: string): Location {\n  const nameWithoutPath = basename(name as Path);\n  const namePath = dirname((path + '/' + name) as Path);\n\n  return {\n    name: nameWithoutPath,\n    path: normalize('/' + namePath),\n  };\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/project.ts",
    "content": "import { workspaces } from '@angular-devkit/core';\nimport { getWorkspace } from './config';\nimport { SchematicsException, Tree } from '@angular-devkit/schematics';\n\nexport interface WorkspaceProject {\n  root: string;\n  projectType: string;\n  architect: {\n    [key: string]: workspaces.TargetDefinition;\n  };\n}\n\nexport function getProject(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n): WorkspaceProject {\n  const workspace = getWorkspace(host);\n\n  if (!options.project) {\n    const defaultProject = (workspace as { defaultProject?: string })\n      .defaultProject;\n    options.project =\n      defaultProject !== undefined\n        ? defaultProject\n        : Object.keys(workspace.projects)[0];\n  }\n\n  return workspace.projects[options.project];\n}\n\nexport function getProjectPath(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  if (project.root.slice(-1) === '/') {\n    project.root = project.root.substring(0, project.root.length - 1);\n  }\n\n  if (options.path === undefined) {\n    const projectDirName =\n      project.projectType === 'application' ? 'app' : 'lib';\n\n    return `${project.root ? `/${project.root}` : ''}/src/${projectDirName}`;\n  }\n\n  return options.path;\n}\n\nexport function isLib(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  const project = getProject(host, options);\n\n  return project.projectType === 'library';\n}\n\nexport function getProjectMainFile(\n  host: Tree,\n  options: { project?: string | undefined; path?: string | undefined }\n) {\n  if (isLib(host, options)) {\n    throw new SchematicsException(`Invalid project type`);\n  }\n  const project = getProject(host, options);\n  const projectOptions = project.architect['build'].options;\n\n  if (!projectOptions?.main && !projectOptions?.browser) {\n    throw new SchematicsException(`Could not find the main file ${project}`);\n  }\n\n  return (projectOptions.browser || projectOptions.main) as string;\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/standalone.ts",
    "content": "// copied from https://github.com/angular/angular-cli/blob/17.3.x/packages/schematics/angular/private/standalone.ts\nimport {\n  SchematicsException,\n  Tree,\n  UpdateRecorder,\n} from '@angular-devkit/schematics';\nimport { dirname, join } from 'path';\nimport { insertImport } from './ast-utils';\nimport { InsertChange } from './change';\nimport * as ts from 'typescript';\n\n/** App config that was resolved to its source node. */\ninterface ResolvedAppConfig {\n  /** Tree-relative path of the file containing the app config. */\n  filePath: string;\n\n  /** Node defining the app config. */\n  node: ts.ObjectLiteralExpression;\n}\n\n/**\n * Checks whether a providers function is being called in a `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path of the file in which to check.\n * @param functionName Name of the function to search for.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function callsProvidersFunction(\n  tree: Tree,\n  filePath: string,\n  functionName: string\n): boolean {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const appConfig = bootstrapCall\n    ? findAppConfig(bootstrapCall, tree, filePath)\n    : null;\n  const providersLiteral = appConfig\n    ? findProvidersLiteral(appConfig.node)\n    : null;\n\n  return !!providersLiteral?.elements.some(\n    (el) =>\n      ts.isCallExpression(el) &&\n      ts.isIdentifier(el.expression) &&\n      el.expression.text === functionName\n  );\n}\n\n/**\n * Adds a providers function call to the `bootstrapApplication` call.\n * @param tree File tree of the project.\n * @param filePath Path to the file that should be updated.\n * @param functionName Name of the function that should be called.\n * @param importPath Path from which to import the function.\n * @param args Arguments to use when calling the function.\n * @return The file path that the provider was added to.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function addFunctionalProvidersToStandaloneBootstrap(\n  tree: Tree,\n  filePath: string,\n  functionName: string,\n  importPath: string,\n  args: ts.Expression[] = []\n): string {\n  const sourceFile = createSourceFile(tree, filePath);\n  const bootstrapCall = findBootstrapApplicationCall(sourceFile);\n  const addImports = (file: ts.SourceFile, recorder: UpdateRecorder) => {\n    const change = insertImport(file, file.getText(), functionName, importPath);\n\n    if (change instanceof InsertChange) {\n      recorder.insertLeft(change.pos, change.toAdd);\n    }\n  };\n\n  if (!bootstrapCall) {\n    throw new SchematicsException(\n      `Could not find bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const providersCall = ts.factory.createCallExpression(\n    ts.factory.createIdentifier(functionName),\n    undefined,\n    args\n  );\n\n  // If there's only one argument, we have to create a new object literal.\n  if (bootstrapCall.arguments.length === 1) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall, providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // If the config is a `mergeApplicationProviders` call, add another config to it.\n  if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {\n    const recorder = tree.beginUpdate(filePath);\n    addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);\n    addImports(sourceFile, recorder);\n    tree.commitUpdate(recorder);\n\n    return filePath;\n  }\n\n  // Otherwise attempt to merge into the current config.\n  const appConfig = findAppConfig(bootstrapCall, tree, filePath);\n\n  if (!appConfig) {\n    throw new SchematicsException(\n      `Could not statically analyze config in bootstrapApplication call in ${filePath}`\n    );\n  }\n\n  const { filePath: configFilePath, node: config } = appConfig;\n  const recorder = tree.beginUpdate(configFilePath);\n  const providersLiteral = findProvidersLiteral(config);\n\n  addImports(config.getSourceFile(), recorder);\n\n  if (providersLiteral) {\n    // If there's a `providers` array, add the import to it.\n    addElementToArray(providersLiteral, providersCall, recorder);\n  } else {\n    // Otherwise add a `providers` array to the existing object literal.\n    addProvidersToObjectLiteral(config, providersCall, recorder);\n  }\n\n  tree.commitUpdate(recorder);\n\n  return configFilePath;\n}\n\n/**\n * Finds the call to `bootstrapApplication` within a file.\n * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from\n * `@schematics/angular/utility` instead.\n */\nexport function findBootstrapApplicationCall(\n  sourceFile: ts.SourceFile\n): ts.CallExpression | null {\n  const localName = findImportLocalName(\n    sourceFile,\n    'bootstrapApplication',\n    '@angular/platform-browser'\n  );\n\n  if (!localName) {\n    return null;\n  }\n\n  let result: ts.CallExpression | null = null;\n\n  sourceFile.forEachChild(function walk(node) {\n    if (\n      ts.isCallExpression(node) &&\n      ts.isIdentifier(node.expression) &&\n      node.expression.text === localName\n    ) {\n      result = node;\n    }\n\n    if (!result) {\n      node.forEachChild(walk);\n    }\n  });\n\n  return result;\n}\n\n/** Finds the `providers` array literal within an application config. */\nfunction findProvidersLiteral(\n  config: ts.ObjectLiteralExpression\n): ts.ArrayLiteralExpression | null {\n  for (const prop of config.properties) {\n    if (\n      ts.isPropertyAssignment(prop) &&\n      ts.isIdentifier(prop.name) &&\n      prop.name.text === 'providers' &&\n      ts.isArrayLiteralExpression(prop.initializer)\n    ) {\n      return prop.initializer;\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the node that defines the app config from a bootstrap call.\n * @param bootstrapCall Call for which to resolve the config.\n * @param tree File tree of the project.\n * @param filePath File path of the bootstrap call.\n */\nfunction findAppConfig(\n  bootstrapCall: ts.CallExpression,\n  tree: Tree,\n  filePath: string\n): ResolvedAppConfig | null {\n  if (bootstrapCall.arguments.length > 1) {\n    const config = bootstrapCall.arguments[1];\n\n    if (ts.isObjectLiteralExpression(config)) {\n      return { filePath, node: config };\n    }\n\n    if (ts.isIdentifier(config)) {\n      return resolveAppConfigFromIdentifier(config, tree, filePath);\n    }\n  }\n\n  return null;\n}\n\n/**\n * Resolves the app config from an identifier referring to it.\n * @param identifier Identifier referring to the app config.\n * @param tree File tree of the project.\n * @param bootstapFilePath Path of the bootstrap call.\n */\nfunction resolveAppConfigFromIdentifier(\n  identifier: ts.Identifier,\n  tree: Tree,\n  bootstapFilePath: string\n): ResolvedAppConfig | null {\n  const sourceFile = identifier.getSourceFile();\n\n  for (const node of sourceFile.statements) {\n    // Only look at relative imports. This will break if the app uses a path\n    // mapping to refer to the import, but in order to resolve those, we would\n    // need knowledge about the entire program.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !node.importClause?.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings) ||\n      !ts.isStringLiteralLike(node.moduleSpecifier) ||\n      !node.moduleSpecifier.text.startsWith('.')\n    ) {\n      continue;\n    }\n\n    for (const specifier of node.importClause.namedBindings.elements) {\n      if (specifier.name.text !== identifier.text) {\n        continue;\n      }\n\n      // Look for a variable with the imported name in the file. Note that ideally we would use\n      // the type checker to resolve this, but we can't because these utilities are set up to\n      // operate on individual files, not the entire program.\n      const filePath = join(\n        dirname(bootstapFilePath),\n        node.moduleSpecifier.text + '.ts'\n      );\n      const importedSourceFile = createSourceFile(tree, filePath);\n      const resolvedVariable = findAppConfigFromVariableName(\n        importedSourceFile,\n        (specifier.propertyName || specifier.name).text\n      );\n\n      if (resolvedVariable) {\n        return { filePath, node: resolvedVariable };\n      }\n    }\n  }\n\n  const variableInSameFile = findAppConfigFromVariableName(\n    sourceFile,\n    identifier.text\n  );\n\n  return variableInSameFile\n    ? { filePath: bootstapFilePath, node: variableInSameFile }\n    : null;\n}\n\n/**\n * Finds an app config within the top-level variables of a file.\n * @param sourceFile File in which to search for the config.\n * @param variableName Name of the variable containing the config.\n */\nfunction findAppConfigFromVariableName(\n  sourceFile: ts.SourceFile,\n  variableName: string\n): ts.ObjectLiteralExpression | null {\n  for (const node of sourceFile.statements) {\n    if (ts.isVariableStatement(node)) {\n      for (const decl of node.declarationList.declarations) {\n        if (\n          ts.isIdentifier(decl.name) &&\n          decl.name.text === variableName &&\n          decl.initializer &&\n          ts.isObjectLiteralExpression(decl.initializer)\n        ) {\n          return decl.initializer;\n        }\n      }\n    }\n  }\n\n  return null;\n}\n\n/**\n * Finds the local name of an imported symbol. Could be the symbol name itself or its alias.\n * @param sourceFile File within which to search for the import.\n * @param name Actual name of the import, not its local alias.\n * @param moduleName Name of the module from which the symbol is imported.\n */\nfunction findImportLocalName(\n  sourceFile: ts.SourceFile,\n  name: string,\n  moduleName: string\n): string | null {\n  for (const node of sourceFile.statements) {\n    // Only look for top-level imports.\n    if (\n      !ts.isImportDeclaration(node) ||\n      !ts.isStringLiteral(node.moduleSpecifier) ||\n      node.moduleSpecifier.text !== moduleName\n    ) {\n      continue;\n    }\n\n    // Filter out imports that don't have the right shape.\n    if (\n      !node.importClause ||\n      !node.importClause.namedBindings ||\n      !ts.isNamedImports(node.importClause.namedBindings)\n    ) {\n      continue;\n    }\n\n    // Look through the elements of the declaration for the specific import.\n    for (const element of node.importClause.namedBindings.elements) {\n      if ((element.propertyName || element.name).text === name) {\n        // The local name is always in `name`.\n        return element.name.text;\n      }\n    }\n  }\n\n  return null;\n}\n\n/** Creates a source file from a file path within a project. */\nfunction createSourceFile(tree: Tree, filePath: string): ts.SourceFile {\n  return ts.createSourceFile(\n    filePath,\n    tree.readText(filePath),\n    ts.ScriptTarget.Latest,\n    true\n  );\n}\n\n/**\n * Creates a new app config object literal and adds it to a call expression as an argument.\n * @param call Call to which to add the config.\n * @param expression Expression that should inserted into the new config.\n * @param recorder Recorder to which to log the change.\n */\nfunction addNewAppConfigToCall(\n  call: ts.CallExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newCall = ts.factory.updateCallExpression(\n    call,\n    call.expression,\n    call.typeArguments,\n    [\n      ...call.arguments,\n      ts.factory.createObjectLiteralExpression(\n        [\n          ts.factory.createPropertyAssignment(\n            'providers',\n            ts.factory.createArrayLiteralExpression([expression])\n          ),\n        ],\n        true\n      ),\n    ]\n  );\n\n  recorder.remove(call.getStart(), call.getWidth());\n  recorder.insertRight(\n    call.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newCall, call.getSourceFile())\n  );\n}\n\n/**\n * Adds an element to an array literal expression.\n * @param node Array to which to add the element.\n * @param element Element to be added.\n * @param recorder Recorder to which to log the change.\n */\nfunction addElementToArray(\n  node: ts.ArrayLiteralExpression,\n  element: ts.Expression,\n  recorder: UpdateRecorder\n): void {\n  const newLiteral = ts.factory.updateArrayLiteralExpression(node, [\n    ...node.elements,\n    element,\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(ts.EmitHint.Unspecified, newLiteral, node.getSourceFile())\n  );\n}\n\n/**\n * Adds a `providers` property to an object literal.\n * @param node Literal to which to add the `providers`.\n * @param expression Provider that should be part of the generated `providers` array.\n * @param recorder Recorder to which to log the change.\n */\nfunction addProvidersToObjectLiteral(\n  node: ts.ObjectLiteralExpression,\n  expression: ts.Expression,\n  recorder: UpdateRecorder\n) {\n  const newOptionsLiteral = ts.factory.updateObjectLiteralExpression(node, [\n    ...node.properties,\n    ts.factory.createPropertyAssignment(\n      'providers',\n      ts.factory.createArrayLiteralExpression([expression])\n    ),\n  ]);\n  recorder.remove(node.getStart(), node.getWidth());\n  recorder.insertRight(\n    node.getStart(),\n    ts\n      .createPrinter()\n      .printNode(\n        ts.EmitHint.Unspecified,\n        newOptionsLiteral,\n        node.getSourceFile()\n      )\n  );\n}\n\n/** Checks whether a node is a call to `mergeApplicationConfig`. */\nfunction isMergeAppConfigCall(node: ts.Node): node is ts.CallExpression {\n  if (!ts.isCallExpression(node)) {\n    return false;\n  }\n\n  const localName = findImportLocalName(\n    node.getSourceFile(),\n    'mergeApplicationConfig',\n    '@angular/core'\n  );\n\n  return (\n    !!localName &&\n    ts.isIdentifier(node.expression) &&\n    node.expression.text === localName\n  );\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/strings.ts",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\nconst STRING_DASHERIZE_REGEXP = /[ _]/g;\nconst STRING_DECAMELIZE_REGEXP = /([a-z\\d])([A-Z])/g;\nconst STRING_CAMELIZE_REGEXP = /(-|_|\\.|\\s)+(.)?/g;\nconst STRING_UNDERSCORE_REGEXP_1 = /([a-z\\d])([A-Z]+)/g;\nconst STRING_UNDERSCORE_REGEXP_2 = /-|\\s+/g;\n\n/**\n * Converts a camelized string into all lower case separated by underscores.\n *\n ```javascript\n decamelize('innerHTML');         // 'inner_html'\n decamelize('action_name');       // 'action_name'\n decamelize('css-class-name');    // 'css-class-name'\n decamelize('my favorite items'); // 'my favorite items'\n ```\n */\nexport function decamelize(str: string): string {\n  return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();\n}\n\n/**\n Replaces underscores, spaces, or camelCase with dashes.\n\n ```javascript\n dasherize('innerHTML');         // 'inner-html'\n dasherize('action_name');       // 'action-name'\n dasherize('css-class-name');    // 'css-class-name'\n dasherize('my favorite items'); // 'my-favorite-items'\n ```\n */\nexport function dasherize(str?: string): string {\n  return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-');\n}\n\n/**\n Returns the lowerCamelCase form of a string.\n\n ```javascript\n camelize('innerHTML');          // 'innerHTML'\n camelize('action_name');        // 'actionName'\n camelize('css-class-name');     // 'cssClassName'\n camelize('my favorite items');  // 'myFavoriteItems'\n camelize('My Favorite Items');  // 'myFavoriteItems'\n ```\n */\nexport function camelize(str: string): string {\n  return str\n    .replace(\n      STRING_CAMELIZE_REGEXP,\n      (_match: string, _separator: string, chr: string) => {\n        return chr ? chr.toUpperCase() : '';\n      }\n    )\n    .replace(/^([A-Z])/, (match: string) => match.toLowerCase());\n}\n\n/**\n Returns the UpperCamelCase form of a string.\n\n ```javascript\n 'innerHTML'.classify();          // 'InnerHTML'\n 'action_name'.classify();        // 'ActionName'\n 'css-class-name'.classify();     // 'CssClassName'\n 'my favorite items'.classify();  // 'MyFavoriteItems'\n ```\n */\nexport function classify(str: string): string {\n  return str\n    .split('.')\n    .map((part) => capitalize(camelize(part)))\n    .join('.');\n}\n\n/**\n More general than decamelize. Returns the lower\\_case\\_and\\_underscored\n form of a string.\n\n ```javascript\n 'innerHTML'.underscore();          // 'inner_html'\n 'action_name'.underscore();        // 'action_name'\n 'css-class-name'.underscore();     // 'css_class_name'\n 'my favorite items'.underscore();  // 'my_favorite_items'\n ```\n */\nexport function underscore(str: string): string {\n  return str\n    .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2')\n    .replace(STRING_UNDERSCORE_REGEXP_2, '_')\n    .toLowerCase();\n}\n\n/**\n Returns the Capitalized form of a string\n\n ```javascript\n 'innerHTML'.capitalize()         // 'InnerHTML'\n 'action_name'.capitalize()       // 'Action_name'\n 'css-class-name'.capitalize()    // 'Css-class-name'\n 'my favorite items'.capitalize() // 'My favorite items'\n ```\n */\nexport function capitalize(str: string): string {\n  return str.charAt(0).toUpperCase() + str.substring(1);\n}\n\n/**\n Returns the plural form of a string\n\n ```javascript\n 'innerHTML'.pluralize()         // 'innerHTMLs'\n 'action_name'.pluralize()       // 'actionNames'\n 'css-class-name'.pluralize()    // 'cssClassNames'\n 'regex'.pluralize()            // 'regexes'\n 'user'.pluralize()             // 'users'\n ```\n */\nexport function pluralize(str: string): string {\n  return camelize(\n    [/([^aeiou])y$/, /()fe?$/, /([^aeiou]o|[sxz]|[cs]h)$/].map(\n      (c, i) => (str = str.replace(c, `$1${'iv'[i] || ''}e`))\n    ) && str + 's'\n  );\n}\n\nexport function group(name: string, group: string | undefined) {\n  return group ? `${group}/${name}` : name;\n}\n\nexport function featurePath(\n  group: boolean | undefined,\n  flat: boolean | undefined,\n  path: string,\n  name: string\n) {\n  if (group && !flat) {\n    return `../../${path}/${name}/`;\n  }\n\n  return group ? `../${path}/` : './';\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/update.ts",
    "content": "import {\n  Rule,\n  SchematicContext,\n  Tree,\n  SchematicsException,\n} from '@angular-devkit/schematics';\n\nexport function updatePackage(name: string): Rule {\n  return (tree: Tree, context: SchematicContext) => {\n    const pkgPath = '/package.json';\n    const buffer = tree.read(pkgPath);\n    if (buffer === null) {\n      throw new SchematicsException('Could not read package.json');\n    }\n    const content = buffer.toString();\n    const pkg = JSON.parse(content);\n\n    if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {\n      throw new SchematicsException('Error reading package.json');\n    }\n\n    const dependencyCategories = ['dependencies', 'devDependencies'];\n\n    dependencyCategories.forEach((category) => {\n      const packageName = `@ngrx/${name}`;\n\n      if (pkg[category] && pkg[category][packageName]) {\n        const firstChar = pkg[category][packageName][0];\n        const suffix = match(firstChar, '^') || match(firstChar, '~');\n\n        pkg[category][packageName] = `${suffix}6.0.0`;\n      }\n    });\n\n    tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));\n\n    return tree;\n  };\n}\n\nfunction match(value: string, test: string) {\n  return value === test ? test : '';\n}\n"
  },
  {
    "path": "modules/store-devtools/schematics-core/utility/visitors.ts",
    "content": "import * as ts from 'typescript';\nimport { normalize, resolve } from '@angular-devkit/core';\nimport { Tree, DirEntry } from '@angular-devkit/schematics';\n\nexport function visitTSSourceFiles<Result = void>(\n  tree: Tree,\n  visitor: (\n    sourceFile: ts.SourceFile,\n    tree: Tree,\n    result?: Result\n  ) => Result | undefined\n): Result | undefined {\n  let result: Result | undefined = undefined;\n  for (const sourceFile of visit(tree.root)) {\n    result = visitor(sourceFile, tree, result);\n  }\n\n  return result;\n}\n\nexport function visitTemplates(\n  tree: Tree,\n  visitor: (\n    template: {\n      fileName: string;\n      content: string;\n      inline: boolean;\n      start: number;\n    },\n    tree: Tree\n  ) => void\n): void {\n  visitTSSourceFiles(tree, (source) => {\n    visitComponents(source, (_, decoratorExpressionNode) => {\n      ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n        if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name)) {\n          if (\n            n.name.text === 'template' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            // Need to add an offset of one to the start because the template quotes are\n            // not part of the template content.\n            const templateStartIdx = n.initializer.getStart() + 1;\n            visitor(\n              {\n                fileName: source.fileName,\n                content: n.initializer.text,\n                inline: true,\n                start: templateStartIdx,\n              },\n              tree\n            );\n            return;\n          } else if (\n            n.name.text === 'templateUrl' &&\n            ts.isStringLiteralLike(n.initializer)\n          ) {\n            const parts = normalize(source.fileName).split('/').slice(0, -1);\n            const templatePath = resolve(\n              normalize(parts.join('/')),\n              normalize(n.initializer.text)\n            );\n            if (!tree.exists(templatePath)) {\n              return;\n            }\n\n            const fileContent = tree.read(templatePath);\n            if (!fileContent) {\n              return;\n            }\n\n            visitor(\n              {\n                fileName: templatePath,\n                content: fileContent.toString(),\n                inline: false,\n                start: 0,\n              },\n              tree\n            );\n            return;\n          }\n        }\n\n        ts.forEachChild(n, findTemplates);\n      });\n    });\n  });\n}\n\nexport function visitNgModuleImports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    importNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'imports');\n}\n\nexport function visitNgModuleExports(\n  sourceFile: ts.SourceFile,\n  callback: (\n    exportNode: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void\n) {\n  visitNgModuleProperty(sourceFile, callback, 'exports');\n}\n\nfunction visitNgModuleProperty(\n  sourceFile: ts.SourceFile,\n  callback: (\n    nodes: ts.PropertyAssignment,\n    elementExpressions: ts.NodeArray<ts.Expression>\n  ) => void,\n  property: string\n) {\n  visitNgModules(sourceFile, (_, decoratorExpressionNode) => {\n    ts.forEachChild(decoratorExpressionNode, function findTemplates(n) {\n      if (\n        ts.isPropertyAssignment(n) &&\n        ts.isIdentifier(n.name) &&\n        n.name.text === property &&\n        ts.isArrayLiteralExpression(n.initializer)\n      ) {\n        callback(n, n.initializer.elements);\n        return;\n      }\n\n      ts.forEachChild(n, findTemplates);\n    });\n  });\n}\nexport function visitComponents(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'Component', callback);\n}\n\nexport function visitNgModules(\n  sourceFile: ts.SourceFile,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  visitDecorator(sourceFile, 'NgModule', callback);\n}\n\nexport function visitDecorator(\n  sourceFile: ts.SourceFile,\n  decoratorName: string,\n  callback: (\n    classDeclarationNode: ts.ClassDeclaration,\n    decoratorExpressionNode: ts.ObjectLiteralExpression\n  ) => void\n) {\n  ts.forEachChild(sourceFile, function findClassDeclaration(node) {\n    if (!ts.isClassDeclaration(node)) {\n      ts.forEachChild(node, findClassDeclaration);\n    }\n\n    const classDeclarationNode = node as ts.ClassDeclaration;\n    const decorators = ts.getDecorators(classDeclarationNode);\n\n    if (!decorators || !decorators.length) {\n      return;\n    }\n\n    const componentDecorator = decorators.find((d) => {\n      return (\n        ts.isCallExpression(d.expression) &&\n        ts.isIdentifier(d.expression.expression) &&\n        d.expression.expression.text === decoratorName\n      );\n    });\n\n    if (!componentDecorator) {\n      return;\n    }\n\n    const { expression } = componentDecorator;\n    if (!ts.isCallExpression(expression)) {\n      return;\n    }\n\n    const [arg] = expression.arguments;\n    if (!arg || !ts.isObjectLiteralExpression(arg)) {\n      return;\n    }\n\n    callback(classDeclarationNode, arg);\n  });\n}\n\nexport function visitImportDeclaration(\n  node: ts.Node,\n  callback: (\n    importDeclaration: ts.ImportDeclaration,\n    moduleName?: string\n  ) => void\n) {\n  if (ts.isImportDeclaration(node)) {\n    const moduleSpecifier = node.moduleSpecifier.getText();\n    const moduleName = moduleSpecifier.replaceAll('\"', '').replaceAll(\"'\", '');\n\n    callback(node, moduleName);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitImportDeclaration(child, callback);\n  });\n}\n\nexport function visitImportSpecifier(\n  node: ts.ImportDeclaration,\n  callback: (importSpecifier: ts.ImportSpecifier) => void\n) {\n  const { importClause } = node;\n  if (!importClause) {\n    return;\n  }\n\n  const importClauseChildren = importClause.getChildren();\n  for (const namedImport of importClauseChildren) {\n    if (ts.isNamedImports(namedImport)) {\n      const namedImportChildren = namedImport.elements;\n      for (const importSpecifier of namedImportChildren) {\n        if (ts.isImportSpecifier(importSpecifier)) {\n          callback(importSpecifier);\n        }\n      }\n    }\n  }\n}\n\nexport function visitTypeReference(\n  node: ts.Node,\n  callback: (typeReference: ts.TypeReferenceNode) => void\n) {\n  if (ts.isTypeReferenceNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeReference(child, callback);\n  });\n}\n\nexport function visitTypeLiteral(\n  node: ts.Node,\n  callback: (typeLiteral: ts.TypeLiteralNode) => void\n) {\n  if (ts.isTypeLiteralNode(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitTypeLiteral(child, callback);\n  });\n}\n\nexport function visitCallExpression(\n  node: ts.Node,\n  callback: (callExpression: ts.CallExpression) => void\n) {\n  if (ts.isCallExpression(node)) {\n    callback(node);\n  }\n\n  ts.forEachChild(node, (child) => {\n    visitCallExpression(child, callback);\n  });\n}\n\nfunction* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {\n  for (const path of directory.subfiles) {\n    if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {\n      const entry = directory.file(path);\n      if (entry) {\n        const content = entry.content;\n        const source = ts.createSourceFile(\n          entry.path,\n          content.toString().replace(/^\\uFEFF/, ''),\n          ts.ScriptTarget.Latest,\n          true\n        );\n        yield source;\n      }\n    }\n  }\n\n  for (const path of directory.subdirs) {\n    if (path === 'node_modules') {\n      continue;\n    }\n\n    yield* visit(directory.dir(path));\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/spec/config.spec.ts",
    "content": "import { Action } from '@ngrx/store';\n\nimport { createConfig, DEFAULT_NAME, noMonitor } from '../src/config';\n\nconst defaultFeatures = {\n  pause: true,\n  lock: true,\n  persist: true,\n  export: true,\n  import: 'custom',\n  jump: true,\n  skip: true,\n  reorder: true,\n  dispatch: true,\n  test: true,\n};\n\ndescribe('StoreDevtoolsOptions', () => {\n  it('creates default options with empty object given', () => {\n    const config = createConfig({});\n    expect(config).toEqual({\n      maxAge: false,\n      monitor: noMonitor,\n      actionSanitizer: undefined,\n      stateSanitizer: undefined,\n      name: DEFAULT_NAME,\n      serialize: false,\n      logOnly: false,\n      autoPause: false,\n      features: defaultFeatures,\n      trace: false,\n      traceLimit: 75,\n      connectInZone: false,\n    });\n  });\n\n  it('creates options that contain passed in options', () => {\n    function stateSanitizer(state: any, index: number): any {\n      return state;\n    }\n    function actionSanitizer(action: Action, id: number): Action {\n      return action;\n    }\n    const config = createConfig({\n      maxAge: 20,\n      actionSanitizer,\n      stateSanitizer,\n      name: 'ABC',\n      serialize: true,\n      autoPause: true,\n      trace: true,\n      traceLimit: 20,\n      features: {\n        test: true,\n      },\n    });\n    expect(config).toEqual({\n      maxAge: 20,\n      monitor: noMonitor,\n      actionSanitizer,\n      stateSanitizer,\n      name: 'ABC',\n      serialize: true,\n      logOnly: false,\n      autoPause: true,\n      trace: true,\n      traceLimit: 20,\n      features: {\n        test: true,\n      },\n      connectInZone: false,\n    });\n  });\n\n  it('can be initialized with a function returning options', () => {\n    const config = createConfig(() => ({ maxAge: 15 }));\n    expect(config).toEqual({\n      maxAge: 15,\n      monitor: noMonitor,\n      actionSanitizer: undefined,\n      stateSanitizer: undefined,\n      name: DEFAULT_NAME,\n      serialize: false,\n      logOnly: false,\n      autoPause: false,\n      trace: false,\n      traceLimit: 75,\n      features: defaultFeatures,\n      connectInZone: false,\n    });\n  });\n\n  it('logOnly will set features', () => {\n    const config = createConfig({\n      logOnly: true,\n    });\n    expect(config).toEqual({\n      maxAge: false,\n      monitor: noMonitor,\n      actionSanitizer: undefined,\n      stateSanitizer: undefined,\n      name: DEFAULT_NAME,\n      serialize: false,\n      logOnly: true,\n      autoPause: false,\n      trace: false,\n      traceLimit: 75,\n      features: {\n        pause: true,\n        export: true,\n        test: true,\n      },\n      connectInZone: false,\n    });\n  });\n\n  it('import \"true\" is updated to \"custom\"', () => {\n    // setting import to true results in an error while importing a persisted state into the devtools\n    // the imported state only contains the new state without the actions (and config)\n    // while testing this, the imported state also wasn't correct and contained the initial state values\n    const config = createConfig({\n      features: {\n        import: true,\n      },\n    });\n    expect(config).toEqual({\n      maxAge: false,\n      monitor: noMonitor,\n      actionSanitizer: undefined,\n      stateSanitizer: undefined,\n      name: DEFAULT_NAME,\n      serialize: false,\n      logOnly: false,\n      autoPause: false,\n      features: {\n        import: 'custom',\n      },\n      trace: false,\n      traceLimit: 75,\n      connectInZone: false,\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store-devtools/spec/extension.spec.ts",
    "content": "import { ComputedState, LiftedAction, LiftedActions } from './../src/reducer';\nimport { PERFORM_ACTION, PerformAction } from './../src/actions';\nimport {\n  ExtensionActionTypes,\n  REDUX_DEVTOOLS_EXTENSION,\n  ReduxDevtoolsExtensionConfig,\n  ReduxDevtoolsExtensionConnection,\n} from './../src/extension';\nimport { Action } from '@ngrx/store';\n\nimport { DevtoolsExtension, ReduxDevtoolsExtension } from '../src/extension';\nimport {\n  createConfig,\n  DevToolsFeatureOptions,\n  STORE_DEVTOOLS_CONFIG,\n  StoreDevtoolsConfig,\n} from '../src/config';\nimport { unliftState } from '../src/utils';\nimport { TestBed } from '@angular/core/testing';\nimport { DevtoolsDispatcher } from '../src/devtools-dispatcher';\nimport { Mock, vi } from 'vitest';\n\nfunction createOptions(\n  name = 'NgRx Store DevTools',\n  features: DevToolsFeatureOptions = {\n    pause: true,\n    lock: true,\n    persist: true,\n    export: true,\n    import: 'custom',\n    jump: true,\n    skip: true,\n    reorder: true,\n    dispatch: true,\n    test: true,\n  },\n  serialize: boolean | undefined = false,\n  maxAge: false | number = false,\n  autoPause = false,\n  trace = false,\n  traceLimit = 75\n) {\n  const options: ReduxDevtoolsExtensionConfig = {\n    name,\n    features,\n    serialize,\n    autoPause,\n    trace,\n    traceLimit,\n  };\n  if (maxAge !== false /* support === 0 */) {\n    options.maxAge = maxAge;\n  }\n  return options;\n}\n\nfunction createState(\n  actionsById?: LiftedActions,\n  computedStates?: ComputedState[],\n  isLocked = false,\n  isPaused = false\n) {\n  return {\n    monitorState: null,\n    nextActionId: 1,\n    actionsById: actionsById || {\n      0: { type: PERFORM_ACTION, action: { type: 'SOME_ACTION' } },\n    },\n    stagedActionIds: [0],\n    skippedActionIds: [],\n    committedState: { count: 0 },\n    currentStateIndex: 0,\n    computedStates: computedStates || [\n      {\n        state: 1,\n        error: null,\n      },\n    ],\n    connectInZone: false,\n    isLocked,\n    isPaused,\n  };\n}\n\nconst testSetup = (options: { config: StoreDevtoolsConfig }) => {\n  const reduxDevtoolsExtension = {\n    send: vi.fn(),\n    connect: vi.fn(),\n  };\n\n  const extensionConnection = {\n    init: vi.fn(),\n    subscribe: vi.fn(),\n    unsubscribe: vi.fn(),\n    send: vi.fn(),\n    error: vi.fn(),\n  };\n\n  reduxDevtoolsExtension.connect.mockReturnValue(extensionConnection);\n\n  TestBed.configureTestingModule({\n    // Provide both the service-to-test and its (spy) dependency\n    providers: [\n      DevtoolsExtension,\n      { provide: REDUX_DEVTOOLS_EXTENSION, useValue: reduxDevtoolsExtension },\n      { provide: STORE_DEVTOOLS_CONFIG, useValue: options.config },\n      { provide: DevtoolsDispatcher, useValue: <any>null },\n    ],\n  });\n\n  return {\n    extensionConnection,\n    reduxDevtoolsExtension,\n    devtoolsExtension: TestBed.inject(DevtoolsExtension),\n  };\n};\n\ndescribe('DevtoolsExtension', () => {\n  function myActionSanitizer(action: Action, idx: number) {\n    return action;\n  }\n\n  function myStateSanitizer(state: any, idx: number) {\n    return state;\n  }\n\n  it('should connect with default options', () => {\n    const { devtoolsExtension, reduxDevtoolsExtension } = testSetup({\n      config: createConfig({}),\n    });\n    // Subscription needed or else extension connection will not be established.\n    devtoolsExtension.actions$.subscribe(() => null);\n    const defaultOptions = createOptions();\n    expect(reduxDevtoolsExtension.connect).toHaveBeenCalledWith(defaultOptions);\n  });\n\n  it('should connect with given options', () => {\n    const { devtoolsExtension, reduxDevtoolsExtension } = testSetup({\n      config: createConfig({\n        name: 'ngrx-store-devtool-todolist',\n        maxAge: 10,\n        serialize: true,\n        autoPause: true,\n        // these two should not be added\n        actionSanitizer: myActionSanitizer,\n        stateSanitizer: myStateSanitizer,\n        trace: true,\n        traceLimit: 20,\n      }),\n    });\n    // Subscription needed or else extension connection will not be established.\n    devtoolsExtension.actions$.subscribe(() => null);\n    const options = createOptions(\n      'ngrx-store-devtool-todolist',\n      undefined,\n      true,\n      10,\n      true,\n      true,\n      20\n    );\n    expect(reduxDevtoolsExtension.connect).toHaveBeenCalledWith(options);\n  });\n\n  it('should connect with custom serializer', () => {\n    const customSerializer = {\n      replacer: (key: {}, value: any) => value,\n    };\n\n    const { devtoolsExtension, reduxDevtoolsExtension } = testSetup({\n      config: createConfig({\n        name: 'ngrx-store-devtool-todolist',\n        serialize: customSerializer,\n      }),\n    });\n\n    // Subscription needed or else extension connection will not be established.\n    devtoolsExtension.actions$.subscribe(() => null);\n    expect(reduxDevtoolsExtension.connect).toHaveBeenCalledWith(\n      expect.objectContaining({ serialize: customSerializer })\n    );\n  });\n\n  for (const { payload, name } of [\n    {\n      payload: \"{type: '[Books] Rent', id: 5, customerId: 12}\",\n      name: 'evaluates payload because of string',\n    },\n    {\n      payload: { type: '[Books] Rent', id: 5, customerId: 12 },\n      name: 'passes payload through if not of type string',\n    },\n  ]) {\n    it(`should handle an unlifted action (dispatched by DevTools) - ${name}`, () => {\n      const { devtoolsExtension, extensionConnection } = testSetup({\n        config: createConfig({}),\n      });\n      let unwrappedAction: Action | undefined = undefined;\n      devtoolsExtension.actions$.subscribe((action) => {\n        return (unwrappedAction = action);\n      });\n\n      const [callback] = extensionConnection.subscribe.mock.lastCall;\n      callback({ type: ExtensionActionTypes.START });\n      callback({ type: ExtensionActionTypes.ACTION, payload });\n      expect(unwrappedAction).toEqual({\n        type: '[Books] Rent',\n        id: 5,\n        customerId: 12,\n      });\n    });\n  }\n\n  describe('notify', () => {\n    it('should send notification with default options', () => {\n      const { devtoolsExtension, reduxDevtoolsExtension } = testSetup({\n        config: createConfig({}),\n      });\n      const defaultOptions = createOptions();\n      const action = {} as LiftedAction;\n      const state = createState();\n      devtoolsExtension.notify(action, state);\n      expect(reduxDevtoolsExtension.send).toHaveBeenCalledWith(\n        null,\n        state,\n        defaultOptions\n      );\n    });\n\n    it('should send notification with given options', () => {\n      const { devtoolsExtension, reduxDevtoolsExtension } = testSetup({\n        config: createConfig({\n          name: 'ngrx-store-devtool-todolist',\n          maxAge: 10,\n          serialize: true,\n          // these two should not be added\n          actionSanitizer: myActionSanitizer,\n          stateSanitizer: myStateSanitizer,\n        }),\n      });\n\n      const options = createOptions(\n        'ngrx-store-devtool-todolist',\n        undefined,\n        true,\n        10\n      );\n      const action = {} as LiftedAction;\n      const state = createState();\n      devtoolsExtension.notify(action, state);\n      expect(reduxDevtoolsExtension.send).toHaveBeenCalledWith(\n        null,\n        state,\n        options\n      );\n    });\n\n    describe('[with Action and State Sanitizer]', () => {\n      const UNSANITIZED_TOKEN = 'UNSANITIZED_ACTION';\n      const SANITIZED_TOKEN = 'SANITIZED_ACTION';\n      const SANITIZED_COUNTER = 42;\n\n      function createPerformAction() {\n        return new PerformAction({ type: UNSANITIZED_TOKEN }, 1234567);\n      }\n\n      function testActionSanitizer(action: Action, id: number) {\n        return { type: SANITIZED_TOKEN };\n      }\n\n      function testStateSanitizer(state: any, index: number) {\n        return SANITIZED_COUNTER;\n      }\n\n      describe('should function normally with no sanitizers', () => {\n        let devtoolsExtension: DevtoolsExtension;\n        let extensionConnection: ReduxDevtoolsExtensionConnection;\n        let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n\n        beforeEach(() => {\n          ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n            testSetup({\n              config: createConfig({}),\n            }));\n\n          // Subscription needed or else extension connection will not be established.\n          devtoolsExtension.actions$.subscribe(() => null);\n        });\n\n        it('for normal action', () => {\n          const action = createPerformAction();\n          const state = createState();\n\n          devtoolsExtension.notify(action, state);\n          expect(extensionConnection.send).toHaveBeenCalledWith(\n            action,\n            unliftState(state)\n          );\n        });\n\n        it('for action that requires full state update', () => {\n          const options = createOptions();\n          const action = {} as LiftedAction;\n          const state = createState();\n\n          devtoolsExtension.notify(action, state);\n          expect(reduxDevtoolsExtension.send).toHaveBeenCalledWith(\n            null,\n            state,\n            options\n          );\n        });\n      });\n\n      describe('should run the action sanitizer on actions', () => {\n        let devtoolsExtension: DevtoolsExtension;\n        let extensionConnection: ReduxDevtoolsExtensionConnection;\n        let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n        beforeEach(() => {\n          ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n            testSetup({\n              config: createConfig({\n                actionSanitizer: testActionSanitizer,\n              }),\n            }));\n          // Subscription needed or else extension connection will not be established.\n          devtoolsExtension.actions$.subscribe(() => null);\n        });\n\n        it('for normal action', () => {\n          const options = createOptions();\n          const action = createPerformAction();\n          const state = createState();\n          const sanitizedAction = {\n            ...action,\n            action: testActionSanitizer(createPerformAction().action, 0),\n          };\n\n          devtoolsExtension.notify(action, state);\n          expect(extensionConnection.send).toHaveBeenCalledWith(\n            sanitizedAction,\n            unliftState(state)\n          );\n        });\n\n        it('for action that requires full state update', () => {\n          const options = createOptions();\n          const action = {} as LiftedAction;\n          const state = createState();\n          const sanitizedState = createState({\n            0: { type: PERFORM_ACTION, action: { type: SANITIZED_TOKEN } },\n          });\n\n          devtoolsExtension.notify(action, state);\n          expect(reduxDevtoolsExtension.send).toHaveBeenCalledWith(\n            null,\n            sanitizedState,\n            options\n          );\n        });\n      });\n\n      describe('should run the state sanitizer on store state', () => {\n        let devtoolsExtension: DevtoolsExtension;\n        let extensionConnection: ReduxDevtoolsExtensionConnection;\n        let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n        beforeEach(() => {\n          ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n            testSetup({\n              config: createConfig({\n                stateSanitizer: testStateSanitizer,\n              }),\n            }));\n\n          // Subscription needed or else extension connection will not be established.\n          devtoolsExtension.actions$.subscribe(() => null);\n        });\n\n        it('for normal action', () => {\n          const action = createPerformAction();\n          const state = createState();\n          const sanitizedState = createState(undefined, [\n            { state: SANITIZED_COUNTER, error: null },\n          ]);\n\n          devtoolsExtension.notify(action, state);\n          expect(extensionConnection.send).toHaveBeenCalledWith(\n            action,\n            unliftState(sanitizedState)\n          );\n        });\n\n        it('for action that requires full state update', () => {\n          const options = createOptions();\n          const action = {} as LiftedAction;\n          const state = createState();\n          const sanitizedState = createState(undefined, [\n            { state: SANITIZED_COUNTER, error: null },\n          ]);\n\n          devtoolsExtension.notify(action, state);\n          expect(reduxDevtoolsExtension.send).toHaveBeenCalledWith(\n            null,\n            sanitizedState,\n            options\n          );\n        });\n      });\n\n      describe('sanitizers should not modify original state or actions', () => {\n        let devtoolsExtension: DevtoolsExtension;\n        let extensionConnection: ReduxDevtoolsExtensionConnection;\n        let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n        beforeEach(() => {\n          ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n            testSetup({\n              config: createConfig({\n                actionSanitizer: testActionSanitizer,\n                stateSanitizer: testStateSanitizer,\n              }),\n            }));\n\n          // Subscription needed or else extension connection will not be established.\n          devtoolsExtension.actions$.subscribe(() => null);\n        });\n\n        it('for normal action', () => {\n          const action = createPerformAction();\n          const state = createState();\n\n          devtoolsExtension.notify(action, state);\n          expect(state).toEqual(createState());\n          expect(action).toEqual(createPerformAction());\n        });\n\n        it('for action that requires full state update', () => {\n          const action = {} as LiftedAction;\n          const state = createState();\n\n          devtoolsExtension.notify(action, state);\n          expect(state).toEqual(createState());\n          expect(action).toEqual({} as LiftedAction);\n        });\n      });\n    });\n\n    describe('with Action and actionsBlocklist', () => {\n      const NORMAL_ACTION = '[Test] NORMAL_ACTION';\n      const BLOCKED_ACTION_1 = '[Test] BLOCKED_ACTION #1';\n      const BLOCKED_ACTION_2 = '[Test] BLOCKED_ACTION #2';\n\n      let devtoolsExtension: DevtoolsExtension;\n      let extensionConnection: ReduxDevtoolsExtensionConnection;\n      let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n\n      beforeEach(() => {\n        ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n          testSetup({\n            config: createConfig({\n              actionsBlocklist: [BLOCKED_ACTION_1, BLOCKED_ACTION_2],\n            }),\n          }));\n        // Subscription needed or else extension connection will not be established.\n        devtoolsExtension.actions$.subscribe(() => null);\n      });\n\n      it('should ignore the blocked action', () => {\n        const options = createOptions();\n        const state = createState();\n\n        devtoolsExtension.notify(\n          new PerformAction({ type: NORMAL_ACTION }, 1234567),\n          state\n        );\n        devtoolsExtension.notify(\n          new PerformAction({ type: NORMAL_ACTION }, 1234567),\n          state\n        );\n        devtoolsExtension.notify(\n          new PerformAction({ type: BLOCKED_ACTION_1 }, 1234567),\n          state\n        );\n        devtoolsExtension.notify(\n          new PerformAction({ type: BLOCKED_ACTION_2 }, 1234567),\n          state\n        );\n\n        expect(extensionConnection.send).toHaveBeenCalledTimes(2);\n      });\n    });\n\n    describe('with Action and actionsSafelist', () => {\n      const NORMAL_ACTION = '[Test] NORMAL_ACTION';\n      const SAFE_ACTION_1 = '[Test] SAFE_ACTION #1';\n      const SAFE_ACTION_2 = '[Test] SAFE_ACTION #2';\n\n      let devtoolsExtension: DevtoolsExtension;\n      let extensionConnection: ReduxDevtoolsExtensionConnection;\n      let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n\n      beforeEach(() => {\n        ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n          testSetup({\n            config: createConfig({\n              actionsSafelist: [SAFE_ACTION_1, SAFE_ACTION_2],\n            }),\n          }));\n\n        // Subscription needed or else extension connection will not be established.\n        devtoolsExtension.actions$.subscribe(() => null);\n      });\n\n      it('should only keep the safe action', () => {\n        const options = createOptions();\n        const state = createState();\n\n        devtoolsExtension.notify(\n          new PerformAction({ type: NORMAL_ACTION }, 1234567),\n          state\n        );\n        devtoolsExtension.notify(\n          new PerformAction({ type: NORMAL_ACTION }, 1234567),\n          state\n        );\n        devtoolsExtension.notify(\n          new PerformAction({ type: SAFE_ACTION_1 }, 1234567),\n          state\n        );\n        devtoolsExtension.notify(\n          new PerformAction({ type: SAFE_ACTION_2 }, 1234567),\n          state\n        );\n\n        expect(extensionConnection.send).toHaveBeenCalledTimes(2);\n      });\n    });\n\n    describe('with Action and predicate', () => {\n      const NORMAL_ACTION = 'NORMAL_ACTION';\n      const RANDOM_ACTION = 'RANDOM_ACTION';\n\n      const predicate = vi.fn((state: any, action: Action) => {\n        if (action.type === RANDOM_ACTION) {\n          return false;\n        }\n        return true;\n      });\n\n      let devtoolsExtension: DevtoolsExtension;\n      let extensionConnection: ReduxDevtoolsExtensionConnection;\n      let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n\n      beforeEach(() => {\n        ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n          testSetup({\n            config: createConfig({\n              predicate,\n            }),\n          }));\n\n        // Subscription needed or else extension connection will not be established.\n        devtoolsExtension.actions$.subscribe(() => null);\n      });\n\n      it('should ignore action according to predicate', () => {\n        const options = createOptions();\n        const state = createState();\n\n        devtoolsExtension.notify(\n          new PerformAction({ type: NORMAL_ACTION }, 1234567),\n          state\n        );\n        expect(predicate).toHaveBeenCalledWith(unliftState(state), {\n          type: NORMAL_ACTION,\n        });\n        devtoolsExtension.notify(\n          new PerformAction({ type: NORMAL_ACTION }, 1234567),\n          state\n        );\n        devtoolsExtension.notify(\n          new PerformAction({ type: RANDOM_ACTION }, 1234567),\n          state\n        );\n        expect(predicate).toHaveBeenCalledTimes(3);\n        expect(extensionConnection.send).toHaveBeenCalledTimes(2);\n      });\n    });\n  });\n\n  describe('with locked recording', () => {\n    let devtoolsExtension: DevtoolsExtension;\n    let extensionConnection: ReduxDevtoolsExtensionConnection;\n    let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n\n    beforeEach(() => {\n      ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n        testSetup({\n          config: createConfig({}),\n        }));\n\n      // Subscription needed or else extension connection will not be established.\n      devtoolsExtension.actions$.subscribe(() => null);\n    });\n\n    it('should not notify extension of PERFORM_ACTIONs', () => {\n      const action = new PerformAction({ type: 'ACTION' }, +Date.now());\n      const state = createState(undefined, undefined, true);\n\n      devtoolsExtension.notify(action, state);\n      expect(extensionConnection.send).not.toHaveBeenCalled();\n      expect(reduxDevtoolsExtension.send).not.toHaveBeenCalled();\n    });\n\n    it('should notify extension of actions that require full state update', () => {\n      const action = {} as LiftedAction;\n      const state = createState(undefined, undefined, true);\n      const options = createOptions();\n\n      devtoolsExtension.notify(action, state);\n      expect(extensionConnection.send).not.toHaveBeenCalled();\n      expect(reduxDevtoolsExtension.send).toHaveBeenCalledWith(\n        null,\n        state,\n        options\n      );\n    });\n  });\n\n  describe('with paused recording', () => {\n    let devtoolsExtension: DevtoolsExtension;\n    let extensionConnection: ReduxDevtoolsExtensionConnection;\n    let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n\n    beforeEach(() => {\n      ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n        testSetup({\n          config: createConfig({}),\n        }));\n      // Subscription needed or else extension connection will not be established.\n      devtoolsExtension.actions$.subscribe(() => null);\n    });\n\n    it('should not notify extension of PERFORM_ACTIONs', () => {\n      const action = new PerformAction({ type: 'ACTION' }, +Date.now());\n      const state = createState(undefined, undefined, undefined, true);\n\n      devtoolsExtension.notify(action, state);\n      expect(extensionConnection.send).not.toHaveBeenCalled();\n      expect(reduxDevtoolsExtension.send).not.toHaveBeenCalled();\n    });\n\n    it('should notify extension of actions that require full state update', () => {\n      const action = {} as LiftedAction;\n      const state = createState(undefined, undefined, undefined, true);\n      const options = createOptions();\n\n      devtoolsExtension.notify(action, state);\n      expect(extensionConnection.send).not.toHaveBeenCalled();\n      expect(reduxDevtoolsExtension.send).toHaveBeenCalledWith(\n        null,\n        state,\n        options\n      );\n    });\n  });\n\n  describe('error handling', () => {\n    let consoleSpy: Mock;\n\n    let devtoolsExtension: DevtoolsExtension;\n    let extensionConnection: ReduxDevtoolsExtensionConnection;\n    let reduxDevtoolsExtension: ReduxDevtoolsExtension;\n\n    beforeEach(() => {\n      ({ devtoolsExtension, extensionConnection, reduxDevtoolsExtension } =\n        testSetup({\n          config: createConfig({}),\n        }));\n      // Subscription needed or else extension connection will not be established.\n      devtoolsExtension.actions$.subscribe();\n      consoleSpy = vi.spyOn(console, 'warn');\n    });\n\n    it('for normal action', () => {\n      (extensionConnection.send as Mock).mockImplementation(() => {\n        throw new Error('uh-oh something went wrong');\n      });\n\n      const action = new PerformAction({ type: 'FOO' }, 1234567);\n      const state = createState();\n\n      devtoolsExtension.notify(action, state);\n      expect(consoleSpy).toHaveBeenCalled();\n    });\n\n    it('for action that requires full state update', () => {\n      (reduxDevtoolsExtension.send as Mock).mockImplementation(() => {\n        throw new Error('uh-oh something went wrong');\n      });\n\n      const action = {} as LiftedAction;\n      const state = createState();\n\n      devtoolsExtension.notify(action, state);\n      expect(consoleSpy).toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store-devtools/spec/integration.spec.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport { StoreModule, Store, Action } from '@ngrx/store';\nimport { StoreDevtoolsModule, StoreDevtools, StoreDevtoolsOptions } from '..';\n\ndescribe('Devtools Integration', () => {\n  function setup(options: Partial<StoreDevtoolsOptions> = {}) {\n    @NgModule({\n      imports: [\n        StoreModule.forFeature('a', (state: any, action: any) => state),\n      ],\n    })\n    class EagerFeatureModule {}\n\n    @NgModule({\n      imports: [\n        StoreModule.forRoot({}),\n        EagerFeatureModule,\n        StoreDevtoolsModule.instrument(options),\n      ],\n    })\n    class RootModule {}\n\n    TestBed.configureTestingModule({\n      imports: [RootModule],\n    });\n\n    const store = TestBed.inject(Store);\n    const devtools = TestBed.inject(StoreDevtools);\n    return { store, devtools };\n  }\n\n  afterEach(() => {\n    const devtools = TestBed.inject(StoreDevtools);\n    devtools.reset();\n  });\n\n  it('should load the store eagerly', () => {\n    let error = false;\n\n    try {\n      const { store } = setup();\n      store.subscribe();\n    } catch (e) {\n      error = true;\n    }\n\n    expect(error).toBeFalsy();\n  });\n\n  it('should not throw if actions are ignored', () =>\n    new Promise<void>((done) => {\n      const { store, devtools } = setup({\n        predicate: (_, { type }: Action) => type !== 'FOO',\n      });\n      store.subscribe();\n      devtools.dispatcher.subscribe((action: Action) => {\n        if (action.type === 'REFRESH') {\n          done();\n        }\n      });\n      store.dispatch({ type: 'FOO' });\n      devtools.refresh();\n    }));\n\n  it('should not throw if actions are blocked', () =>\n    new Promise<void>((done: any) => {\n      const { store, devtools } = setup({\n        actionsBlocklist: ['FOO'],\n      });\n      store.subscribe();\n      devtools.dispatcher.subscribe((action: Action) => {\n        if (action.type === 'REFRESH') {\n          done();\n        }\n      });\n      store.dispatch({ type: 'FOO' });\n      devtools.refresh();\n    }));\n\n  it('should not throw if actions are safe', () =>\n    new Promise<void>((done: any) => {\n      const { store, devtools } = setup({\n        actionsSafelist: ['BAR'],\n      });\n      store.subscribe();\n      devtools.dispatcher.subscribe((action: Action) => {\n        if (action.type === 'REFRESH') {\n          done();\n        }\n      });\n      store.dispatch({ type: 'FOO' });\n      devtools.refresh();\n    }));\n});\n"
  },
  {
    "path": "modules/store-devtools/spec/store.spec.ts",
    "content": "import { getTestBed, TestBed } from '@angular/core/testing';\nimport { vi } from 'vitest';\n\nimport {\n  Action,\n  ActionReducer,\n  ReducerManager,\n  StateObservable,\n  Store,\n  StoreModule,\n  UPDATE,\n} from '@ngrx/store';\nimport {\n  LiftedState,\n  StoreDevtools,\n  StoreDevtoolsModule,\n  StoreDevtoolsOptions,\n} from '../';\nimport { RECOMPUTE } from '../src/reducer';\nimport { IS_EXTENSION_OR_MONITOR_PRESENT } from '../src/provide-store-devtools';\n\nconst counter = vi.fn(function (state = 0, action: Action) {\n  switch (action.type) {\n    case 'INCREMENT':\n      return state + 1;\n    case 'DECREMENT':\n      return state - 1;\n    default:\n      return state;\n  }\n});\n\ndeclare let mistake: any;\nfunction counterWithBug(state = 0, action: Action) {\n  switch (action.type) {\n    case 'INCREMENT':\n      return state + 1;\n    case 'DECREMENT':\n      return mistake - 1; // mistake is undefined\n    case 'SET_UNDEFINED':\n      return undefined;\n    default:\n      return state;\n  }\n}\n\nfunction counterWithAnotherBug(state = 0, action: Action) {\n  switch (action.type) {\n    case 'INCREMENT':\n      return mistake + 1; // eslint-disable-line no-undef\n    case 'DECREMENT':\n      return state - 1;\n    case 'SET_UNDEFINED':\n      return undefined;\n    default:\n      return state;\n  }\n}\n\nfunction doubleCounter(state = 0, action: Action) {\n  switch (action.type) {\n    case 'INCREMENT':\n      return state + 2;\n    case 'DECREMENT':\n      return state - 2;\n    default:\n      return state;\n  }\n}\n\ntype Fixture<T> = {\n  store: Store<T>;\n  state: StateObservable;\n  devtools: StoreDevtools;\n  cleanup: () => void;\n  getState: () => T;\n  getLiftedState: () => LiftedState;\n  replaceReducer: (reducer: ActionReducer<any, any>) => void;\n};\n\nfunction createStore<T>(\n  reducer: ActionReducer<T | undefined, Action>,\n  options: StoreDevtoolsOptions = {}\n): Fixture<T> {\n  TestBed.configureTestingModule({\n    imports: [\n      StoreModule.forRoot({ state: reducer }),\n      StoreDevtoolsModule.instrument(options),\n    ],\n    providers: [{ provide: IS_EXTENSION_OR_MONITOR_PRESENT, useValue: true }],\n  });\n\n  const testbed: TestBed = getTestBed();\n  const store = testbed.inject(Store);\n\n  const devtools = testbed.inject(StoreDevtools);\n  const state = testbed.inject(StateObservable);\n  const reducerManager = testbed.inject(ReducerManager);\n  let liftedValue: LiftedState;\n  let value: any;\n\n  const liftedStateSub = devtools.liftedState.subscribe(\n    (s) => (liftedValue = s)\n  );\n  const stateSub = devtools.state.subscribe((s) => (value = s));\n\n  const getState = (): T => value.state;\n  const getLiftedState = (): LiftedState => liftedValue;\n\n  const cleanup = () => {\n    liftedStateSub.unsubscribe();\n    stateSub.unsubscribe();\n  };\n\n  const replaceReducer = (reducer: ActionReducer<any, any>) => {\n    reducerManager.addReducer('state', reducer);\n  };\n\n  return {\n    store,\n    state,\n    devtools,\n    cleanup,\n    getState,\n    getLiftedState,\n    replaceReducer,\n  };\n}\n\ndescribe('Store Devtools', () => {\n  describe('reducer', () => {\n    it('should call @ngrx/store-devtools/recompute action', () => {\n      const fixture = createStore(doubleCounter);\n      counter.mockClear();\n      fixture.replaceReducer(counter);\n\n      const allArgs = counter.mock.calls;\n      expect(allArgs.length).toEqual(3);\n      expect(allArgs[0][1].type).toEqual(UPDATE);\n      expect(allArgs[1][1].type).toEqual(RECOMPUTE);\n      expect(allArgs[2][1].type).toEqual(RECOMPUTE);\n    });\n  });\n\n  describe('Instrumentation', () => {\n    let fixture: Fixture<number>;\n    let store: Store<number>;\n    let devtools: StoreDevtools;\n    let getState: () => number;\n    let getLiftedState: () => LiftedState;\n\n    beforeEach(() => {\n      fixture = createStore(counter);\n      store = fixture.store;\n      devtools = fixture.devtools;\n      getState = fixture.getState;\n      getLiftedState = fixture.getLiftedState;\n    });\n\n    afterEach(() => {\n      fixture.cleanup();\n    });\n\n    it(`should alias devtools unlifted state to Store's state`, () => {\n      expect(devtools.state).toBe(fixture.state as any);\n    });\n\n    it('should perform actions', () => {\n      expect(getState()).toBe(0);\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(1);\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(2);\n    });\n\n    it('should rollback state to the last committed state', () => {\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(2);\n\n      devtools.commit();\n      expect(getState()).toBe(2);\n\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(4);\n\n      devtools.rollback();\n      expect(getState()).toBe(2);\n\n      store.dispatch({ type: 'DECREMENT' });\n      expect(getState()).toBe(1);\n\n      devtools.rollback();\n      expect(getState()).toBe(2);\n    });\n\n    it('should refresh to show current state as is', () => {\n      // actionId 0 = @@INIT\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n\n      expect(getState()).toBe(4);\n      expect(getLiftedState().stagedActionIds).toEqual([0, 1, 2, 3, 4]);\n      expect(getLiftedState().skippedActionIds).toEqual([]);\n\n      devtools.refresh();\n\n      expect(getState()).toBe(4);\n      expect(getLiftedState().stagedActionIds).toEqual([0, 1, 2, 3, 4]);\n      expect(getLiftedState().skippedActionIds).toEqual([]);\n    });\n\n    it('should reset to initial state', () => {\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(1);\n\n      devtools.commit();\n      expect(getState()).toBe(1);\n\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(2);\n\n      devtools.rollback();\n      expect(getState()).toBe(1);\n\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(2);\n\n      devtools.reset();\n      expect(getState()).toBe(0);\n    });\n\n    it('should toggle an action', () => {\n      // actionId 0 = @@INIT\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'DECREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(1);\n\n      devtools.toggleAction(2);\n      expect(getState()).toBe(2);\n\n      devtools.toggleAction(2);\n      expect(getState()).toBe(1);\n    });\n\n    it('should sweep disabled actions', () => {\n      // actionId 0 = @@INIT\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'DECREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n\n      expect(getState()).toBe(2);\n      expect(getLiftedState().stagedActionIds).toEqual([0, 1, 2, 3, 4]);\n      expect(getLiftedState().skippedActionIds).toEqual([]);\n\n      devtools.toggleAction(2);\n\n      expect(getState()).toBe(3);\n      expect(getLiftedState().stagedActionIds).toEqual([0, 1, 2, 3, 4]);\n      expect(getLiftedState().skippedActionIds).toEqual([2]);\n\n      devtools.sweep();\n      expect(getState()).toBe(3);\n      expect(getLiftedState().stagedActionIds).toEqual([0, 1, 3, 4]);\n      expect(getLiftedState().skippedActionIds).toEqual([]);\n    });\n\n    it('should jump to state', () => {\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'DECREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(1);\n\n      devtools.jumpToState(0);\n      expect(getState()).toBe(0);\n\n      devtools.jumpToState(1);\n      expect(getState()).toBe(1);\n\n      devtools.jumpToState(2);\n      expect(getState()).toBe(0);\n\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(0);\n\n      devtools.jumpToState(4);\n      expect(getState()).toBe(2);\n    });\n\n    it('should jump to action', () => {\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'DECREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      expect(getState()).toBe(1);\n\n      devtools.jumpToAction(2);\n      expect(getState()).toBe(0);\n    });\n\n    it('should replace the reducer and preserve previous states', () => {\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'DECREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n\n      expect(getState()).toBe(1);\n\n      fixture.replaceReducer(doubleCounter);\n\n      expect(getState()).toBe(1);\n    });\n\n    it('should replace the reducer and compute new state with latest reducer', () => {\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'DECREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n\n      expect(getState()).toBe(1);\n\n      fixture.replaceReducer(doubleCounter);\n\n      store.dispatch({ type: 'INCREMENT' });\n\n      expect(getState()).toBe(3);\n    });\n\n    it('should catch and record errors', () => {\n      vi.spyOn(console, 'error');\n      fixture.replaceReducer(counterWithBug);\n\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'DECREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n\n      const { computedStates } = fixture.getLiftedState();\n      expect(computedStates[3].error).toMatch(/ReferenceError/);\n      expect(computedStates[4].error).toMatch(\n        /Interrupted by an error up the chain/\n      );\n\n      expect(console.error).toHaveBeenCalled();\n    });\n\n    it('should catch invalid action type', () => {\n      expect(() => {\n        store.dispatch({ type: undefined } as any);\n      }).toThrowError('Actions must have a type property');\n    });\n\n    it('should not recompute old states when toggling an action', () => {\n      counter.mockClear();\n\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n\n      expect(counter).toHaveBeenCalledTimes(3);\n\n      devtools.toggleAction(3);\n      expect(counter).toHaveBeenCalledTimes(3);\n\n      devtools.toggleAction(3);\n      expect(counter).toHaveBeenCalledTimes(4);\n\n      devtools.toggleAction(2);\n      expect(counter).toHaveBeenCalledTimes(5);\n\n      devtools.toggleAction(2);\n      expect(counter).toHaveBeenCalledTimes(7);\n\n      devtools.toggleAction(1);\n      expect(counter).toHaveBeenCalledTimes(9);\n\n      devtools.toggleAction(2);\n      expect(counter).toHaveBeenCalledTimes(10);\n\n      devtools.toggleAction(3);\n      expect(counter).toHaveBeenCalledTimes(10);\n\n      devtools.toggleAction(1);\n      expect(counter).toHaveBeenCalledTimes(11);\n\n      devtools.toggleAction(3);\n      expect(counter).toHaveBeenCalledTimes(12);\n\n      devtools.toggleAction(2);\n      expect(counter).toHaveBeenCalledTimes(14);\n    });\n\n    it('should not recompute states when jumping to state', () => {\n      counter.mockClear();\n\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n\n      expect(counter).toHaveBeenCalledTimes(3);\n\n      const savedComputedStates = getLiftedState().computedStates;\n\n      devtools.jumpToState(0);\n\n      expect(counter).toHaveBeenCalledTimes(3);\n\n      devtools.jumpToState(1);\n\n      expect(counter).toHaveBeenCalledTimes(3);\n\n      devtools.jumpToState(3);\n\n      expect(counter).toHaveBeenCalledTimes(3);\n\n      expect(getLiftedState().computedStates).toBe(savedComputedStates);\n    });\n\n    it('should not recompute states on monitor actions', () => {\n      counter.mockClear();\n\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n      store.dispatch({ type: 'INCREMENT' });\n\n      expect(counter).toHaveBeenCalledTimes(3);\n\n      const savedComputedStates = getLiftedState().computedStates;\n\n      devtools.dispatch({ type: 'lol' });\n\n      expect(counter).toHaveBeenCalledTimes(3);\n\n      devtools.dispatch({ type: 'wat' });\n\n      expect(counter).toHaveBeenCalledTimes(3);\n\n      expect(getLiftedState().computedStates).toBe(savedComputedStates);\n    });\n  });\n\n  describe('Filtered actions', () => {\n    it('should respect the predicate option', () => {\n      const fixture = createStore(counter, {\n        predicate: (s, a) => a.type !== 'INCREMENT',\n      });\n\n      expect(fixture.getState()).toBe(0);\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      expect(fixture.getState()).toBe(5);\n\n      // init, decrement, decrement\n      const {\n        stagedActionIds,\n        actionsById,\n        computedStates,\n        currentStateIndex,\n      } = fixture.getLiftedState();\n      expect(stagedActionIds.length).toBe(3);\n      expect(Object.keys(actionsById).length).toBe(3);\n      expect(computedStates.length).toBe(3);\n      expect(currentStateIndex).toBe(2);\n\n      fixture.devtools.jumpToAction(0);\n      expect(fixture.getState()).toBe(1);\n\n      fixture.devtools.jumpToAction(1);\n      expect(fixture.getState()).toBe(6);\n\n      fixture.devtools.jumpToAction(2);\n      expect(fixture.getState()).toBe(5);\n    });\n\n    it('should respect the blocked option', () => {\n      const fixture = createStore(counter, {\n        actionsBlocklist: ['INCREMENT'],\n      });\n\n      expect(fixture.getState()).toBe(0);\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      expect(fixture.getState()).toBe(5);\n\n      // init, decrement, decrement\n      const {\n        stagedActionIds,\n        actionsById,\n        computedStates,\n        currentStateIndex,\n      } = fixture.getLiftedState();\n      expect(stagedActionIds.length).toBe(3);\n      expect(Object.keys(actionsById).length).toBe(3);\n      expect(computedStates.length).toBe(3);\n      expect(currentStateIndex).toBe(2);\n\n      fixture.devtools.jumpToAction(0);\n      expect(fixture.getState()).toBe(1);\n\n      fixture.devtools.jumpToAction(1);\n      expect(fixture.getState()).toBe(6);\n\n      fixture.devtools.jumpToAction(2);\n      expect(fixture.getState()).toBe(5);\n    });\n\n    it('should respect the safe option', () => {\n      const fixture = createStore(counter, {\n        actionsSafelist: ['DECREMENT'],\n      });\n\n      expect(fixture.getState()).toBe(0);\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      expect(fixture.getState()).toBe(5);\n\n      // init, decrement, decrement\n      const {\n        stagedActionIds,\n        actionsById,\n        computedStates,\n        currentStateIndex,\n      } = fixture.getLiftedState();\n      expect(stagedActionIds.length).toBe(3);\n      expect(Object.keys(actionsById).length).toBe(3);\n      expect(computedStates.length).toBe(3);\n      expect(currentStateIndex).toBe(2);\n\n      fixture.devtools.jumpToAction(0);\n      expect(fixture.getState()).toBe(1);\n\n      fixture.devtools.jumpToAction(1);\n      expect(fixture.getState()).toBe(6);\n\n      fixture.devtools.jumpToAction(2);\n      expect(fixture.getState()).toBe(5);\n    });\n  });\n\n  describe('maxAge option', () => {\n    it('should auto-commit earliest non-@@INIT action when maxAge is reached', () => {\n      const fixture = createStore(counter, { maxAge: 3 });\n\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      let liftedStoreState = fixture.getLiftedState();\n\n      expect(fixture.getState()).toBe(2);\n      expect(Object.keys(liftedStoreState.actionsById).length).toBe(3);\n      expect(liftedStoreState.committedState).toBe(undefined);\n      expect(liftedStoreState.stagedActionIds).toContain(1);\n\n      // Trigger auto-commit.\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      liftedStoreState = fixture.getLiftedState();\n\n      expect(fixture.getState()).toBe(3);\n      expect(Object.keys(liftedStoreState.actionsById).length).toBe(3);\n      expect(liftedStoreState.stagedActionIds).not.toContain(1);\n      expect(liftedStoreState.computedStates[0].state).toEqual({ state: 1 });\n      expect(liftedStoreState.committedState).toEqual({ state: 1 });\n      expect(liftedStoreState.currentStateIndex).toBe(2);\n\n      fixture.cleanup();\n    });\n\n    it('should remove skipped actions once committed', () => {\n      const fixture = createStore(counter, { maxAge: 3 });\n\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.devtools.toggleAction(1);\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      expect(fixture.getLiftedState().skippedActionIds.indexOf(1)).not.toBe(-1);\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      expect(fixture.getLiftedState().skippedActionIds.indexOf(1)).toBe(-1);\n\n      fixture.cleanup();\n    });\n\n    it('should not auto-commit errors', () => {\n      vi.spyOn(console, 'error');\n      const fixture = createStore(counterWithBug, { maxAge: 3 });\n\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      expect(fixture.getLiftedState().stagedActionIds.length).toBe(3);\n\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      expect(fixture.getLiftedState().stagedActionIds.length).toBe(4);\n\n      fixture.cleanup();\n    });\n\n    it('should auto-commit actions after hot reload fixes error', () => {\n      vi.spyOn(console, 'error');\n      const fixture = createStore(counterWithBug, { maxAge: 3 });\n\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      expect(fixture.getLiftedState().stagedActionIds.length).toBe(7);\n\n      // Auto-commit 2 actions by \"fixing\" reducer bug, but introducing another.\n      fixture.replaceReducer(counterWithAnotherBug);\n      expect(fixture.getLiftedState().stagedActionIds.length).toBe(5);\n\n      // Auto-commit 2 more actions by \"fixing\" other reducer bug.\n      fixture.replaceReducer(counter);\n      expect(fixture.getLiftedState().stagedActionIds.length).toBe(3);\n\n      fixture.cleanup();\n    });\n\n    it('should update currentStateIndex when auto-committing', () => {\n      const fixture = createStore(counter, { maxAge: 3 });\n\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n\n      expect(fixture.getLiftedState().currentStateIndex).toBe(2);\n\n      // currentStateIndex should stay at 2 as actions are committed.\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      const liftedStoreState = fixture.getLiftedState();\n      const currentComputedState =\n        liftedStoreState.computedStates[liftedStoreState.currentStateIndex];\n\n      expect(liftedStoreState.currentStateIndex).toBe(2);\n      expect(currentComputedState.state).toEqual({ state: 3 });\n\n      fixture.cleanup();\n    });\n\n    it('should continue to increment currentStateIndex while error blocks commit', () => {\n      vi.spyOn(console, 'error');\n      const fixture = createStore(counterWithBug, { maxAge: 3 });\n\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n\n      const liftedStoreState = fixture.getLiftedState();\n      const currentComputedState =\n        liftedStoreState.computedStates[liftedStoreState.currentStateIndex];\n      expect(liftedStoreState.currentStateIndex).toBe(4);\n      expect(currentComputedState.state).toEqual({ state: 0 });\n      expect(currentComputedState.error).toBeDefined();\n\n      fixture.cleanup();\n    });\n\n    it('should adjust currentStateIndex correctly when multiple actions are committed', () => {\n      vi.spyOn(console, 'error');\n      const fixture = createStore(counterWithBug, { maxAge: 3 });\n\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n\n      // Auto-commit 2 actions by \"fixing\" reducer bug.\n      fixture.replaceReducer(counter);\n      const liftedStoreState = fixture.getLiftedState();\n      const currentComputedState =\n        liftedStoreState.computedStates[liftedStoreState.currentStateIndex];\n      expect(liftedStoreState.currentStateIndex).toBe(2);\n      expect(currentComputedState.state).toEqual({ state: -4 });\n\n      fixture.cleanup();\n    });\n\n    it('should not allow currentStateIndex to drop below 0', () => {\n      vi.spyOn(console, 'error');\n      const fixture = createStore(counterWithBug, { maxAge: 3 });\n\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.devtools.jumpToState(1);\n\n      // Auto-commit 2 actions by \"fixing\" reducer bug.\n      fixture.replaceReducer(counter);\n      const liftedStoreState = fixture.getLiftedState();\n      const currentComputedState =\n        liftedStoreState.computedStates[liftedStoreState.currentStateIndex];\n      expect(liftedStoreState.currentStateIndex).toBe(0);\n      expect(currentComputedState.state).toEqual({ state: -2 });\n\n      fixture.cleanup();\n    });\n\n    it('should throw error when maxAge < 2', () => {\n      expect(() => {\n        createStore(counter, { maxAge: 1 });\n      }).toThrowError(/cannot be less than/);\n    });\n\n    it('should support a function to return devtools options', () => {\n      expect(() => {\n        createStore(counter, function () {\n          return { maxAge: 1 };\n        });\n      }).toThrowError(/cannot be less than/);\n    });\n  });\n\n  describe('Import State', () => {\n    let fixture: Fixture<number>;\n    let exportedState: LiftedState;\n\n    beforeEach(() => {\n      fixture = createStore(counter);\n\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n\n      exportedState = fixture.getLiftedState();\n    });\n\n    afterEach(() => {\n      fixture.cleanup();\n    });\n\n    it('should replay all the steps when a state is imported', () => {\n      fixture.devtools.importState(exportedState);\n      expect(fixture.getLiftedState()).toEqual(exportedState);\n    });\n\n    it('should replace the existing action log with the one imported', () => {\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      fixture.store.dispatch({ type: 'DECREMENT' });\n\n      fixture.devtools.importState(exportedState);\n      expect(fixture.getLiftedState()).toEqual(exportedState);\n    });\n  });\n\n  describe('Lock Changes', () => {\n    let fixture: Fixture<number>;\n    beforeEach(() => {\n      fixture = createStore(counter);\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.devtools.lockChanges(true);\n    });\n\n    afterEach(() => {\n      fixture.cleanup();\n    });\n\n    it('should update state correctly', () => {\n      expect(fixture.getLiftedState().isLocked).toBe(true);\n      expect(fixture.getLiftedState().nextActionId).toBe(3);\n      expect(fixture.getState()).toBe(2);\n    });\n\n    it('should not accept changes during lock', () => {\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      expect(fixture.getLiftedState().nextActionId).toBe(3);\n      expect(fixture.getState()).toBe(2);\n    });\n\n    it('should be able to skip / time travel during lock', () => {\n      fixture.devtools.toggleAction(1);\n      expect(fixture.getState()).toBe(1);\n      fixture.devtools.toggleAction(1);\n      expect(fixture.getState()).toBe(2);\n      fixture.devtools.jumpToAction(1);\n      expect(fixture.getState()).toBe(1);\n      fixture.devtools.jumpToAction(2);\n      expect(fixture.getState()).toBe(2);\n    });\n\n    it('should work correctly after unlock', () => {\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.devtools.jumpToAction(1);\n      fixture.devtools.jumpToAction(2);\n      fixture.devtools.lockChanges(false);\n      expect(fixture.getLiftedState().isLocked).toBe(false);\n      expect(fixture.getLiftedState().nextActionId).toBe(3);\n      expect(fixture.getState()).toBe(2);\n\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      expect(fixture.getLiftedState().nextActionId).toBe(4);\n      expect(fixture.getState()).toBe(3);\n    });\n  });\n\n  describe('pause recording', () => {\n    let fixture: Fixture<number>;\n    beforeEach(() => {\n      fixture = createStore(counter);\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.store.dispatch({ type: 'INCREMENT' });\n      fixture.devtools.pauseRecording(true);\n    });\n\n    afterEach(() => {\n      fixture.cleanup();\n    });\n\n    it('should update pause correctly', () => {\n      expect(fixture.getLiftedState().isPaused).toBe(true);\n      fixture.devtools.pauseRecording(false);\n      expect(fixture.getLiftedState().isPaused).toBe(false);\n    });\n\n    it('should create a copy of the last state before pausing', () => {\n      const computedStates = fixture.getLiftedState().computedStates;\n      expect(computedStates.length).toBe(4);\n      expect(computedStates[3]).toEqual(computedStates[2]);\n      expect(fixture.getLiftedState().currentStateIndex).toBe(3);\n      expect(fixture.getState()).toBe(2);\n    });\n\n    it('should add pause action', () => {\n      const liftedState = fixture.getLiftedState();\n      expect(liftedState.nextActionId).toBe(4);\n      expect(liftedState.actionsById[3].action.type).toEqual(\n        '@ngrx/devtools/pause'\n      );\n    });\n\n    it('should overwrite last state during pause but keep action', () => {\n      fixture.store.dispatch({ type: 'DECREMENT' });\n      const liftedState = fixture.getLiftedState();\n      expect(liftedState.currentStateIndex).toBe(3);\n      expect(liftedState.computedStates.length).toBe(4);\n      expect(fixture.getState()).toEqual(1);\n      expect(liftedState.nextActionId).toBe(4);\n      expect(liftedState.actionsById[3].action.type).toEqual(\n        '@ngrx/devtools/pause'\n      );\n    });\n\n    it('recomputation of states should preserve last state', () => {\n      fixture.devtools.jumpToState(1);\n      expect(fixture.getState()).toBe(1);\n      fixture.devtools.jumpToState(3);\n      expect(fixture.getState()).toBe(2);\n      fixture.devtools.toggleAction(1);\n      expect(fixture.getState()).toBe(2);\n      fixture.devtools.jumpToState(2);\n      expect(fixture.getState()).toBe(1);\n    });\n\n    it('reducer update should not be recorded but should still be applied', () => {\n      const oldComputedStates = fixture.getLiftedState().computedStates;\n      fixture.store.dispatch({ type: UPDATE });\n      expect(fixture.getState()).toBe(2);\n      const liftedState = fixture.getLiftedState();\n      expect(liftedState.nextActionId).toBe(4);\n      expect(liftedState.actionsById[3].action.type).toEqual(\n        '@ngrx/devtools/pause'\n      );\n      expect(oldComputedStates).not.toBe(liftedState.computedStates);\n    });\n  });\n\n  describe('StateObservable', () => {\n    it('should contain state signal that is updated on state changes', () => {\n      const { state, store } = createStore(counter);\n      expect(state.state()).toEqual({ state: 0 });\n\n      store.dispatch({ type: 'INCREMENT' });\n      expect(store.state()).toEqual({ state: 1 });\n\n      store.dispatch({ type: 'DECREMENT' });\n      expect(store.state()).toEqual({ state: 0 });\n    });\n  });\n});\n"
  },
  {
    "path": "modules/store-devtools/src/actions.ts",
    "content": "import { Action } from '@ngrx/store';\n\nexport const PERFORM_ACTION = 'PERFORM_ACTION';\nexport const REFRESH = 'REFRESH';\nexport const RESET = 'RESET';\nexport const ROLLBACK = 'ROLLBACK';\nexport const COMMIT = 'COMMIT';\nexport const SWEEP = 'SWEEP';\nexport const TOGGLE_ACTION = 'TOGGLE_ACTION';\nexport const SET_ACTIONS_ACTIVE = 'SET_ACTIONS_ACTIVE';\nexport const JUMP_TO_STATE = 'JUMP_TO_STATE';\nexport const JUMP_TO_ACTION = 'JUMP_TO_ACTION';\nexport const IMPORT_STATE = 'IMPORT_STATE';\nexport const LOCK_CHANGES = 'LOCK_CHANGES';\nexport const PAUSE_RECORDING = 'PAUSE_RECORDING';\n\nexport class PerformAction implements Action {\n  readonly type = PERFORM_ACTION;\n\n  constructor(\n    public action: Action,\n    public timestamp: number\n  ) {\n    if (typeof action.type === 'undefined') {\n      throw new Error(\n        'Actions may not have an undefined \"type\" property. ' +\n          'Have you misspelled a constant?'\n      );\n    }\n  }\n}\n\nexport class Refresh implements Action {\n  readonly type = REFRESH;\n}\n\nexport class Reset implements Action {\n  readonly type = RESET;\n\n  constructor(public timestamp: number) {}\n}\n\nexport class Rollback implements Action {\n  readonly type = ROLLBACK;\n\n  constructor(public timestamp: number) {}\n}\n\nexport class Commit implements Action {\n  readonly type = COMMIT;\n\n  constructor(public timestamp: number) {}\n}\n\nexport class Sweep implements Action {\n  readonly type = SWEEP;\n}\n\nexport class ToggleAction implements Action {\n  readonly type = TOGGLE_ACTION;\n\n  constructor(public id: number) {}\n}\n\nexport class SetActionsActive implements Action {\n  readonly type = SET_ACTIONS_ACTIVE;\n\n  constructor(\n    public start: number,\n    public end: number,\n    public active = true\n  ) {}\n}\n\nexport class JumpToState implements Action {\n  readonly type = JUMP_TO_STATE;\n\n  constructor(public index: number) {}\n}\n\nexport class JumpToAction implements Action {\n  readonly type = JUMP_TO_ACTION;\n\n  constructor(public actionId: number) {}\n}\n\nexport class ImportState implements Action {\n  readonly type = IMPORT_STATE;\n\n  constructor(public nextLiftedState: any) {}\n}\n\nexport class LockChanges implements Action {\n  readonly type = LOCK_CHANGES;\n\n  constructor(public status: boolean) {}\n}\n\nexport class PauseRecording implements Action {\n  readonly type = PAUSE_RECORDING;\n\n  constructor(public status: boolean) {}\n}\n\nexport type All =\n  | PerformAction\n  | Refresh\n  | Reset\n  | Rollback\n  | Commit\n  | Sweep\n  | ToggleAction\n  | SetActionsActive\n  | JumpToState\n  | JumpToAction\n  | ImportState\n  | LockChanges\n  | PauseRecording;\n"
  },
  {
    "path": "modules/store-devtools/src/config.ts",
    "content": "import { ActionReducer, Action } from '@ngrx/store';\nimport { InjectionToken } from '@angular/core';\n\nexport type ActionSanitizer = (action: Action, id: number) => Action;\nexport type StateSanitizer = (state: any, index: number) => any;\nexport type SerializationOptions = {\n  options?: boolean | any;\n  replacer?: (key: any, value: any) => {};\n  reviver?: (key: any, value: any) => {};\n  immutable?: any;\n  refs?: Array<any>;\n};\nexport type Predicate = (state: any, action: Action) => boolean;\n\n/**\n * Chrome extension documentation\n * @see https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md#features\n * Firefox extension documentation\n * @see https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#features\n */\nexport interface DevToolsFeatureOptions {\n  /**\n   * Start/pause recording of dispatched actions\n   */\n  pause?: boolean;\n  /**\n   * Lock/unlock dispatching actions and side effects\n   */\n  lock?: boolean;\n  /**\n   * Persist states on page reloading\n   */\n  persist?: boolean;\n  /**\n   * Export history of actions in a file\n   */\n  export?: boolean;\n  /**\n   * Import history of actions from a file\n   */\n  import?: 'custom' | boolean;\n  /**\n   * Jump back and forth (time travelling)\n   */\n  jump?: boolean;\n  /**\n   * Skip (cancel) actions\n   */\n  skip?: boolean;\n  /**\n   * Drag and drop actions in the history list\n   */\n  reorder?: boolean;\n  /**\n   * Dispatch custom actions or action creators\n   */\n  dispatch?: boolean;\n  /**\n   * Generate tests for the selected actions\n   */\n  test?: boolean;\n}\n\n/**\n * Chrome extension documentation\n * @see https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md\n * Firefox extension documentation\n * @see https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md\n */\nexport class StoreDevtoolsConfig {\n  /**\n   * Maximum allowed actions to be stored in the history tree (default: `false`)\n   */\n  maxAge: number | false = false;\n  monitor?: ActionReducer<any, any>;\n  /**\n   * Function which takes `action` object and id number as arguments, and should return `action` object back.\n   */\n  actionSanitizer?: ActionSanitizer;\n  /**\n   * Function which takes `state` object and index as arguments, and should return `state` object back.\n   */\n  stateSanitizer?: StateSanitizer;\n  /**\n   * The instance name to be shown on the monitor page (default: `document.title`)\n   */\n  name?: string;\n  serialize?: boolean | SerializationOptions;\n  logOnly?: boolean;\n  features?: DevToolsFeatureOptions;\n  /**\n   * Action types to be hidden in the monitors. If `actionsSafelist` specified, `actionsBlocklist` is ignored.\n   */\n  actionsBlocklist?: string[];\n  /**\n   * Action types to be shown in the monitors\n   */\n  actionsSafelist?: string[];\n  /**\n   * Called for every action before sending, takes state and action object, and returns true in case it allows sending the current data to the monitor.\n   */\n  predicate?: Predicate;\n  /**\n   * Auto pauses when the extension’s window is not opened, and so has zero impact on your app when not in use.\n   */\n  autoPause?: boolean;\n\n  /**\n   * If set to true, will include stack trace for every dispatched action\n   */\n  trace?: boolean | (() => string);\n\n  /**\n   * Maximum stack trace frames to be stored (in case trace option was provided as true).\n   */\n  traceLimit?: number;\n\n  /**\n   * The property determines whether the extension connection is established within the\n   * Angular zone or not. It is set to `false` by default.\n   */\n  connectInZone?: boolean;\n}\n\nexport const STORE_DEVTOOLS_CONFIG = new InjectionToken<StoreDevtoolsConfig>(\n  '@ngrx/store-devtools Options'\n);\n\n/**\n * Used to provide a `StoreDevtoolsConfig` for the store-devtools.\n */\nexport const INITIAL_OPTIONS = new InjectionToken<StoreDevtoolsConfig>(\n  '@ngrx/store-devtools Initial Config'\n);\n\nexport type StoreDevtoolsOptions =\n  | Partial<StoreDevtoolsConfig>\n  | (() => Partial<StoreDevtoolsConfig>);\n\nexport function noMonitor(): null {\n  return null;\n}\n\nexport const DEFAULT_NAME = 'NgRx Store DevTools';\n\nexport function createConfig(\n  optionsInput: StoreDevtoolsOptions\n): StoreDevtoolsConfig {\n  const DEFAULT_OPTIONS: StoreDevtoolsConfig = {\n    maxAge: false,\n    monitor: noMonitor,\n    actionSanitizer: undefined,\n    stateSanitizer: undefined,\n    name: DEFAULT_NAME,\n    serialize: false,\n    logOnly: false,\n    autoPause: false,\n    trace: false,\n    traceLimit: 75,\n    // Add all features explicitly. This prevent buggy behavior for\n    // options like \"lock\" which might otherwise not show up.\n    features: {\n      pause: true, // Start/pause recording of dispatched actions\n      lock: true, // Lock/unlock dispatching actions and side effects\n      persist: true, // Persist states on page reloading\n      export: true, // Export history of actions in a file\n      import: 'custom', // Import history of actions from a file\n      jump: true, // Jump back and forth (time travelling)\n      skip: true, // Skip (cancel) actions\n      reorder: true, // Drag and drop actions in the history list\n      dispatch: true, // Dispatch custom actions or action creators\n      test: true, // Generate tests for the selected actions\n    },\n    connectInZone: false,\n  };\n\n  const options =\n    typeof optionsInput === 'function' ? optionsInput() : optionsInput;\n  const logOnly = options.logOnly\n    ? { pause: true, export: true, test: true }\n    : false;\n  const features: NonNullable<Partial<StoreDevtoolsConfig['features']>> =\n    options.features ||\n    logOnly ||\n    (DEFAULT_OPTIONS.features as NonNullable<\n      Partial<StoreDevtoolsConfig['features']>\n    >);\n  if (features.import === true) {\n    features.import = 'custom';\n  }\n  const config = Object.assign({}, DEFAULT_OPTIONS, { features }, options);\n\n  if (config.maxAge && config.maxAge < 2) {\n    throw new Error(\n      `Devtools 'maxAge' cannot be less than 2, got ${config.maxAge}`\n    );\n  }\n\n  return config;\n}\n"
  },
  {
    "path": "modules/store-devtools/src/devtools-dispatcher.ts",
    "content": "import { ActionsSubject } from '@ngrx/store';\nimport { Injectable } from '@angular/core';\n\n@Injectable()\nexport class DevtoolsDispatcher extends ActionsSubject {}\n"
  },
  {
    "path": "modules/store-devtools/src/devtools.ts",
    "content": "import {\n  Injectable,\n  Inject,\n  ErrorHandler,\n  OnDestroy,\n  NgZone,\n  inject,\n} from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport {\n  Action,\n  ActionReducer,\n  ActionsSubject,\n  INITIAL_STATE,\n  ReducerObservable,\n  ScannedActionsSubject,\n  StateObservable,\n} from '@ngrx/store';\nimport {\n  merge,\n  MonoTypeOperatorFunction,\n  Observable,\n  Observer,\n  queueScheduler,\n  ReplaySubject,\n  Subscription,\n} from 'rxjs';\nimport { map, observeOn, scan, skip, withLatestFrom } from 'rxjs/operators';\n\nimport * as Actions from './actions';\nimport { STORE_DEVTOOLS_CONFIG, StoreDevtoolsConfig } from './config';\nimport { DevtoolsExtension } from './extension';\nimport { LiftedState, liftInitialState, liftReducerWith } from './reducer';\nimport {\n  liftAction,\n  unliftState,\n  shouldFilterActions,\n  filterLiftedState,\n} from './utils';\nimport { DevtoolsDispatcher } from './devtools-dispatcher';\nimport { PERFORM_ACTION } from './actions';\nimport { ZoneConfig, injectZoneConfig } from './zone-config';\n\n@Injectable()\nexport class StoreDevtools implements Observer<any>, OnDestroy {\n  private liftedStateSubscription: Subscription;\n  private extensionStartSubscription: Subscription;\n  public dispatcher: ActionsSubject;\n  public liftedState: Observable<LiftedState>;\n  public state: StateObservable;\n\n  constructor(\n    dispatcher: DevtoolsDispatcher,\n    actions$: ActionsSubject,\n    reducers$: ReducerObservable,\n    extension: DevtoolsExtension,\n    scannedActions: ScannedActionsSubject,\n    errorHandler: ErrorHandler,\n    @Inject(INITIAL_STATE) initialState: any,\n    @Inject(STORE_DEVTOOLS_CONFIG) config: StoreDevtoolsConfig\n  ) {\n    const liftedInitialState = liftInitialState(initialState, config.monitor);\n    const liftReducer = liftReducerWith(\n      initialState,\n      liftedInitialState,\n      errorHandler,\n      config.monitor,\n      config\n    );\n\n    const liftedAction$ = merge(\n      merge(actions$.asObservable().pipe(skip(1)), extension.actions$).pipe(\n        map(liftAction)\n      ),\n      dispatcher,\n      extension.liftedActions$\n    ).pipe(observeOn(queueScheduler));\n\n    const liftedReducer$ = reducers$.pipe(map(liftReducer));\n\n    const zoneConfig = injectZoneConfig(config.connectInZone!);\n\n    const liftedStateSubject = new ReplaySubject<LiftedState>(1);\n\n    this.liftedStateSubscription = liftedAction$\n      .pipe(\n        withLatestFrom(liftedReducer$),\n        // The extension would post messages back outside of the Angular zone\n        // because we call `connect()` wrapped with `runOutsideAngular`. We run change\n        // detection only once at the end after all the required asynchronous tasks have\n        // been processed (for instance, `setInterval` scheduled by the `timeout` operator).\n        // We have to re-enter the Angular zone before the `scan` since it runs the reducer\n        // which must be run within the Angular zone.\n        emitInZone(zoneConfig),\n        scan<\n          [any, ActionReducer<LiftedState, Actions.All>],\n          {\n            state: LiftedState;\n            action: any;\n          }\n        >(\n          ({ state: liftedState }, [action, reducer]) => {\n            let reducedLiftedState = reducer(liftedState, action);\n            // On full state update\n            // If we have actions filters, we must filter completely our lifted state to be sync with the extension\n            if (action.type !== PERFORM_ACTION && shouldFilterActions(config)) {\n              reducedLiftedState = filterLiftedState(\n                reducedLiftedState,\n                config.predicate,\n                config.actionsSafelist,\n                config.actionsBlocklist\n              );\n            }\n            // Extension should be sent the sanitized lifted state\n            extension.notify(action, reducedLiftedState);\n            return { state: reducedLiftedState, action };\n          },\n          { state: liftedInitialState, action: null as any }\n        )\n      )\n      .subscribe(({ state, action }) => {\n        liftedStateSubject.next(state);\n\n        if (action.type === Actions.PERFORM_ACTION) {\n          const unliftedAction = (action as Actions.PerformAction).action;\n\n          scannedActions.next(unliftedAction);\n        }\n      });\n\n    this.extensionStartSubscription = extension.start$\n      .pipe(emitInZone(zoneConfig))\n      .subscribe(() => {\n        this.refresh();\n      });\n\n    const liftedState$ =\n      liftedStateSubject.asObservable() as Observable<LiftedState>;\n    const state$ = liftedState$.pipe(map(unliftState)) as StateObservable;\n    Object.defineProperty(state$, 'state', {\n      value: toSignal(state$, { manualCleanup: true, requireSync: true }),\n    });\n\n    this.dispatcher = dispatcher;\n    this.liftedState = liftedState$;\n    this.state = state$;\n  }\n\n  ngOnDestroy(): void {\n    // Even though the store devtools plugin is recommended to be\n    // used only in development mode, it can still cause a memory leak\n    // in microfrontend applications that are being created and destroyed\n    // multiple times during development. This results in excessive memory\n    // consumption, as it prevents entire apps from being garbage collected.\n    this.liftedStateSubscription.unsubscribe();\n    this.extensionStartSubscription.unsubscribe();\n  }\n\n  dispatch(action: Action) {\n    this.dispatcher.next(action);\n  }\n\n  next(action: any) {\n    this.dispatcher.next(action);\n  }\n\n  error(error: any) {}\n\n  complete() {}\n\n  performAction(action: any) {\n    this.dispatch(new Actions.PerformAction(action, +Date.now()));\n  }\n\n  refresh() {\n    this.dispatch(new Actions.Refresh());\n  }\n\n  reset() {\n    this.dispatch(new Actions.Reset(+Date.now()));\n  }\n\n  rollback() {\n    this.dispatch(new Actions.Rollback(+Date.now()));\n  }\n\n  commit() {\n    this.dispatch(new Actions.Commit(+Date.now()));\n  }\n\n  sweep() {\n    this.dispatch(new Actions.Sweep());\n  }\n\n  toggleAction(id: number) {\n    this.dispatch(new Actions.ToggleAction(id));\n  }\n\n  jumpToAction(actionId: number) {\n    this.dispatch(new Actions.JumpToAction(actionId));\n  }\n\n  jumpToState(index: number) {\n    this.dispatch(new Actions.JumpToState(index));\n  }\n\n  importState(nextLiftedState: any) {\n    this.dispatch(new Actions.ImportState(nextLiftedState));\n  }\n\n  lockChanges(status: boolean) {\n    this.dispatch(new Actions.LockChanges(status));\n  }\n\n  pauseRecording(status: boolean) {\n    this.dispatch(new Actions.PauseRecording(status));\n  }\n}\n\n/**\n * If the devtools extension is connected out of the Angular zone,\n * this operator will emit all events within the zone.\n */\nfunction emitInZone<T>({\n  ngZone,\n  connectInZone,\n}: ZoneConfig): MonoTypeOperatorFunction<T> {\n  return (source) =>\n    connectInZone\n      ? new Observable<T>((subscriber) =>\n          source.subscribe({\n            next: (value) => ngZone.run(() => subscriber.next(value)),\n            error: (error) => ngZone.run(() => subscriber.error(error)),\n            complete: () => ngZone.run(() => subscriber.complete()),\n          })\n        )\n      : source;\n}\n"
  },
  {
    "path": "modules/store-devtools/src/extension.ts",
    "content": "import { Inject, Injectable, InjectionToken } from '@angular/core';\nimport { Action, UPDATE } from '@ngrx/store';\nimport { EMPTY, Observable, of } from 'rxjs';\nimport {\n  catchError,\n  concatMap,\n  debounceTime,\n  filter,\n  map,\n  share,\n  switchMap,\n  take,\n  takeUntil,\n  timeout,\n} from 'rxjs/operators';\n\nimport { IMPORT_STATE, PERFORM_ACTION } from './actions';\nimport {\n  SerializationOptions,\n  STORE_DEVTOOLS_CONFIG,\n  StoreDevtoolsConfig,\n} from './config';\nimport { DevtoolsDispatcher } from './devtools-dispatcher';\nimport { LiftedAction, LiftedState } from './reducer';\nimport {\n  isActionFiltered,\n  sanitizeAction,\n  sanitizeActions,\n  sanitizeState,\n  sanitizeStates,\n  shouldFilterActions,\n  unliftState,\n} from './utils';\nimport { injectZoneConfig } from './zone-config';\n\nexport const ExtensionActionTypes = {\n  START: 'START',\n  DISPATCH: 'DISPATCH',\n  STOP: 'STOP',\n  ACTION: 'ACTION',\n};\n\nexport const REDUX_DEVTOOLS_EXTENSION =\n  new InjectionToken<ReduxDevtoolsExtension>(\n    '@ngrx/store-devtools Redux Devtools Extension'\n  );\n\nexport interface ReduxDevtoolsExtensionConnection {\n  subscribe(listener: (change: any) => void): void;\n  unsubscribe(): void;\n  send(action: any, state: any): void;\n  init(state?: any): void;\n  error(anyErr: any): void;\n}\nexport interface ReduxDevtoolsExtensionConfig {\n  features?: object | boolean;\n  name: string | undefined;\n  maxAge?: number;\n  autoPause?: boolean;\n  serialize?: boolean | SerializationOptions;\n  trace?: boolean | (() => string);\n  traceLimit?: number;\n}\n\nexport interface ReduxDevtoolsExtension {\n  connect(\n    options: ReduxDevtoolsExtensionConfig\n  ): ReduxDevtoolsExtensionConnection;\n  send(action: any, state: any, options: ReduxDevtoolsExtensionConfig): void;\n}\n\n@Injectable()\nexport class DevtoolsExtension {\n  private devtoolsExtension: ReduxDevtoolsExtension;\n  private extensionConnection!: ReduxDevtoolsExtensionConnection;\n\n  liftedActions$!: Observable<any>;\n  actions$!: Observable<any>;\n  start$!: Observable<any>;\n\n  private zoneConfig = injectZoneConfig(this.config.connectInZone!);\n\n  constructor(\n    @Inject(REDUX_DEVTOOLS_EXTENSION) devtoolsExtension: ReduxDevtoolsExtension,\n    @Inject(STORE_DEVTOOLS_CONFIG) private config: StoreDevtoolsConfig,\n    private dispatcher: DevtoolsDispatcher\n  ) {\n    this.devtoolsExtension = devtoolsExtension;\n    this.createActionStreams();\n  }\n\n  notify(action: LiftedAction, state: LiftedState) {\n    if (!this.devtoolsExtension) {\n      return;\n    }\n    // Check to see if the action requires a full update of the liftedState.\n    // If it is a simple action generated by the user's app and the recording\n    // is not locked/paused, only send the action and the current state (fast).\n    //\n    // A full liftedState update (slow: serializes the entire liftedState) is\n    // only required when:\n    //   a) redux-devtools-extension fires the @@Init action (ignored by\n    //      @ngrx/store-devtools)\n    //   b) an action is generated by an @ngrx module (e.g. @ngrx/effects/init\n    //      or @ngrx/store/update-reducers)\n    //   c) the state has been recomputed due to time-traveling\n    //   d) any action that is not a PerformAction to err on the side of\n    //      caution.\n    if (action.type === PERFORM_ACTION) {\n      if (state.isLocked || state.isPaused) {\n        return;\n      }\n\n      const currentState = unliftState(state);\n      if (\n        shouldFilterActions(this.config) &&\n        isActionFiltered(\n          currentState,\n          action,\n          this.config.predicate,\n          this.config.actionsSafelist,\n          this.config.actionsBlocklist\n        )\n      ) {\n        return;\n      }\n      const sanitizedState = this.config.stateSanitizer\n        ? sanitizeState(\n            this.config.stateSanitizer,\n            currentState,\n            state.currentStateIndex\n          )\n        : currentState;\n      const sanitizedAction = this.config.actionSanitizer\n        ? sanitizeAction(\n            this.config.actionSanitizer,\n            action,\n            state.nextActionId\n          )\n        : action;\n\n      this.sendToReduxDevtools(() =>\n        this.extensionConnection.send(sanitizedAction, sanitizedState)\n      );\n    } else {\n      // Requires full state update\n      const sanitizedLiftedState = {\n        ...state,\n        stagedActionIds: state.stagedActionIds,\n        actionsById: this.config.actionSanitizer\n          ? sanitizeActions(this.config.actionSanitizer, state.actionsById)\n          : state.actionsById,\n        computedStates: this.config.stateSanitizer\n          ? sanitizeStates(this.config.stateSanitizer, state.computedStates)\n          : state.computedStates,\n      };\n\n      this.sendToReduxDevtools(() =>\n        this.devtoolsExtension.send(\n          null,\n          sanitizedLiftedState,\n          this.getExtensionConfig(this.config)\n        )\n      );\n    }\n  }\n\n  private createChangesObservable(): Observable<any> {\n    if (!this.devtoolsExtension) {\n      return EMPTY;\n    }\n\n    return new Observable((subscriber) => {\n      const connection = this.zoneConfig.connectInZone\n        ? // To reduce change detection cycles, we need to run the `connect` method\n          // outside of the Angular zone. The `connect` method adds a `message`\n          // event listener to communicate with an extension using `window.postMessage`\n          // and handle message events.\n          this.zoneConfig.ngZone.runOutsideAngular(() =>\n            this.devtoolsExtension.connect(this.getExtensionConfig(this.config))\n          )\n        : this.devtoolsExtension.connect(this.getExtensionConfig(this.config));\n\n      this.extensionConnection = connection;\n      connection.init();\n\n      connection.subscribe((change: any) => subscriber.next(change));\n      return connection.unsubscribe;\n    });\n  }\n\n  private createActionStreams() {\n    // Listens to all changes\n    const changes$ = this.createChangesObservable().pipe(share());\n\n    // Listen for the start action\n    const start$ = changes$.pipe(\n      filter((change: any) => change.type === ExtensionActionTypes.START)\n    );\n\n    // Listen for the stop action\n    const stop$ = changes$.pipe(\n      filter((change: any) => change.type === ExtensionActionTypes.STOP)\n    );\n\n    // Listen for lifted actions\n    const liftedActions$ = changes$.pipe(\n      filter((change) => change.type === ExtensionActionTypes.DISPATCH),\n      map((change) => this.unwrapAction(change.payload)),\n      concatMap((action: any) => {\n        if (action.type === IMPORT_STATE) {\n          // State imports may happen in two situations:\n          // 1. Explicitly by user\n          // 2. User activated the \"persist state accross reloads\" option\n          //    and now the state is imported during reload.\n          // Because of option 2, we need to give possible\n          // lazy loaded reducers time to instantiate.\n          // As soon as there is no UPDATE action within 1 second,\n          // it is assumed that all reducers are loaded.\n          return this.dispatcher.pipe(\n            filter((action) => action.type === UPDATE),\n            timeout(1000),\n            debounceTime(1000),\n            map(() => action),\n            catchError(() => of(action)),\n            take(1)\n          );\n        } else {\n          return of(action);\n        }\n      })\n    );\n\n    // Listen for unlifted actions\n    const actions$ = changes$.pipe(\n      filter((change) => change.type === ExtensionActionTypes.ACTION),\n      map((change) => this.unwrapAction(change.payload))\n    );\n\n    const actionsUntilStop$ = actions$.pipe(takeUntil(stop$));\n    const liftedUntilStop$ = liftedActions$.pipe(takeUntil(stop$));\n    this.start$ = start$.pipe(takeUntil(stop$));\n\n    // Only take the action sources between the start/stop events\n    this.actions$ = this.start$.pipe(switchMap(() => actionsUntilStop$));\n    this.liftedActions$ = this.start$.pipe(switchMap(() => liftedUntilStop$));\n  }\n\n  private unwrapAction(action: Action) {\n    // indirect eval according to https://esbuild.github.io/content-types/#direct-eval\n    return typeof action === 'string' ? (0, eval)(`(${action})`) : action;\n  }\n\n  private getExtensionConfig(config: StoreDevtoolsConfig) {\n    const extensionOptions: ReduxDevtoolsExtensionConfig = {\n      name: config.name,\n      features: config.features,\n      serialize: config.serialize,\n      autoPause: config.autoPause ?? false,\n      trace: config.trace ?? false,\n      traceLimit: config.traceLimit ?? 75,\n      // The action/state sanitizers are not added to the config\n      // because sanitation is done in this class already.\n      // It is done before sending it to the devtools extension for consistency:\n      // - If we call extensionConnection.send(...),\n      //   the extension would call the sanitizers.\n      // - If we call devtoolsExtension.send(...) (aka full state update),\n      //   the extension would NOT call the sanitizers, so we have to do it ourselves.\n    };\n    if (config.maxAge !== false /* support === 0 */) {\n      extensionOptions.maxAge = config.maxAge;\n    }\n    return extensionOptions;\n  }\n\n  private sendToReduxDevtools(send: Function) {\n    try {\n      send();\n    } catch (err: any) {\n      console.warn(\n        '@ngrx/store-devtools: something went wrong inside the redux devtools',\n        err\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/src/index.ts",
    "content": "export { StoreDevtoolsModule } from './instrument';\nexport { LiftedState, RECOMPUTE } from './reducer';\nexport { StoreDevtools } from './devtools';\nexport { REDUX_DEVTOOLS_EXTENSION } from './extension';\nexport {\n  StoreDevtoolsConfig,\n  StoreDevtoolsOptions,\n  DevToolsFeatureOptions,\n  INITIAL_OPTIONS,\n} from './config';\nexport { provideStoreDevtools } from './provide-store-devtools';\n"
  },
  {
    "path": "modules/store-devtools/src/instrument.ts",
    "content": "import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { StoreDevtoolsOptions } from './config';\nimport { provideStoreDevtools } from './provide-store-devtools';\n\n@NgModule({})\nexport class StoreDevtoolsModule {\n  static instrument(\n    options: StoreDevtoolsOptions = {}\n  ): ModuleWithProviders<StoreDevtoolsModule> {\n    return {\n      ngModule: StoreDevtoolsModule,\n      providers: [provideStoreDevtools(options)],\n    };\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/src/provide-store-devtools.ts",
    "content": "import {\n  EnvironmentProviders,\n  InjectionToken,\n  makeEnvironmentProviders,\n} from '@angular/core';\nimport {\n  DevtoolsExtension,\n  REDUX_DEVTOOLS_EXTENSION,\n  ReduxDevtoolsExtension,\n} from './extension';\nimport { DevtoolsDispatcher } from './devtools-dispatcher';\nimport {\n  createConfig,\n  INITIAL_OPTIONS,\n  noMonitor,\n  STORE_DEVTOOLS_CONFIG,\n  StoreDevtoolsConfig,\n  StoreDevtoolsOptions,\n} from './config';\nimport { ReducerManagerDispatcher, StateObservable } from '@ngrx/store';\nimport { StoreDevtools } from './devtools';\n\nexport const IS_EXTENSION_OR_MONITOR_PRESENT = new InjectionToken<boolean>(\n  '@ngrx/store-devtools Is Devtools Extension or Monitor Present'\n);\n\nexport function createIsExtensionOrMonitorPresent(\n  extension: ReduxDevtoolsExtension | null,\n  config: StoreDevtoolsConfig\n) {\n  return Boolean(extension) || config.monitor !== noMonitor;\n}\n\nexport function createReduxDevtoolsExtension() {\n  const extensionKey = '__REDUX_DEVTOOLS_EXTENSION__';\n\n  if (\n    typeof window === 'object' &&\n    typeof (window as any)[extensionKey] !== 'undefined'\n  ) {\n    return (window as any)[extensionKey];\n  } else {\n    return null;\n  }\n}\n\nexport function createStateObservable(\n  devtools: StoreDevtools\n): StateObservable {\n  return devtools.state;\n}\n\n/**\n * Provides developer tools and instrumentation for `Store`.\n *\n * @usageNotes\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n *   providers: [\n *     provideStoreDevtools({\n *       maxAge: 25,\n *       logOnly: !isDevMode(),\n *     }),\n *   ],\n * });\n * ```\n */\nexport function provideStoreDevtools(\n  options: StoreDevtoolsOptions = {}\n): EnvironmentProviders {\n  return makeEnvironmentProviders([\n    DevtoolsExtension,\n    DevtoolsDispatcher,\n    StoreDevtools,\n    {\n      provide: INITIAL_OPTIONS,\n      useValue: options,\n    },\n    {\n      provide: IS_EXTENSION_OR_MONITOR_PRESENT,\n      deps: [REDUX_DEVTOOLS_EXTENSION, STORE_DEVTOOLS_CONFIG],\n      useFactory: createIsExtensionOrMonitorPresent,\n    },\n    {\n      provide: REDUX_DEVTOOLS_EXTENSION,\n      useFactory: createReduxDevtoolsExtension,\n    },\n    {\n      provide: STORE_DEVTOOLS_CONFIG,\n      deps: [INITIAL_OPTIONS],\n      useFactory: createConfig,\n    },\n    {\n      provide: StateObservable,\n      deps: [StoreDevtools],\n      useFactory: createStateObservable,\n    },\n    {\n      provide: ReducerManagerDispatcher,\n      useExisting: DevtoolsDispatcher,\n    },\n  ]);\n}\n"
  },
  {
    "path": "modules/store-devtools/src/reducer.ts",
    "content": "import { ErrorHandler } from '@angular/core';\nimport { Action, ActionReducer, UPDATE, INIT } from '@ngrx/store';\n\nimport { difference, liftAction, isActionFiltered } from './utils';\nimport * as DevtoolsActions from './actions';\nimport { StoreDevtoolsConfig } from './config';\nimport { PerformAction } from './actions';\n\nexport type InitAction = {\n  readonly type: typeof INIT;\n};\n\nexport type UpdateReducerAction = {\n  readonly type: typeof UPDATE;\n};\n\nexport type CoreActions = InitAction | UpdateReducerAction;\nexport type Actions = DevtoolsActions.All | CoreActions;\n\nexport const INIT_ACTION = { type: INIT };\n\nexport const RECOMPUTE = '@ngrx/store-devtools/recompute' as const;\nexport const RECOMPUTE_ACTION = { type: RECOMPUTE };\n\nexport interface ComputedState {\n  state: any;\n  error: any;\n}\n\nexport interface LiftedAction {\n  type: string;\n  action: Action;\n}\n\nexport interface LiftedActions {\n  [id: number]: LiftedAction;\n}\n\nexport interface LiftedState {\n  monitorState: any;\n  nextActionId: number;\n  actionsById: LiftedActions;\n  stagedActionIds: number[];\n  skippedActionIds: number[];\n  committedState: any;\n  currentStateIndex: number;\n  computedStates: ComputedState[];\n  isLocked: boolean;\n  isPaused: boolean;\n}\n\n/**\n * Computes the next entry in the log by applying an action.\n */\nfunction computeNextEntry(\n  reducer: ActionReducer<any, any>,\n  action: Action,\n  state: any,\n  error: any,\n  errorHandler: ErrorHandler\n) {\n  if (error) {\n    return {\n      state,\n      error: 'Interrupted by an error up the chain',\n    };\n  }\n\n  let nextState = state;\n  let nextError;\n  try {\n    nextState = reducer(state, action);\n  } catch (err: any) {\n    nextError = err.toString();\n    errorHandler.handleError(err);\n  }\n\n  return {\n    state: nextState,\n    error: nextError,\n  };\n}\n\n/**\n * Runs the reducer on invalidated actions to get a fresh computation log.\n */\nfunction recomputeStates(\n  computedStates: ComputedState[],\n  minInvalidatedStateIndex: number,\n  reducer: ActionReducer<any, any>,\n  committedState: any,\n  actionsById: LiftedActions,\n  stagedActionIds: number[],\n  skippedActionIds: number[],\n  errorHandler: ErrorHandler,\n  isPaused: boolean\n) {\n  // Optimization: exit early and return the same reference\n  // if we know nothing could have changed.\n  if (\n    minInvalidatedStateIndex >= computedStates.length &&\n    computedStates.length === stagedActionIds.length\n  ) {\n    return computedStates;\n  }\n\n  const nextComputedStates = computedStates.slice(0, minInvalidatedStateIndex);\n  // If the recording is paused, recompute all states up until the pause state,\n  // else recompute all states.\n  const lastIncludedActionId = stagedActionIds.length - (isPaused ? 1 : 0);\n  for (let i = minInvalidatedStateIndex; i < lastIncludedActionId; i++) {\n    const actionId = stagedActionIds[i];\n    const action = actionsById[actionId].action;\n\n    const previousEntry = nextComputedStates[i - 1];\n    const previousState = previousEntry ? previousEntry.state : committedState;\n    const previousError = previousEntry ? previousEntry.error : undefined;\n\n    const shouldSkip = skippedActionIds.indexOf(actionId) > -1;\n    const entry: ComputedState = shouldSkip\n      ? previousEntry\n      : computeNextEntry(\n          reducer,\n          action,\n          previousState,\n          previousError,\n          errorHandler\n        );\n\n    nextComputedStates.push(entry);\n  }\n  // If the recording is paused, the last state will not be recomputed,\n  // because it's essentially not part of the state history.\n  if (isPaused) {\n    nextComputedStates.push(computedStates[computedStates.length - 1]);\n  }\n\n  return nextComputedStates;\n}\n\nexport function liftInitialState(\n  initialCommittedState?: any,\n  monitorReducer?: any\n): LiftedState {\n  return {\n    monitorState: monitorReducer(undefined, {}),\n    nextActionId: 1,\n    actionsById: { 0: liftAction(INIT_ACTION) },\n    stagedActionIds: [0],\n    skippedActionIds: [],\n    committedState: initialCommittedState,\n    currentStateIndex: 0,\n    computedStates: [],\n    isLocked: false,\n    isPaused: false,\n  };\n}\n\n/**\n * Creates a history state reducer from an app's reducer.\n */\nexport function liftReducerWith(\n  initialCommittedState: any,\n  initialLiftedState: LiftedState,\n  errorHandler: ErrorHandler,\n  monitorReducer?: any,\n  options: Partial<StoreDevtoolsConfig> = {}\n) {\n  /**\n   * Manages how the history actions modify the history state.\n   */\n  return (\n      reducer: ActionReducer<any, any>\n    ): ActionReducer<LiftedState, Actions> =>\n    (liftedState, liftedAction) => {\n      let {\n        monitorState,\n        actionsById,\n        nextActionId,\n        stagedActionIds,\n        skippedActionIds,\n        committedState,\n        currentStateIndex,\n        computedStates,\n        isLocked,\n        isPaused,\n      } = liftedState || initialLiftedState;\n\n      if (!liftedState) {\n        // Prevent mutating initialLiftedState\n        actionsById = Object.create(actionsById);\n      }\n\n      function commitExcessActions(n: number) {\n        // Auto-commits n-number of excess actions.\n        let excess = n;\n        let idsToDelete = stagedActionIds.slice(1, excess + 1);\n\n        for (let i = 0; i < idsToDelete.length; i++) {\n          if (computedStates[i + 1].error) {\n            // Stop if error is found. Commit actions up to error.\n            excess = i;\n            idsToDelete = stagedActionIds.slice(1, excess + 1);\n            break;\n          } else {\n            delete actionsById[idsToDelete[i]];\n          }\n        }\n\n        skippedActionIds = skippedActionIds.filter(\n          (id) => idsToDelete.indexOf(id) === -1\n        );\n        stagedActionIds = [0, ...stagedActionIds.slice(excess + 1)];\n        committedState = computedStates[excess].state;\n        computedStates = computedStates.slice(excess);\n        currentStateIndex =\n          currentStateIndex > excess ? currentStateIndex - excess : 0;\n      }\n\n      function commitChanges() {\n        // Consider the last committed state the new starting point.\n        // Squash any staged actions into a single committed state.\n        actionsById = { 0: liftAction(INIT_ACTION) };\n        nextActionId = 1;\n        stagedActionIds = [0];\n        skippedActionIds = [];\n        committedState = computedStates[currentStateIndex].state;\n        currentStateIndex = 0;\n        computedStates = [];\n      }\n\n      // By default, aggressively recompute every state whatever happens.\n      // This has O(n) performance, so we'll override this to a sensible\n      // value whenever we feel like we don't have to recompute the states.\n      let minInvalidatedStateIndex = 0;\n\n      switch (liftedAction.type) {\n        case DevtoolsActions.LOCK_CHANGES: {\n          isLocked = liftedAction.status;\n          minInvalidatedStateIndex = Infinity;\n          break;\n        }\n        case DevtoolsActions.PAUSE_RECORDING: {\n          isPaused = liftedAction.status;\n          if (isPaused) {\n            // Add a pause action to signal the devtools-user the recording is paused.\n            // The corresponding state will be overwritten on each update to always contain\n            // the latest state (see Actions.PERFORM_ACTION).\n            stagedActionIds = [...stagedActionIds, nextActionId];\n            actionsById[nextActionId] = new PerformAction(\n              {\n                type: '@ngrx/devtools/pause',\n              },\n              +Date.now()\n            );\n            nextActionId++;\n            minInvalidatedStateIndex = stagedActionIds.length - 1;\n            computedStates = computedStates.concat(\n              computedStates[computedStates.length - 1]\n            );\n\n            if (currentStateIndex === stagedActionIds.length - 2) {\n              currentStateIndex++;\n            }\n            minInvalidatedStateIndex = Infinity;\n          } else {\n            commitChanges();\n          }\n          break;\n        }\n        case DevtoolsActions.RESET: {\n          // Get back to the state the store was created with.\n          actionsById = { 0: liftAction(INIT_ACTION) };\n          nextActionId = 1;\n          stagedActionIds = [0];\n          skippedActionIds = [];\n          committedState = initialCommittedState;\n          currentStateIndex = 0;\n          computedStates = [];\n          break;\n        }\n        case DevtoolsActions.COMMIT: {\n          commitChanges();\n          break;\n        }\n        case DevtoolsActions.ROLLBACK: {\n          // Forget about any staged actions.\n          // Start again from the last committed state.\n          actionsById = { 0: liftAction(INIT_ACTION) };\n          nextActionId = 1;\n          stagedActionIds = [0];\n          skippedActionIds = [];\n          currentStateIndex = 0;\n          computedStates = [];\n          break;\n        }\n        case DevtoolsActions.TOGGLE_ACTION: {\n          // Toggle whether an action with given ID is skipped.\n          // Being skipped means it is a no-op during the computation.\n          const { id: actionId } = liftedAction;\n          const index = skippedActionIds.indexOf(actionId);\n          if (index === -1) {\n            skippedActionIds = [actionId, ...skippedActionIds];\n          } else {\n            skippedActionIds = skippedActionIds.filter((id) => id !== actionId);\n          }\n          // Optimization: we know history before this action hasn't changed\n          minInvalidatedStateIndex = stagedActionIds.indexOf(actionId);\n          break;\n        }\n        case DevtoolsActions.SET_ACTIONS_ACTIVE: {\n          // Toggle whether an action with given ID is skipped.\n          // Being skipped means it is a no-op during the computation.\n          const { start, end, active } = liftedAction;\n          const actionIds = [];\n          for (let i = start; i < end; i++) actionIds.push(i);\n          if (active) {\n            skippedActionIds = difference(skippedActionIds, actionIds);\n          } else {\n            skippedActionIds = [...skippedActionIds, ...actionIds];\n          }\n\n          // Optimization: we know history before this action hasn't changed\n          minInvalidatedStateIndex = stagedActionIds.indexOf(start);\n          break;\n        }\n        case DevtoolsActions.JUMP_TO_STATE: {\n          // Without recomputing anything, move the pointer that tell us\n          // which state is considered the current one. Useful for sliders.\n          currentStateIndex = liftedAction.index;\n          // Optimization: we know the history has not changed.\n          minInvalidatedStateIndex = Infinity;\n          break;\n        }\n        case DevtoolsActions.JUMP_TO_ACTION: {\n          // Jumps to a corresponding state to a specific action.\n          // Useful when filtering actions.\n          const index = stagedActionIds.indexOf(liftedAction.actionId);\n          if (index !== -1) currentStateIndex = index;\n          minInvalidatedStateIndex = Infinity;\n          break;\n        }\n        case DevtoolsActions.SWEEP: {\n          // Forget any actions that are currently being skipped.\n          stagedActionIds = difference(stagedActionIds, skippedActionIds);\n          skippedActionIds = [];\n          currentStateIndex = Math.min(\n            currentStateIndex,\n            stagedActionIds.length - 1\n          );\n          break;\n        }\n        case DevtoolsActions.PERFORM_ACTION: {\n          // Ignore action and return state as is if recording is locked\n          if (isLocked) {\n            return liftedState || initialLiftedState;\n          }\n\n          if (\n            isPaused ||\n            (liftedState &&\n              isActionFiltered(\n                liftedState.computedStates[currentStateIndex],\n                liftedAction,\n                options.predicate,\n                options.actionsSafelist,\n                options.actionsBlocklist\n              ))\n          ) {\n            // If recording is paused or if the action should be ignored, overwrite the last state\n            // (corresponds to the pause action) and keep everything else as is.\n            // This way, the app gets the new current state while the devtools\n            // do not record another action.\n            const lastState = computedStates[computedStates.length - 1];\n            computedStates = [\n              ...computedStates.slice(0, -1),\n              computeNextEntry(\n                reducer,\n                liftedAction.action,\n                lastState.state,\n                lastState.error,\n                errorHandler\n              ),\n            ];\n            minInvalidatedStateIndex = Infinity;\n            break;\n          }\n\n          // Auto-commit as new actions come in.\n          if (options.maxAge && stagedActionIds.length === options.maxAge) {\n            commitExcessActions(1);\n          }\n\n          if (currentStateIndex === stagedActionIds.length - 1) {\n            currentStateIndex++;\n          }\n          const actionId = nextActionId++;\n          // Mutation! This is the hottest path, and we optimize on purpose.\n          // It is safe because we set a new key in a cache dictionary.\n          actionsById[actionId] = liftedAction;\n\n          stagedActionIds = [...stagedActionIds, actionId];\n          // Optimization: we know that only the new action needs computing.\n          minInvalidatedStateIndex = stagedActionIds.length - 1;\n          break;\n        }\n        case DevtoolsActions.IMPORT_STATE: {\n          // Completely replace everything.\n          ({\n            monitorState,\n            actionsById,\n            nextActionId,\n            stagedActionIds,\n            skippedActionIds,\n            committedState,\n            currentStateIndex,\n            computedStates,\n            isLocked,\n            isPaused,\n          } = liftedAction.nextLiftedState);\n          break;\n        }\n        case INIT: {\n          // Always recompute states on hot reload and init.\n          minInvalidatedStateIndex = 0;\n\n          if (options.maxAge && stagedActionIds.length > options.maxAge) {\n            // States must be recomputed before committing excess.\n            computedStates = recomputeStates(\n              computedStates,\n              minInvalidatedStateIndex,\n              reducer,\n              committedState,\n              actionsById,\n              stagedActionIds,\n              skippedActionIds,\n              errorHandler,\n              isPaused\n            );\n\n            commitExcessActions(stagedActionIds.length - options.maxAge);\n\n            // Avoid double computation.\n            minInvalidatedStateIndex = Infinity;\n          }\n\n          break;\n        }\n        case UPDATE: {\n          const stateHasErrors =\n            computedStates.filter((state) => state.error).length > 0;\n\n          if (stateHasErrors) {\n            // Recompute all states\n            minInvalidatedStateIndex = 0;\n\n            if (options.maxAge && stagedActionIds.length > options.maxAge) {\n              // States must be recomputed before committing excess.\n              computedStates = recomputeStates(\n                computedStates,\n                minInvalidatedStateIndex,\n                reducer,\n                committedState,\n                actionsById,\n                stagedActionIds,\n                skippedActionIds,\n                errorHandler,\n                isPaused\n              );\n\n              commitExcessActions(stagedActionIds.length - options.maxAge);\n\n              // Avoid double computation.\n              minInvalidatedStateIndex = Infinity;\n            }\n          } else {\n            // If not paused/locked, add a new action to signal devtools-user\n            // that there was a reducer update.\n            if (!isPaused && !isLocked) {\n              if (currentStateIndex === stagedActionIds.length - 1) {\n                currentStateIndex++;\n              }\n\n              // Add a new action to only recompute state\n              const actionId = nextActionId++;\n              actionsById[actionId] = new PerformAction(\n                liftedAction,\n                +Date.now()\n              );\n              stagedActionIds = [...stagedActionIds, actionId];\n\n              minInvalidatedStateIndex = stagedActionIds.length - 1;\n\n              computedStates = recomputeStates(\n                computedStates,\n                minInvalidatedStateIndex,\n                reducer,\n                committedState,\n                actionsById,\n                stagedActionIds,\n                skippedActionIds,\n                errorHandler,\n                isPaused\n              );\n            }\n\n            // Recompute state history with latest reducer and update action\n            computedStates = computedStates.map((cmp) => ({\n              ...cmp,\n              state: reducer(cmp.state, RECOMPUTE_ACTION),\n            }));\n\n            currentStateIndex = stagedActionIds.length - 1;\n\n            if (options.maxAge && stagedActionIds.length > options.maxAge) {\n              commitExcessActions(stagedActionIds.length - options.maxAge);\n            }\n\n            // Avoid double computation.\n            minInvalidatedStateIndex = Infinity;\n          }\n\n          break;\n        }\n        default: {\n          // If the action is not recognized, it's a monitor action.\n          // Optimization: a monitor action can't change history.\n          minInvalidatedStateIndex = Infinity;\n          break;\n        }\n      }\n\n      computedStates = recomputeStates(\n        computedStates,\n        minInvalidatedStateIndex,\n        reducer,\n        committedState,\n        actionsById,\n        stagedActionIds,\n        skippedActionIds,\n        errorHandler,\n        isPaused\n      );\n      monitorState = monitorReducer(monitorState, liftedAction);\n\n      return {\n        monitorState,\n        actionsById,\n        nextActionId,\n        stagedActionIds,\n        skippedActionIds,\n        committedState,\n        currentStateIndex,\n        computedStates,\n        isLocked,\n        isPaused,\n      };\n    };\n}\n"
  },
  {
    "path": "modules/store-devtools/src/utils.ts",
    "content": "import { Action } from '@ngrx/store';\n\nimport * as Actions from './actions';\nimport {\n  ActionSanitizer,\n  StateSanitizer,\n  Predicate,\n  StoreDevtoolsConfig,\n} from './config';\nimport {\n  ComputedState,\n  LiftedAction,\n  LiftedActions,\n  LiftedState,\n} from './reducer';\n\nexport function difference(first: any[], second: any[]) {\n  return first.filter((item) => second.indexOf(item) < 0);\n}\n\n/**\n * Provides an app's view into the state of the lifted store.\n */\nexport function unliftState(liftedState: LiftedState) {\n  const { computedStates, currentStateIndex } = liftedState;\n\n  // At start up NgRx dispatches init actions,\n  // When these init actions are being filtered out by the predicate or safe/block list options\n  // we don't have a complete computed states yet.\n  // At this point it could happen that we're out of bounds, when this happens we fall back to the last known state\n  if (currentStateIndex >= computedStates.length) {\n    const { state } = computedStates[computedStates.length - 1];\n    return state;\n  }\n\n  const { state } = computedStates[currentStateIndex];\n  return state;\n}\n\nexport function unliftAction(liftedState: LiftedState): LiftedAction {\n  return liftedState.actionsById[liftedState.nextActionId - 1];\n}\n\n/**\n * Lifts an app's action into an action on the lifted store.\n */\nexport function liftAction(action: Action) {\n  return new Actions.PerformAction(action, +Date.now());\n}\n\n/**\n * Sanitizes given actions with given function.\n */\nexport function sanitizeActions(\n  actionSanitizer: ActionSanitizer,\n  actions: LiftedActions\n): LiftedActions {\n  return Object.keys(actions).reduce(\n    (sanitizedActions, actionIdx) => {\n      const idx = Number(actionIdx);\n      sanitizedActions[idx] = sanitizeAction(\n        actionSanitizer,\n        actions[idx],\n        idx\n      );\n      return sanitizedActions;\n    },\n    <LiftedActions>{}\n  );\n}\n\n/**\n * Sanitizes given action with given function.\n */\nexport function sanitizeAction(\n  actionSanitizer: ActionSanitizer,\n  action: LiftedAction,\n  actionIdx: number\n): LiftedAction {\n  return {\n    ...action,\n    action: actionSanitizer(action.action, actionIdx),\n  };\n}\n\n/**\n * Sanitizes given states with given function.\n */\nexport function sanitizeStates(\n  stateSanitizer: StateSanitizer,\n  states: ComputedState[]\n): ComputedState[] {\n  return states.map((computedState, idx) => ({\n    state: sanitizeState(stateSanitizer, computedState.state, idx),\n    error: computedState.error,\n  }));\n}\n\n/**\n * Sanitizes given state with given function.\n */\nexport function sanitizeState(\n  stateSanitizer: StateSanitizer,\n  state: any,\n  stateIdx: number\n) {\n  return stateSanitizer(state, stateIdx);\n}\n\n/**\n * Read the config and tell if actions should be filtered\n */\nexport function shouldFilterActions(config: StoreDevtoolsConfig) {\n  return config.predicate || config.actionsSafelist || config.actionsBlocklist;\n}\n\n/**\n * Return a full filtered lifted state\n */\nexport function filterLiftedState(\n  liftedState: LiftedState,\n  predicate?: Predicate,\n  safelist?: string[],\n  blocklist?: string[]\n): LiftedState {\n  const filteredStagedActionIds: number[] = [];\n  const filteredActionsById: LiftedActions = {};\n  const filteredComputedStates: ComputedState[] = [];\n  liftedState.stagedActionIds.forEach((id, idx) => {\n    const liftedAction = liftedState.actionsById[id];\n    if (!liftedAction) return;\n    if (\n      idx &&\n      isActionFiltered(\n        liftedState.computedStates[idx],\n        liftedAction,\n        predicate,\n        safelist,\n        blocklist\n      )\n    ) {\n      return;\n    }\n    filteredActionsById[id] = liftedAction;\n    filteredStagedActionIds.push(id);\n    filteredComputedStates.push(liftedState.computedStates[idx]);\n  });\n  return {\n    ...liftedState,\n    stagedActionIds: filteredStagedActionIds,\n    actionsById: filteredActionsById,\n    computedStates: filteredComputedStates,\n  };\n}\n\n/**\n * Return true is the action should be ignored\n */\nexport function isActionFiltered(\n  state: any,\n  action: LiftedAction,\n  predicate?: Predicate,\n  safelist?: string[],\n  blockedlist?: string[]\n) {\n  const predicateMatch = predicate && !predicate(state, action.action);\n  const safelistMatch =\n    safelist &&\n    !action.action.type.match(safelist.map((s) => escapeRegExp(s)).join('|'));\n  const blocklistMatch =\n    blockedlist &&\n    action.action.type.match(blockedlist.map((s) => escapeRegExp(s)).join('|'));\n  return predicateMatch || safelistMatch || blocklistMatch;\n}\n\n/**\n * Return string with escaped RegExp special characters\n * https://stackoverflow.com/a/6969486/1337347\n */\nfunction escapeRegExp(s: string): string {\n  return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n"
  },
  {
    "path": "modules/store-devtools/src/zone-config.ts",
    "content": "import { NgZone, inject } from '@angular/core';\n\nexport type ZoneConfig =\n  | { connectInZone: true; ngZone: NgZone }\n  | { connectInZone: false; ngZone: null };\n\nexport function injectZoneConfig(connectInZone: boolean) {\n  const ngZone = connectInZone ? inject(NgZone) : null;\n  return { ngZone, connectInZone } as ZoneConfig;\n}\n"
  },
  {
    "path": "modules/store-devtools/test-setup.ts",
    "content": "import {\n  TextEncoder as NodeTextEncoder,\n  TextDecoder as NodeTextDecoder,\n} from 'util';\n\n// Only assign if not already defined, using type assertion to satisfy TypeScript\nif (typeof globalThis.TextEncoder === 'undefined') {\n  globalThis.TextEncoder = NodeTextEncoder as unknown as {\n    new (): TextEncoder;\n    prototype: TextEncoder;\n  };\n}\n\nif (typeof globalThis.TextDecoder === 'undefined') {\n  globalThis.TextDecoder = NodeTextDecoder as unknown as {\n    new (): TextDecoder;\n    prototype: TextDecoder;\n  };\n}\n\nimport '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-zone';\n\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\ngetTestBed().initTestEnvironment(\n  BrowserTestingModule,\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "modules/store-devtools/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"../../\",\n    \"declaration\": true,\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmitOnError\": false,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/store-devtools\",\n    \"paths\": {},\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"skipLibCheck\": true\n  },\n  \"files\": [\"public_api.ts\"],\n  \"angularCompilerOptions\": {\n    \"annotateForClosureCompiler\": true,\n    \"compilationMode\": \"partial\",\n    \"flatModuleOutFile\": \"index.js\",\n    \"flatModuleId\": \"@ngrx/store-devtools\"\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/tsconfig.schematics.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"stripInternal\": true,\n    \"experimentalDecorators\": true,\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n    \"moduleResolution\": \"nodenext\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../../dist/modules/store-devtools\",\n    \"paths\": {\n      \"@ngrx/store-devtools/schematics-core\": [\"./schematics-core\"],\n      \"@ngrx/store-devtools\": [\"./src\"]\n    },\n    \"sourceMap\": true,\n    \"inlineSources\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"include\": [\"migrations/**/*.ts\", \"schematics/**/*.ts\"],\n  \"exclude\": [\"**/*.spec.ts\"],\n  \"angularCompilerOptions\": {\n    \"skipMetadataEmit\": true,\n    \"enableSummariesForJit\": false,\n    \"enableIvy\": false\n  }\n}\n"
  },
  {
    "path": "modules/store-devtools/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"node\", \"vitest/globals\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "modules/store-devtools/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [angular(), nxViteTsPaths()],\n    test: {\n      globals: true,\n      pool: 'forks',\n      environment: 'jsdom',\n      setupFiles: ['test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default'],\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "nx.json",
    "content": "{\n  \"tasksRunnerOptions\": {\n    \"default\": {\n      \"options\": {\n        \"canTrackAnalytics\": false,\n        \"showUsageWarnings\": true,\n        \"useLightClient\": true\n      }\n    }\n  },\n  \"workspaceLayout\": {\n    \"appsDir\": \"projects\",\n    \"libsDir\": \"modules\"\n  },\n  \"cli\": {\n    \"analytics\": false\n  },\n  \"generators\": {\n    \"@schematics/angular:component\": {\n      \"inlineStyle\": true,\n      \"inlineTemplate\": true,\n      \"flat\": true,\n      \"skipTests\": true,\n      \"prefix\": \"bc\",\n      \"style\": \"css\"\n    },\n    \"@schematics/angular:directive\": {\n      \"prefix\": \"bc\"\n    },\n    \"@nx/angular:application\": {\n      \"style\": \"css\",\n      \"linter\": \"eslint\",\n      \"unitTestRunner\": \"jest\",\n      \"e2eTestRunner\": \"cypress\"\n    },\n    \"@nx/angular:library\": {\n      \"linter\": \"eslint\",\n      \"unitTestRunner\": \"jest\"\n    },\n    \"@nx/angular:component\": {\n      \"style\": \"css\"\n    }\n  },\n  \"defaultProject\": \"example-app\",\n  \"$schema\": \"./node_modules/nx/schemas/nx-schema.json\",\n  \"targetDefaults\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"inputs\": [\"production\", \"^production\"],\n      \"cache\": true\n    },\n    \"e2e\": {\n      \"inputs\": [\"default\", \"^production\"],\n      \"cache\": true\n    },\n    \"test\": {\n      \"inputs\": [\"default\", \"^production\", \"{workspaceRoot}/jest.preset.js\"],\n      \"cache\": true\n    },\n    \"lint\": {\n      \"inputs\": [\"default\", \"{workspaceRoot}/eslint.config.mjs\"],\n      \"cache\": true\n    },\n    \"build-package\": {\n      \"cache\": true\n    },\n    \"@nx/jest:jest\": {\n      \"inputs\": [\"default\", \"^production\", \"{workspaceRoot}/jest.preset.js\"],\n      \"cache\": true,\n      \"options\": {\n        \"passWithNoTests\": true\n      },\n      \"configurations\": {\n        \"ci\": {\n          \"ci\": true,\n          \"codeCoverage\": true\n        }\n      }\n    }\n  },\n  \"namedInputs\": {\n    \"default\": [\"{projectRoot}/**/*\", \"sharedGlobals\"],\n    \"sharedGlobals\": [\n      \"{workspaceRoot}/angular.json\",\n      \"{workspaceRoot}/nx.json\",\n      \"{workspaceRoot}/tsconfig.json\",\n      \"{workspaceRoot}/.circleci/config.yml\"\n    ],\n    \"production\": [\n      \"default\",\n      \"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)\",\n      \"!{projectRoot}/tsconfig.spec.json\",\n      \"!{projectRoot}/jest.config.[jt]s\",\n      \"!{projectRoot}/src/test-setup.[jt]s\",\n      \"!{projectRoot}/eslint.config.mjs\"\n    ]\n  },\n  \"nxCloudAccessToken\": \"NTlmOGIxYmItZjM0OC00YzAxLTgzZTgtZDNiZmExMzcwZTA4fHJlYWQ=\",\n  \"defaultBase\": \"main\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@ngrx/platform\",\n  \"version\": \"21.0.1\",\n  \"description\": \"monorepo for ngrx development\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"build\": \"nx run-many --target=build --all\",\n    \"deploy:builds\": \"npx tsx ./build/deploy-build.ts\",\n    \"test\": \"nx run-many --target=test --all\",\n    \"clean\": \"git clean -xdf\",\n    \"coverage:html\": \"nyc report --reporter=html\",\n    \"copy:dist\": \"ncp dist/ ./node_modules/@ngrx/\",\n    \"ci\": \"pnpm exec run test && nyc report --reporter=text-lcov | coveralls\",\n    \"prettier\": \"prettier --write \\\"**/*.ts\\\"\",\n    \"postinstall\": \"opencollective postinstall\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s\",\n    \"copy:schematics\": \"npx tsx ./build/copy-schematics-core.ts\",\n    \"schematics:check\": \"git diff --name-only --exit-code ./modules\",\n    \"build:stackblitz\": \"npx tsx ./build/stackblitz.ts && git add ./stackblitz.html\",\n    \"publish:next\": \"npx tsx ./build/publish-rc.1\",\n    \"publish:latest\": \"npx tsx ./build/publish-latest.ts\",\n    \"nx\": \"nx\",\n    \"update\": \"nx migrate latest\",\n    \"update:check\": \"ng update\",\n    \"update:versions\": \"npx tsx ./build/update-version-numbers.ts\",\n    \"postupdate:versions\": \"npm run changelog\",\n    \"lint\": \"nx run-many --target=lint --all\",\n    \"eslint-plugin:update\": \"npx tsx ./build/generate-eslint-plugin.ts\",\n    \"prepare\": \"husky install\"\n  },\n  \"engines\": {\n    \"node\": \"^18.19.1 || ^20.11.1 || >=22.0.0\",\n    \"pnpm\": \"^9.0.0\"\n  },\n  \"packageManager\": \"pnpm@9.10.0\",\n  \"lint-staged\": {\n    \"*.{ts,json,md}\": [\n      \"prettier --write\",\n      \"git add\"\n    ]\n  },\n  \"keywords\": [\n    \"ngrx\",\n    \"angular\",\n    \"rxjs\"\n  ],\n  \"author\": \"Rob Wormald <robwormald@gmail.com>\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ngrx/platform.git\"\n  },\n  \"nyc\": {\n    \"extension\": [\n      \".ts\"\n    ],\n    \"exclude\": [\n      \"**/*.spec\",\n      \"**/spec/**/*\",\n      \"/modules/schematics/src/*/files/**/*\",\n      \"/modules/**/schematics/**/files/**/*\",\n      \"**/schematics/src/utility/*\"\n    ],\n    \"include\": [\n      \"**/*.ts\"\n    ]\n  },\n  \"dependencies\": {\n    \"@analogjs/content\": \"2.1.1\",\n    \"@analogjs/router\": \"2.1.1\",\n    \"@angular/animations\": \"21.0.1\",\n    \"@angular/cdk\": \"21.0.1\",\n    \"@angular/common\": \"21.0.1\",\n    \"@angular/compiler\": \"21.0.1\",\n    \"@angular/core\": \"21.0.1\",\n    \"@angular/elements\": \"21.0.1\",\n    \"@angular/forms\": \"21.0.1\",\n    \"@angular/material\": \"21.0.1\",\n    \"@angular/platform-browser\": \"21.0.1\",\n    \"@angular/platform-browser-dynamic\": \"21.0.1\",\n    \"@angular/platform-server\": \"21.0.1\",\n    \"@angular/router\": \"21.0.1\",\n    \"@microsoft/api-extractor\": \"^7.55.1\",\n    \"@microsoft/tsdoc\": \"^0.15.1\",\n    \"@nx/angular\": \"22.0.3\",\n    \"firebase-admin\": \"^12.1.1\",\n    \"firebase-functions\": \"^5.0.1\",\n    \"front-matter\": \"^4.0.2\",\n    \"highlight.js\": \"^11.9.0\",\n    \"marked\": \"^15.0.7\",\n    \"marked-gfm-heading-id\": \"^4.1.1\",\n    \"marked-highlight\": \"^2.2.1\",\n    \"marked-mangle\": \"^1.1.10\",\n    \"marked-shiki\": \"^1.2.0\",\n    \"opencollective\": \"^1.0.3\",\n    \"rxjs\": \"7.8.0\",\n    \"semver\": \"^7.7.2\",\n    \"shiki\": \"~1.6.1\",\n    \"strip-json-comments\": \"5.0.3\",\n    \"tslib\": \"^2.7.0\",\n    \"tsx\": \"^4.11.2\",\n    \"zone.js\": \"0.15.0\"\n  },\n  \"devDependencies\": {\n    \"@analogjs/platform\": \"2.1.1\",\n    \"@analogjs/vite-plugin-angular\": \"2.1.1\",\n    \"@analogjs/vitest-angular\": \"2.1.1\",\n    \"@angular-devkit/build-angular\": \"21.0.1\",\n    \"@angular-devkit/core\": \"21.0.1\",\n    \"@angular-devkit/schematics\": \"21.0.1\",\n    \"@angular-eslint/builder\": \"21.0.1\",\n    \"@angular-eslint/eslint-plugin\": \"21.0.1\",\n    \"@angular-eslint/eslint-plugin-template\": \"21.0.1\",\n    \"@angular-eslint/schematics\": \"21.0.1\",\n    \"@angular-eslint/template-parser\": \"21.0.1\",\n    \"@angular-eslint/test-utils\": \"21.0.1\",\n    \"@angular/build\": \"21.0.1\",\n    \"@angular/cli\": \"21.0.1\",\n    \"@angular/compiler-cli\": \"21.0.1\",\n    \"@angular/language-service\": \"21.0.1\",\n    \"@nx/cypress\": \"22.0.3\",\n    \"@nx/eslint\": \"22.0.3\",\n    \"@nx/eslint-plugin\": \"22.0.3\",\n    \"@nx/jest\": \"22.0.3\",\n    \"@nx/node\": \"22.0.3\",\n    \"@nx/vite\": \"22.0.3\",\n    \"@nx/workspace\": \"22.0.3\",\n    \"@octokit/rest\": \"^15.17.0\",\n    \"@schematics/angular\": \"21.0.1\",\n    \"@stackblitz/sdk\": \"^1.11.0\",\n    \"@testing-library/cypress\": \"9.0.0\",\n    \"@types/jasmine\": \"4.0.3\",\n    \"@types/jasminewd2\": \"^2.0.2\",\n    \"@types/jest\": \"29.5.13\",\n    \"@types/ncp\": \"^2.0.8\",\n    \"@types/node\": \"^24.3.0\",\n    \"@types/semver\": \"^7.7.0\",\n    \"@types/shelljs\": \"^0.8.17\",\n    \"@typescript-eslint/rule-tester\": \"8.46.4\",\n    \"@typescript-eslint/types\": \"8.46.4\",\n    \"@typescript-eslint/utils\": \"8.46.4\",\n    \"conventional-changelog-cli\": \"^1.3.21\",\n    \"cpy-cli\": \"^6.0.0\",\n    \"cypress\": \"14.2.1\",\n    \"deep-freeze\": \"^0.0.1\",\n    \"eslint\": \"9.34.0\",\n    \"eslint-plugin-import\": \"2.32.0\",\n    \"express\": \"4.21.2\",\n    \"husky\": \"^8.0.1\",\n    \"jasmine\": \"^3.4.0\",\n    \"jasmine-core\": \"4.2.0\",\n    \"jasmine-marbles\": \"0.9.2\",\n    \"jasmine-spec-reporter\": \"7.0.0\",\n    \"jest\": \"29.7.0\",\n    \"jest-environment-jsdom\": \"29.7.0\",\n    \"jest-jasmine2\": \"29.7.0\",\n    \"jest-preset-angular\": \"14.4.2\",\n    \"jiti\": \"2.4.2\",\n    \"jsdom\": \"^22.0.0\",\n    \"karma\": \"6.4.0\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-cli\": \"~1.0.1\",\n    \"karma-coverage-istanbul-reporter\": \"3.0.3\",\n    \"karma-jasmine\": \"5.1.0\",\n    \"karma-jasmine-html-reporter\": \"2.0.0\",\n    \"lint-staged\": \"^8.0.0\",\n    \"mkdirp\": \"^3.0.1\",\n    \"ncp\": \"^2.0.0\",\n    \"ng-packagr\": \"21.0.0\",\n    \"nx\": \"22.0.3\",\n    \"ora\": \"^8.2.0\",\n    \"prettier\": \"^3.6.2\",\n    \"rimraf\": \"^6.0.1\",\n    \"rollup\": \"~2.59.0\",\n    \"rollup-plugin-alias\": \"^1.4.0\",\n    \"rollup-plugin-commonjs\": \"10.1.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-sourcemaps\": \"^0.6.3\",\n    \"rxjs-marbles\": \"^7.0.1\",\n    \"tinyglobby\": \"^0.2.14\",\n    \"ts-jest\": \"29.1.0\",\n    \"ts-loader\": \"^5.3.3\",\n    \"ts-node\": \"^10.9.2\",\n    \"ts-snippet\": \"5.0.2\",\n    \"tsconfig-paths\": \"^3.1.3\",\n    \"typescript\": \"5.9.3\",\n    \"typescript-eslint\": \"8.46.4\",\n    \"vite\": \"^7.0.0\",\n    \"vite-tsconfig-paths\": \"^5.1.4\",\n    \"vitest\": \"^4.0.0\",\n    \"yaml\": \"^2.4.5\"\n  },\n  \"collective\": {\n    \"type\": \"opencollective\",\n    \"url\": \"https://opencollective.com/ngrx\",\n    \"logo\": \"https://opencollective.com/opencollective/logo.txt\"\n  }\n}\n"
  },
  {
    "path": "prettier.config.js",
    "content": "/**\n * @see https://prettier.io/docs/configuration\n * @type {import(\"prettier\").Config}\n*/\nmodule.exports = {\n  singleQuote: true,\n  trailingComma: 'es5',\n  overrides: [\n    {\n      files: '*.md',\n      options: {\n        printWidth: 70,\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "projects/example-app/README.md",
    "content": "# @ngrx example application\n\nExample application utilizing @ngrx libraries, showcasing common patterns and best practices. Try it on [StackBlitz](https://ngrx.github.io/platform/stackblitz.html).\n\nThis app is a book collection manager. The user can authenticate, use the Google Books API to search for\nbooks and add them to their collection. This application utilizes [@ngrx/store](https://ngrx.io/guide/store) to manage\nthe state of the app and to cache requests made to the Google Books API;\n[@ngrx/effects](https://ngrx.io/guide/effects) to isolate side effects; [@angular/router](https://angular.dev/guide/routing) to manage navigation between routes; [@angular/material](https://github.com/angular/material2) to provide design and styling.\n\nBuilt with [@angular/cli](https://github.com/angular/angular-cli)\n\n### Included\n\n- [@ngrx/store](https://ngrx.io/guide/store) - RxJS powered state management for Angular apps, inspired by Redux\n- [@ngrx/effects](https://ngrx.io/guide/effects) - Side effect model for @ngrx/store\n- [@ngrx/router-store](https://ngrx.io/guide/router-store) - Bindings to connect the Angular Router to @ngrx/store\n- [@ngrx/entity](https://ngrx.io/guide/entity) - Entity State adapter for managing record collections.\n- [@ngrx/store-devtools](https://ngrx.io/guide/store-devtools) - Instrumentation for @ngrx/store enabling time-travel debugging\n- [@angular/router](https://angular.dev/guide/routing) - Angular Router\n- [@angular/material](https://material.angular.io) - Angular Material\n- [vitest](https://vitest.dev) - JavaScript test runner with easy setup, isolated browser testing and snapshot testing\n\n### Quick start\n\n```bash\n# Clone the repository\ngit clone https://github.com/ngrx/platform.git\n\n# Go to the example directory\ncd platform\n\n# Install the dependencies\npnpm install\n\n# Start the server\npnpm exec nx serve example-app\n```\n\nNavigate to [http://localhost:4200/](http://localhost:4200/) in your browser. To log in, the username and password is `test`.\n\n_NOTE:_ The above setup instructions assume you have added local npm bin folders to your path.\nIf this is not the case you will need to install the Angular CLI globally.\n\n### Try it on StackBlitz\n\nTry the example-app on [StackBlitz](https://ngrx.github.io/platform/stackblitz.html).\n"
  },
  {
    "path": "projects/example-app/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': [\n          'error',\n          {\n            type: 'attribute',\n            prefix: 'bc',\n            style: 'camelCase',\n          },\n        ],\n        '@angular-eslint/component-selector': [\n          'error',\n          {\n            type: 'element',\n            prefix: 'bc',\n            style: 'kebab-case',\n          },\n        ],\n        '@typescript-eslint/prefer-namespace-keyword': 'error',\n        '@nx/enforce-module-boundaries': 'off',\n        eqeqeq: ['off', 'smart'],\n        'id-blacklist': [\n          'error',\n          'any',\n          'Number',\n          'number',\n          'String',\n          'string',\n          'Boolean',\n          'boolean',\n          'Undefined',\n          'undefined',\n        ],\n        'id-match': 'error',\n        'no-eval': 'off',\n        'no-redeclare': 'error',\n        'no-underscore-dangle': 'error',\n        'no-var': 'error',\n        'no-case-declarations': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['projects/example-app/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/template/prefer-control-flow': 'off',\n      },\n    })),\n  {\n    ignores: ['**/environment.prod.ts'],\n  },\n];\n"
  },
  {
    "path": "projects/example-app/project.json",
    "content": "{\n  \"name\": \"example-app\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"sourceRoot\": \"projects/example-app/src\",\n  \"projectType\": \"application\",\n  \"prefix\": \"bc\",\n  \"tags\": [],\n  \"generators\": {},\n  \"targets\": {\n    \"build\": {\n      \"executor\": \"@angular-devkit/build-angular:application\",\n      \"options\": {\n        \"outputPath\": \"dist/projects/example-app\",\n        \"index\": \"projects/example-app/src/index.html\",\n        \"browser\": \"projects/example-app/src/main.ts\",\n        \"polyfills\": [\"projects/example-app/src/polyfills.ts\"],\n        \"tsConfig\": \"projects/example-app/tsconfig.app.json\",\n        \"assets\": [\n          \"projects/example-app/src/favicon.ico\",\n          \"projects/example-app/src/assets\"\n        ],\n        \"styles\": [\"projects/example-app/src/styles.css\"],\n        \"scripts\": []\n      },\n      \"configurations\": {\n        \"production\": {\n          \"budgets\": [\n            {\n              \"type\": \"anyComponentStyle\",\n              \"maximumWarning\": \"6kb\"\n            }\n          ],\n          \"outputHashing\": \"all\"\n        },\n        \"development\": {\n          \"optimization\": false,\n          \"extractLicenses\": false,\n          \"sourceMap\": true,\n          \"namedChunks\": true\n        }\n      },\n      \"defaultConfiguration\": \"production\",\n      \"outputs\": [\"{options.outputPath}\"]\n    },\n    \"serve\": {\n      \"executor\": \"@angular-devkit/build-angular:dev-server\",\n      \"configurations\": {\n        \"production\": {\n          \"buildTarget\": \"example-app:build:production\"\n        },\n        \"development\": {\n          \"buildTarget\": \"example-app:build:development\"\n        }\n      },\n      \"defaultConfiguration\": \"development\",\n      \"continuous\": true\n    },\n    \"extract-i18n\": {\n      \"executor\": \"@angular-devkit/build-angular:extract-i18n\",\n      \"options\": {\n        \"buildTarget\": \"example-app:build\"\n      }\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"projects/example-app/*/**/*.ts\",\n          \"projects/example-app/*/**/*.html\"\n        ]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\"\n    }\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { authGuard } from '@example-app/auth/services';\nimport { NotFoundPageComponent } from '@example-app/core/containers';\n\nexport const routes: Routes = [\n  { path: '', redirectTo: '/books', pathMatch: 'full' },\n  {\n    path: 'books',\n    loadChildren: () =>\n      import('@example-app/books/books.module').then((m) => m.BooksModule),\n    canActivate: [authGuard],\n  },\n  {\n    path: '**',\n    component: NotFoundPageComponent,\n    data: { title: 'Not found' },\n  },\n];\n\n@NgModule({\n  imports: [\n    RouterModule.forRoot(routes, {\n      useHash: true,\n    }),\n  ],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "projects/example-app/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { HttpClientModule } from '@angular/common/http';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\n\nimport { StoreModule } from '@ngrx/store';\nimport { EffectsModule } from '@ngrx/effects';\nimport { StoreRouterConnectingModule } from '@ngrx/router-store';\nimport { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\nimport { AuthModule } from '@example-app/auth';\n\nimport { rootReducers, metaReducers } from '@example-app/reducers';\n\nimport { CoreModule } from '@example-app/core';\nimport { AppRoutingModule } from '@example-app/app-routing.module';\nimport { UserEffects, RouterEffects } from '@example-app/core/effects';\nimport { AppComponent } from '@example-app/core/containers';\n\n@NgModule({\n  imports: [\n    CommonModule,\n    BrowserModule,\n    BrowserAnimationsModule,\n    HttpClientModule,\n    AuthModule,\n    AppRoutingModule,\n\n    /**\n     * StoreModule.forRoot is imported once in the root module, accepting a reducer\n     * function or object map of reducer functions. If passed an object of\n     * reducers, combineReducers will be run creating your application\n     * meta-reducer. This returns all providers for an @ngrx/store\n     * based application.\n     */\n    StoreModule.forRoot(rootReducers, {\n      metaReducers,\n      runtimeChecks: {\n        // strictStateImmutability and strictActionImmutability are enabled by default\n        strictStateSerializability: true,\n        strictActionSerializability: true,\n        strictActionWithinNgZone: true,\n        strictActionTypeUniqueness: true,\n      },\n    }),\n\n    /**\n     * @ngrx/router-store keeps router state up-to-date in the store.\n     */\n    StoreRouterConnectingModule.forRoot(),\n\n    /**\n     * Store devtools instrument the store retaining past versions of state\n     * and recalculating new states. This enables powerful time-travel\n     * debugging.\n     *\n     * To use the debugger, install the Redux Devtools extension for either\n     * Chrome or Firefox\n     *\n     * See: https://github.com/zalmoxisus/redux-devtools-extension\n     */\n    StoreDevtoolsModule.instrument({\n      name: 'NgRx Book Store App',\n      // In a production build you would want to disable the Store Devtools\n      // logOnly: !isDevMode(),\n    }),\n\n    /**\n     * EffectsModule.forRoot() is imported once in the root module and\n     * sets up the effects class to be initialized immediately when the\n     * application starts.\n     *\n     * See: https://ngrx.io/guide/effects#registering-root-effects\n     */\n    EffectsModule.forRoot(UserEffects, RouterEffects),\n    CoreModule,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "projects/example-app/src/app/auth/actions/auth-api.actions.ts",
    "content": "import { props, createActionGroup, emptyProps } from '@ngrx/store';\nimport { User } from '@example-app/auth/models';\n\nexport const AuthApiActions = createActionGroup({\n  source: 'Auth/API',\n  events: {\n    'Login Success': props<{ user: User }>(),\n    'Login Failure': props<{ error: any }>(),\n    'Login Redirect': emptyProps(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/actions/auth.actions.ts",
    "content": "import { createActionGroup, emptyProps } from '@ngrx/store';\n\nexport const AuthActions = createActionGroup({\n  source: 'Auth',\n  events: {\n    Logout: emptyProps(),\n    'Logout Confirmation': emptyProps(),\n    'Logout Confirmation Dismiss': emptyProps(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/actions/login-page.actions.ts",
    "content": "import { createActionGroup, props } from '@ngrx/store';\nimport { Credentials } from '@example-app/auth/models';\n\nexport const LoginPageActions = createActionGroup({\n  source: 'Login Page',\n  events: {\n    Login: props<{ credentials: Credentials }>(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/auth-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { LoginPageComponent } from '@example-app/auth/containers';\n\nconst routes: Routes = [\n  { path: 'login', component: LoginPageComponent, data: { title: 'Login' } },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class AuthRoutingModule {}\n"
  },
  {
    "path": "projects/example-app/src/app/auth/auth.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { StoreModule } from '@ngrx/store';\nimport { EffectsModule } from '@ngrx/effects';\nimport { LoginPageComponent } from '@example-app/auth/containers';\nimport {\n  LoginFormComponent,\n  LogoutConfirmationDialogComponent,\n} from '@example-app/auth/components';\n\nimport { AuthEffects } from '@example-app/auth/effects';\nimport * as fromAuth from '@example-app/auth/reducers';\nimport { MaterialModule } from '@example-app/material';\nimport { AuthRoutingModule } from './auth-routing.module';\n\nexport const COMPONENTS = [\n  LoginPageComponent,\n  LoginFormComponent,\n  LogoutConfirmationDialogComponent,\n];\n\n@NgModule({\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    MaterialModule,\n    AuthRoutingModule,\n    StoreModule.forFeature({\n      name: fromAuth.authFeatureKey,\n      reducer: fromAuth.reducers,\n    }),\n    EffectsModule.forFeature(AuthEffects),\n  ],\n  declarations: COMPONENTS,\n})\nexport class AuthModule {}\n"
  },
  {
    "path": "projects/example-app/src/app/auth/components/__snapshots__/login-form.component.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Login Page > should compile 1`] = `\n<bc-login-form\n  errormessage=\"[object Object]\"\n  pending=\"[object Object]\"\n  submitted=\"[object Object]\"\n>\n  <mat-card\n    _ngcontent-a-c1882380116=\"\"\n  >\n    <mat-card-title\n      _ngcontent-a-c1882380116=\"\"\n    >\n      Login\n    </mat-card-title>\n    <mat-card-content\n      _ngcontent-a-c1882380116=\"\"\n    >\n      <form\n        _ngcontent-a-c1882380116=\"\"\n        class=\"ng-untouched ng-pristine ng-valid\"\n        novalidate=\"\"\n      >\n        <p\n          _ngcontent-a-c1882380116=\"\"\n        >\n          <mat-form-field\n            _ngcontent-a-c1882380116=\"\"\n          >\n            <input\n              _ngcontent-a-c1882380116=\"\"\n              class=\"ng-untouched ng-pristine ng-valid\"\n              formcontrolname=\"username\"\n              matinput=\"\"\n              placeholder=\"Username\"\n              type=\"text\"\n            />\n          </mat-form-field>\n        </p>\n        <p\n          _ngcontent-a-c1882380116=\"\"\n        >\n          <mat-form-field\n            _ngcontent-a-c1882380116=\"\"\n          >\n            <input\n              _ngcontent-a-c1882380116=\"\"\n              class=\"ng-untouched ng-pristine ng-valid\"\n              formcontrolname=\"password\"\n              matinput=\"\"\n              placeholder=\"Password\"\n              type=\"password\"\n            />\n          </mat-form-field>\n        </p>\n        <!--container-->\n        <div\n          _ngcontent-a-c1882380116=\"\"\n          class=\"login-buttons\"\n        >\n          <button\n            _ngcontent-a-c1882380116=\"\"\n            mat-button=\"\"\n            type=\"submit\"\n          >\n            Login\n          </button>\n        </div>\n      </form>\n    </mat-card-content>\n  </mat-card>\n</bc-login-form>\n`;\n\nexports[`Login Page > should disable the form if pending 1`] = `\n<bc-login-form\n  errormessage=\"[object Object]\"\n  pending=\"[object Object]\"\n  submitted=\"[object Object]\"\n>\n  <mat-card\n    _ngcontent-a-c1882380116=\"\"\n  >\n    <mat-card-title\n      _ngcontent-a-c1882380116=\"\"\n    >\n      Login\n    </mat-card-title>\n    <mat-card-content\n      _ngcontent-a-c1882380116=\"\"\n    >\n      <form\n        _ngcontent-a-c1882380116=\"\"\n        class=\"ng-untouched ng-pristine\"\n        novalidate=\"\"\n      >\n        <p\n          _ngcontent-a-c1882380116=\"\"\n        >\n          <mat-form-field\n            _ngcontent-a-c1882380116=\"\"\n          >\n            <input\n              _ngcontent-a-c1882380116=\"\"\n              class=\"ng-untouched ng-pristine\"\n              disabled=\"\"\n              formcontrolname=\"username\"\n              matinput=\"\"\n              placeholder=\"Username\"\n              type=\"text\"\n            />\n          </mat-form-field>\n        </p>\n        <p\n          _ngcontent-a-c1882380116=\"\"\n        >\n          <mat-form-field\n            _ngcontent-a-c1882380116=\"\"\n          >\n            <input\n              _ngcontent-a-c1882380116=\"\"\n              class=\"ng-untouched ng-pristine\"\n              disabled=\"\"\n              formcontrolname=\"password\"\n              matinput=\"\"\n              placeholder=\"Password\"\n              type=\"password\"\n            />\n          </mat-form-field>\n        </p>\n        <!--container-->\n        <div\n          _ngcontent-a-c1882380116=\"\"\n          class=\"login-buttons\"\n        >\n          <button\n            _ngcontent-a-c1882380116=\"\"\n            mat-button=\"\"\n            type=\"submit\"\n          >\n            Login\n          </button>\n        </div>\n      </form>\n    </mat-card-content>\n  </mat-card>\n</bc-login-form>\n`;\n\nexports[`Login Page > should display an error message if provided 1`] = `\n<bc-login-form\n  errormessage=\"[object Object]\"\n  pending=\"[object Object]\"\n  submitted=\"[object Object]\"\n>\n  <mat-card\n    _ngcontent-a-c1882380116=\"\"\n  >\n    <mat-card-title\n      _ngcontent-a-c1882380116=\"\"\n    >\n      Login\n    </mat-card-title>\n    <mat-card-content\n      _ngcontent-a-c1882380116=\"\"\n    >\n      <form\n        _ngcontent-a-c1882380116=\"\"\n        class=\"ng-untouched ng-pristine ng-valid\"\n        novalidate=\"\"\n      >\n        <p\n          _ngcontent-a-c1882380116=\"\"\n        >\n          <mat-form-field\n            _ngcontent-a-c1882380116=\"\"\n          >\n            <input\n              _ngcontent-a-c1882380116=\"\"\n              class=\"ng-untouched ng-pristine ng-valid\"\n              formcontrolname=\"username\"\n              matinput=\"\"\n              placeholder=\"Username\"\n              type=\"text\"\n            />\n          </mat-form-field>\n        </p>\n        <p\n          _ngcontent-a-c1882380116=\"\"\n        >\n          <mat-form-field\n            _ngcontent-a-c1882380116=\"\"\n          >\n            <input\n              _ngcontent-a-c1882380116=\"\"\n              class=\"ng-untouched ng-pristine ng-valid\"\n              formcontrolname=\"password\"\n              matinput=\"\"\n              placeholder=\"Password\"\n              type=\"password\"\n            />\n          </mat-form-field>\n        </p>\n        <p\n          _ngcontent-a-c1882380116=\"\"\n          class=\"login-error\"\n        >\n           Invalid credentials \n        </p>\n        <!--container-->\n        <div\n          _ngcontent-a-c1882380116=\"\"\n          class=\"login-buttons\"\n        >\n          <button\n            _ngcontent-a-c1882380116=\"\"\n            mat-button=\"\"\n            type=\"submit\"\n          >\n            Login\n          </button>\n        </div>\n      </form>\n    </mat-card-content>\n  </mat-card>\n</bc-login-form>\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/auth/components/__snapshots__/logout-confirmation-dialog.component.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Logout Confirmation Dialog > should compile 1`] = `\n<undefined>\n  <h2\n    _ngcontent-a-c626405556=\"\"\n    class=\"mat-mdc-dialog-title mdc-dialog__title\"\n    id=\"mat-mdc-dialog-title-a0\"\n    mat-dialog-title=\"\"\n  >\n    Logout\n  </h2>\n  <mat-dialog-content\n    _ngcontent-a-c626405556=\"\"\n    class=\"mat-mdc-dialog-content mdc-dialog__content\"\n  >\n    Are you sure you want to logout?\n  </mat-dialog-content>\n  <mat-dialog-actions\n    _ngcontent-a-c626405556=\"\"\n    class=\"mat-mdc-dialog-actions mdc-dialog__actions\"\n  >\n    <button\n      _ngcontent-a-c626405556=\"\"\n      class=\"mdc-button mat-mdc-button-base mat-mdc-button mat-unthemed\"\n      mat-button=\"\"\n      mat-ripple-loader-class-name=\"mat-mdc-button-ripple\"\n      mat-ripple-loader-uninitialized=\"\"\n      type=\"button\"\n    >\n      <span\n        class=\"mat-mdc-button-persistent-ripple mdc-button__ripple\"\n      />\n      <span\n        class=\"mdc-button__label\"\n      >\n        Cancel\n      </span>\n      <span\n        class=\"mat-focus-indicator\"\n      />\n      <span\n        class=\"mat-mdc-button-touch-target\"\n      />\n    </button>\n    <button\n      _ngcontent-a-c626405556=\"\"\n      class=\"mdc-button mat-mdc-button-base mat-mdc-button mat-unthemed\"\n      mat-button=\"\"\n      mat-ripple-loader-class-name=\"mat-mdc-button-ripple\"\n      mat-ripple-loader-uninitialized=\"\"\n      type=\"button\"\n    >\n      <span\n        class=\"mat-mdc-button-persistent-ripple mdc-button__ripple\"\n      />\n      <span\n        class=\"mdc-button__label\"\n      >\n        OK\n      </span>\n      <span\n        class=\"mat-focus-indicator\"\n      />\n      <span\n        class=\"mat-mdc-button-touch-target\"\n      />\n    </button>\n  </mat-dialog-actions>\n</undefined>\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/auth/components/index.ts",
    "content": "export * from './login-form.component';\nexport * from './logout-confirmation-dialog.component';\n"
  },
  {
    "path": "projects/example-app/src/app/auth/components/login-form.component.spec.ts",
    "content": "import { TestBed, ComponentFixture } from '@angular/core/testing';\nimport { NO_ERRORS_SCHEMA } from '@angular/core';\nimport { LoginFormComponent } from '@example-app/auth/components';\nimport { ReactiveFormsModule } from '@angular/forms';\n\ndescribe('Login Page', () => {\n  let fixture: ComponentFixture<LoginFormComponent>;\n  let instance: LoginFormComponent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [ReactiveFormsModule],\n      declarations: [LoginFormComponent],\n      schemas: [NO_ERRORS_SCHEMA],\n    });\n\n    fixture = TestBed.createComponent(LoginFormComponent);\n    instance = fixture.componentInstance;\n  });\n\n  it('should compile', () => {\n    fixture.detectChanges();\n\n    /**\n     * The login form is a presentational component, as it\n     * only derives its state from inputs and communicates\n     * externally through outputs. We can use snapshot\n     * tests to validate the presentation state of this component\n     * by changing its inputs and snapshotting the generated\n     * HTML.\n     *\n     * We can also use this as a validation tool against changes\n     * to the component's template against the currently stored\n     * snapshot.\n     */\n    expect(fixture).toMatchSnapshot();\n  });\n\n  it('should disable the form if pending', () => {\n    instance.pending = true;\n\n    fixture.detectChanges();\n\n    expect(fixture).toMatchSnapshot();\n  });\n\n  it('should display an error message if provided', () => {\n    instance.errorMessage = 'Invalid credentials';\n\n    fixture.detectChanges();\n\n    expect(fixture).toMatchSnapshot();\n  });\n\n  it('should emit an event if the form is valid when submitted', () => {\n    const credentials = {\n      username: 'user',\n      password: 'pass',\n    };\n    instance.form.setValue(credentials);\n\n    vi.spyOn(instance.submitted, 'emit');\n    instance.submit();\n\n    expect(instance.submitted.emit).toHaveBeenCalledWith(credentials);\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/components/login-form.component.ts",
    "content": "import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { FormGroup, FormControl } from '@angular/forms';\nimport { Credentials } from '@example-app/auth/models';\n\n@Component({\n  selector: 'bc-login-form',\n  template: `\n    <mat-card>\n      <mat-card-title>Login</mat-card-title>\n      <mat-card-content>\n        <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n          <p>\n            <mat-form-field>\n              <input\n                type=\"text\"\n                matInput\n                placeholder=\"Username\"\n                formControlName=\"username\"\n              />\n            </mat-form-field>\n          </p>\n\n          <p>\n            <mat-form-field>\n              <input\n                type=\"password\"\n                matInput\n                placeholder=\"Password\"\n                formControlName=\"password\"\n              />\n            </mat-form-field>\n          </p>\n\n          <p *ngIf=\"errorMessage\" class=\"login-error\">\n            {{ errorMessage }}\n          </p>\n\n          <div class=\"login-buttons\">\n            <button type=\"submit\" mat-button>Login</button>\n          </div>\n        </form>\n      </mat-card-content>\n    </mat-card>\n  `,\n  styles: [\n    `\n      :host {\n        display: flex;\n        justify-content: center;\n        margin: 4.5rem 0;\n      }\n\n      .mat-mdc-form-field {\n        width: 100%;\n        min-width: 300px;\n      }\n\n      mat-card-title {\n        text-align: center;\n        margin: 1rem 0;\n      }\n\n      mat-card-content {\n        justify-content: center;\n      }\n\n      .login-error {\n        padding: 1rem;\n        width: 300px;\n        color: white;\n        background-color: red;\n      }\n\n      .login-buttons {\n        display: flex;\n        flex-direction: row;\n        justify-content: flex-end;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class LoginFormComponent {\n  @Input()\n  set pending(isPending: boolean) {\n    if (isPending) {\n      this.form.disable();\n    } else {\n      this.form.enable();\n    }\n  }\n\n  @Input() errorMessage!: string | null;\n\n  @Output() submitted = new EventEmitter<Credentials>();\n\n  form: FormGroup = new FormGroup({\n    username: new FormControl('ngrx'),\n    password: new FormControl(''),\n  });\n\n  submit() {\n    if (this.form.valid) {\n      this.submitted.emit(this.form.value);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/auth/components/logout-confirmation-dialog.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { LogoutConfirmationDialogComponent } from '@example-app/auth/components';\nimport { MaterialModule } from '@example-app/material';\n\ndescribe('Logout Confirmation Dialog', () => {\n  let fixture: ComponentFixture<LogoutConfirmationDialogComponent>;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [MaterialModule],\n      declarations: [LogoutConfirmationDialogComponent],\n    });\n\n    fixture = TestBed.createComponent(LogoutConfirmationDialogComponent);\n  });\n\n  it('should compile', () => {\n    fixture.detectChanges();\n\n    expect(fixture).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/components/logout-confirmation-dialog.component.ts",
    "content": "import { Component } from '@angular/core';\n\n/**\n * The dialog will close with true if user clicks the ok button,\n * otherwise it will close with undefined.\n */\n@Component({\n  template: `\n    <h2 mat-dialog-title>Logout</h2>\n    <mat-dialog-content>Are you sure you want to logout?</mat-dialog-content>\n    <mat-dialog-actions>\n      <button mat-button [mat-dialog-close]=\"false\">Cancel</button>\n      <button mat-button [mat-dialog-close]=\"true\">OK</button>\n    </mat-dialog-actions>\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n        width: 100%;\n        max-width: 300px;\n      }\n\n      mat-dialog-actions {\n        display: flex;\n        justify-content: flex-end;\n      }\n\n      [mat-button] {\n        padding: 0;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class LogoutConfirmationDialogComponent {}\n"
  },
  {
    "path": "projects/example-app/src/app/auth/containers/__snapshots__/login-page.component.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Login Page > should compile 1`] = `\n<bc-login-page>\n  <bc-login-form\n    _nghost-a-c1882380116=\"\"\n  >\n    <mat-card\n      _ngcontent-a-c1882380116=\"\"\n      class=\"mat-mdc-card mdc-card\"\n    >\n      <mat-card-title\n        _ngcontent-a-c1882380116=\"\"\n        class=\"mat-mdc-card-title\"\n      >\n        Login\n      </mat-card-title>\n      <mat-card-content\n        _ngcontent-a-c1882380116=\"\"\n        class=\"mat-mdc-card-content\"\n      >\n        <form\n          _ngcontent-a-c1882380116=\"\"\n          class=\"ng-untouched ng-pristine ng-valid\"\n          novalidate=\"\"\n        >\n          <p\n            _ngcontent-a-c1882380116=\"\"\n          >\n            <mat-form-field\n              _ngcontent-a-c1882380116=\"\"\n              class=\"mat-mdc-form-field mat-mdc-form-field-type-mat-input mat-form-field-appearance-fill mat-primary ng-untouched ng-pristine ng-valid\"\n            >\n              <!--container-->\n            </mat-form-field>\n          </p>\n          <div\n            class=\"mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label\"\n          >\n            <div\n              class=\"mat-mdc-form-field-focus-overlay\"\n            />\n            <!--container-->\n            <div\n              class=\"mat-mdc-form-field-flex\"\n            >\n              <!--container-->\n              <!--container-->\n              <!--container-->\n              <div\n                class=\"mat-mdc-form-field-infix\"\n              >\n                <!--container-->\n                <!--container-->\n                <!--container-->\n                <input\n                  _ngcontent-a-c1882380116=\"\"\n                  aria-invalid=\"false\"\n                  aria-required=\"false\"\n                  class=\"mat-mdc-input-element mat-mdc-form-field-input-control mdc-text-field__input ng-untouched ng-pristine ng-valid cdk-text-field-autofill-monitored\"\n                  formcontrolname=\"username\"\n                  id=\"mat-input-a0\"\n                  matinput=\"\"\n                  placeholder=\"Username\"\n                  type=\"text\"\n                />\n              </div>\n              <!--container-->\n              <!--container-->\n            </div>\n            <div\n              class=\"mdc-line-ripple mdc-line-ripple--deactivating\"\n              matformfieldlineripple=\"\"\n            />\n            <!--container-->\n          </div>\n          <div\n            aria-atomic=\"true\"\n            aria-live=\"polite\"\n            class=\"mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align\"\n          >\n            <!--container-->\n            <div\n              class=\"mat-mdc-form-field-hint-wrapper\"\n            >\n              <!--container-->\n              <div\n                class=\"mat-mdc-form-field-hint-spacer\"\n              />\n            </div>\n            <!--container-->\n          </div>\n          <p />\n          <p\n            _ngcontent-a-c1882380116=\"\"\n          >\n            <mat-form-field\n              _ngcontent-a-c1882380116=\"\"\n              class=\"mat-mdc-form-field mat-mdc-form-field-type-mat-input mat-form-field-appearance-fill mat-primary ng-untouched ng-pristine ng-valid\"\n            >\n              <!--container-->\n            </mat-form-field>\n          </p>\n          <div\n            class=\"mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label\"\n          >\n            <div\n              class=\"mat-mdc-form-field-focus-overlay\"\n            />\n            <!--container-->\n            <div\n              class=\"mat-mdc-form-field-flex\"\n            >\n              <!--container-->\n              <!--container-->\n              <!--container-->\n              <div\n                class=\"mat-mdc-form-field-infix\"\n              >\n                <!--container-->\n                <!--container-->\n                <!--container-->\n                <input\n                  _ngcontent-a-c1882380116=\"\"\n                  aria-invalid=\"false\"\n                  aria-required=\"false\"\n                  class=\"mat-mdc-input-element mat-mdc-form-field-input-control mdc-text-field__input ng-untouched ng-pristine ng-valid cdk-text-field-autofill-monitored\"\n                  formcontrolname=\"password\"\n                  id=\"mat-input-a1\"\n                  matinput=\"\"\n                  placeholder=\"Password\"\n                  type=\"password\"\n                />\n              </div>\n              <!--container-->\n              <!--container-->\n            </div>\n            <div\n              class=\"mdc-line-ripple mdc-line-ripple--deactivating\"\n              matformfieldlineripple=\"\"\n            />\n            <!--container-->\n          </div>\n          <div\n            aria-atomic=\"true\"\n            aria-live=\"polite\"\n            class=\"mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align\"\n          >\n            <!--container-->\n            <div\n              class=\"mat-mdc-form-field-hint-wrapper\"\n            >\n              <!--container-->\n              <div\n                class=\"mat-mdc-form-field-hint-spacer\"\n              />\n            </div>\n            <!--container-->\n          </div>\n          <p />\n          <!--container-->\n          <div\n            _ngcontent-a-c1882380116=\"\"\n            class=\"login-buttons\"\n          >\n            <button\n              _ngcontent-a-c1882380116=\"\"\n              class=\"mdc-button mat-mdc-button-base mat-mdc-button mat-unthemed _mat-animation-noopable\"\n              mat-button=\"\"\n              mat-ripple-loader-class-name=\"mat-mdc-button-ripple\"\n              mat-ripple-loader-uninitialized=\"\"\n              type=\"submit\"\n            >\n              <span\n                class=\"mat-mdc-button-persistent-ripple mdc-button__ripple\"\n              />\n              <span\n                class=\"mdc-button__label\"\n              >\n                Login\n              </span>\n              <span\n                class=\"mat-focus-indicator\"\n              />\n              <span\n                class=\"mat-mdc-button-touch-target\"\n              />\n            </button>\n          </div>\n        </form>\n      </mat-card-content>\n    </mat-card>\n  </bc-login-form>\n</bc-login-page>\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/auth/containers/index.ts",
    "content": "export * from './login-page.component';\n"
  },
  {
    "path": "projects/example-app/src/app/auth/containers/login-page.component.spec.ts",
    "content": "import { TestBed, ComponentFixture } from '@angular/core/testing';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { LoginPageComponent } from '@example-app/auth/containers';\nimport { LoginFormComponent } from '@example-app/auth/components';\nimport * as fromAuth from '@example-app/auth/reducers';\nimport { LoginPageActions } from '@example-app/auth/actions/login-page.actions';\nimport { provideMockStore, MockStore } from '@ngrx/store/testing';\nimport { MaterialModule } from '@example-app/material';\n\ndescribe('Login Page', () => {\n  let fixture: ComponentFixture<LoginPageComponent>;\n  let store: MockStore;\n  let instance: LoginPageComponent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [NoopAnimationsModule, MaterialModule, ReactiveFormsModule],\n      declarations: [LoginPageComponent, LoginFormComponent],\n      providers: [\n        provideMockStore({\n          selectors: [\n            { selector: fromAuth.selectLoginPagePending, value: false },\n            { selector: fromAuth.selectLoginPageError, value: null },\n          ],\n        }),\n      ],\n    });\n\n    fixture = TestBed.createComponent(LoginPageComponent);\n    instance = fixture.componentInstance;\n    store = TestBed.inject(MockStore);\n\n    vi.spyOn(store, 'dispatch');\n  });\n\n  /**\n   * Container components are used as integration points for connecting\n   * the store to presentational components and dispatching\n   * actions to the store.\n   *\n   * Container methods that dispatch events are like a component's output observables.\n   * Container properties that select state from store are like a component's input properties.\n   * If pure components are functions of their inputs, containers are functions of state\n   *\n   * Traditionally you would query the components rendered template\n   * to validate its state. Since the components are analogous to\n   * pure functions, we take snapshots of these components for a given state\n   * to validate the rendered output and verify the component's output\n   * against changes in state.\n   */\n  it('should compile', () => {\n    fixture.detectChanges();\n\n    expect(fixture).toMatchSnapshot();\n  });\n\n  it('should dispatch a login event on submit', () => {\n    const credentials: any = {};\n    const action = LoginPageActions.login({ credentials });\n\n    instance.onSubmit(credentials);\n\n    expect(store.dispatch).toHaveBeenCalledWith(action);\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/containers/login-page.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { Credentials } from '@example-app/auth/models';\nimport * as fromAuth from '@example-app/auth/reducers';\nimport { LoginPageActions } from '@example-app/auth/actions/login-page.actions';\n\n@Component({\n  selector: 'bc-login-page',\n  template: `\n    <bc-login-form\n      (submitted)=\"onSubmit($event)\"\n      [pending]=\"(pending$ | async)!\"\n      [errorMessage]=\"error$ | async\"\n    >\n    </bc-login-form>\n  `,\n  styles: [],\n  standalone: false,\n})\nexport class LoginPageComponent {\n  pending$ = this.store.select(fromAuth.selectLoginPagePending);\n  error$ = this.store.select(fromAuth.selectLoginPageError);\n\n  constructor(private store: Store) {}\n\n  onSubmit(credentials: Credentials) {\n    this.store.dispatch(LoginPageActions.login({ credentials }));\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/auth/effects/auth.effects.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { MatDialog } from '@angular/material/dialog';\nimport { Router } from '@angular/router';\nimport { Actions } from '@ngrx/effects';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport { cold, hot } from 'jasmine-marbles';\nimport { firstValueFrom, Observable, of } from 'rxjs';\nimport { LoginPageActions } from '@example-app/auth/actions/login-page.actions';\nimport { AuthApiActions } from '@example-app/auth/actions/auth-api.actions';\nimport { AuthActions } from '@example-app/auth/actions/auth.actions';\n\nimport { Credentials, User } from '@example-app/auth/models';\nimport { AuthService } from '@example-app/auth/services';\nimport { AuthEffects } from '@example-app/auth/effects';\n\ndescribe('AuthEffects', () => {\n  let effects: AuthEffects;\n  let authService: any;\n  let actions$: Observable<any>;\n  let routerService: any;\n  let dialog: any;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        AuthEffects,\n        {\n          provide: AuthService,\n          useValue: { login: vi.fn() },\n        },\n        provideMockActions(() => actions$),\n        {\n          provide: Router,\n          useValue: { navigate: vi.fn() },\n        },\n        {\n          provide: MatDialog,\n          useValue: {\n            open: vi.fn(),\n          },\n        },\n      ],\n    });\n\n    effects = TestBed.inject(AuthEffects);\n    authService = TestBed.inject(AuthService);\n    actions$ = TestBed.inject(Actions);\n    routerService = TestBed.inject(Router);\n    dialog = TestBed.inject(MatDialog);\n\n    vi.spyOn(routerService, 'navigate');\n  });\n\n  describe('login$', () => {\n    it('should return an auth.loginSuccess action, with user information if login succeeds', () => {\n      const credentials: Credentials = { username: 'test', password: '' };\n      const user = { name: 'User' } as User;\n      const action = LoginPageActions.login({ credentials });\n      const completion = AuthApiActions.loginSuccess({ user });\n\n      actions$ = hot('-a---', { a: action });\n      const response = cold('-a|', { a: user });\n      const expected = cold('--b', { b: completion });\n      authService.login = vi.fn(() => response);\n\n      expect(effects.login$).toBeObservable(expected);\n    });\n\n    it('should return a new auth.loginFailure if the login service throws', () => {\n      const credentials: Credentials = { username: 'someOne', password: '' };\n      const action = LoginPageActions.login({ credentials });\n      const completion = AuthApiActions.loginFailure({\n        error: 'Invalid username or password',\n      });\n      const error = 'Invalid username or password';\n\n      actions$ = hot('-a---', { a: action });\n      const response = cold('-#', {}, error);\n      const expected = cold('--b', { b: completion });\n      authService.login = vi.fn(() => response);\n\n      expect(effects.login$).toBeObservable(expected);\n    });\n  });\n\n  describe('loginSuccess$', () => {\n    it('should dispatch a RouterNavigation action', async () => {\n      const user = { name: 'User' } as User;\n      const action = AuthApiActions.loginSuccess({ user });\n\n      actions$ = of(action);\n\n      await firstValueFrom(effects.loginSuccess$);\n      expect(routerService.navigate).toHaveBeenCalledWith(['/']);\n    });\n  });\n\n  describe('loginRedirect$', () => {\n    it('should dispatch a RouterNavigation action when auth.loginRedirect is dispatched', async () => {\n      const action = AuthApiActions.loginRedirect();\n\n      actions$ = of(action);\n\n      await firstValueFrom(effects.loginRedirect$);\n\n      expect(routerService.navigate).toHaveBeenCalledWith(['/login']);\n    });\n\n    it('should dispatch a RouterNavigation action when auth.logout is dispatched', async () => {\n      const action = AuthActions.logout();\n\n      actions$ = of(action);\n\n      await firstValueFrom(effects.loginRedirect$);\n\n      expect(routerService.navigate).toHaveBeenCalledWith(['/login']);\n    });\n  });\n\n  describe('logoutConfirmation$', () => {\n    it('should dispatch a Logout action if dialog closes with true result', () => {\n      const action = AuthActions.logoutConfirmation();\n      const completion = AuthActions.logout();\n\n      actions$ = hot('-a', { a: action });\n      const expected = cold('-b', { b: completion });\n\n      dialog.open = () => ({\n        afterClosed: vi.fn(() => of(true)),\n      });\n\n      expect(effects.logoutConfirmation$).toBeObservable(expected);\n    });\n\n    it('should dispatch a LogoutConfirmationDismiss action if dialog closes with falsy result', () => {\n      const action = AuthActions.logoutConfirmation();\n      const completion = AuthActions.logoutConfirmationDismiss();\n\n      actions$ = hot('-a', { a: action });\n      const expected = cold('-b', { b: completion });\n\n      dialog.open = () => ({\n        afterClosed: vi.fn(() => of(false)),\n      });\n\n      expect(effects.logoutConfirmation$).toBeObservable(expected);\n    });\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/effects/auth.effects.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { MatDialog } from '@angular/material/dialog';\nimport { Router } from '@angular/router';\nimport { Actions, ofType, createEffect } from '@ngrx/effects';\nimport { of } from 'rxjs';\nimport { catchError, exhaustMap, map, tap } from 'rxjs/operators';\nimport { LoginPageActions } from '@example-app/auth/actions/login-page.actions';\nimport { AuthApiActions } from '@example-app/auth/actions/auth-api.actions';\nimport { AuthActions } from '@example-app/auth/actions/auth.actions';\nimport { Credentials } from '@example-app/auth/models';\nimport { AuthService } from '@example-app/auth/services';\nimport { LogoutConfirmationDialogComponent } from '@example-app/auth/components';\nimport { UserActions } from '@example-app/core/actions/user.actions';\n\n@Injectable()\nexport class AuthEffects {\n  login$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(LoginPageActions.login),\n      map((action) => action.credentials),\n      exhaustMap((auth: Credentials) =>\n        this.authService.login(auth).pipe(\n          map((user) => AuthApiActions.loginSuccess({ user })),\n          catchError((error) => of(AuthApiActions.loginFailure({ error })))\n        )\n      )\n    )\n  );\n\n  loginSuccess$ = createEffect(\n    () =>\n      this.actions$.pipe(\n        ofType(AuthApiActions.loginSuccess),\n        tap(() => this.router.navigate(['/']))\n      ),\n    { dispatch: false }\n  );\n\n  loginRedirect$ = createEffect(\n    () =>\n      this.actions$.pipe(\n        ofType(AuthApiActions.loginRedirect, AuthActions.logout),\n        tap(() => {\n          this.router.navigate(['/login']);\n        })\n      ),\n    { dispatch: false }\n  );\n\n  logoutConfirmation$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(AuthActions.logoutConfirmation),\n      exhaustMap(() => {\n        const dialogRef = this.dialog.open<\n          LogoutConfirmationDialogComponent,\n          undefined,\n          boolean\n        >(LogoutConfirmationDialogComponent);\n\n        return dialogRef.afterClosed();\n      }),\n      map((result) =>\n        result ? AuthActions.logout() : AuthActions.logoutConfirmationDismiss()\n      )\n    )\n  );\n\n  logoutIdleUser$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(UserActions.idleTimeout),\n      map(() => AuthActions.logout())\n    )\n  );\n\n  constructor(\n    private actions$: Actions,\n    private authService: AuthService,\n    private router: Router,\n    private dialog: MatDialog\n  ) {}\n}\n"
  },
  {
    "path": "projects/example-app/src/app/auth/effects/index.ts",
    "content": "export * from './auth.effects';\n"
  },
  {
    "path": "projects/example-app/src/app/auth/index.ts",
    "content": "export * from './auth.module';\n"
  },
  {
    "path": "projects/example-app/src/app/auth/models/index.ts",
    "content": "export * from './user';\n"
  },
  {
    "path": "projects/example-app/src/app/auth/models/user.ts",
    "content": "export interface Credentials {\n  username: string;\n  password: string;\n}\n\nexport interface User {\n  name: string;\n}\n"
  },
  {
    "path": "projects/example-app/src/app/auth/reducers/__snapshots__/auth.reducer.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`AuthReducer > LOGIN_SUCCESS > should add a user set loggedIn to true in auth state 1`] = `\n{\n  \"user\": {\n    \"name\": \"test\",\n  },\n}\n`;\n\nexports[`AuthReducer > LOGOUT > should logout a user 1`] = `\n{\n  \"user\": null,\n}\n`;\n\nexports[`AuthReducer > undefined action > should return the default state 1`] = `\n{\n  \"user\": null,\n}\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/auth/reducers/__snapshots__/login-page.reducer.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`LoginPageReducer > LOGIN > should make pending to true 1`] = `\n{\n  \"error\": null,\n  \"pending\": true,\n}\n`;\n\nexports[`LoginPageReducer > LOGIN_FAILURE > should have an error and no pending state 1`] = `\n{\n  \"error\": \"login failed\",\n  \"pending\": false,\n}\n`;\n\nexports[`LoginPageReducer > LOGIN_SUCCESS > should have no error and no pending state 1`] = `\n{\n  \"error\": null,\n  \"pending\": false,\n}\n`;\n\nexports[`LoginPageReducer > undefined action > should return the default state 1`] = `\n{\n  \"error\": null,\n  \"pending\": false,\n}\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/auth/reducers/auth.reducer.spec.ts",
    "content": "import { reducer } from '@example-app/auth/reducers/auth.reducer';\nimport * as fromAuth from '@example-app/auth/reducers/auth.reducer';\nimport { AuthApiActions } from '@example-app/auth/actions/auth-api.actions';\nimport { AuthActions } from '@example-app/auth/actions/auth.actions';\n\nimport { User } from '@example-app/auth/models';\n\ndescribe('AuthReducer', () => {\n  describe('undefined action', () => {\n    it('should return the default state', () => {\n      const action = {} as any;\n\n      const result = reducer(undefined, action);\n\n      /**\n       * Snapshot tests are a quick way to validate\n       * the state produced by a reducer since\n       * its plain JavaScript object. These snapshots\n       * are used to validate against the current state\n       * if the functionality of the reducer ever changes.\n       */\n      expect(result).toMatchSnapshot();\n    });\n  });\n\n  describe('LOGIN_SUCCESS', () => {\n    it('should add a user set loggedIn to true in auth state', () => {\n      const user = { name: 'test' } as User;\n      const createAction = AuthApiActions.loginSuccess({ user });\n\n      const result = reducer(fromAuth.initialState, createAction);\n\n      expect(result).toMatchSnapshot();\n    });\n  });\n\n  describe('LOGOUT', () => {\n    it('should logout a user', () => {\n      const initialState = {\n        user: { name: 'test' },\n      } as fromAuth.State;\n      const createAction = AuthActions.logout();\n\n      const result = reducer(initialState, createAction);\n\n      expect(result).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/reducers/auth.reducer.ts",
    "content": "import { createReducer, on } from '@ngrx/store';\nimport { AuthApiActions } from '@example-app/auth/actions/auth-api.actions';\nimport { AuthActions } from '@example-app/auth/actions/auth.actions';\nimport { User } from '@example-app/auth/models';\n\nexport const statusFeatureKey = 'status';\n\nexport interface State {\n  user: User | null;\n}\n\nexport const initialState: State = {\n  user: null,\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(AuthApiActions.loginSuccess, (state, { user }) => ({ ...state, user })),\n  on(AuthActions.logout, () => initialState)\n);\n\nexport const getUser = (state: State) => state.user;\n"
  },
  {
    "path": "projects/example-app/src/app/auth/reducers/index.ts",
    "content": "import {\n  createSelector,\n  createFeatureSelector,\n  Action,\n  combineReducers,\n} from '@ngrx/store';\nimport * as fromRoot from '@example-app/reducers';\nimport * as fromAuth from '@example-app/auth/reducers/auth.reducer';\nimport * as fromLoginPage from '@example-app/auth/reducers/login-page.reducer';\n\nexport const authFeatureKey = 'auth';\n\nexport interface AuthState {\n  [fromAuth.statusFeatureKey]: fromAuth.State;\n  [fromLoginPage.loginPageFeatureKey]: fromLoginPage.State;\n}\n\nexport interface State extends fromRoot.State {\n  [authFeatureKey]: AuthState;\n}\n\nexport function reducers(state: AuthState | undefined, action: Action) {\n  return combineReducers({\n    [fromAuth.statusFeatureKey]: fromAuth.reducer,\n    [fromLoginPage.loginPageFeatureKey]: fromLoginPage.reducer,\n  })(state, action);\n}\n\nexport const selectAuthState = createFeatureSelector<AuthState>(authFeatureKey);\n\nexport const selectAuthStatusState = createSelector(\n  selectAuthState,\n  (state) => state.status\n);\nexport const selectUser = createSelector(\n  selectAuthStatusState,\n  fromAuth.getUser\n);\nexport const selectLoggedIn = createSelector(selectUser, (user) => !!user);\n\nexport const selectLoginPageState = createSelector(\n  selectAuthState,\n  (state) => state.loginPage\n);\nexport const selectLoginPageError = createSelector(\n  selectLoginPageState,\n  fromLoginPage.getError\n);\nexport const selectLoginPagePending = createSelector(\n  selectLoginPageState,\n  fromLoginPage.getPending\n);\n"
  },
  {
    "path": "projects/example-app/src/app/auth/reducers/login-page.reducer.spec.ts",
    "content": "import { reducer } from '@example-app/auth/reducers/login-page.reducer';\nimport * as fromLoginPage from '@example-app/auth/reducers/login-page.reducer';\n\nimport { LoginPageActions } from '@example-app/auth/actions/login-page.actions';\nimport { AuthApiActions } from '@example-app/auth/actions/auth-api.actions';\n\nimport { Credentials, User } from '@example-app/auth/models';\n\ndescribe('LoginPageReducer', () => {\n  describe('undefined action', () => {\n    it('should return the default state', () => {\n      const action = {} as any;\n\n      const result = reducer(undefined, action);\n\n      expect(result).toMatchSnapshot();\n    });\n  });\n\n  describe('LOGIN', () => {\n    it('should make pending to true', () => {\n      const user = { username: 'test' } as Credentials;\n      const createAction = LoginPageActions.login({ credentials: user });\n\n      const result = reducer(fromLoginPage.initialState, createAction);\n\n      expect(result).toMatchSnapshot();\n    });\n  });\n\n  describe('LOGIN_SUCCESS', () => {\n    it('should have no error and no pending state', () => {\n      const user = { name: 'test' } as User;\n      const createAction = AuthApiActions.loginSuccess({ user });\n\n      const result = reducer(fromLoginPage.initialState, createAction);\n\n      expect(result).toMatchSnapshot();\n    });\n  });\n\n  describe('LOGIN_FAILURE', () => {\n    it('should have an error and no pending state', () => {\n      const error = 'login failed';\n      const createAction = AuthApiActions.loginFailure({ error });\n\n      const result = reducer(fromLoginPage.initialState, createAction);\n\n      expect(result).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/reducers/login-page.reducer.ts",
    "content": "import { AuthApiActions } from '@example-app/auth/actions/auth-api.actions';\nimport { LoginPageActions } from '@example-app/auth/actions/login-page.actions';\nimport { createReducer, on } from '@ngrx/store';\n\nexport const loginPageFeatureKey = 'loginPage';\n\nexport interface State {\n  error: string | null;\n  pending: boolean;\n}\n\nexport const initialState: State = {\n  error: null,\n  pending: false,\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(LoginPageActions.login, (state) => ({\n    ...state,\n    error: null,\n    pending: true,\n  })),\n\n  on(AuthApiActions.loginSuccess, (state) => ({\n    ...state,\n    error: null,\n    pending: false,\n  })),\n  on(AuthApiActions.loginFailure, (state, { error }) => ({\n    ...state,\n    error,\n    pending: false,\n  }))\n);\n\nexport const getError = (state: State) => state.error;\nexport const getPending = (state: State) => state.pending;\n"
  },
  {
    "path": "projects/example-app/src/app/auth/services/auth-guard.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { MemoizedSelector } from '@ngrx/store';\nimport { cold } from 'jasmine-marbles';\nimport { authGuard } from '@example-app/auth/services';\nimport * as fromAuth from '@example-app/auth/reducers';\nimport { provideMockStore, MockStore } from '@ngrx/store/testing';\nimport { Observable } from 'rxjs';\n\ndescribe('Auth Guard', () => {\n  let guard: Observable<boolean>;\n  let store: MockStore;\n  let loggedIn: MemoizedSelector<fromAuth.State, boolean>;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [provideMockStore()],\n    });\n\n    store = TestBed.inject(MockStore);\n    guard = TestBed.runInInjectionContext(authGuard);\n    loggedIn = store.overrideSelector(fromAuth.selectLoggedIn, false);\n  });\n\n  it('should return false if the user state is not logged in', () => {\n    const expected = cold('(a|)', { a: false });\n\n    expect(guard).toBeObservable(expected);\n  });\n\n  it('should return true if the user state is logged in', () => {\n    const expected = cold('(a|)', { a: true });\n\n    loggedIn.setResult(true);\n\n    expect(guard).toBeObservable(expected);\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/auth/services/auth-guard.service.ts",
    "content": "import { inject } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { Observable } from 'rxjs';\nimport { map, take } from 'rxjs/operators';\nimport { AuthApiActions } from '@example-app/auth/actions/auth-api.actions';\nimport * as fromAuth from '@example-app/auth/reducers';\n\nexport const authGuard = (): Observable<boolean> => {\n  const store = inject(Store);\n\n  return store.select(fromAuth.selectLoggedIn).pipe(\n    map((authed) => {\n      if (!authed) {\n        store.dispatch(AuthApiActions.loginRedirect());\n        return false;\n      }\n\n      return true;\n    }),\n    take(1)\n  );\n};\n"
  },
  {
    "path": "projects/example-app/src/app/auth/services/auth.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Observable, of, throwError } from 'rxjs';\n\nimport { Credentials, User } from '@example-app/auth/models';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthService {\n  login({ username, password }: Credentials): Observable<User> {\n    /**\n     * Simulate a failed login to display the error\n     * message for the login form.\n     */\n    if (username !== 'test' && username !== 'ngrx') {\n      return throwError(() => 'Invalid username or password');\n    }\n\n    return of({ name: 'User' });\n  }\n\n  logout() {\n    return of(true);\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/auth/services/index.ts",
    "content": "export * from './auth.service';\nexport * from './auth-guard.service';\n"
  },
  {
    "path": "projects/example-app/src/app/books/actions/book.actions.ts",
    "content": "import { createActionGroup, props } from '@ngrx/store';\n\nimport { Book } from '@example-app/books/models';\n\nexport const BookActions = createActionGroup({\n  source: 'Book Exists Guard',\n  events: {\n    'Load Book': props<{ book: Book }>(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/actions/books-api.actions.ts",
    "content": "import { createActionGroup, props } from '@ngrx/store';\n\nimport { Book } from '@example-app/books/models';\n\nexport const BooksApiActions = createActionGroup({\n  source: 'Books/API',\n  events: {\n    'Search Success': props<{ books: Book[] }>(),\n    'Search Failure': props<{ errorMsg: string }>(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/actions/collection-api.actions.ts",
    "content": "import { createActionGroup, props } from '@ngrx/store';\n\nimport { Book } from '@example-app/books/models';\n\nexport const CollectionApiActions = createActionGroup({\n  source: 'Collection/API',\n  events: {\n    /**\n     * Add Book to Collection Actions\n     */\n    'Add Book Success': props<{ book: Book }>(),\n    'Add Book Failure': props<{ book: Book }>(),\n\n    /**\n     * Remove Book from Collection Actions\n     */\n    'Remove Book Success': props<{ book: Book }>(),\n    'Remove Book Failure': props<{ book: Book }>(),\n\n    /**\n     * Load Collection Actions\n     */\n    'Load Books Success': props<{ books: Book[] }>(),\n    'Load Books Failure': props<{ error: any }>(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/actions/collection-page.actions.ts",
    "content": "import { createActionGroup, emptyProps } from '@ngrx/store';\n\nexport const CollectionPageActions = createActionGroup({\n  source: 'Collection Page',\n  events: {\n    /**\n     * Load Collection Action\n     */\n    Enter: emptyProps(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/actions/find-book-page.actions.ts",
    "content": "import { createActionGroup, props } from '@ngrx/store';\n\nexport const FindBookPageActions = createActionGroup({\n  source: 'Find Book Page',\n  events: {\n    'Search Books': props<{ query: string }>(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/actions/selected-book-page.actions.ts",
    "content": "import { createActionGroup, props } from '@ngrx/store';\n\nimport { Book } from '@example-app/books/models';\n\nexport const SelectedBookPageActions = createActionGroup({\n  source: 'Selected Book Page',\n  events: {\n    /**\n     * Add Book to Collection Action\n     */\n    'Add Book': props<{ book: Book }>(),\n\n    /**\n     * Remove Book from Collection Action\n     */\n    'Remove Book': props<{ book: Book }>(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/actions/view-book-page.actions.ts",
    "content": "import { createActionGroup, props } from '@ngrx/store';\n\nexport const ViewBookPageActions = createActionGroup({\n  source: 'View Book Page',\n  events: {\n    'Select Book': props<{ id: string }>(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/books-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\n\nimport {\n  CollectionPageComponent,\n  FindBookPageComponent,\n  ViewBookPageComponent,\n} from '@example-app/books/containers';\nimport { bookExistsGuard } from '@example-app/books/guards';\n\nexport const routes: Routes = [\n  {\n    path: 'find',\n    component: FindBookPageComponent,\n    data: { title: 'Find book' },\n  },\n  {\n    path: ':id',\n    component: ViewBookPageComponent,\n    canActivate: [bookExistsGuard],\n    data: { title: 'Book details' },\n  },\n  {\n    path: '',\n    component: CollectionPageComponent,\n    data: { title: 'Collection' },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class BooksRoutingModule {}\n"
  },
  {
    "path": "projects/example-app/src/app/books/books.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\n\nimport { EffectsModule } from '@ngrx/effects';\nimport { StoreModule } from '@ngrx/store';\n\nimport { BooksRoutingModule } from '@example-app/books/books-routing.module';\nimport {\n  BookAuthorsComponent,\n  BookDetailComponent,\n  BookPreviewComponent,\n  BookPreviewListComponent,\n  BookSearchComponent,\n} from '@example-app/books/components';\nimport {\n  CollectionPageComponent,\n  FindBookPageComponent,\n  SelectedBookPageComponent,\n  ViewBookPageComponent,\n} from '@example-app/books/containers';\nimport { BookEffects, CollectionEffects } from '@example-app/books/effects';\n\nimport * as fromBooks from '@example-app/books/reducers';\nimport { MaterialModule } from '@example-app/material';\nimport { PipesModule } from '@example-app/shared/pipes';\n\nexport const COMPONENTS = [\n  BookAuthorsComponent,\n  BookDetailComponent,\n  BookPreviewComponent,\n  BookPreviewListComponent,\n  BookSearchComponent,\n];\n\nexport const CONTAINERS = [\n  FindBookPageComponent,\n  ViewBookPageComponent,\n  SelectedBookPageComponent,\n  CollectionPageComponent,\n];\n\n@NgModule({\n  imports: [\n    CommonModule,\n    MaterialModule,\n    BooksRoutingModule,\n\n    /**\n     * StoreModule.forFeature is used for composing state\n     * from feature modules. These modules can be loaded\n     * eagerly or lazily and will be dynamically added to\n     * the existing state.\n     */\n    StoreModule.forFeature(fromBooks.booksFeatureKey, fromBooks.reducers),\n\n    /**\n     * Effects.forFeature is used to register effects\n     * from feature modules. Effects can be loaded\n     * eagerly or lazily and will be started immediately.\n     *\n     * All Effects will only be instantiated once regardless of\n     * whether they are registered once or multiple times.\n     */\n    EffectsModule.forFeature(BookEffects, CollectionEffects),\n    PipesModule,\n  ],\n  declarations: [COMPONENTS, CONTAINERS],\n})\nexport class BooksModule {}\n"
  },
  {
    "path": "projects/example-app/src/app/books/components/book-authors.component.ts",
    "content": "import { Component, Input } from '@angular/core';\n\nimport { Book } from '@example-app/books/models';\n\n@Component({\n  selector: 'bc-book-authors',\n  template: `\n    <h5 mat-subheader>Written By:</h5>\n    <span>\n      {{ authors | bcAddCommas }}\n    </span>\n  `,\n  styles: [\n    `\n      h5 {\n        margin-bottom: 5px;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class BookAuthorsComponent {\n  @Input() book!: Book;\n\n  get authors() {\n    return this.book.volumeInfo.authors;\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/components/book-detail.component.ts",
    "content": "import { Component, EventEmitter, Input, Output } from '@angular/core';\n\nimport { Book } from '@example-app/books/models';\n\n@Component({\n  selector: 'bc-book-detail',\n  template: `\n    <mat-card *ngIf=\"book\">\n      <mat-card-title-group>\n        <mat-card-title>{{ title }}</mat-card-title>\n        <mat-card-subtitle *ngIf=\"subtitle\">{{ subtitle }}</mat-card-subtitle>\n        <img mat-card-sm-image *ngIf=\"thumbnail\" [src]=\"thumbnail\" />\n      </mat-card-title-group>\n      <mat-card-content>\n        <p [innerHtml]=\"description\"></p>\n      </mat-card-content>\n      <mat-card-footer class=\"footer\">\n        <bc-book-authors [book]=\"book\"></bc-book-authors>\n      </mat-card-footer>\n      <mat-card-actions align=\"start\">\n        <button\n          mat-raised-button\n          color=\"warn\"\n          *ngIf=\"inCollection\"\n          (click)=\"remove.emit(book)\"\n        >\n          Remove Book from Collection\n        </button>\n\n        <button\n          mat-raised-button\n          color=\"primary\"\n          *ngIf=\"!inCollection\"\n          (click)=\"add.emit(book)\"\n        >\n          Add Book to Collection\n        </button>\n      </mat-card-actions>\n    </mat-card>\n  `,\n  styles: [\n    `\n      :host {\n        display: flex;\n        justify-content: center;\n        margin: 4.5rem 0;\n      }\n\n      mat-card {\n        padding: 1rem;\n        max-width: 600px;\n      }\n\n      img {\n        width: 60px;\n        min-width: 60px;\n        margin-left: 5px;\n      }\n\n      mat-card-content {\n        padding: 0;\n        margin: 1rem 0;\n      }\n\n      mat-card-actions {\n        justify-content: center;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class BookDetailComponent {\n  /**\n   * Presentational components receive data through @Input() and communicate events\n   * through @Output() but generally maintain no internal state of their\n   * own. All decisions are delegated to 'container', or 'smart'\n   * components before data updates flow back down.\n   *\n   * More on 'smart' and 'presentational' components: https://gist.github.com/btroncone/a6e4347326749f938510#utilizing-container-components\n   */\n  @Input() book!: Book;\n  @Input() inCollection!: boolean;\n  @Output() add = new EventEmitter<Book>();\n  @Output() remove = new EventEmitter<Book>();\n\n  /**\n   * Tip: Utilize getters to keep templates clean\n   */\n  get id() {\n    return this.book.id;\n  }\n\n  get title() {\n    return this.book.volumeInfo.title;\n  }\n\n  get subtitle() {\n    return this.book.volumeInfo.subtitle;\n  }\n\n  get description() {\n    return this.book.volumeInfo.description;\n  }\n\n  get thumbnail() {\n    return (\n      this.book.volumeInfo.imageLinks &&\n      this.book.volumeInfo.imageLinks.smallThumbnail &&\n      this.book.volumeInfo.imageLinks.smallThumbnail.replace('http:', '')\n    );\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/components/book-preview-list.component.ts",
    "content": "import { Component, Input } from '@angular/core';\n\nimport { Book } from '@example-app/books/models';\n\n@Component({\n  selector: 'bc-book-preview-list',\n  template: `\n    <bc-book-preview *ngFor=\"let book of books\" [book]=\"book\"></bc-book-preview>\n  `,\n  styles: [\n    `\n      :host {\n        display: flex;\n        flex-wrap: wrap;\n        justify-content: center;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class BookPreviewListComponent {\n  @Input() books!: Book[];\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/components/book-preview.component.ts",
    "content": "import { Component, Input } from '@angular/core';\n\nimport { Book } from '@example-app/books/models';\n\n@Component({\n  selector: 'bc-book-preview',\n  template: `\n    <a [routerLink]=\"['/books', id]\">\n      <mat-card>\n        <mat-card-title-group>\n          <img\n            mat-card-sm-image\n            *ngIf=\"thumbnail\"\n            [src]=\"thumbnail\"\n            [alt]=\"title\"\n          />\n          <mat-card-title>{{ title | bcEllipsis: 35 }}</mat-card-title>\n          <mat-card-subtitle *ngIf=\"subtitle\">{{\n            subtitle | bcEllipsis: 40\n          }}</mat-card-subtitle>\n        </mat-card-title-group>\n        <mat-card-content>\n          <p *ngIf=\"description\">{{ description | bcEllipsis }}</p>\n        </mat-card-content>\n        <mat-card-footer>\n          <bc-book-authors [book]=\"book\"></bc-book-authors>\n        </mat-card-footer>\n      </mat-card>\n    </a>\n  `,\n  styles: [\n    `\n      :host,\n      a {\n        display: flex;\n      }\n\n      mat-card {\n        width: 400px;\n        margin: 1rem;\n        padding: 1rem;\n        display: flex;\n        flex-direction: column;\n        justify-content: space-between;\n      }\n\n      @media only screen and (max-width: 768px) {\n        mat-card {\n          margin: 1rem 0 !important;\n        }\n      }\n\n      mat-card:hover {\n        box-shadow: 3px 3px 16px -2px rgba(0, 0, 0, 0.5);\n      }\n\n      a {\n        color: inherit;\n        text-decoration: none;\n      }\n\n      img {\n        width: 60px;\n        min-width: 60px;\n        margin-left: 5px;\n      }\n\n      span {\n        display: inline-block;\n        font-size: 13px;\n      }\n\n      mat-card-content {\n        padding: 0;\n        margin: 1rem 0;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class BookPreviewComponent {\n  @Input() book!: Book;\n\n  get id() {\n    return this.book.id;\n  }\n\n  get title() {\n    return this.book.volumeInfo.title;\n  }\n\n  get subtitle() {\n    return this.book.volumeInfo.subtitle;\n  }\n\n  get description() {\n    return this.book.volumeInfo.description;\n  }\n\n  get thumbnail(): string | boolean {\n    if (this.book.volumeInfo.imageLinks) {\n      return this.book.volumeInfo.imageLinks.smallThumbnail.replace(\n        'http:',\n        ''\n      );\n    }\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/components/book-search.component.ts",
    "content": "import { Component, Output, Input, EventEmitter } from '@angular/core';\n\n@Component({\n  selector: 'bc-book-search',\n  template: `\n    <mat-card>\n      <mat-card-title>Find a Book</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <input\n            matInput\n            placeholder=\"Search for a book\"\n            [value]=\"query\"\n            (keyup)=\"onSearch($event)\"\n          />\n        </mat-form-field>\n        <mat-spinner\n          [class.show]=\"searching\"\n          [diameter]=\"30\"\n          [strokeWidth]=\"3\"\n        ></mat-spinner>\n      </mat-card-content>\n      <mat-card-footer>\n        <span *ngIf=\"error\">{{ error }}</span>\n      </mat-card-footer>\n    </mat-card>\n  `,\n  styles: [\n    `\n      mat-card-title,\n      mat-card-content,\n      mat-card-footer {\n        display: flex;\n        justify-content: center;\n      }\n\n      mat-card-title {\n        padding: 1rem;\n      }\n\n      mat-card-footer {\n        color: #ff0000;\n        padding: 5px 0;\n      }\n\n      .mat-mdc-form-field {\n        min-width: 300px;\n        margin-right: 10px;\n      }\n\n      .mat-mdc-progress-spinner {\n        position: relative;\n        top: 10px;\n        left: 10px;\n        visibility: hidden;\n      }\n\n      .mat-mdc-progress-spinner.show {\n        visibility: visible;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class BookSearchComponent {\n  @Input() query = '';\n  @Input() searching = false;\n  @Input() error = '';\n  @Output() searchBooks = new EventEmitter<string>();\n\n  onSearch(event: KeyboardEvent): void {\n    this.searchBooks.emit((event.target as HTMLInputElement).value);\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/components/index.ts",
    "content": "export * from './book-authors.component';\nexport * from './book-detail.component';\nexport * from './book-preview.component';\nexport * from './book-preview-list.component';\nexport * from './book-search.component';\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/__snapshots__/collection-page.component.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Collection Page > should compile 1`] = `\n<bc-collection-page>\n  <mat-card\n    _ngcontent-a-c1432040493=\"\"\n    class=\"mat-mdc-card mdc-card\"\n  >\n    <mat-card-title\n      _ngcontent-a-c1432040493=\"\"\n      class=\"mat-mdc-card-title\"\n    >\n      My Collection\n    </mat-card-title>\n  </mat-card>\n  <bc-book-preview-list\n    _ngcontent-a-c1432040493=\"\"\n    _nghost-a-c3778324089=\"\"\n  >\n    <!--container-->\n  </bc-book-preview-list>\n</bc-collection-page>\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/__snapshots__/find-book-page.component.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Find Book Page > should compile 1`] = `\n<bc-find-book-page>\n  <bc-book-search\n    _nghost-a-c3898412319=\"\"\n  >\n    <mat-card\n      _ngcontent-a-c3898412319=\"\"\n      class=\"mat-mdc-card mdc-card\"\n    >\n      <mat-card-title\n        _ngcontent-a-c3898412319=\"\"\n        class=\"mat-mdc-card-title\"\n      >\n        Find a Book\n      </mat-card-title>\n      <mat-card-content\n        _ngcontent-a-c3898412319=\"\"\n        class=\"mat-mdc-card-content\"\n      >\n        <mat-form-field\n          _ngcontent-a-c3898412319=\"\"\n          class=\"mat-mdc-form-field mat-mdc-form-field-type-mat-input mat-form-field-appearance-fill mat-primary\"\n        >\n          <!--container-->\n          <div\n            class=\"mat-mdc-text-field-wrapper mdc-text-field mdc-text-field--filled mdc-text-field--no-label\"\n          >\n            <div\n              class=\"mat-mdc-form-field-focus-overlay\"\n            />\n            <!--container-->\n            <div\n              class=\"mat-mdc-form-field-flex\"\n            >\n              <!--container-->\n              <!--container-->\n              <!--container-->\n              <div\n                class=\"mat-mdc-form-field-infix\"\n              >\n                <!--container-->\n                <!--container-->\n                <!--container-->\n                <input\n                  _ngcontent-a-c3898412319=\"\"\n                  aria-invalid=\"false\"\n                  aria-required=\"false\"\n                  class=\"mat-mdc-input-element mat-mdc-form-field-input-control mdc-text-field__input cdk-text-field-autofill-monitored\"\n                  id=\"mat-input-a0\"\n                  matinput=\"\"\n                  placeholder=\"Search for a book\"\n                />\n              </div>\n              <!--container-->\n              <!--container-->\n            </div>\n            <div\n              class=\"mdc-line-ripple mdc-line-ripple--deactivating\"\n              matformfieldlineripple=\"\"\n            />\n            <!--container-->\n          </div>\n          <div\n            aria-atomic=\"true\"\n            aria-live=\"polite\"\n            class=\"mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align\"\n          >\n            <!--container-->\n            <div\n              class=\"mat-mdc-form-field-hint-wrapper\"\n            >\n              <!--container-->\n              <div\n                class=\"mat-mdc-form-field-hint-spacer\"\n              />\n            </div>\n            <!--container-->\n          </div>\n        </mat-form-field>\n        <mat-spinner\n          _ngcontent-a-c3898412319=\"\"\n          aria-valuemax=\"100\"\n          aria-valuemin=\"0\"\n          class=\"mat-mdc-progress-spinner mdc-circular-progress mat-primary _mat-animation-noopable mdc-circular-progress--indeterminate\"\n          mode=\"indeterminate\"\n          role=\"progressbar\"\n          style=\"width: 30px; height: 30px; --mat-progress-spinner-size: 30px; --mat-progress-spinner-active-indicator-width: 30px;\"\n          tabindex=\"-1\"\n        >\n          <!--container-->\n          <div\n            aria-hidden=\"true\"\n            class=\"mdc-circular-progress__determinate-container\"\n          >\n            <svg\n              class=\"mdc-circular-progress__determinate-circle-graphic\"\n              focusable=\"false\"\n              viewBox=\"0 0 23 23\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n            >\n              <circle\n                class=\"mdc-circular-progress__determinate-circle\"\n                cx=\"50%\"\n                cy=\"50%\"\n                r=\"10\"\n                style=\"stroke-dasharray: 62.83185307179586px; stroke-width: 10%;\"\n              />\n            </svg>\n          </div>\n          <div\n            aria-hidden=\"true\"\n            class=\"mdc-circular-progress__indeterminate-container\"\n          >\n            <div\n              class=\"mdc-circular-progress__spinner-layer\"\n            >\n              <div\n                class=\"mdc-circular-progress__circle-clipper mdc-circular-progress__circle-left\"\n              >\n                <svg\n                  class=\"mdc-circular-progress__indeterminate-circle-graphic\"\n                  focusable=\"false\"\n                  viewBox=\"0 0 23 23\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <circle\n                    cx=\"50%\"\n                    cy=\"50%\"\n                    r=\"10\"\n                    style=\"stroke-dasharray: 62.83185307179586px; stroke-dashoffset: 31.41592653589793px; stroke-width: 10%;\"\n                  />\n                </svg>\n                <!--ng-container-->\n              </div>\n              <div\n                class=\"mdc-circular-progress__gap-patch\"\n              >\n                <svg\n                  class=\"mdc-circular-progress__indeterminate-circle-graphic\"\n                  focusable=\"false\"\n                  viewBox=\"0 0 23 23\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <circle\n                    cx=\"50%\"\n                    cy=\"50%\"\n                    r=\"10\"\n                    style=\"stroke-dasharray: 62.83185307179586px; stroke-dashoffset: 31.41592653589793px; stroke-width: 10%;\"\n                  />\n                </svg>\n                <!--ng-container-->\n              </div>\n              <div\n                class=\"mdc-circular-progress__circle-clipper mdc-circular-progress__circle-right\"\n              >\n                <svg\n                  class=\"mdc-circular-progress__indeterminate-circle-graphic\"\n                  focusable=\"false\"\n                  viewBox=\"0 0 23 23\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <circle\n                    cx=\"50%\"\n                    cy=\"50%\"\n                    r=\"10\"\n                    style=\"stroke-dasharray: 62.83185307179586px; stroke-dashoffset: 31.41592653589793px; stroke-width: 10%;\"\n                  />\n                </svg>\n                <!--ng-container-->\n              </div>\n            </div>\n          </div>\n        </mat-spinner>\n      </mat-card-content>\n      <mat-card-footer\n        _ngcontent-a-c3898412319=\"\"\n        class=\"mat-mdc-card-footer\"\n      >\n        <!--container-->\n      </mat-card-footer>\n    </mat-card>\n  </bc-book-search>\n  <bc-book-preview-list\n    _nghost-a-c3778324089=\"\"\n  >\n    <!--container-->\n  </bc-book-preview-list>\n</bc-find-book-page>\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/__snapshots__/selected-book-page.component.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Selected Book Page > should compile 1`] = `\n<bc-selected-book-page>\n  <bc-book-detail\n    _nghost-a-c973509077=\"\"\n  >\n    <!--container-->\n  </bc-book-detail>\n</bc-selected-book-page>\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/__snapshots__/view-book-page.component.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`View Book Page > should compile 1`] = `\n<bc-view-book-page>\n  <bc-selected-book-page>\n    <bc-book-detail\n      _nghost-a-c973509077=\"\"\n    >\n      <!--container-->\n    </bc-book-detail>\n  </bc-selected-book-page>\n</bc-view-book-page>\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/collection-page.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { RouterTestingModule } from '@angular/router/testing';\n\nimport { MockStore, provideMockStore } from '@ngrx/store/testing';\n\nimport { CollectionPageActions } from '@example-app/books/actions/collection-page.actions';\nimport {\n  BookAuthorsComponent,\n  BookPreviewComponent,\n  BookPreviewListComponent,\n} from '@example-app/books/components';\nimport { CollectionPageComponent } from '@example-app/books/containers';\nimport * as fromBooks from '@example-app/books/reducers';\nimport { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';\nimport { EllipsisPipe } from '@example-app/shared/pipes/ellipsis.pipe';\nimport { MaterialModule } from '@example-app/material';\n\ndescribe('Collection Page', () => {\n  let fixture: ComponentFixture<CollectionPageComponent>;\n  let store: MockStore;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [NoopAnimationsModule, MaterialModule, RouterTestingModule],\n      declarations: [\n        CollectionPageComponent,\n        BookPreviewListComponent,\n        BookPreviewComponent,\n        BookAuthorsComponent,\n        AddCommasPipe,\n        EllipsisPipe,\n      ],\n      providers: [\n        provideMockStore({\n          selectors: [{ selector: fromBooks.selectBookCollection, value: [] }],\n        }),\n      ],\n    });\n\n    fixture = TestBed.createComponent(CollectionPageComponent);\n    store = TestBed.inject(MockStore);\n\n    vi.spyOn(store, 'dispatch');\n  });\n\n  it('should compile', () => {\n    fixture.detectChanges();\n\n    expect(fixture).toMatchSnapshot();\n  });\n\n  it('should dispatch a collection.Load on init', () => {\n    const action = CollectionPageActions.enter();\n\n    fixture.detectChanges();\n\n    expect(store.dispatch).toHaveBeenCalledWith(action);\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/collection-page.component.ts",
    "content": "import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\n\nimport { Store } from '@ngrx/store';\nimport { Observable } from 'rxjs';\n\nimport { CollectionPageActions } from '@example-app/books/actions/collection-page.actions';\nimport { Book } from '@example-app/books/models';\nimport * as fromBooks from '@example-app/books/reducers';\n\n@Component({\n  selector: 'bc-collection-page',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\n    <mat-card>\n      <mat-card-title>My Collection</mat-card-title>\n    </mat-card>\n\n    <bc-book-preview-list [books]=\"(books$ | async)!\"></bc-book-preview-list>\n  `,\n  /**\n   * Container components are permitted to have just enough styles\n   * to bring the view together. If the number of styles grow,\n   * consider breaking them out into presentational\n   * components.\n   */\n  styles: [\n    `\n      mat-card-title {\n        display: flex;\n        justify-content: center;\n        padding: 1rem;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class CollectionPageComponent implements OnInit {\n  books$: Observable<Book[]>;\n\n  constructor(private store: Store) {\n    this.books$ = store.select(fromBooks.selectBookCollection);\n  }\n\n  ngOnInit() {\n    this.store.dispatch(CollectionPageActions.enter());\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/find-book-page.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { RouterTestingModule } from '@angular/router/testing';\n\nimport { MockStore, provideMockStore } from '@ngrx/store/testing';\n\nimport { FindBookPageActions } from '@example-app/books/actions/find-book-page.actions';\nimport {\n  BookAuthorsComponent,\n  BookPreviewComponent,\n  BookPreviewListComponent,\n  BookSearchComponent,\n} from '@example-app/books/components';\nimport { FindBookPageComponent } from '@example-app/books/containers';\nimport * as fromBooks from '@example-app/books/reducers';\nimport { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';\nimport { EllipsisPipe } from '@example-app/shared/pipes/ellipsis.pipe';\nimport { MaterialModule } from '@example-app/material';\n\ndescribe('Find Book Page', () => {\n  let fixture: ComponentFixture<FindBookPageComponent>;\n  let store: MockStore;\n  let instance: FindBookPageComponent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        RouterTestingModule,\n        MaterialModule,\n        ReactiveFormsModule,\n      ],\n      declarations: [\n        FindBookPageComponent,\n        BookSearchComponent,\n        BookPreviewComponent,\n        BookPreviewListComponent,\n        BookAuthorsComponent,\n        AddCommasPipe,\n        EllipsisPipe,\n      ],\n      providers: [\n        provideMockStore({\n          selectors: [\n            { selector: fromBooks.selectSearchQuery, value: '' },\n            { selector: fromBooks.selectSearchResults, value: [] },\n            { selector: fromBooks.selectSearchLoading, value: false },\n            { selector: fromBooks.selectSearchError, value: '' },\n          ],\n        }),\n      ],\n    });\n\n    fixture = TestBed.createComponent(FindBookPageComponent);\n    instance = fixture.componentInstance;\n    store = TestBed.inject(MockStore);\n\n    vi.spyOn(store, 'dispatch');\n  });\n\n  it('should compile', () => {\n    fixture.detectChanges();\n\n    expect(fixture).toMatchSnapshot();\n  });\n\n  it('should dispatch a book.Search action on search', () => {\n    const $event = 'book name';\n    const action = FindBookPageActions.searchBooks({ query: $event });\n\n    instance.search($event);\n\n    expect(store.dispatch).toHaveBeenCalledWith(action);\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/find-book-page.component.ts",
    "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\n\nimport { Store } from '@ngrx/store';\nimport { Observable } from 'rxjs';\nimport { take } from 'rxjs/operators';\n\nimport { FindBookPageActions } from '@example-app/books/actions/find-book-page.actions';\nimport { Book } from '@example-app/books/models';\nimport * as fromBooks from '@example-app/books/reducers';\n\n@Component({\n  selector: 'bc-find-book-page',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\n    <bc-book-search\n      [query]=\"(searchQuery$ | async)!\"\n      [searching]=\"(loading$ | async)!\"\n      [error]=\"(error$ | async)!\"\n      (searchBooks)=\"search($event)\"\n    >\n    </bc-book-search>\n    <bc-book-preview-list [books]=\"(books$ | async)!\"> </bc-book-preview-list>\n  `,\n  standalone: false,\n})\nexport class FindBookPageComponent {\n  searchQuery$: Observable<string>;\n  books$: Observable<Book[]>;\n  loading$: Observable<boolean>;\n  error$: Observable<string>;\n\n  constructor(private store: Store) {\n    this.searchQuery$ = store.select(fromBooks.selectSearchQuery).pipe(take(1));\n    this.books$ = store.select(fromBooks.selectSearchResults);\n    this.loading$ = store.select(fromBooks.selectSearchLoading);\n    this.error$ = store.select(fromBooks.selectSearchError);\n  }\n\n  search(query: string) {\n    this.store.dispatch(FindBookPageActions.searchBooks({ query }));\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/index.ts",
    "content": "export * from './collection-page.component';\nexport * from './find-book-page.component';\nexport * from './selected-book-page.component';\nexport * from './view-book-page.component';\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/selected-book-page.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\n\nimport { MockStore, provideMockStore } from '@ngrx/store/testing';\n\nimport { SelectedBookPageActions } from '@example-app/books/actions/selected-book-page.actions';\nimport {\n  BookAuthorsComponent,\n  BookDetailComponent,\n} from '@example-app/books/components';\nimport { SelectedBookPageComponent } from '@example-app/books/containers';\nimport { Book, generateMockBook } from '@example-app/books/models';\nimport * as fromBooks from '@example-app/books/reducers';\nimport { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';\nimport { MaterialModule } from '@example-app/material';\n\ndescribe('Selected Book Page', () => {\n  let fixture: ComponentFixture<SelectedBookPageComponent>;\n  let store: MockStore;\n  let instance: SelectedBookPageComponent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [NoopAnimationsModule, MaterialModule],\n      declarations: [\n        SelectedBookPageComponent,\n        BookDetailComponent,\n        BookAuthorsComponent,\n        AddCommasPipe,\n      ],\n      providers: [\n        provideMockStore({\n          selectors: [\n            { selector: fromBooks.selectSelectedBook, value: null },\n            { selector: fromBooks.isSelectedBookInCollection, value: false },\n          ],\n        }),\n      ],\n    });\n\n    fixture = TestBed.createComponent(SelectedBookPageComponent);\n    instance = fixture.componentInstance;\n    store = TestBed.inject(MockStore);\n\n    vi.spyOn(store, 'dispatch');\n  });\n\n  it('should compile', () => {\n    fixture.detectChanges();\n\n    expect(fixture).toMatchSnapshot();\n  });\n\n  it('should dispatch a collection.AddBook action when addToCollection is called', () => {\n    const $event: Book = generateMockBook();\n    const action = SelectedBookPageActions.addBook({ book: $event });\n\n    instance.addToCollection($event);\n\n    expect(store.dispatch).toHaveBeenLastCalledWith(action);\n  });\n\n  it('should dispatch a collection.RemoveBook action on removeFromCollection', () => {\n    const $event: Book = generateMockBook();\n    const action = SelectedBookPageActions.removeBook({ book: $event });\n\n    instance.removeFromCollection($event);\n\n    expect(store.dispatch).toHaveBeenLastCalledWith(action);\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/selected-book-page.component.ts",
    "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\n\nimport { Store } from '@ngrx/store';\nimport { Observable } from 'rxjs';\n\nimport { SelectedBookPageActions } from '@example-app/books/actions/selected-book-page.actions';\nimport { Book } from '@example-app/books/models';\nimport * as fromBooks from '@example-app/books/reducers';\n\n@Component({\n  selector: 'bc-selected-book-page',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\n    <bc-book-detail\n      [book]=\"(book$ | async)!\"\n      [inCollection]=\"(isSelectedBookInCollection$ | async)!\"\n      (add)=\"addToCollection($event)\"\n      (remove)=\"removeFromCollection($event)\"\n    >\n    </bc-book-detail>\n  `,\n  standalone: false,\n})\nexport class SelectedBookPageComponent {\n  book$: Observable<Book>;\n  isSelectedBookInCollection$: Observable<boolean>;\n\n  constructor(private store: Store) {\n    this.book$ = store.select(fromBooks.selectSelectedBook) as Observable<Book>;\n    this.isSelectedBookInCollection$ = store.select(\n      fromBooks.isSelectedBookInCollection\n    );\n  }\n\n  addToCollection(book: Book) {\n    this.store.dispatch(SelectedBookPageActions.addBook({ book }));\n  }\n\n  removeFromCollection(book: Book) {\n    this.store.dispatch(SelectedBookPageActions.removeBook({ book }));\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/view-book-page.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { provideMockStore, MockStore } from '@ngrx/store/testing';\nimport { BehaviorSubject } from 'rxjs';\n\nimport {\n  BookAuthorsComponent,\n  BookDetailComponent,\n} from '@example-app/books/components';\nimport { SelectedBookPageComponent } from '@example-app/books/containers';\nimport { ViewBookPageComponent } from '@example-app/books/containers';\nimport { ViewBookPageActions } from '@example-app/books/actions/view-book-page.actions';\nimport * as fromBooks from '@example-app/books/reducers';\nimport { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';\nimport { MaterialModule } from '@example-app/material';\n\ndescribe('View Book Page', () => {\n  let fixture: ComponentFixture<ViewBookPageComponent>;\n  let store: MockStore;\n  let route: ActivatedRoute;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [MaterialModule],\n      providers: [\n        {\n          provide: ActivatedRoute,\n          useValue: { params: new BehaviorSubject({}) },\n        },\n        provideMockStore({\n          selectors: [\n            { selector: fromBooks.selectSelectedBook, value: null },\n            { selector: fromBooks.isSelectedBookInCollection, value: false },\n          ],\n        }),\n      ],\n      declarations: [\n        ViewBookPageComponent,\n        SelectedBookPageComponent,\n        BookDetailComponent,\n        BookAuthorsComponent,\n        AddCommasPipe,\n      ],\n    });\n\n    fixture = TestBed.createComponent(ViewBookPageComponent);\n    store = TestBed.inject(MockStore);\n    route = TestBed.inject(ActivatedRoute);\n\n    vi.spyOn(store, 'dispatch');\n  });\n\n  it('should compile', () => {\n    fixture.detectChanges();\n\n    expect(fixture).toMatchSnapshot();\n  });\n\n  it('should dispatch a book.Select action on init', () => {\n    const action = ViewBookPageActions.selectBook({ id: '2' });\n\n    (route.params as BehaviorSubject<any>).next({ id: '2' });\n\n    expect(store.dispatch).toHaveBeenLastCalledWith(action);\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/containers/view-book-page.component.ts",
    "content": "import { Component, OnDestroy, ChangeDetectionStrategy } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Store } from '@ngrx/store';\nimport { Subscription } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { ViewBookPageActions } from '@example-app/books/actions/view-book-page.actions';\n\n/**\n * Note: Container components are also reusable. Whether or not\n * a component is a presentation component or a container\n * component is an implementation detail.\n *\n * The View Book Page's responsibility is to map router params\n * to a 'Select' book action. Actually showing the selected\n * book remains a responsibility of the\n * SelectedBookPageComponent\n */\n@Component({\n  selector: 'bc-view-book-page',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: ` <bc-selected-book-page></bc-selected-book-page> `,\n  standalone: false,\n})\nexport class ViewBookPageComponent implements OnDestroy {\n  actionsSubscription: Subscription;\n\n  constructor(store: Store, route: ActivatedRoute) {\n    this.actionsSubscription = route.params\n      .pipe(map((params) => ViewBookPageActions.selectBook({ id: params.id })))\n      .subscribe((action) => store.dispatch(action));\n  }\n\n  ngOnDestroy() {\n    this.actionsSubscription.unsubscribe();\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/effects/book.effects.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { Actions } from '@ngrx/effects';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport { cold, getTestScheduler, hot } from 'jasmine-marbles';\nimport { Observable } from 'rxjs';\n\nimport { FindBookPageActions } from '@example-app/books/actions/find-book-page.actions';\nimport { BooksApiActions } from '@example-app/books/actions/books-api.actions';\nimport { BookEffects } from '@example-app/books/effects';\nimport { Book } from '@example-app/books/models';\nimport { GoogleBooksService } from '@example-app/core/services';\n\ndescribe('BookEffects', () => {\n  let effects: BookEffects;\n  let googleBooksService: any;\n  let actions$: Observable<any>;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        BookEffects,\n        {\n          provide: GoogleBooksService,\n          useValue: { searchBooks: vi.fn() },\n        },\n        provideMockActions(() => actions$),\n      ],\n    });\n\n    effects = TestBed.inject(BookEffects);\n    googleBooksService = TestBed.inject(GoogleBooksService);\n    actions$ = TestBed.inject(Actions);\n  });\n\n  describe('search$', () => {\n    it('should return a book.SearchComplete, with the books, on success, after the de-bounce', () => {\n      const book1 = { id: '111', volumeInfo: {} } as Book;\n      const book2 = { id: '222', volumeInfo: {} } as Book;\n      const books = [book1, book2];\n      const action = FindBookPageActions.searchBooks({ query: 'query' });\n      const completion = BooksApiActions.searchSuccess({ books });\n\n      actions$ = hot('-a---', { a: action });\n      const response = cold('-a|', { a: books });\n      const expected = cold('-----b', { b: completion });\n      googleBooksService.searchBooks = vi.fn(() => response);\n\n      expect(\n        effects.search$({\n          debounce: 30,\n          scheduler: getTestScheduler(),\n        })\n      ).toBeObservable(expected);\n    });\n\n    it('should return a book.SearchError if the books service throws', () => {\n      const action = FindBookPageActions.searchBooks({ query: 'query' });\n      const completion = BooksApiActions.searchFailure({\n        errorMsg: 'Unexpected Error. Try again later.',\n      });\n      const error = { message: 'Unexpected Error. Try again later.' };\n\n      actions$ = hot('-a---', { a: action });\n      const response = cold('-#|', {}, error);\n      const expected = cold('-----b', { b: completion });\n      googleBooksService.searchBooks = vi.fn(() => response);\n\n      expect(\n        effects.search$({\n          debounce: 30,\n          scheduler: getTestScheduler(),\n        })\n      ).toBeObservable(expected);\n    });\n\n    it(`should not do anything if the query is an empty string`, () => {\n      const action = FindBookPageActions.searchBooks({ query: '' });\n\n      actions$ = hot('-a---', { a: action });\n      const expected = cold('---');\n\n      expect(\n        effects.search$({\n          debounce: 30,\n          scheduler: getTestScheduler(),\n        })\n      ).toBeObservable(expected);\n    });\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/effects/book.effects.ts",
    "content": "import { Injectable } from '@angular/core';\n\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { asyncScheduler, EMPTY as empty, of } from 'rxjs';\nimport {\n  catchError,\n  debounceTime,\n  map,\n  skip,\n  switchMap,\n  takeUntil,\n} from 'rxjs/operators';\n\nimport { Book } from '@example-app/books/models';\nimport { BooksApiActions } from '@example-app/books/actions/books-api.actions';\nimport { FindBookPageActions } from '@example-app/books/actions/find-book-page.actions';\nimport { GoogleBooksService } from '@example-app/core/services';\n\n/**\n * Effects offer a way to isolate and easily test side-effects within your\n * application.\n *\n * If you are unfamiliar with the operators being used in these examples, please\n * check out the sources below:\n *\n * Official Docs: http://reactivex.io/rxjs/manual/overview.html#categories-of-operators\n * RxJS 5 Operators By Example: https://gist.github.com/btroncone/d6cf141d6f2c00dc6b35\n */\n\n@Injectable()\nexport class BookEffects {\n  search$ = createEffect(\n    () =>\n      ({ debounce = 300, scheduler = asyncScheduler } = {}) =>\n        this.actions$.pipe(\n          ofType(FindBookPageActions.searchBooks),\n          debounceTime(debounce, scheduler),\n          switchMap(({ query }) => {\n            if (query === '') {\n              return empty;\n            }\n\n            const nextSearch$ = this.actions$.pipe(\n              ofType(FindBookPageActions.searchBooks),\n              skip(1)\n            );\n\n            return this.googleBooks.searchBooks(query).pipe(\n              takeUntil(nextSearch$),\n              map((books: Book[]) => BooksApiActions.searchSuccess({ books })),\n              catchError((err) =>\n                of(BooksApiActions.searchFailure({ errorMsg: err.message }))\n              )\n            );\n          })\n        )\n  );\n\n  constructor(\n    private actions$: Actions,\n    private googleBooks: GoogleBooksService\n  ) {}\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/effects/collection.effects.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { CollectionApiActions } from '@example-app/books/actions/collection-api.actions';\nimport { CollectionPageActions } from '@example-app/books/actions/collection-page.actions';\nimport { SelectedBookPageActions } from '@example-app/books/actions/selected-book-page.actions';\nimport { CollectionEffects } from '@example-app/books/effects';\nimport { Book } from '@example-app/books/models';\nimport {\n  BookStorageService,\n  LOCAL_STORAGE_TOKEN,\n} from '@example-app/core/services';\nimport { Actions } from '@ngrx/effects';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport { cold, hot } from 'jasmine-marbles';\nimport { Observable, of } from 'rxjs';\n\ndescribe('CollectionEffects', () => {\n  let db: any;\n  let effects: CollectionEffects;\n  let actions$: Observable<any>;\n\n  const book1 = { id: '111', volumeInfo: {} } as Book;\n  const book2 = { id: '222', volumeInfo: {} } as Book;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        CollectionEffects,\n        {\n          provide: BookStorageService,\n          useValue: {\n            supported: vi.fn(() => of(true)),\n            deleteStoredCollection: vi.fn(),\n            addToCollection: vi.fn(),\n            getCollection: vi.fn(),\n            removeFromCollection: vi.fn(),\n          },\n        },\n        {\n          provide: LOCAL_STORAGE_TOKEN,\n          useValue: {\n            removeItem: vi.fn(),\n            setItem: vi.fn(),\n            getItem: vi.fn((_) => JSON.stringify([])),\n          },\n        },\n        provideMockActions(() => actions$),\n      ],\n    });\n\n    db = TestBed.inject(BookStorageService);\n    effects = TestBed.inject(CollectionEffects);\n    actions$ = TestBed.inject(Actions);\n  });\n  describe('checkStorageSupport$', () => {\n    it('should call db.checkStorageSupport when initially subscribed to', () => {\n      effects.checkStorageSupport$.subscribe();\n      expect(db.supported).toHaveBeenCalled();\n    });\n  });\n  describe('loadCollection$', () => {\n    it('should return a collection.LoadSuccess, with the books, on success', () => {\n      const action = CollectionPageActions.enter();\n      const completion = CollectionApiActions.loadBooksSuccess({\n        books: [book1, book2],\n      });\n\n      actions$ = hot('-a', { a: action });\n      const response = cold('-a|', { a: [book1, book2] });\n      const expected = cold('--c', { c: completion });\n      db.getCollection = vi.fn(() => response);\n\n      expect(effects.loadCollection$).toBeObservable(expected);\n    });\n\n    it('should return a collection.LoadFail, if the query throws', () => {\n      const action = CollectionPageActions.enter();\n      const error = 'Error!';\n      const completion = CollectionApiActions.loadBooksFailure({ error });\n\n      actions$ = hot('-a', { a: action });\n      const response = cold('-#', {}, error);\n      const expected = cold('--c', { c: completion });\n      db.getCollection = vi.fn(() => response);\n\n      expect(effects.loadCollection$).toBeObservable(expected);\n    });\n  });\n\n  describe('addBookToCollection$', () => {\n    it('should return a collection.AddBookSuccess, with the book, on success', () => {\n      const action = SelectedBookPageActions.addBook({ book: book1 });\n      const completion = CollectionApiActions.addBookSuccess({ book: book1 });\n\n      actions$ = hot('-a', { a: action });\n      const response = cold('-b', { b: true });\n      const expected = cold('--c', { c: completion });\n      db.addToCollection = vi.fn(() => response);\n\n      expect(effects.addBookToCollection$).toBeObservable(expected);\n      expect(db.addToCollection).toHaveBeenCalledWith([book1]);\n    });\n\n    it('should return a collection.AddBookFail, with the book, when the db insert throws', () => {\n      const action = SelectedBookPageActions.addBook({ book: book1 });\n      const completion = CollectionApiActions.addBookFailure({ book: book1 });\n      const error = 'Error!';\n\n      actions$ = hot('-a', { a: action });\n      const response = cold('-#', {}, error);\n      const expected = cold('--c', { c: completion });\n      db.addToCollection = vi.fn(() => response);\n\n      expect(effects.addBookToCollection$).toBeObservable(expected);\n    });\n\n    describe('removeBookFromCollection$', () => {\n      it('should return a collection.RemoveBookSuccess, with the book, on success', () => {\n        const action = SelectedBookPageActions.removeBook({ book: book1 });\n        const completion = CollectionApiActions.removeBookSuccess({\n          book: book1,\n        });\n\n        actions$ = hot('-a', { a: action });\n        const response = cold('-b', { b: true });\n        const expected = cold('--c', { c: completion });\n        db.removeFromCollection = vi.fn(() => response);\n\n        expect(effects.removeBookFromCollection$).toBeObservable(expected);\n        expect(db.removeFromCollection).toHaveBeenCalledWith([book1.id]);\n      });\n\n      it('should return a collection.RemoveBookFail, with the book, when the db insert throws', () => {\n        const action = SelectedBookPageActions.removeBook({ book: book1 });\n        const completion = CollectionApiActions.removeBookFailure({\n          book: book1,\n        });\n        const error = 'Error!';\n\n        actions$ = hot('-a', { a: action });\n        const response = cold('-#', {}, error);\n        const expected = cold('--c', { c: completion });\n        db.removeFromCollection = vi.fn(() => response);\n\n        expect(effects.removeBookFromCollection$).toBeObservable(expected);\n        expect(db.removeFromCollection).toHaveBeenCalledWith([book1.id]);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/effects/collection.effects.ts",
    "content": "import { Injectable } from '@angular/core';\n\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { defer, of } from 'rxjs';\nimport { catchError, map, mergeMap, switchMap } from 'rxjs/operators';\n\nimport { CollectionApiActions } from '@example-app/books/actions/collection-api.actions';\nimport { CollectionPageActions } from '@example-app/books/actions/collection-page.actions';\nimport { SelectedBookPageActions } from '@example-app/books/actions/selected-book-page.actions';\nimport { Book } from '@example-app/books/models';\nimport { BookStorageService } from '@example-app/core/services';\n\n@Injectable()\nexport class CollectionEffects {\n  /**\n   * This effect does not yield any actions back to the store. Set\n   * `dispatch` to false to hint to @ngrx/effects that it should\n   * ignore any elements of this effect stream.\n   *\n   * The `defer` observable accepts an observable factory function\n   * that is called when the observable is subscribed to.\n   * Wrapping the supported call in `defer` makes\n   * effect easier to test.\n   */\n  checkStorageSupport$ = createEffect(\n    () => defer(() => this.storageService.supported()),\n    { dispatch: false }\n  );\n\n  loadCollection$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(CollectionPageActions.enter),\n      switchMap(() =>\n        this.storageService.getCollection().pipe(\n          map((books: Book[]) =>\n            CollectionApiActions.loadBooksSuccess({ books })\n          ),\n          catchError((error) =>\n            of(CollectionApiActions.loadBooksFailure({ error }))\n          )\n        )\n      )\n    )\n  );\n\n  addBookToCollection$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(SelectedBookPageActions.addBook),\n      mergeMap(({ book }) =>\n        this.storageService.addToCollection([book]).pipe(\n          map(() => CollectionApiActions.addBookSuccess({ book })),\n          catchError(() => of(CollectionApiActions.addBookFailure({ book })))\n        )\n      )\n    )\n  );\n\n  removeBookFromCollection$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(SelectedBookPageActions.removeBook),\n      mergeMap(({ book }) =>\n        this.storageService.removeFromCollection([book.id]).pipe(\n          map(() => CollectionApiActions.removeBookSuccess({ book })),\n          catchError(() => of(CollectionApiActions.removeBookFailure({ book })))\n        )\n      )\n    )\n  );\n\n  constructor(\n    private actions$: Actions,\n    private storageService: BookStorageService\n  ) {}\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/effects/index.ts",
    "content": "export * from './book.effects';\nexport * from './collection.effects';\n"
  },
  {
    "path": "projects/example-app/src/app/books/guards/book-exists.guard.ts",
    "content": "import { inject } from '@angular/core';\nimport { Router, ActivatedRouteSnapshot } from '@angular/router';\nimport { Store } from '@ngrx/store';\nimport { Observable, of } from 'rxjs';\nimport { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators';\n\nimport { GoogleBooksService } from '@example-app/core/services';\nimport { BookActions } from '@example-app/books/actions/book.actions';\nimport * as fromBooks from '@example-app/books/reducers';\n\n/**\n * Guards are hooks into the route resolution process, providing an opportunity\n * to inform the router's navigation process whether the route should continue\n * to activate this route. Guards must return an observable of true or false.\n */\n\nexport const bookExistsGuard = (\n  route: ActivatedRouteSnapshot\n): Observable<boolean> => {\n  const store = inject(Store);\n  const googleBooks = inject(GoogleBooksService);\n  const router = inject(Router);\n\n  /**\n   * This method creates an observable that waits for the `loaded` property\n   * of the collection state to turn `true`, emitting one time once loading\n   * has finished.\n   */\n  function waitForCollectionToLoad(): Observable<boolean> {\n    return store.select(fromBooks.selectCollectionLoaded).pipe(\n      filter((loaded) => loaded),\n      take(1)\n    );\n  }\n\n  /**\n   * This method checks if a book with the given ID is already registered\n   * in the Store\n   */\n  function hasBookInStore(id: string): Observable<boolean> {\n    return store.select(fromBooks.selectBookEntities).pipe(\n      map((entities) => !!entities[id]),\n      take(1)\n    );\n  }\n\n  /**\n   * This method loads a book with the given ID from the API and caches\n   * it in the store, returning `true` or `false` if it was found.\n   */\n  function hasBookInApi(id: string): Observable<boolean> {\n    return googleBooks.retrieveBook(id).pipe(\n      map((bookEntity) => BookActions.loadBook({ book: bookEntity })),\n      tap((action) => store.dispatch(action)),\n      map((book) => !!book),\n      catchError(() => {\n        router.navigate(['/404']);\n        return of(false);\n      })\n    );\n  }\n\n  /**\n   * `hasBook` composes `hasBookInStore` and `hasBookInApi`. It first checks\n   * if the book is in store, and if not it then checks if it is in the\n   * API.\n   */\n  function hasBook(id: string): Observable<boolean> {\n    return hasBookInStore(id).pipe(\n      switchMap((inStore) => {\n        if (inStore) {\n          return of(inStore);\n        }\n\n        return hasBookInApi(id);\n      })\n    );\n  }\n\n  /**\n   * This is the actual method the router will call when our guard is run.\n   *\n   * Our guard waits for the collection to load, then it checks if we need\n   * to request a book from the API or if we already have it in our cache.\n   * If it finds it in the cache or in the API, it returns an Observable\n   * of `true` and the route is rendered successfully.\n   *\n   * If it was unable to find it in our cache or in the API, this guard\n   * will return an Observable of `false`, causing the router to move\n   * on to the next candidate route. In this case, it will move on\n   * to the 404 page.\n   */\n  return waitForCollectionToLoad().pipe(\n    switchMap(() => hasBook(route.params['id']))\n  );\n};\n"
  },
  {
    "path": "projects/example-app/src/app/books/guards/index.ts",
    "content": "export * from './book-exists.guard';\n"
  },
  {
    "path": "projects/example-app/src/app/books/models/book.ts",
    "content": "export interface Book {\n  id: string;\n  volumeInfo: {\n    title: string;\n    subtitle: string;\n    authors: string[];\n    publisher: string;\n    publishDate: string;\n    description: string;\n    averageRating: number;\n    ratingsCount: number;\n    imageLinks: {\n      thumbnail: string;\n      smallThumbnail: string;\n    };\n  };\n}\n\nexport function generateMockBook(): Book {\n  return {\n    id: '1',\n    volumeInfo: {\n      title: 'title',\n      subtitle: 'subtitle',\n      authors: ['author'],\n      publisher: 'publisher',\n      publishDate: '',\n      description: 'description',\n      averageRating: 3,\n      ratingsCount: 5,\n      imageLinks: {\n        thumbnail: 'string',\n        smallThumbnail: 'string',\n      },\n    },\n  };\n}\n"
  },
  {
    "path": "projects/example-app/src/app/books/models/index.ts",
    "content": "export * from './book';\n"
  },
  {
    "path": "projects/example-app/src/app/books/reducers/__snapshots__/books.reducer.spec.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`BooksReducer > LOAD > should add a single book, if the book does not exist 1`] = `\n{\n  \"entities\": {\n    \"1\": {\n      \"id\": \"1\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n  },\n  \"ids\": [\n    \"1\",\n  ],\n  \"selectedBookId\": null,\n}\n`;\n\nexports[`BooksReducer > LOAD > should return the existing state if the book exists 1`] = `\n{\n  \"entities\": {\n    \"1\": {\n      \"id\": \"1\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n  },\n  \"ids\": [\n    \"1\",\n  ],\n  \"selectedBookId\": null,\n}\n`;\n\nexports[`BooksReducer > SEARCH_COMPLETE & LOAD_SUCCESS > should add all books in the payload when none exist 1`] = `\n{\n  \"entities\": {\n    \"1\": {\n      \"id\": \"1\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n    \"222\": {\n      \"id\": \"222\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n  },\n  \"ids\": [\n    \"1\",\n    \"222\",\n  ],\n  \"selectedBookId\": null,\n}\n`;\n\nexports[`BooksReducer > SEARCH_COMPLETE & LOAD_SUCCESS > should add all books in the payload when none exist 2`] = `\n{\n  \"entities\": {\n    \"1\": {\n      \"id\": \"1\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n    \"222\": {\n      \"id\": \"222\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n  },\n  \"ids\": [\n    \"1\",\n    \"222\",\n  ],\n  \"selectedBookId\": null,\n}\n`;\n\nexports[`BooksReducer > SEARCH_COMPLETE & LOAD_SUCCESS > should add only books when books already exist 1`] = `\n{\n  \"entities\": {\n    \"1\": {\n      \"id\": \"1\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n    \"222\": {\n      \"id\": \"222\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n    \"333\": {\n      \"id\": \"333\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n  },\n  \"ids\": [\n    \"1\",\n    \"222\",\n    \"333\",\n  ],\n  \"selectedBookId\": null,\n}\n`;\n\nexports[`BooksReducer > SEARCH_COMPLETE & LOAD_SUCCESS > should add only books when books already exist 2`] = `\n{\n  \"entities\": {\n    \"1\": {\n      \"id\": \"1\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n    \"222\": {\n      \"id\": \"222\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n    \"333\": {\n      \"id\": \"333\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n  },\n  \"ids\": [\n    \"1\",\n    \"222\",\n    \"333\",\n  ],\n  \"selectedBookId\": null,\n}\n`;\n\nexports[`BooksReducer > SELECT > should set the selected book id on the state 1`] = `\n{\n  \"entities\": {\n    \"1\": {\n      \"id\": \"1\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n    \"222\": {\n      \"id\": \"222\",\n      \"volumeInfo\": {\n        \"authors\": [\n          \"author\",\n        ],\n        \"averageRating\": 3,\n        \"description\": \"description\",\n        \"imageLinks\": {\n          \"smallThumbnail\": \"string\",\n          \"thumbnail\": \"string\",\n        },\n        \"publishDate\": \"\",\n        \"publisher\": \"publisher\",\n        \"ratingsCount\": 5,\n        \"subtitle\": \"subtitle\",\n        \"title\": \"title\",\n      },\n    },\n  },\n  \"ids\": [\n    \"1\",\n    \"222\",\n  ],\n  \"selectedBookId\": \"1\",\n}\n`;\n\nexports[`BooksReducer > Selectors > selectId > should return the selected id 1`] = `\"1\"`;\n\nexports[`BooksReducer > undefined action > should return the default state 1`] = `\n{\n  \"entities\": {},\n  \"ids\": [],\n  \"selectedBookId\": null,\n}\n`;\n"
  },
  {
    "path": "projects/example-app/src/app/books/reducers/books.reducer.spec.ts",
    "content": "import { reducer } from '@example-app/books/reducers/books.reducer';\nimport * as fromBooks from '@example-app/books/reducers/books.reducer';\nimport { BooksApiActions } from '@example-app/books/actions/books-api.actions';\nimport { BookActions } from '@example-app/books/actions/book.actions';\nimport { CollectionApiActions } from '@example-app/books/actions/collection-api.actions';\nimport { ViewBookPageActions } from '@example-app/books/actions/view-book-page.actions';\nimport { Book, generateMockBook } from '@example-app/books/models';\n\ndescribe('BooksReducer', () => {\n  const book1 = generateMockBook();\n  const book2 = { ...book1, id: '222' };\n  const book3 = { ...book1, id: '333' };\n  const initialState: fromBooks.State = {\n    ids: [book1.id, book2.id],\n    entities: {\n      [book1.id]: book1,\n      [book2.id]: book2,\n    },\n    selectedBookId: null,\n  };\n\n  describe('undefined action', () => {\n    it('should return the default state', () => {\n      const result = reducer(undefined, {} as any);\n\n      expect(result).toMatchSnapshot();\n    });\n  });\n\n  describe('SEARCH_COMPLETE & LOAD_SUCCESS', () => {\n    type BooksActions =\n      | typeof BooksApiActions.searchSuccess\n      | typeof CollectionApiActions.loadBooksSuccess;\n    function noExistingBooks(\n      action: BooksActions,\n      booksInitialState: any,\n      books: Book[]\n    ) {\n      const createAction = action({ books });\n\n      const result = reducer(booksInitialState, createAction);\n\n      expect(result).toMatchSnapshot();\n    }\n\n    function existingBooks(\n      action: BooksActions,\n      booksInitialState: any,\n      books: Book[]\n    ) {\n      // should not replace existing books\n      const differentBook2 = { ...books[0], foo: 'bar' };\n      const createAction = action({ books: [books[1], differentBook2] });\n\n      const result = reducer(booksInitialState, createAction);\n\n      expect(result).toMatchSnapshot();\n    }\n\n    it('should add all books in the payload when none exist', () => {\n      noExistingBooks(BooksApiActions.searchSuccess, initialState, [\n        book1,\n        book2,\n      ]);\n\n      noExistingBooks(CollectionApiActions.loadBooksSuccess, initialState, [\n        book1,\n        book2,\n      ]);\n    });\n\n    it('should add only books when books already exist', () => {\n      existingBooks(BooksApiActions.searchSuccess, initialState, [\n        book2,\n        book3,\n      ]);\n\n      existingBooks(CollectionApiActions.loadBooksSuccess, initialState, [\n        book2,\n        book3,\n      ]);\n    });\n  });\n\n  describe('LOAD', () => {\n    const expectedResult = {\n      ids: [book1.id],\n      entities: {\n        [book1.id]: book1,\n      },\n      selectedBookId: null,\n    };\n\n    it('should add a single book, if the book does not exist', () => {\n      const action = BookActions.loadBook({ book: book1 });\n\n      const result = reducer(fromBooks.initialState, action);\n\n      expect(result).toMatchSnapshot();\n    });\n\n    it('should return the existing state if the book exists', () => {\n      const action = BookActions.loadBook({ book: book1 });\n\n      const result = reducer(expectedResult, action);\n\n      expect(result).toMatchSnapshot();\n    });\n  });\n\n  describe('SELECT', () => {\n    it('should set the selected book id on the state', () => {\n      const action = ViewBookPageActions.selectBook({ id: book1.id });\n\n      const result = reducer(initialState, action);\n\n      expect(result).toMatchSnapshot();\n    });\n  });\n\n  describe('Selectors', () => {\n    describe('selectId', () => {\n      it('should return the selected id', () => {\n        const result = fromBooks.selectId({\n          ...initialState,\n          selectedBookId: book1.id,\n        });\n\n        expect(result).toMatchSnapshot();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/books/reducers/books.reducer.ts",
    "content": "import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';\nimport { createReducer, on } from '@ngrx/store';\n\nimport { BooksApiActions } from '@example-app/books/actions/books-api.actions';\nimport { BookActions } from '@example-app/books/actions/book.actions';\nimport { CollectionApiActions } from '@example-app/books/actions/collection-api.actions';\nimport { ViewBookPageActions } from '@example-app/books/actions/view-book-page.actions';\nimport { Book } from '@example-app/books/models';\n\nexport const booksFeatureKey = 'books';\n\n/**\n * @ngrx/entity provides a predefined interface for handling\n * a structured dictionary of records. This interface\n * includes an array of ids, and a dictionary of the provided\n * model type by id. This interface is extended to include\n * any additional interface properties.\n */\nexport interface State extends EntityState<Book> {\n  selectedBookId: string | null;\n}\n\n/**\n * createEntityAdapter creates an object of many helper\n * functions for single or multiple operations\n * against the dictionary of records. The configuration\n * object takes a record id selector function and\n * a sortComparer option which is set to a compare\n * function if the records are to be sorted.\n */\nexport const adapter: EntityAdapter<Book> = createEntityAdapter<Book>({\n  selectId: (book: Book) => book.id,\n  sortComparer: false,\n});\n\n/**\n * getInitialState returns the default initial state\n * for the generated entity state. Initial state\n * additional properties can also be defined.\n */\nexport const initialState: State = adapter.getInitialState({\n  selectedBookId: null,\n});\n\nexport const reducer = createReducer(\n  initialState,\n  /**\n   * The addMany function provided by the created adapter\n   * adds many records to the entity dictionary\n   * and returns a new state including those records. If\n   * the collection is to be sorted, the adapter will\n   * sort each record upon entry into the sorted array.\n   */\n  on(\n    BooksApiActions.searchSuccess,\n    CollectionApiActions.loadBooksSuccess,\n    (state, { books }) => adapter.addMany(books, state)\n  ),\n  /**\n   * The addOne function provided by the created adapter\n   * adds one record to the entity dictionary\n   * and returns a new state including that records if it doesn't\n   * exist already. If the collection is to be sorted, the adapter will\n   * insert the new record into the sorted array.\n   */\n  on(BookActions.loadBook, (state, { book }) => adapter.addOne(book, state)),\n  on(ViewBookPageActions.selectBook, (state, { id }) => ({\n    ...state,\n    selectedBookId: id,\n  }))\n);\n\n/**\n * Because the data structure is defined within the reducer it is optimal to\n * locate our selector functions at this level. If store is to be thought of\n * as a database, and reducers the tables, selectors can be considered the\n * queries into said database. Remember to keep your selectors small and\n * focused so they can be combined and composed to fit each particular\n * use-case.\n */\n\nexport const selectId = (state: State) => state.selectedBookId;\n"
  },
  {
    "path": "projects/example-app/src/app/books/reducers/collection.reducer.ts",
    "content": "import { createReducer, on } from '@ngrx/store';\n\nimport { CollectionApiActions } from '@example-app/books/actions/collection-api.actions';\nimport { CollectionPageActions } from '@example-app/books/actions/collection-page.actions';\nimport { SelectedBookPageActions } from '@example-app/books/actions/selected-book-page.actions';\n\nexport const collectionFeatureKey = 'collection';\n\nexport interface State {\n  loaded: boolean;\n  loading: boolean;\n  ids: string[];\n}\n\nconst initialState: State = {\n  loaded: false,\n  loading: false,\n  ids: [],\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(CollectionPageActions.enter, (state) => ({\n    ...state,\n    loading: true,\n  })),\n  on(CollectionApiActions.loadBooksSuccess, (_state, { books }) => ({\n    loaded: true,\n    loading: false,\n    ids: books.map((book) => book.id),\n  })),\n  /**\n   * Optimistically add book to collection.\n   * If this succeeds there's nothing to do.\n   * If this fails we revert state by removing the book.\n   *\n   * `on` supports handling multiple types of actions\n   */\n  on(\n    SelectedBookPageActions.addBook,\n    CollectionApiActions.removeBookFailure,\n    (state, { book }) => {\n      if (state.ids.indexOf(book.id) > -1) {\n        return state;\n      }\n      return {\n        ...state,\n        ids: [...state.ids, book.id],\n      };\n    }\n  ),\n  /**\n   * Optimistically remove book from collection.\n   * If addBook fails, we \"undo\" adding the book.\n   */\n  on(\n    SelectedBookPageActions.removeBook,\n    CollectionApiActions.addBookFailure,\n    (state, { book }) => ({\n      ...state,\n      ids: state.ids.filter((id) => id !== book.id),\n    })\n  )\n);\n\nexport const getLoaded = (state: State) => state.loaded;\n\nexport const getLoading = (state: State) => state.loading;\n\nexport const getIds = (state: State) => state.ids;\n"
  },
  {
    "path": "projects/example-app/src/app/books/reducers/index.ts",
    "content": "import { Book } from '@example-app/books/models';\nimport {\n  createSelector,\n  createFeatureSelector,\n  combineReducers,\n  Action,\n} from '@ngrx/store';\nimport * as fromSearch from '@example-app/books/reducers/search.reducer';\nimport * as fromBooks from '@example-app/books/reducers/books.reducer';\nimport * as fromCollection from '@example-app/books/reducers/collection.reducer';\nimport * as fromRoot from '@example-app/reducers';\n\nexport const booksFeatureKey = 'books';\n\nexport interface BooksState {\n  [fromSearch.searchFeatureKey]: fromSearch.State;\n  [fromBooks.booksFeatureKey]: fromBooks.State;\n  [fromCollection.collectionFeatureKey]: fromCollection.State;\n}\n\nexport interface State extends fromRoot.State {\n  [booksFeatureKey]: BooksState;\n}\n\n/** Provide reducer in AoT-compilation happy way */\nexport function reducers(state: BooksState | undefined, action: Action) {\n  return combineReducers({\n    [fromSearch.searchFeatureKey]: fromSearch.reducer,\n    [fromBooks.booksFeatureKey]: fromBooks.reducer,\n    [fromCollection.collectionFeatureKey]: fromCollection.reducer,\n  })(state, action);\n}\n\n/**\n * A selector function is a map function factory. We pass it parameters and it\n * returns a function that maps from the larger state tree into a smaller\n * piece of state. This selector simply selects the `books` state.\n *\n * Selectors are used with the `select` operator.\n *\n * ```ts\n * class MyComponent {\n *   constructor(state$: Observable<State>) {\n *     this.booksState$ = state$.pipe(select(getBooksState));\n *   }\n * }\n * ```\n */\n\n/**\n * The createFeatureSelector function selects a piece of state from the root of the state object.\n * This is used for selecting feature states that are loaded eagerly or lazily.\n */\nexport const selectBooksState =\n  createFeatureSelector<BooksState>(booksFeatureKey);\n\n/**\n * Every reducer module exports selector functions, however child reducers\n * have no knowledge of the overall state tree. To make them usable, we\n * need to make new selectors that wrap them.\n *\n * The createSelector function creates very efficient selectors that are memoized and\n * only recompute when arguments change. The created selectors can also be composed\n * together to select different pieces of state.\n */\nexport const selectBookEntitiesState = createSelector(\n  selectBooksState,\n  (state) => state.books\n);\n\nexport const selectSelectedBookId = createSelector(\n  selectBookEntitiesState,\n  fromBooks.selectId\n);\n\n/**\n * Adapters created with @ngrx/entity generate\n * commonly used selector functions including\n * getting all ids in the record set, a dictionary\n * of the records by id, an array of records and\n * the total number of records. This reduces boilerplate\n * in selecting records from the entity state.\n */\nexport const {\n  selectIds: selectBookIds,\n  selectEntities: selectBookEntities,\n  selectAll: selectAllBooks,\n  selectTotal: selectTotalBooks,\n} = fromBooks.adapter.getSelectors(selectBookEntitiesState);\n\nexport const selectSelectedBook = createSelector(\n  selectBookEntities,\n  selectSelectedBookId,\n  (entities, selectedId) => {\n    return selectedId && entities[selectedId];\n  }\n);\n\n/**\n * Just like with the books selectors, we also have to compose the search\n * reducer's and collection reducer's selectors.\n */\nexport const selectSearchState = createSelector(\n  selectBooksState,\n  (state) => state.search\n);\n\nexport const selectSearchBookIds = createSelector(\n  selectSearchState,\n  fromSearch.getIds\n);\nexport const selectSearchQuery = createSelector(\n  selectSearchState,\n  fromSearch.getQuery\n);\nexport const selectSearchLoading = createSelector(\n  selectSearchState,\n  fromSearch.getLoading\n);\nexport const selectSearchError = createSelector(\n  selectSearchState,\n  fromSearch.getError\n);\n\n/**\n * Some selector functions create joins across parts of state. This selector\n * composes the search result IDs to return an array of books in the store.\n */\nexport const selectSearchResults = createSelector(\n  selectBookEntities,\n  selectSearchBookIds,\n  (books, searchIds) => {\n    return searchIds\n      .map((id) => books[id])\n      .filter((book): book is Book => book != null);\n  }\n);\n\nexport const selectCollectionState = createSelector(\n  selectBooksState,\n  (state) => state.collection\n);\n\nexport const selectCollectionLoaded = createSelector(\n  selectCollectionState,\n  fromCollection.getLoaded\n);\nexport const getCollectionLoading = createSelector(\n  selectCollectionState,\n  fromCollection.getLoading\n);\nexport const selectCollectionBookIds = createSelector(\n  selectCollectionState,\n  fromCollection.getIds\n);\n\nexport const selectBookCollection = createSelector(\n  selectBookEntities,\n  selectCollectionBookIds,\n  (entities, ids) => {\n    return ids\n      .map((id) => entities[id])\n      .filter((book): book is Book => book != null);\n  }\n);\n\nexport const isSelectedBookInCollection = createSelector(\n  selectCollectionBookIds,\n  selectSelectedBookId,\n  (ids, selected) => {\n    return !!selected && ids.indexOf(selected) > -1;\n  }\n);\n"
  },
  {
    "path": "projects/example-app/src/app/books/reducers/search.reducer.ts",
    "content": "import { BooksApiActions } from '@example-app/books/actions/books-api.actions';\nimport { FindBookPageActions } from '@example-app/books/actions/find-book-page.actions';\nimport { createReducer, on } from '@ngrx/store';\n\nexport const searchFeatureKey = 'search';\n\nexport interface State {\n  ids: string[];\n  loading: boolean;\n  error: string;\n  query: string;\n}\n\nconst initialState: State = {\n  ids: [],\n  loading: false,\n  error: '',\n  query: '',\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(FindBookPageActions.searchBooks, (state, { query }) => {\n    return query === ''\n      ? {\n          ids: [],\n          loading: false,\n          error: '',\n          query,\n        }\n      : {\n          ...state,\n          loading: true,\n          error: '',\n          query,\n        };\n  }),\n  on(BooksApiActions.searchSuccess, (state, { books }) => ({\n    ids: books.map((book) => book.id),\n    loading: false,\n    error: '',\n    query: state.query,\n  })),\n  on(BooksApiActions.searchFailure, (state, { errorMsg }) => ({\n    ...state,\n    loading: false,\n    error: errorMsg,\n  }))\n);\n\nexport const getIds = (state: State) => state.ids;\n\nexport const getQuery = (state: State) => state.query;\n\nexport const getLoading = (state: State) => state.loading;\n\nexport const getError = (state: State) => state.error;\n"
  },
  {
    "path": "projects/example-app/src/app/core/actions/layout.actions.ts",
    "content": "import { createActionGroup, emptyProps } from '@ngrx/store';\n\nexport const LayoutActions = createActionGroup({\n  source: 'Layout',\n  events: {\n    'Open Sidenav': emptyProps(),\n    'Close Sidenav': emptyProps(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/core/actions/user.actions.ts",
    "content": "import { createActionGroup, emptyProps } from '@ngrx/store';\n\nexport const UserActions = createActionGroup({\n  source: 'User',\n  events: {\n    'Idle Timeout': emptyProps(),\n  },\n});\n"
  },
  {
    "path": "projects/example-app/src/app/core/components/index.ts",
    "content": "export * from './layout.component';\nexport * from './nav-item.component';\nexport * from './sidenav.component';\nexport * from './toolbar.component';\n"
  },
  {
    "path": "projects/example-app/src/app/core/components/layout.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'bc-layout',\n  template: `\n    <mat-sidenav-container fullscreen>\n      <ng-content></ng-content>\n    </mat-sidenav-container>\n  `,\n  styles: [\n    `\n      mat-sidenav-container {\n        background: rgba(0, 0, 0, 0.03);\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class LayoutComponent {}\n"
  },
  {
    "path": "projects/example-app/src/app/core/components/nav-item.component.ts",
    "content": "import { Component, Input, Output, EventEmitter } from '@angular/core';\n\n@Component({\n  selector: 'bc-nav-item',\n  template: `\n    <a mat-list-item [routerLink]=\"routerLink\" (click)=\"navigate.emit()\">\n      <mat-icon matListItemIcon>{{ icon }}</mat-icon>\n      <div matListItemTitle><ng-content></ng-content></div>\n      <div *ngIf=\"hint\" matListItemLine>{{ hint }}</div>\n    </a>\n  `,\n  styles: [\n    `\n      a:hover {\n        cursor: pointer;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class NavItemComponent {\n  @Input() icon = '';\n  @Input() hint = '';\n  @Input() routerLink: string | any[] = '/';\n  @Output() navigate = new EventEmitter<void>();\n}\n"
  },
  {
    "path": "projects/example-app/src/app/core/components/sidenav.component.ts",
    "content": "import { Component, EventEmitter, Input, Output } from '@angular/core';\n\n@Component({\n  selector: 'bc-sidenav',\n  template: `\n    <mat-sidenav\n      #sidenav\n      [opened]=\"open\"\n      (keydown.escape)=\"sidenav.close()\"\n      (closedStart)=\"closeMenu.emit()\"\n      disableClose\n    >\n      <mat-nav-list>\n        <ng-content></ng-content>\n      </mat-nav-list>\n    </mat-sidenav>\n  `,\n  styles: [\n    `\n      mat-sidenav {\n        width: 300px;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class SidenavComponent {\n  @Input() open = false;\n  @Output() closeMenu = new EventEmitter<void>();\n}\n"
  },
  {
    "path": "projects/example-app/src/app/core/components/toolbar.component.ts",
    "content": "import { Component, Output, EventEmitter } from '@angular/core';\n\n@Component({\n  selector: 'bc-toolbar',\n  template: `\n    <mat-toolbar color=\"primary\">\n      <button mat-icon-button (click)=\"openMenu.emit()\" aria-label=\"menu\">\n        <mat-icon>menu</mat-icon>\n      </button>\n      <ng-content></ng-content>\n    </mat-toolbar>\n  `,\n  standalone: false,\n})\nexport class ToolbarComponent {\n  @Output() openMenu = new EventEmitter<void>();\n}\n"
  },
  {
    "path": "projects/example-app/src/app/core/containers/app.component.ts",
    "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { Observable } from 'rxjs';\n\nimport { AuthActions } from '@example-app/auth/actions/auth.actions';\nimport * as fromAuth from '@example-app/auth/reducers';\nimport * as fromRoot from '@example-app/reducers';\nimport { LayoutActions } from '@example-app/core/actions/layout.actions';\n\n@Component({\n  selector: 'bc-app',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\n    <bc-layout>\n      <bc-sidenav [open]=\"(showSidenav$ | async)!\" (closeMenu)=\"closeSidenav()\">\n        <bc-nav-item\n          (navigate)=\"closeSidenav()\"\n          *ngIf=\"loggedIn$ | async\"\n          routerLink=\"/\"\n          icon=\"book\"\n          hint=\"View your book collection\"\n        >\n          My Collection\n        </bc-nav-item>\n        <bc-nav-item\n          (navigate)=\"closeSidenav()\"\n          *ngIf=\"loggedIn$ | async\"\n          routerLink=\"/books/find\"\n          icon=\"search\"\n          hint=\"Find your next book!\"\n        >\n          Browse Books\n        </bc-nav-item>\n        <bc-nav-item\n          (navigate)=\"closeSidenav()\"\n          *ngIf=\"(loggedIn$ | async) === false\"\n        >\n          Sign In\n        </bc-nav-item>\n        <bc-nav-item (navigate)=\"logout()\" *ngIf=\"loggedIn$ | async\">\n          Sign Out\n        </bc-nav-item>\n      </bc-sidenav>\n      <bc-toolbar (openMenu)=\"openSidenav()\"> Book Collection </bc-toolbar>\n\n      <router-outlet></router-outlet>\n    </bc-layout>\n  `,\n  standalone: false,\n})\nexport class AppComponent {\n  showSidenav$: Observable<boolean>;\n  loggedIn$: Observable<boolean>;\n\n  constructor(private store: Store) {\n    /**\n     * Selectors can be applied with the `select` operator which passes the state\n     * tree to the provided selector\n     */\n    this.showSidenav$ = this.store.select(fromRoot.selectShowSidenav);\n    this.loggedIn$ = this.store.select(fromAuth.selectLoggedIn);\n  }\n\n  closeSidenav() {\n    /**\n     * All state updates are handled through dispatched actions in 'container'\n     * components. This provides a clear, reproducible history of state\n     * updates and user interaction through the life of our\n     * application.\n     */\n    this.store.dispatch(LayoutActions.closeSidenav());\n  }\n\n  openSidenav() {\n    this.store.dispatch(LayoutActions.openSidenav());\n  }\n\n  logout() {\n    this.store.dispatch(AuthActions.logoutConfirmation());\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/core/containers/index.ts",
    "content": "export * from './app.component';\nexport * from './not-found-page.component';\n"
  },
  {
    "path": "projects/example-app/src/app/core/containers/not-found-page.component.ts",
    "content": "import { Component, ChangeDetectionStrategy } from '@angular/core';\n\n@Component({\n  selector: 'bc-not-found-page',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\n    <mat-card>\n      <mat-card-title>404: Not Found</mat-card-title>\n      <mat-card-content>\n        <p>Hey! It looks like this page doesn't exist yet.</p>\n      </mat-card-content>\n      <mat-card-actions>\n        <button mat-raised-button color=\"primary\" routerLink=\"/\">\n          Take Me Home\n        </button>\n      </mat-card-actions>\n    </mat-card>\n  `,\n  styles: [\n    `\n      :host {\n        text-align: center;\n      }\n\n      mat-card-title,\n      mat-card-content {\n        margin-top: 1rem;\n      }\n\n      mat-card-actions {\n        justify-content: center;\n        margin-top: 1rem;\n      }\n    `,\n  ],\n  standalone: false,\n})\nexport class NotFoundPageComponent {}\n"
  },
  {
    "path": "projects/example-app/src/app/core/core.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { RouterModule } from '@angular/router';\n\nimport { MaterialModule } from '@example-app/material';\nimport {\n  LayoutComponent,\n  NavItemComponent,\n  SidenavComponent,\n  ToolbarComponent,\n} from '@example-app/core/components';\nimport {\n  AppComponent,\n  NotFoundPageComponent,\n} from '@example-app/core/containers';\n\nexport const COMPONENTS = [\n  AppComponent,\n  NotFoundPageComponent,\n  LayoutComponent,\n  NavItemComponent,\n  SidenavComponent,\n  ToolbarComponent,\n];\n\n@NgModule({\n  imports: [CommonModule, RouterModule, MaterialModule],\n  declarations: COMPONENTS,\n  exports: COMPONENTS,\n})\nexport class CoreModule {}\n"
  },
  {
    "path": "projects/example-app/src/app/core/effects/index.ts",
    "content": "export * from './user.effects';\nexport * from './router.effects';\n"
  },
  {
    "path": "projects/example-app/src/app/core/effects/router.effects.spec.ts",
    "content": "import { Title } from '@angular/platform-browser';\nimport { TestBed } from '@angular/core/testing';\n\nimport { of } from 'rxjs';\n\nimport { Actions } from '@ngrx/effects';\nimport { routerNavigatedAction } from '@ngrx/router-store';\nimport { provideMockStore } from '@ngrx/store/testing';\n\nimport { RouterEffects } from '@example-app/core/effects';\nimport * as fromRoot from '@example-app/reducers';\n\ndescribe('RouterEffects', () => {\n  let effects: RouterEffects;\n  let titleService: Title;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        RouterEffects,\n        {\n          provide: Actions,\n          useValue: of(routerNavigatedAction),\n        },\n        provideMockStore({\n          selectors: [\n            { selector: fromRoot.selectRouteData, value: { title: 'Search' } },\n          ],\n        }),\n        { provide: Title, useValue: { setTitle: vi.fn() } },\n      ],\n    });\n\n    effects = TestBed.inject(RouterEffects);\n    titleService = TestBed.inject(Title);\n  });\n\n  describe('updateTitle$', () => {\n    it('should update the title on router navigation', () => {\n      effects.updateTitle$.subscribe();\n      expect(titleService.setTitle).toHaveBeenCalledWith(\n        'Book Collection - Search'\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/core/effects/router.effects.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\n\nimport { map, tap } from 'rxjs/operators';\n\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { concatLatestFrom } from '@ngrx/operators';\nimport { Store } from '@ngrx/store';\nimport { routerNavigatedAction } from '@ngrx/router-store';\n\nimport * as fromRoot from '@example-app/reducers';\n\n@Injectable()\nexport class RouterEffects {\n  updateTitle$ = createEffect(\n    () =>\n      this.actions$.pipe(\n        ofType(routerNavigatedAction),\n        concatLatestFrom(() => this.store.select(fromRoot.selectRouteData)),\n        map(([, data]) => `Book Collection - ${data['title']}`),\n        tap((title) => this.titleService.setTitle(title))\n      ),\n    {\n      dispatch: false,\n    }\n  );\n\n  constructor(\n    private actions$: Actions,\n    private store: Store,\n    private titleService: Title\n  ) {}\n}\n"
  },
  {
    "path": "projects/example-app/src/app/core/effects/user.effects.spec.ts",
    "content": "import { Action } from '@ngrx/store';\nimport { TestBed } from '@angular/core/testing';\n\nimport { UserEffects } from '@example-app/core/effects';\nimport { UserActions } from '@example-app/core/actions/user.actions';\n\ndescribe('UserEffects', () => {\n  let effects: UserEffects;\n  const eventsMap: { [key: string]: any } = {};\n\n  beforeAll(() => {\n    document.addEventListener = vi.fn((event, cb) => {\n      eventsMap[event] = cb;\n    });\n  });\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [UserEffects],\n    });\n\n    effects = TestBed.inject(UserEffects);\n\n    vi.useFakeTimers();\n  });\n\n  afterEach(() => {\n    vi.useRealTimers();\n  });\n\n  describe('idle$', () => {\n    it('should trigger idleTimeout action after 5 minutes', () => {\n      let action: Action | undefined;\n      effects.idle$.subscribe((res) => (action = res));\n\n      // Initial action to trigger the effect\n      eventsMap['click']();\n\n      vi.advanceTimersByTime(2 * 60 * 1000);\n      expect(action).toBeUndefined();\n\n      vi.advanceTimersByTime(3 * 60 * 1000);\n      expect(action).toBeDefined();\n      expect(action?.type).toBe(UserActions.idleTimeout.type);\n    });\n\n    it('should reset timeout on user activity', () => {\n      let action: Action | undefined;\n      effects.idle$.subscribe((res) => (action = res));\n\n      // Initial action to trigger the effect\n      eventsMap['keydown']();\n\n      vi.advanceTimersByTime(4 * 60 * 1000);\n      eventsMap['mousemove']();\n\n      vi.advanceTimersByTime(4 * 60 * 1000);\n      expect(action).toBeUndefined();\n\n      vi.advanceTimersByTime(1 * 60 * 1000);\n      expect(action).toBeDefined();\n      expect(action?.type).toBe(UserActions.idleTimeout.type);\n    });\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/core/effects/user.effects.ts",
    "content": "import { Injectable } from '@angular/core';\n\nimport { fromEvent, merge, timer } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\n\nimport { createEffect } from '@ngrx/effects';\nimport { UserActions } from '@example-app/core/actions/user.actions';\n\n@Injectable()\nexport class UserEffects {\n  clicks$ = fromEvent(document, 'click');\n  keys$ = fromEvent(document, 'keydown');\n  mouse$ = fromEvent(document, 'mousemove');\n\n  idle$ = createEffect(() =>\n    merge(this.clicks$, this.keys$, this.mouse$).pipe(\n      // 5 minute inactivity timeout\n      switchMap(() => timer(5 * 60 * 1000)),\n      map(() => UserActions.idleTimeout())\n    )\n  );\n}\n"
  },
  {
    "path": "projects/example-app/src/app/core/index.ts",
    "content": "export * from './core.module';\n"
  },
  {
    "path": "projects/example-app/src/app/core/reducers/layout.reducer.ts",
    "content": "import { createReducer, on } from '@ngrx/store';\n\nimport { LayoutActions } from '@example-app/core/actions/layout.actions';\nimport { AuthActions } from '@example-app/auth/actions/auth.actions';\n\nexport const layoutFeatureKey = 'layout';\n\nexport interface State {\n  showSidenav: boolean;\n}\n\nconst initialState: State = {\n  showSidenav: false,\n};\n\nexport const reducer = createReducer(\n  initialState,\n  on(LayoutActions.closeSidenav, () => ({ showSidenav: false })),\n  on(LayoutActions.openSidenav, () => ({ showSidenav: true })),\n  on(AuthActions.logoutConfirmation, () => ({ showSidenav: false }))\n);\n\nexport const selectShowSidenav = (state: State) => state.showSidenav;\n"
  },
  {
    "path": "projects/example-app/src/app/core/services/book-storage.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\n\nimport { cold } from 'jasmine-marbles';\n\nimport { Book } from '@example-app/books/models';\nimport {\n  BookStorageService,\n  LOCAL_STORAGE_TOKEN,\n} from '@example-app/core/services/book-storage.service';\n\ndescribe('BookStorageService', () => {\n  let fixture: any;\n\n  const localStorageFake: Storage & any = {\n    removeItem: vi.fn(),\n    setItem: vi.fn(),\n    getItem: vi.fn((_) => JSON.stringify(persistedCollection)),\n  };\n\n  const book1 = { id: '111', volumeInfo: {} } as Book;\n  const book2 = { id: '222', volumeInfo: {} } as Book;\n  const book3 = { id: '333', volumeInfo: {} } as Book;\n  const book4 = { id: '444', volumeInfo: {} } as Book;\n\n  const persistedStorageKey = 'books-app';\n  const persistedCollection = [book2, book4];\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        {\n          provide: LOCAL_STORAGE_TOKEN,\n          useValue: localStorageFake,\n        },\n      ],\n    });\n    fixture = TestBed.inject(BookStorageService);\n  });\n\n  describe('supported', () => {\n    it('should have truthy value if localStorage provider set', () => {\n      const expected = cold('(-a|)', { a: true });\n      expect(fixture.supported()).toBeObservable(expected);\n    });\n\n    it('should throw error if localStorage provider  not available', () => {\n      TestBed.resetTestingModule().configureTestingModule({\n        providers: [\n          {\n            provide: LOCAL_STORAGE_TOKEN,\n            useValue: null,\n          },\n        ],\n      });\n\n      fixture = TestBed.inject(BookStorageService);\n      const expected = cold('#', {}, 'Local Storage Not Supported');\n      expect(fixture.supported()).toBeObservable(expected);\n    });\n  });\n\n  describe('getCollection', () => {\n    it('should call get collection', () => {\n      const expected = cold('(-a|)', { a: persistedCollection });\n      expect(fixture.getCollection()).toBeObservable(expected);\n      expect(localStorageFake.getItem).toHaveBeenCalledWith(\n        persistedStorageKey\n      );\n      localStorageFake.getItem.mockClear();\n    });\n  });\n\n  describe('addToCollection', () => {\n    it('should add single item', () => {\n      const result = [...persistedCollection, book1];\n      const expected = cold('(-a|)', { a: result });\n      expect(fixture.addToCollection([book1])).toBeObservable(expected);\n      expect(localStorageFake.setItem).toHaveBeenCalledWith(\n        persistedStorageKey,\n        JSON.stringify(result)\n      );\n\n      localStorageFake.setItem.mockClear();\n    });\n\n    it('should add multiple items', () => {\n      const result = [...persistedCollection, book1, book3];\n      const expected = cold('(-a|)', { a: result });\n      expect(fixture.addToCollection([book1, book3])).toBeObservable(expected);\n      expect(localStorageFake.setItem).toHaveBeenCalledWith(\n        persistedStorageKey,\n        JSON.stringify(result)\n      );\n      localStorageFake.setItem.mockClear();\n    });\n  });\n\n  describe('removeFromCollection', () => {\n    it('should remove item from collection', () => {\n      const filterCollection = persistedCollection.filter(\n        (f) => f.id !== book2.id\n      );\n      const expected = cold('(-a|)', { a: filterCollection });\n      expect(fixture.removeFromCollection([book2.id])).toBeObservable(expected);\n      expect(localStorageFake.getItem).toHaveBeenCalledWith(\n        persistedStorageKey\n      );\n      expect(localStorageFake.setItem).toHaveBeenCalledWith(\n        persistedStorageKey,\n        JSON.stringify(filterCollection)\n      );\n      localStorageFake.getItem.mockClear();\n    });\n\n    it('should remove multiple items from collection', () => {\n      const filterCollection = persistedCollection.filter(\n        (f) => f.id !== book4.id\n      );\n      const expected = cold('(-a|)', { a: filterCollection });\n      expect(fixture.removeFromCollection([book4.id])).toBeObservable(expected);\n      expect(localStorageFake.getItem).toHaveBeenCalledWith(\n        persistedStorageKey\n      );\n      expect(localStorageFake.setItem).toHaveBeenCalledWith(\n        persistedStorageKey,\n        JSON.stringify(filterCollection)\n      );\n      localStorageFake.getItem.mockClear();\n    });\n\n    it('should ignore items not present in collection', () => {\n      const filterCollection = persistedCollection;\n      const expected = cold('(-a|)', { a: filterCollection });\n      expect(fixture.removeFromCollection([book1.id])).toBeObservable(expected);\n    });\n  });\n\n  describe('deleteCollection', () => {\n    it('should delete storage key and collection', () => {\n      const expected = cold('(-a|)', { a: true });\n      expect(fixture.deleteCollection()).toBeObservable(expected);\n      expect(localStorageFake.removeItem).toHaveBeenCalledWith(\n        persistedStorageKey\n      );\n      localStorageFake.removeItem.mockClear();\n    });\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/core/services/book-storage.service.ts",
    "content": "import { Inject, Injectable, InjectionToken } from '@angular/core';\n\nimport { Observable, of, throwError } from 'rxjs';\nimport { map, tap } from 'rxjs/operators';\n\nimport { Book } from '@example-app/books/models';\n\nexport function storageFactory() {\n  return typeof window === 'undefined' || typeof localStorage === 'undefined'\n    ? null\n    : localStorage;\n}\n\nexport const LOCAL_STORAGE_TOKEN = new InjectionToken(\n  'example-app-local-storage',\n  { factory: storageFactory }\n);\n\n@Injectable({ providedIn: 'root' })\nexport class BookStorageService {\n  private collectionKey = 'books-app';\n\n  supported(): Observable<boolean> {\n    return this.storage !== null\n      ? of(true)\n      : throwError(() => 'Local Storage Not Supported');\n  }\n\n  getCollection(): Observable<Book[]> {\n    return this.supported().pipe(\n      map((_) => this.storage.getItem(this.collectionKey)),\n      map((value: string | null) => (value ? JSON.parse(value) : []))\n    );\n  }\n\n  addToCollection(records: Book[]): Observable<Book[]> {\n    return this.getCollection().pipe(\n      map((value: Book[]) => [...value, ...records]),\n      tap((value: Book[]) =>\n        this.storage.setItem(this.collectionKey, JSON.stringify(value))\n      )\n    );\n  }\n\n  removeFromCollection(ids: Array<string>): Observable<Book[]> {\n    return this.getCollection().pipe(\n      map((value: Book[]) => value.filter((item) => !ids.includes(item.id))),\n      tap((value: Book[]) =>\n        this.storage.setItem(this.collectionKey, JSON.stringify(value))\n      )\n    );\n  }\n\n  deleteCollection(): Observable<boolean> {\n    return this.supported().pipe(\n      tap(() => this.storage.removeItem(this.collectionKey))\n    );\n  }\n\n  constructor(@Inject(LOCAL_STORAGE_TOKEN) private storage: Storage) {}\n}\n"
  },
  {
    "path": "projects/example-app/src/app/core/services/google-books.service.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { HttpClient } from '@angular/common/http';\n\nimport { cold } from 'jasmine-marbles';\n\nimport { GoogleBooksService } from './google-books.service';\n\ndescribe('Service: GoogleBooks', () => {\n  let service: GoogleBooksService;\n  let http: HttpClient;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [{ provide: HttpClient, useValue: { get: vi.fn() } }],\n    });\n\n    service = TestBed.inject(GoogleBooksService);\n    http = TestBed.inject(HttpClient);\n  });\n\n  const data = {\n    title: 'Book Title',\n    author: 'John Smith',\n    volumeId: '12345',\n  };\n\n  const books = {\n    items: [\n      { id: '12345', volumeInfo: { title: 'Title' } },\n      { id: '67890', volumeInfo: { title: 'Another Title' } },\n    ],\n  };\n\n  const queryTitle = 'Book Title';\n\n  it('should call the search api and return the search results', () => {\n    const response = cold('-a|', { a: books });\n    const expected = cold('-b|', { b: books.items });\n    http.get = vi.fn(() => response);\n\n    expect(service.searchBooks(queryTitle)).toBeObservable(expected);\n    expect(http.get).toHaveBeenCalledWith(\n      `https://www.googleapis.com/books/v1/volumes?orderBy=newest&q=${queryTitle}`\n    );\n  });\n\n  it('should retrieve the book from the volumeId', () => {\n    const response = cold('-a|', { a: data });\n    const expected = cold('-b|', { b: data });\n    http.get = vi.fn(() => response);\n\n    expect(service.retrieveBook(data.volumeId)).toBeObservable(expected);\n    expect(http.get).toHaveBeenCalledWith(\n      `https://www.googleapis.com/books/v1/volumes/${data.volumeId}`\n    );\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/core/services/google-books.service.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\n\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { Book } from '@example-app/books/models';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class GoogleBooksService {\n  private API_PATH = 'https://www.googleapis.com/books/v1/volumes';\n\n  constructor(private http: HttpClient) {}\n\n  searchBooks(queryTitle: string): Observable<Book[]> {\n    return this.http\n      .get<{ items: Book[] }>(`${this.API_PATH}?orderBy=newest&q=${queryTitle}`)\n      .pipe(map((books) => books.items || []));\n  }\n\n  retrieveBook(volumeId: string): Observable<Book> {\n    return this.http.get<Book>(`${this.API_PATH}/${volumeId}`);\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/core/services/index.ts",
    "content": "export * from './book-storage.service';\nexport * from './google-books.service';\n"
  },
  {
    "path": "projects/example-app/src/app/material/index.ts",
    "content": "export * from '@example-app/material/material.module';\n"
  },
  {
    "path": "projects/example-app/src/app/material/material.module.ts",
    "content": "import { NgModule } from '@angular/core';\n\nimport { MatInputModule } from '@angular/material/input';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatListModule } from '@angular/material/list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  imports: [\n    MatInputModule,\n    MatCardModule,\n    MatButtonModule,\n    MatSidenavModule,\n    MatListModule,\n    MatIconModule,\n    MatToolbarModule,\n    MatProgressSpinnerModule,\n    MatDialogModule,\n  ],\n  exports: [\n    MatInputModule,\n    MatCardModule,\n    MatButtonModule,\n    MatSidenavModule,\n    MatListModule,\n    MatIconModule,\n    MatToolbarModule,\n    MatProgressSpinnerModule,\n    MatDialogModule,\n  ],\n})\nexport class MaterialModule {}\n"
  },
  {
    "path": "projects/example-app/src/app/reducers/index.ts",
    "content": "import {\n  createSelector,\n  createFeatureSelector,\n  ActionReducer,\n  MetaReducer,\n  ActionReducerMap,\n} from '@ngrx/store';\nimport {\n  getRouterSelectors,\n  routerReducer,\n  RouterReducerState,\n} from '@ngrx/router-store';\n\n/**\n * Every reducer module's default export is the reducer function itself. In\n * addition, each module should export a type or interface that describes\n * the state of the reducer plus any selector functions. The `* as`\n * notation packages up all of the exports into a single object.\n */\n\nimport * as fromLayout from '@example-app/core/reducers/layout.reducer';\nimport { isDevMode } from '@angular/core';\n\n/**\n * As mentioned, we treat each reducer like a table in a database. This means\n * our top level state interface is just a map of keys to inner state types.\n */\nexport interface State {\n  [fromLayout.layoutFeatureKey]: fromLayout.State;\n  router: RouterReducerState;\n}\n\n/**\n * Our state is composed of a map of action reducer functions.\n * These reducer functions are called with each dispatched action\n * and the current or initial state and return a new immutable state.\n */\nexport const rootReducers: ActionReducerMap<State> = {\n  [fromLayout.layoutFeatureKey]: fromLayout.reducer,\n  router: routerReducer,\n};\n\n// console.log all actions\nexport function logger(reducer: ActionReducer<State>): ActionReducer<State> {\n  return (state, action) => {\n    const result = reducer(state, action);\n    console.groupCollapsed(action.type);\n    console.log('prev state', state);\n    console.log('action', action);\n    console.log('next state', result);\n    console.groupEnd();\n\n    return result;\n  };\n}\n\n/**\n * By default, @ngrx/store uses combineReducers with the reducer map to compose\n * the root meta-reducer. To add more meta-reducers, provide an array of meta-reducers\n * that will be composed to form the root meta-reducer.\n */\nexport const metaReducers: MetaReducer<State>[] = isDevMode() ? [logger] : [];\n\n/**\n * Layout Selectors\n */\nexport const selectLayoutState = createFeatureSelector<fromLayout.State>(\n  fromLayout.layoutFeatureKey\n);\n\nexport const selectShowSidenav = createSelector(\n  selectLayoutState,\n  fromLayout.selectShowSidenav\n);\n\n/**\n * Router Selectors\n */\nexport const { selectRouteData } = getRouterSelectors();\n"
  },
  {
    "path": "projects/example-app/src/app/shared/pipes/add-commas.pipe.spec.ts",
    "content": "import { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';\n\ndescribe('Pipe: Add Commas', () => {\n  let pipe: AddCommasPipe;\n\n  beforeEach(() => {\n    pipe = new AddCommasPipe();\n  });\n\n  it('should transform [\"Rick\"] to \"Rick\"', () => {\n    expect(pipe.transform(['Rick'])).toEqual('Rick');\n  });\n\n  it('should transform [\"Jeremy\", \"Andrew\"] to \"Jeremy and Andrew\"', () => {\n    expect(pipe.transform(['Jeremy', 'Andrew'])).toEqual('Jeremy and Andrew');\n  });\n\n  it('should transform [\"Kim\", \"Ryan\", \"Amanda\"] to \"Kim, Ryan, and Amanda\"', () => {\n    expect(pipe.transform(['Kim', 'Ryan', 'Amanda'])).toEqual(\n      'Kim, Ryan, and Amanda'\n    );\n  });\n\n  it('transforms undefined to \"Author Unknown\"', () => {\n    expect(pipe.transform(null)).toEqual('Author Unknown');\n  });\n\n  it('transforms [] to \"Author Unknown\"', () => {\n    expect(pipe.transform([])).toEqual('Author Unknown');\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/shared/pipes/add-commas.pipe.ts",
    "content": "import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'bcAddCommas', standalone: false })\nexport class AddCommasPipe implements PipeTransform {\n  transform(authors: null | string[]) {\n    if (!authors) {\n      return 'Author Unknown';\n    }\n\n    switch (authors.length) {\n      case 0:\n        return 'Author Unknown';\n      case 1:\n        return authors[0];\n      case 2:\n        return authors.join(' and ');\n      default:\n        const last = authors[authors.length - 1];\n        const remaining = authors.slice(0, -1);\n        return `${remaining.join(', ')}, and ${last}`;\n    }\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/shared/pipes/ellipsis.pipe.spec.ts",
    "content": "import { EllipsisPipe } from '@example-app/shared/pipes/ellipsis.pipe';\n\ndescribe('Pipe: Ellipsis', () => {\n  let pipe: EllipsisPipe;\n  const longStr = `Lorem ipsum dolor sit amet,\n  consectetur adipisicing elit. Quibusdam ab similique, odio sit\n  harum laborum rem, nesciunt atque iure a pariatur nam nihil dolore necessitatibus quos ea autem accusantium dolor\n  voluptates voluptatibus. Doloribus libero, facilis ea nam\n  quibusdam aut labore itaque aliquid, optio. Rerum, dolorum!\n  Error ratione tempore nesciunt magnam reprehenderit earum\n  tempora aliquam laborum consectetur repellendus, nam hic\n  maiores, qui corrupti saepe possimus, velit impedit eveniet\n  totam. Aliquid qui corrupti facere. Alias itaque pariatur\n  aliquam, nemo praesentium. Iure delectus, nemo natus! Libero\n  ducimus aspernatur laborum voluptatibus officiis eaque enim\n  minus accusamus, harum facilis sed eum! Sit vero vitae\n  voluptatibus deleniti, corporis deserunt? Optio reprehenderit\n  quae nesciunt minus at, sint fuga impedit, laborum praesentium\n  illo nisi natus quia illum obcaecati id error suscipit eaque!\n  Sed quam, ab dolorum qui sit dolorem fuga laudantium est,\n  voluptas sequi consequuntur dolores animi veritatis doloremque\n  at placeat maxime suscipit provident? Mollitia deserunt\n  repudiandae illo. Similique voluptatem repudiandae possimus\n  veritatis amet incidunt alias, debitis eveniet voluptate\n  magnam consequatur eum molestiae provident est dicta. A autem\n  praesentium voluptas, quis itaque doloremque quidem debitis?\n  Ex qui, corporis voluptatibus assumenda necessitatibus\n  accusamus earum rem cum quidem quasi! Porro assumenda, modi.\n  Voluptatibus enim dignissimos fugit voluptas hic ducimus ullam,\n  minus. Soluta architecto ratione, accusamus vitae eligendi\n  explicabo beatae reprehenderit. Officiis voluptatibus\n  dignissimos cum magni! Deleniti fuga reiciendis, ab dicta\n  quasi impedit voluptatibus earum ratione inventore cum\n  voluptas eligendi vel ut tenetur numquam, alias praesentium\n  iusto asperiores, ipsa. Odit a ea, quaerat culpa dolore\n  veritatis mollitia veniam quidem, velit, natus sint at.`;\n\n  beforeEach(() => {\n    pipe = new EllipsisPipe();\n  });\n\n  it(\"should return the string if it's length is less than 250\", () => {\n    expect(pipe.transform('string')).toEqual('string');\n  });\n\n  it('should return up to 250 characters followed by an ellipsis', () => {\n    expect(pipe.transform(longStr)).toEqual(`${longStr.substring(0, 250)}...`);\n  });\n\n  it('should return only 20 characters followed by an ellipsis', () => {\n    expect(pipe.transform(longStr, 20)).toEqual(\n      `${longStr.substring(0, 20)}...`\n    );\n  });\n});\n"
  },
  {
    "path": "projects/example-app/src/app/shared/pipes/ellipsis.pipe.ts",
    "content": "import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'bcEllipsis', standalone: false })\nexport class EllipsisPipe implements PipeTransform {\n  transform(str: string, strLength = 250) {\n    const withoutHtml = str.replace(/(<([^>]+)>)/gi, '');\n\n    if (str.length >= strLength) {\n      return `${withoutHtml.slice(0, strLength)}...`;\n    }\n\n    return withoutHtml;\n  }\n}\n"
  },
  {
    "path": "projects/example-app/src/app/shared/pipes/index.ts",
    "content": "import { NgModule } from '@angular/core';\n\nimport { AddCommasPipe } from '@example-app/shared/pipes/add-commas.pipe';\nimport { EllipsisPipe } from '@example-app/shared/pipes/ellipsis.pipe';\n\nexport const PIPES = [AddCommasPipe, EllipsisPipe];\n\n@NgModule({\n  declarations: PIPES,\n  exports: PIPES,\n})\nexport class PipesModule {}\n"
  },
  {
    "path": "projects/example-app/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "projects/example-app/src/assets/.npmignore",
    "content": ""
  },
  {
    "path": "projects/example-app/src/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Book Collection</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\">\n    <link href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\" rel=\"stylesheet\">\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <bc-app>Loading...</bc-app>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/example-app/src/main.ts",
    "content": "import './polyfills';\n\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { AppModule } from './app/app.module';\n\nplatformBrowserDynamic().bootstrapModule(AppModule);\n"
  },
  {
    "path": "projects/example-app/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js'; // Included with Angular CLI.\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "projects/example-app/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import '@angular/material/prebuilt-themes/deeppurple-amber.css';\n\n* {\n  box-sizing: border-box;\n}\n\nhtml {\n  -webkit-font-smoothing: antialiased;\n  -ms-overflow-style: none;\n  overflow: auto;\n}\n\n.mat-mdc-progress-spinner svg {\n  width: 30px !important;\n  height: 30px !important;\n}\n"
  },
  {
    "path": "projects/example-app/src/test-setup.ts",
    "content": "import '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-snapshots';\nimport { setupTestBed } from '@analogjs/vitest-angular/setup-testbed';\n\nsetupTestBed();\n"
  },
  {
    "path": "projects/example-app/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"ES2022\",\n    \"types\": []\n  },\n  \"files\": [\"src/main.ts\", \"src/polyfills.ts\"],\n  \"include\": [\"src/**/*.d.ts\"],\n  \"exclude\": [\"**/*.spec.ts\", \"**/*.test.ts\"],\n  \"angularCompilerOptions\": {\n    \"strictTemplates\": true,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true\n  }\n}\n"
  },
  {
    "path": "projects/example-app/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"es2022\",\n    \"types\": [\"node\", \"vitest/globals\"],\n    \"strict\": false,\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"src/test-setup.ts\"],\n  \"include\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "projects/example-app/vite.config.mts",
    "content": "/// <reference types=\"vitest\" />\n\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\n\nimport { defineConfig } from 'vite';\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    plugins: [angular(), nxViteTsPaths()],\n    test: {\n      globals: true,\n      environment: 'jsdom',\n      setupFiles: ['src/test-setup.ts'],\n      include: ['**/*.spec.ts'],\n      reporters: ['default'],\n    },\n    define: {\n      'import.meta.vitest': mode !== 'production',\n    },\n  };\n});\n"
  },
  {
    "path": "projects/example-app-e2e/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\nimport { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';\n\nconst cypressJsonConfig = {\n  fileServerFolder: '.',\n  fixturesFolder: './src/fixtures',\n  video: true,\n  videosFolder: '../../dist/cypress/projects/example-app-e2e/videos',\n  screenshotsFolder: '../../dist/cypress/projects/example-app-e2e/screenshots',\n  chromeWebSecurity: false,\n  specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',\n  supportFile: 'src/support/e2e.ts',\n};\nexport default defineConfig({\n  e2e: {\n    ...nxE2EPreset(__dirname),\n    ...cypressJsonConfig,\n    testIsolation: false,\n    // Please ensure you use `cy.origin()` when navigating between domains and remove this option.\n    // See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin\n    injectDocumentDomain: true,\n  },\n});\n"
  },
  {
    "path": "projects/example-app-e2e/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist', '**/environment.prod.ts'],\n  },\n  ...baseConfig,\n\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': [\n          'error',\n          {\n            type: 'attribute',\n            prefix: 'bc',\n            style: 'camelCase',\n          },\n        ],\n        '@angular-eslint/component-selector': [\n          'error',\n          {\n            type: 'element',\n            prefix: 'bc',\n            style: 'kebab-case',\n          },\n        ],\n        '@typescript-eslint/prefer-namespace-keyword': 'error',\n        '@nx/enforce-module-boundaries': 'off',\n        eqeqeq: ['off', 'smart'],\n        'id-blacklist': [\n          'error',\n          'any',\n          'Number',\n          'number',\n          'String',\n          'string',\n          'Boolean',\n          'boolean',\n          'Undefined',\n          'undefined',\n        ],\n        'id-match': 'error',\n        'no-eval': 'off',\n        'no-redeclare': 'error',\n        'no-underscore-dangle': 'error',\n        'no-var': 'error',\n        'no-case-declarations': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['projects/example-app-e2e/tsconfig.*.json'],\n        },\n      },\n    })),\n\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n  {\n    files: ['src/**/*.cy.{ts,tsx}'],\n    languageOptions: {\n      parserOptions: {\n        project: ['projects/example-app-e2e/tsconfig.*.json'],\n      },\n      globals: {\n        cy: true,\n        describe: true,\n        it: true,\n        before: true,\n        beforeEach: true,\n        after: true,\n        afterEach: true,\n      },\n    },\n    rules: {\n      // Add optional Cypress-safe tweaks\n      'no-unused-expressions': 'off', // Allow Chai-style assertions\n      '@typescript-eslint/no-floating-promises': 'off', // Often triggered by cy.* commands\n    },\n  },\n];\n"
  },
  {
    "path": "projects/example-app-e2e/project.json",
    "content": "{\n  \"name\": \"example-app-e2e\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"sourceRoot\": \"projects/example-app-e2e/src\",\n  \"projectType\": \"application\",\n  \"tags\": [],\n  \"implicitDependencies\": [\"example-app\"],\n  \"targets\": {\n    \"e2e\": {\n      \"executor\": \"@nx/cypress:cypress\",\n      \"options\": {\n        \"cypressConfig\": \"projects/example-app-e2e/cypress.config.ts\",\n        \"testingType\": \"e2e\"\n      },\n      \"configurations\": {\n        \"production\": {\n          \"devServerTarget\": \"example-app:serve:production\"\n        },\n        \"development\": {\n          \"devServerTarget\": \"example-app:serve:development\"\n        }\n      },\n      \"defaultConfiguration\": \"production\"\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\"projects/example-app-e2e/**/*.{js,ts}\"]\n      },\n      \"outputs\": [\"{options.outputFile}\"]\n    }\n  }\n}\n"
  },
  {
    "path": "projects/example-app-e2e/src/integration/round-trip.cy.ts",
    "content": "context('Full round trip', () => {\n  before(() => {\n    window.localStorage.removeItem('books_app');\n    cy.visit('/');\n  });\n\n  beforeEach(() => {\n    cy.restoreLocalStorage();\n  });\n\n  afterEach(() => {\n    cy.saveLocalStorage();\n  });\n\n  it('shows a message when the credentials are wrong', () => {\n    cy.findByPlaceholderText(/username/i)\n      .clear()\n      .type('wronguser');\n    cy.findByPlaceholderText(/password/i).type('supersafepassword');\n    cy.findByRole('button', { name: /login/i }).click();\n    cy.contains('Invalid username or password').should('be.visible');\n  });\n\n  it('is possible to login', () => {\n    cy.findByPlaceholderText(/username/i)\n      .clear()\n      .type('test{enter}');\n  });\n\n  it('is possible to search for books', () => {\n    cy.contains('My Collection');\n    cy.findByRole('button', { name: /menu/i }).click();\n    cy.findByText(/browse books/i).click();\n    cy.findByPlaceholderText(/search for a book/i).type('The Alchemist');\n    cy.get('bc-book-preview').its('length').should('be.gte', 1);\n  });\n\n  it('is possible to add books', () => {\n    cy.get('bc-book-preview').eq(0).click();\n\n    cy.findByRole('button', { name: /add book to collection/i }).click();\n    cy.findByRole('button', { name: /add book to collection/i }).should(\n      'not.exist'\n    );\n  });\n\n  it('is possible to remove books', () => {\n    cy.go('back');\n\n    cy.get('bc-book-preview').eq(4).click();\n\n    cy.findByRole('button', { name: /add book to collection/i }).click();\n    cy.findByRole('button', { name: /remove book from collection/i }).click();\n    cy.findByRole('button', { name: /remove book from collection/i }).should(\n      'not.exist'\n    );\n  });\n\n  it('is possible to show the collection', () => {\n    cy.findByRole('button', { name: /menu/i }).click();\n    cy.findByText(/my collection/i).click();\n    cy.get('bc-book-preview').its('length').should('eq', 1);\n  });\n\n  it('is possible to sign out', () => {\n    cy.findByRole('button', { name: /menu/i }).click();\n    cy.findByText(/sign out/i).click();\n    cy.findByRole('button', { name: /ok/i }).click();\n    cy.findByPlaceholderText(/username/i).should('exist');\n    cy.findByPlaceholderText(/password/i).should('exist');\n  });\n});\n"
  },
  {
    "path": "projects/example-app-e2e/src/support/commands.ts",
    "content": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n\nimport '@testing-library/cypress/add-commands';\n\nconst LOCAL_STORAGE_MEMORY: any = {};\n\nCypress.Commands.add('saveLocalStorage', () => {\n  Object.keys(localStorage).forEach((key) => {\n    LOCAL_STORAGE_MEMORY[key] = localStorage[key];\n  });\n});\n\nCypress.Commands.add('restoreLocalStorage', () => {\n  Object.keys(LOCAL_STORAGE_MEMORY).forEach((key) => {\n    localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);\n  });\n});\n"
  },
  {
    "path": "projects/example-app-e2e/src/support/e2e.ts",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport './commands';\n\ndeclare global {\n  // eslint-disable-next-line @typescript-eslint/no-namespace\n  namespace Cypress {\n    interface Chainable {\n      saveLocalStorage(): Chainable<void>;\n      restoreLocalStorage(): Chainable<void>;\n    }\n  }\n}\n"
  },
  {
    "path": "projects/example-app-e2e/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"sourceMap\": false,\n    \"types\": [\"cypress\", \"node\", \"@testing-library/cypress\"],\n    \"outDir\": \"../../dist/out-tsc\"\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.js\", \"cypress.config.ts\"],\n  \"files\": [],\n  \"references\": [],\n  \"exclude\": []\n}\n"
  },
  {
    "path": "projects/standalone-app/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': [\n          'error',\n          {\n            type: 'attribute',\n            prefix: 'ngrx',\n            style: 'camelCase',\n          },\n        ],\n        '@angular-eslint/component-selector': [\n          'error',\n          {\n            type: 'element',\n            prefix: 'ngrx',\n            style: 'kebab-case',\n          },\n        ],\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "projects/standalone-app/jest.config.ts",
    "content": "/* eslint-disable */\nexport default {\n  displayName: 'standalone-app',\n  preset: '../../jest.preset.js',\n  coverageDirectory: '../../coverage/projects/standalone-app',\n  setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],\n  transform: {\n    '^.+\\\\.(ts|mjs|js|html)$': [\n      'jest-preset-angular',\n      {\n        tsconfig: '<rootDir>/tsconfig.spec.json',\n        stringifyContentPathRegex: '\\\\.(html|svg)$',\n      },\n    ],\n  },\n  transformIgnorePatterns: ['node_modules/(?!.*\\\\.mjs$)'],\n  snapshotSerializers: [\n    'jest-preset-angular/build/serializers/no-ng-attributes',\n    'jest-preset-angular/build/serializers/ng-snapshot',\n    'jest-preset-angular/build/serializers/html-comment',\n  ],\n};\n"
  },
  {
    "path": "projects/standalone-app/project.json",
    "content": "{\n  \"name\": \"standalone-app\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"application\",\n  \"sourceRoot\": \"projects/standalone-app/src\",\n  \"prefix\": \"ngrx\",\n  \"tags\": [],\n  \"targets\": {\n    \"build\": {\n      \"executor\": \"@angular-devkit/build-angular:application\",\n      \"outputs\": [\"{options.outputPath}\"],\n      \"options\": {\n        \"outputPath\": \"dist/projects/standalone-app\",\n        \"index\": \"projects/standalone-app/src/index.html\",\n        \"browser\": \"projects/standalone-app/src/main.ts\",\n        \"polyfills\": [\"projects/standalone-app/src/polyfills.ts\"],\n        \"tsConfig\": \"projects/standalone-app/tsconfig.app.json\",\n        \"assets\": [\n          \"projects/standalone-app/src/favicon.ico\",\n          \"projects/standalone-app/src/assets\"\n        ],\n        \"styles\": [\"projects/standalone-app/src/styles.css\"],\n        \"scripts\": []\n      },\n      \"configurations\": {\n        \"production\": {\n          \"budgets\": [\n            {\n              \"type\": \"initial\",\n              \"maximumWarning\": \"500kb\",\n              \"maximumError\": \"1mb\"\n            },\n            {\n              \"type\": \"anyComponentStyle\",\n              \"maximumWarning\": \"2kb\",\n              \"maximumError\": \"4kb\"\n            }\n          ],\n          \"outputHashing\": \"all\"\n        },\n        \"development\": {\n          \"optimization\": false,\n          \"extractLicenses\": false,\n          \"sourceMap\": true,\n          \"namedChunks\": true\n        }\n      },\n      \"defaultConfiguration\": \"production\"\n    },\n    \"serve\": {\n      \"executor\": \"@angular-devkit/build-angular:dev-server\",\n      \"configurations\": {\n        \"production\": {\n          \"buildTarget\": \"standalone-app:build:production\"\n        },\n        \"development\": {\n          \"buildTarget\": \"standalone-app:build:development\"\n        }\n      },\n      \"defaultConfiguration\": \"development\",\n      \"continuous\": true\n    },\n    \"extract-i18n\": {\n      \"executor\": \"@angular-devkit/build-angular:extract-i18n\",\n      \"options\": {\n        \"buildTarget\": \"standalone-app:build\"\n      }\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\n          \"projects/standalone-app/**/*.ts\",\n          \"projects/standalone-app/**/*.html\"\n        ]\n      }\n    },\n    \"test\": {\n      \"executor\": \"@nx/jest:jest\",\n      \"outputs\": [\"{workspaceRoot}/coverage/projects/standalone-app\"],\n      \"options\": {\n        \"jestConfig\": \"projects/standalone-app/jest.config.ts\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/standalone-app/src/app/app.component.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { AppComponent } from './app.component';\nimport { RouterTestingModule } from '@angular/router/testing';\nimport { provideMockStore } from '@ngrx/store/testing';\n\ndescribe('AppComponent', () => {\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      imports: [RouterTestingModule, AppComponent],\n      providers: [provideMockStore()],\n    }).compileComponents();\n  });\n\n  it('should create the app', () => {\n    const fixture = TestBed.createComponent(AppComponent);\n    const app = fixture.componentInstance;\n    expect(app).toBeTruthy();\n  });\n\n  it(`should have as title 'ngrx-standalone-app'`, () => {\n    const fixture = TestBed.createComponent(AppComponent);\n    const app = fixture.componentInstance;\n    expect(app.title).toEqual('ngrx-standalone-app');\n  });\n\n  it('should render title', () => {\n    const fixture = TestBed.createComponent(AppComponent);\n    fixture.detectChanges();\n    const compiled = fixture.nativeElement as HTMLElement;\n    expect(compiled.querySelector('h1')?.textContent).toContain(\n      'Welcome ngrx-standalone-app'\n    );\n  });\n});\n"
  },
  {
    "path": "projects/standalone-app/src/app/app.component.ts",
    "content": "import { Component, inject, signal } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport {\n  ComponentStore,\n  INITIAL_STATE_TOKEN,\n  provideComponentStore,\n} from '@ngrx/component-store';\nimport { TestPipe } from './test.pipe';\n\n@Component({\n  selector: 'ngrx-root',\n  standalone: true,\n  imports: [RouterModule, TestPipe],\n  template: `\n    <h1>Welcome {{ title }} {{ val() }}</h1>\n\n    <a routerLink=\"/feature\">Load Feature</a>\n\n    {{ 3 | test }}\n\n    <router-outlet></router-outlet>\n  `,\n  providers: [\n    provideComponentStore(ComponentStore),\n    { provide: INITIAL_STATE_TOKEN, useValue: { test: true } },\n  ],\n})\nexport class AppComponent {\n  title = 'ngrx-standalone-app';\n  cs = inject(ComponentStore<{ test: number }>);\n  num = signal(1);\n  val = this.cs.selectSignal((s) => s.test);\n\n  ngOnInit() {\n    this.num.set(2);\n  }\n}\n"
  },
  {
    "path": "projects/standalone-app/src/app/app.config.ts",
    "content": "import { ApplicationConfig } from '@angular/core';\nimport { provideStore } from '@ngrx/store';\nimport { provideStoreDevtools } from '@ngrx/store-devtools';\nimport { provideRouterStore, routerReducer } from '@ngrx/router-store';\nimport {\n  provideRouter,\n  withEnabledBlockingInitialNavigation,\n} from '@angular/router';\nimport { isDevMode } from '@angular/core';\nimport { provideEffects } from '@ngrx/effects';\nimport { AppEffects } from './app.effects';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideStore({ router: routerReducer }),\n    provideRouter(\n      [\n        {\n          path: 'feature',\n          loadChildren: () =>\n            import('./lazy/feature.routes').then((m) => m.routes),\n        },\n      ],\n      withEnabledBlockingInitialNavigation()\n    ),\n    provideStoreDevtools({\n      maxAge: 25,\n      logOnly: !isDevMode(),\n      name: 'NgRx Standalone App',\n    }),\n    provideRouterStore(),\n    provideEffects(AppEffects),\n  ],\n};\n"
  },
  {
    "path": "projects/standalone-app/src/app/app.effects.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { Actions, createEffect } from '@ngrx/effects';\nimport { tap } from 'rxjs';\n\n@Injectable()\nexport class AppEffects {\n  logger$ = createEffect(\n    () => {\n      return this.actions$.pipe(tap((action) => console.log(action)));\n    },\n    { dispatch: false }\n  );\n\n  constructor(private actions$: Actions) {}\n}\n"
  },
  {
    "path": "projects/standalone-app/src/app/lazy/feature.component.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { Component, OnInit } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { FeatureActions, feature } from './feature.state';\n\n@Component({\n  selector: 'ngrx-feature',\n  standalone: true,\n  imports: [CommonModule],\n  template: `\n    <p>lazy component works!!</p>\n\n    Feature State: {{ feature$ | async | json }}\n  `,\n})\nexport class FeatureComponent implements OnInit {\n  feature$ = this.store.select(feature.selectFeatureState);\n\n  constructor(private store: Store) {}\n\n  ngOnInit() {\n    this.store.dispatch(FeatureActions.init());\n  }\n}\n"
  },
  {
    "path": "projects/standalone-app/src/app/lazy/feature.routes.ts",
    "content": "import { Routes } from '@angular/router';\nimport { provideState } from '@ngrx/store';\nimport { FeatureComponent } from './feature.component';\nimport { feature } from './feature.state';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    component: FeatureComponent,\n    providers: [provideState(feature)],\n  },\n];\n"
  },
  {
    "path": "projects/standalone-app/src/app/lazy/feature.state.ts",
    "content": "import {\n  createActionGroup,\n  createFeature,\n  createReducer,\n  emptyProps,\n} from '@ngrx/store';\n\nexport const FeatureActions = createActionGroup({\n  source: 'Feature Page',\n  events: {\n    init: emptyProps(),\n  },\n});\n\nexport interface FeatureState {\n  loaded: boolean;\n}\n\nexport const initialState: FeatureState = {\n  loaded: true,\n};\n\nexport const feature = createFeature({\n  name: 'feature',\n  reducer: createReducer(initialState),\n});\n"
  },
  {
    "path": "projects/standalone-app/src/app/test.pipe.ts",
    "content": "import { inject, Pipe, PipeTransform } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\n@Pipe({ name: 'test', standalone: true })\nexport class TestPipe implements PipeTransform {\n  store = inject(Store);\n  transform(s: number) {\n    this.store.select('count');\n    return s * 2;\n  }\n}\n"
  },
  {
    "path": "projects/standalone-app/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "projects/standalone-app/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>StandaloneApp</title>\n    <base href=\"/\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <ngrx-root></ngrx-root>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/standalone-app/src/main.ts",
    "content": "import { bootstrapApplication } from '@angular/platform-browser';\nimport { AppComponent } from './app/app.component';\nimport { appConfig } from './app/app.config';\n\nbootstrapApplication(AppComponent, appConfig);\n"
  },
  {
    "path": "projects/standalone-app/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js'; // Included with Angular CLI.\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "projects/standalone-app/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n"
  },
  {
    "path": "projects/standalone-app/src/test-setup.ts",
    "content": "import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';\n\nsetupZoneTestEnv();\n"
  },
  {
    "path": "projects/standalone-app/tsconfig.app.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"ES2022\",\n    \"types\": []\n  },\n  \"files\": [\"src/main.ts\", \"src/polyfills.ts\"],\n  \"include\": [\"src/**/*.d.ts\"],\n  \"exclude\": [\"**/*.test.ts\", \"**/*.spec.ts\", \"jest.config.ts\"]\n}\n"
  },
  {
    "path": "projects/standalone-app/tsconfig.editor.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"include\": [\"**/*.ts\"],\n  \"compilerOptions\": {\n    \"types\": []\n  }\n}\n"
  },
  {
    "path": "projects/standalone-app/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.app.json\"\n    },\n    {\n      \"path\": \"./tsconfig.spec.json\"\n    },\n    {\n      \"path\": \"./tsconfig.editor.json\"\n    }\n  ],\n  \"compilerOptions\": {\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"angularCompilerOptions\": {\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true\n  }\n}\n"
  },
  {
    "path": "projects/standalone-app/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"commonjs\",\n    \"types\": [\"jest\", \"node\"],\n    \"target\": \"es2016\"\n  },\n  \"files\": [\"src/test-setup.ts\"],\n  \"include\": [\"jest.config.ts\", \"**/*.test.ts\", \"**/*.spec.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "projects/standalone-app-e2e/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\nimport { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';\n\nconst cypressJsonConfig = {\n  fileServerFolder: '.',\n  fixturesFolder: './src/fixtures',\n  video: true,\n  videosFolder: '../../dist/cypress/projects/standalone-app-e2e/videos',\n  screenshotsFolder:\n    '../../dist/cypress/projects/standalone-app-e2e/screenshots',\n  chromeWebSecurity: false,\n  specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',\n  supportFile: 'src/support/e2e.ts',\n};\nexport default defineConfig({\n  e2e: {\n    ...nxE2EPreset(__dirname),\n    ...cypressJsonConfig,\n    testIsolation: false,\n    // Please ensure you use `cy.origin()` when navigating between domains and remove this option.\n    // See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin\n    injectDocumentDomain: true,\n  },\n});\n"
  },
  {
    "path": "projects/standalone-app-e2e/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist', '**/environment.prod.ts'],\n  },\n  ...baseConfig,\n\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': [\n          'error',\n          {\n            type: 'attribute',\n            prefix: 'bc',\n            style: 'camelCase',\n          },\n        ],\n        '@angular-eslint/component-selector': [\n          'error',\n          {\n            type: 'element',\n            prefix: 'bc',\n            style: 'kebab-case',\n          },\n        ],\n        '@typescript-eslint/prefer-namespace-keyword': 'error',\n        '@nx/enforce-module-boundaries': 'off',\n        eqeqeq: ['off', 'smart'],\n        'id-blacklist': [\n          'error',\n          'any',\n          'Number',\n          'number',\n          'String',\n          'string',\n          'Boolean',\n          'boolean',\n          'Undefined',\n          'undefined',\n        ],\n        'id-match': 'error',\n        'no-eval': 'off',\n        'no-redeclare': 'error',\n        'no-underscore-dangle': 'error',\n        'no-var': 'error',\n        'no-case-declarations': 'off',\n        '@angular-eslint/prefer-standalone': 'off',\n        '@angular-eslint/prefer-inject': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['projects/standalone-app-e2e/tsconfig.*.json'],\n        },\n      },\n    })),\n\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n  {\n    files: ['src/**/*.cy.{ts,tsx}'],\n    languageOptions: {\n      parserOptions: {\n        project: ['projects/standalone-app-e2e/tsconfig.*.json'],\n      },\n      globals: {\n        cy: true,\n        describe: true,\n        it: true,\n        before: true,\n        beforeEach: true,\n        after: true,\n        afterEach: true,\n      },\n    },\n    rules: {\n      // Add optional Cypress-safe tweaks\n      'no-unused-expressions': 'off', // Allow Chai-style assertions\n      '@typescript-eslint/no-floating-promises': 'off', // Often triggered by cy.* commands\n    },\n  },\n];\n"
  },
  {
    "path": "projects/standalone-app-e2e/project.json",
    "content": "{\n  \"name\": \"standalone-app-e2e\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"sourceRoot\": \"projects/standalone-app-e2e/src\",\n  \"projectType\": \"application\",\n  \"tags\": [],\n  \"implicitDependencies\": [\"standalone-app\"],\n  \"targets\": {\n    \"e2e\": {\n      \"executor\": \"@nx/cypress:cypress\",\n      \"options\": {\n        \"cypressConfig\": \"projects/standalone-app-e2e/cypress.config.ts\",\n        \"devServerTarget\": \"standalone-app:serve:development\",\n        \"testingType\": \"e2e\"\n      },\n      \"configurations\": {\n        \"production\": {\n          \"devServerTarget\": \"standalone-app:serve:production\"\n        }\n      }\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"outputs\": [\"{options.outputFile}\"],\n      \"options\": {\n        \"lintFilePatterns\": [\"projects/standalone-app-e2e/**/*.{js,ts}\"]\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/standalone-app-e2e/src/integration/app.cy.ts",
    "content": "import { getGreeting, loadFeature } from '../support/app.po';\n\ndescribe('standalone-app', () => {\n  beforeEach(() => cy.visit('/'));\n\n  it('should display welcome message', () => {\n    getGreeting().contains('Welcome ngrx-standalone-app');\n    loadFeature();\n    cy.contains('Feature State: { \"loaded\": true }');\n  });\n});\n"
  },
  {
    "path": "projects/standalone-app-e2e/src/support/app.po.ts",
    "content": "export const getGreeting = () => cy.get('h1');\nexport const loadFeature = () => cy.get('a').click();\n"
  },
  {
    "path": "projects/standalone-app-e2e/src/support/e2e.ts",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n"
  },
  {
    "path": "projects/standalone-app-e2e/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"sourceMap\": false,\n    \"outDir\": \"../../dist/out-tsc\",\n    \"allowJs\": true,\n    \"types\": [\"cypress\", \"node\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.js\", \"cypress.config.ts\"],\n  \"angularCompilerOptions\": {\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true\n  }\n}\n"
  },
  {
    "path": "projects/www/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc';\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport js from '@eslint/js';\nimport baseConfig from '../../eslint.config.mjs';\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n});\n\nexport default [\n  {\n    ignores: ['**/dist', '**/node_modules', '**/examples'],\n  },\n  ...baseConfig,\n  ...compat\n    .config({\n      extends: [\n        'plugin:@nx/angular',\n        'plugin:@angular-eslint/template/process-inline-templates',\n      ],\n      plugins: ['@typescript-eslint'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts'],\n      rules: {\n        ...config.rules,\n        '@angular-eslint/directive-selector': [\n          'error',\n          {\n            type: 'attribute',\n            prefix: 'ngrx',\n            style: 'camelCase',\n          },\n        ],\n        '@angular-eslint/component-selector': [\n          'error',\n          {\n            type: 'element',\n            prefix: 'ngrx',\n            style: 'kebab-case',\n          },\n        ],\n        '@nx/enforce-module-boundaries': 'off',\n      },\n      languageOptions: {\n        parserOptions: {\n          project: ['projects/www/tsconfig.*.json'],\n        },\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/angular-template'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.html'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n];\n"
  },
  {
    "path": "projects/www/firebase.json",
    "content": "{\n  \"functions\": {\n    \"source\": \"../../dist/projects/www/analog/server\"\n  },\n  \"hosting\": [\n    {\n      \"site\": \"<your_project_id>\",\n      \"public\": \"../../dist/projects/www/analog/public\",\n      \"cleanUrls\": true,\n      \"rewrites\": [\n        {\n          \"source\": \"**\",\n          \"function\": \"server\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "projects/www/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>NgRx</title>\n    <base href=\"/\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\" />\n    <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\" />\n    <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\" />\n    <link rel=\"manifest\" href=\"/site.webmanifest\" />\n    <link rel=\"mask-icon\" href=\"/safari-pinned-tab.svg\" color=\"#5bbad5\" />\n    <meta name=\"msapplication-TileColor\" content=\"#da532c\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <link rel=\"stylesheet\" href=\"/src/styles.scss\" />\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Oxanium:wght@200..800&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Oxanium:wght@200..800&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography mat-app-background\">\n    <ngrx-root></ngrx-root>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/www/package.json",
    "content": "{\n  \"type\": \"module\",\n  \"scripts\": {\n    \"collect-docs\": \"npx tsx src/tools/extract-docs-content.ts\",\n    \"prepare-examples\": \"npx tsx src/tools/prepare-examples.ts\"\n  }\n}\n"
  },
  {
    "path": "projects/www/project.json",
    "content": "{\n  \"name\": \"www\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"application\",\n  \"sourceRoot\": \"projects/www/src\",\n  \"tags\": [],\n  \"targets\": {\n    \"build\": {\n      \"dependsOn\": [\n        { \"target\": \"collect-docs\" },\n        { \"target\": \"prepare-examples\" }\n      ],\n      \"executor\": \"@analogjs/platform:vite\",\n      \"defaultConfiguration\": \"production\",\n      \"configurations\": {\n        \"development\": {\n          \"mode\": \"development\"\n        },\n        \"production\": {\n          \"sourcemap\": false,\n          \"mode\": \"production\"\n        }\n      },\n      \"outputs\": [\n        \"{options.outputPath}\",\n        \"{workspaceRoot}/dist/projects/www/.nitro\",\n        \"{workspaceRoot}/dist/projects/www/ssr\",\n        \"{workspaceRoot}/dist/projects/www/analog\"\n      ],\n      \"options\": {\n        \"main\": \"projects/www/src/main.ts\",\n        \"configFile\": \"projects/www/vite.config.ts\",\n        \"outputPath\": \"dist/projects/www/client\",\n        \"tsConfig\": \"projects/www/tsconfig.app.json\"\n      }\n    },\n    \"serve\": {\n      \"executor\": \"@analogjs/platform:vite-dev-server\",\n      \"defaultConfiguration\": \"development\",\n      \"options\": {\n        \"buildTarget\": \"www:build\",\n        \"port\": 4200\n      },\n      \"configurations\": {\n        \"development\": {\n          \"buildTarget\": \"www:build:development\",\n          \"hmr\": true\n        },\n        \"production\": {\n          \"buildTarget\": \"www:build:production\"\n        }\n      }\n    },\n    \"test\": {\n      \"executor\": \"@analogjs/vitest-angular:test\",\n      \"outputs\": [\"{projectRoot}/coverage\"]\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"options\": {\n        \"lintFilePatterns\": [\"projects/www/**/*.ts\", \"projects/www/**/*.html\"],\n        \"config\": \"eslint.config.mjs\"\n      }\n    },\n    \"collect-docs\": {\n      \"executor\": \"nx:run-script\",\n      \"options\": {\n        \"script\": \"collect-docs\"\n      },\n      \"dependsOn\": [\n        {\n          \"projects\": [\n            \"component\",\n            \"component-store\",\n            \"data\",\n            \"effects\",\n            \"entity\",\n            \"operators\",\n            \"router-store\",\n            \"signals\",\n            \"store\",\n            \"store-devtools\"\n          ],\n          \"target\": \"build\"\n        }\n      ]\n    },\n    \"prepare-examples\": {\n      \"executor\": \"nx:run-script\",\n      \"options\": {\n        \"script\": \"prepare-examples\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/www/public/.gitkeep",
    "content": ""
  },
  {
    "path": "projects/www/public/_redirects",
    "content": "/* /index.html 200"
  },
  {
    "path": "projects/www/public/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"/mstile-150x150.png\"/>\n            <TileColor>#da532c</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "projects/www/public/site.webmanifest",
    "content": "{\n  \"name\": \"NgRx\",\n  \"short_name\": \"NgRx\",\n  \"icons\": [\n    {\n      \"src\": \"/android-chrome-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/android-chrome-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#000000\",\n  \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "projects/www/src/_code_theme.scss",
    "content": "pre code.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 1em;\n}\n\npre {\n  margin: 14px 0 24px;\n  overflow-x: auto;\n  border: 1px solid var(--ngrx-border-color);\n}\n\ncode.hljs {\n  padding: 3px 5px;\n}\n\n.hljs {\n  color: var(--ngrx-code-text);\n  background: var(--ngrx-code-bg);\n}\n.hljs-comment,\n.hljs-quote {\n  color: var(--ngrx-code-comment);\n  font-style: italic;\n}\n.hljs-doctag,\n.hljs-keyword,\n.hljs-formula {\n  color: var(--ngrx-code-keyword);\n}\n.hljs-section,\n.hljs-name,\n.hljs-selector-tag,\n.hljs-deletion,\n.hljs-subst {\n  color: var(--ngrx-code-deletion);\n}\n.hljs-literal {\n  color: var(--ngrx-code-literal);\n}\n.hljs-string,\n.hljs-regexp,\n.hljs-addition,\n.hljs-attribute,\n.hljs-meta .hljs-string {\n  color: var(--ngrx-code-string);\n}\n.hljs-attr,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-type,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-number {\n  color: var(--ngrx-code-number);\n}\n.hljs-symbol,\n.hljs-bullet,\n.hljs-link,\n.hljs-meta,\n.hljs-selector-id,\n.hljs-title {\n  color: var(--ngrx-code-title);\n}\n.hljs-built_in,\n.hljs-title.class_,\n.hljs-class .hljs-title {\n  color: var(--ngrx-code-class);\n}\n.hljs-emphasis {\n  font-style: italic;\n}\n.hljs-strong {\n  font-weight: bold;\n}\n.hljs-link {\n  text-decoration: underline;\n}\n"
  },
  {
    "path": "projects/www/src/_theme.scss",
    "content": "// This file was generated by running 'ng generate @angular/material:m3-theme'.\n// Proceed with caution if making changes to this file.\n\n@use 'sass:map';\n@use '@angular/material' as mat;\n\n// Note: Color palettes are generated from primary: B1219B, secondary: FB9C2D\n$_palettes: (\n  primary: (\n    0: #000000,\n    10: #3a0032,\n    20: #5e0052,\n    25: #710063,\n    30: #850074,\n    35: #990086,\n    40: #a91794,\n    50: #c839b0,\n    60: #e856cc,\n    70: #ff7be1,\n    80: #fface6,\n    90: #ffd7f0,\n    95: #ffecf5,\n    98: #fff7f9,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n  secondary: (\n    0: #000000,\n    10: #2d1600,\n    20: #4a2800,\n    25: #5a3200,\n    30: #6a3c00,\n    35: #7a4600,\n    40: #8b5000,\n    50: #ae6500,\n    60: #d37c00,\n    70: #f39526,\n    80: #ffb871,\n    90: #ffdcbe,\n    95: #ffeee1,\n    98: #fff8f5,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n  tertiary: (\n    0: #000000,\n    10: #321205,\n    20: #4b2717,\n    25: #583121,\n    30: #663c2b,\n    35: #734836,\n    40: #815341,\n    50: #9d6b57,\n    60: #ba846f,\n    70: #d79e88,\n    80: #f5b9a2,\n    90: #ffdbce,\n    95: #ffede7,\n    98: #fff8f6,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n  neutral: (\n    0: #000000,\n    10: #1f1a1d,\n    20: #342f32,\n    25: #3f3a3d,\n    30: #4b4548,\n    35: #575154,\n    40: #635d60,\n    50: #7c7578,\n    60: #978f92,\n    70: #b2a9ac,\n    80: #cdc4c7,\n    90: #eae0e3,\n    95: #f8eef1,\n    98: #fff7f9,\n    99: #fffbff,\n    100: #ffffff,\n    4: #110d0f,\n    6: #161215,\n    12: #231e21,\n    17: #2e292b,\n    22: #393336,\n    24: #3d383a,\n    87: #e1d7db,\n    92: #f0e6e9,\n    94: #f5ebef,\n    96: #fbf1f4,\n  ),\n  neutral-variant: (\n    0: #000000,\n    10: #22191f,\n    20: #382e34,\n    25: #43383f,\n    30: #4f444a,\n    35: #5b4f56,\n    40: #675b62,\n    50: #81737b,\n    60: #9b8d94,\n    70: #b6a7af,\n    80: #d2c2ca,\n    90: #efdee6,\n    95: #feecf4,\n    98: #fff7f9,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n  error: (\n    0: #000000,\n    10: #410002,\n    20: #690005,\n    25: #7e0007,\n    30: #93000a,\n    35: #a80710,\n    40: #ba1a1a,\n    50: #de3730,\n    60: #ff5449,\n    70: #ff897d,\n    80: #ffb4ab,\n    90: #ffdad6,\n    95: #ffedea,\n    98: #fff8f7,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n);\n\n$_rest: (\n  secondary: map.get($_palettes, secondary),\n  neutral: map.get($_palettes, neutral),\n  neutral-variant: map.get($_palettes, neutral-variant),\n  error: map.get($_palettes, error),\n);\n$_primary: map.merge(map.get($_palettes, primary), $_rest);\n$_tertiary: map.merge(map.get($_palettes, tertiary), $_rest);\n\n$light-theme: mat.define-theme(\n  (\n    color: (\n      theme-type: light,\n      primary: $_primary,\n      tertiary: $_tertiary,\n    ),\n    typography: (\n      brand-family: 'Oxanium',\n    ),\n    density: (\n      scale: -1,\n    ),\n  )\n);\n$dark-theme: mat.define-theme(\n  (\n    color: (\n      theme-type: dark,\n      primary: $_primary,\n      tertiary: $_tertiary,\n    ),\n    typography: (\n      brand-family: 'Oxanium',\n    ),\n    density: (\n      scale: -1,\n    ),\n  )\n);\n"
  },
  {
    "path": "projects/www/src/app/app.component.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { AppComponent } from './app.component';\nimport { describe, beforeEach, it, expect } from 'vitest';\nimport { provideRouter } from '@angular/router';\nimport { PLATFORM_ID } from '@angular/core';\n\ndescribe('AppComponent', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        provideRouter([]),\n        { provide: PLATFORM_ID, useValue: 'server' },\n      ],\n      imports: [AppComponent],\n    });\n  });\n\n  it('should create the app', () => {\n    const fixture = TestBed.createComponent(AppComponent);\n    const app = fixture.componentInstance;\n    expect(app).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "projects/www/src/app/app.component.ts",
    "content": "import {\n  Component,\n  Injector,\n  PLATFORM_ID,\n  inject,\n  signal,\n  effect,\n} from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MenuComponent } from './components/menu.component';\nimport { MarkdownSymbolLinkComponent } from './components/docs/markdown-symbol-link.component';\nimport { AlertComponent } from './components/docs/alert.component';\nimport { CodeExampleComponent } from './components/docs/code-example.component';\nimport { CodeTabsComponent } from './components/docs/code-tabs.component';\nimport { StackblitzComponent } from './components/docs/stackblitz.component';\nimport { InstallInstructionsComponent } from './components/docs/install-instructions.component';\nimport { FooterComponent } from './components/footer.component';\nimport {\n  TOP_BANNER_DISMISSED_STORAGE_KEY,\n  TopBannerComponent,\n} from './components/top-banner.component';\n\n@Component({\n  selector: 'ngrx-root',\n  imports: [\n    RouterOutlet,\n    MenuComponent,\n    TopBannerComponent,\n    MarkdownSymbolLinkComponent,\n    AlertComponent,\n    CodeExampleComponent,\n    StackblitzComponent,\n    InstallInstructionsComponent,\n    FooterComponent,\n  ],\n  template: `\n    @if (isTopBannerVisible()) {\n      <ngrx-top-banner (dismiss)=\"isTopBannerVisible.set(false)\" />\n    }\n    <ngrx-menu />\n    <div class=\"content\">\n      <router-outlet />\n      <ngrx-footer />\n    </div>\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n      }\n\n      :host.top-banner-visible {\n        --top-banner-height: 44px;\n\n        @media only screen and (max-width: 1280px) {\n          --top-banner-height: 70px;\n        }\n      }\n\n      ngrx-menu {\n        position: fixed;\n        left: 0;\n        top: 0;\n      }\n\n      .content {\n        position: relative;\n        width: calc(100% - 270px);\n        left: 270px;\n\n        @media only screen and (max-width: 1280px) {\n          width: 100%;\n          left: 0;\n          padding-top: var(--top-banner-height, 0px);\n        }\n      }\n    `,\n  ],\n  host: {\n    '[class.top-banner-visible]': 'isTopBannerVisible()',\n  },\n})\nexport class AppComponent {\n  readonly #injector = inject(Injector);\n  readonly #platformId = inject(PLATFORM_ID);\n\n  readonly isTopBannerVisible = signal(false);\n\n  constructor() {\n    if (isPlatformBrowser(this.#platformId)) {\n      this.initTopBanner();\n      this.installCustomElements();\n    }\n  }\n\n  initTopBanner(): void {\n    if (localStorage.getItem(TOP_BANNER_DISMISSED_STORAGE_KEY) !== 'true') {\n      this.isTopBannerVisible.set(true);\n    }\n\n    effect(() => {\n      localStorage.setItem(\n        TOP_BANNER_DISMISSED_STORAGE_KEY,\n        `${!this.isTopBannerVisible()}`\n      );\n    });\n  }\n\n  async installCustomElements(): Promise<void> {\n    const { createCustomElement } = await import('@angular/elements');\n\n    const symbolLinkElement = createCustomElement(MarkdownSymbolLinkComponent, {\n      injector: this.#injector,\n    });\n    customElements.define('ngrx-docs-symbol-link', symbolLinkElement);\n\n    const alertElement = createCustomElement(AlertComponent, {\n      injector: this.#injector,\n    });\n    customElements.define('ngrx-docs-alert', alertElement);\n\n    const codeExampleElement = createCustomElement(CodeExampleComponent, {\n      injector: this.#injector,\n    });\n    customElements.define('ngrx-code-example', codeExampleElement);\n\n    const codeTabsElement = createCustomElement(CodeTabsComponent, {\n      injector: this.#injector,\n    });\n    customElements.define('ngrx-code-tabs', codeTabsElement);\n\n    const stackblitzElement = createCustomElement(StackblitzComponent, {\n      injector: this.#injector,\n    });\n    customElements.define('ngrx-docs-stackblitz', stackblitzElement);\n\n    const installInstructionsElement = createCustomElement(\n      InstallInstructionsComponent,\n      { injector: this.#injector }\n    );\n    customElements.define('ngrx-docs-install', installInstructionsElement);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/app.config.browser.ts",
    "content": "import {\n  mergeApplicationConfig,\n  ApplicationConfig,\n  provideZonelessChangeDetection,\n} from '@angular/core';\nimport { appConfig } from './app.config';\n\nconst serverConfig: ApplicationConfig = {\n  providers: [provideZonelessChangeDetection()],\n};\n\nexport const config = mergeApplicationConfig(appConfig, serverConfig);\n"
  },
  {
    "path": "projects/www/src/app/app.config.server.ts",
    "content": "import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';\nimport { provideServerRendering } from '@angular/platform-server';\n\nimport { appConfig } from './app.config';\n\nconst serverConfig: ApplicationConfig = {\n  providers: [provideServerRendering()],\n};\n\nexport const config = mergeApplicationConfig(appConfig, serverConfig);\n"
  },
  {
    "path": "projects/www/src/app/app.config.ts",
    "content": "import {\n  ApplicationConfig,\n  provideZonelessChangeDetection,\n} from '@angular/core';\nimport { provideHttpClient, withFetch } from '@angular/common/http';\nimport { provideClientHydration } from '@angular/platform-browser';\nimport { provideFileRouter } from '@analogjs/router';\nimport {\n  withComponentInputBinding,\n  withInMemoryScrolling,\n  withViewTransitions,\n} from '@angular/router';\nimport { provideAnimations } from '@angular/platform-browser/animations';\nimport { provideContent, withMarkdownRenderer } from '@analogjs/content';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideZonelessChangeDetection(),\n    provideFileRouter(\n      withComponentInputBinding(),\n      withViewTransitions(),\n      withInMemoryScrolling({\n        scrollPositionRestoration: 'enabled',\n        anchorScrolling: 'enabled',\n      })\n    ),\n    provideClientHydration(),\n    provideHttpClient(withFetch()),\n    provideAnimations(),\n    provideContent(withMarkdownRenderer()),\n  ],\n};\n"
  },
  {
    "path": "projects/www/src/app/components/banner-animation.component.ts",
    "content": "import { isPlatformServer } from '@angular/common';\nimport {\n  AfterViewInit,\n  Component,\n  ElementRef,\n  OnDestroy,\n  PLATFORM_ID,\n  computed,\n  inject,\n  viewChild,\n  viewChildren,\n} from '@angular/core';\n\ntype ComputedLineGroup = {\n  group: SVGElement;\n  path1: SVGPathElement;\n  path2: SVGPathElement;\n  path3: SVGPathElement;\n  path1Length: number;\n  path2Length: number;\n  path3Length: number;\n  circle: SVGCircleElement;\n  ellipse: SVGEllipseElement;\n};\n\ntype ColorPairs = [dark: string, light: string][];\n\nconst COLOR_PAIRS: ColorPairs = [\n  ['FF77EA', 'AA1BB6'],\n  ['7F00FF', 'B770FF'],\n  ['FB9C2D', 'FFCE94'],\n];\n\n@Component({\n  selector: 'ngrx-banner-animation',\n  standalone: true,\n  template: `\n    <svg\n      #svg\n      [attr.viewBox]=\"viewBox\"\n      fill=\"none\"\n      preserveAspectRatio=\"xMidYMid slice\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <g filter=\"url(#filter0_f_232_269)\" transform=\"translate(0 40)\">\n        <path\n          d=\"M2436.88 841.563L2265.72 840.245L2091.19 739.48L2040.92 646.956L2096.35 614.956L1921.82 514.191L1866.39 546.191L1622.05 405.12L1240.21 504.661L821.333 262.825L938.317 74.3662L833.599 13.9072L861.312 -2.09285L686.782 -102.858L659.069 -86.8579L554.351 -147.317L623.103 -267.623L310.681 -450M2293.99 924.063L2241.08 893.519L2145.39 866.268L2040.67 805.809L1992.69 715.109L1853.07 634.497L1700.12 642.191L1665.21 622.038L1623.11 565.732L1518.39 505.273L1212.49 520.661L793.62 278.825L764.847 134.213L729.941 114.06L666.262 -50.7049L491.732 -151.47L359.301 -195.929L254.583 -256.388L198.097 -385M2168.28 976.486L2043.04 968.18L1973.23 927.874L1890.99 815.392L1752.46 735.41L1650.7 711.659L1442.45 589.426L1184.78 536.661L765.907 294.825L688.902 218.366L584.184 157.907L625.223 53.6011L590.317 33.4481L562.604 49.4481L492.792 9.14213L520.505 -6.85788L401.4 -139.623L261.776 -220.235L31.8203 -289M2029.72 1056.49L2022.52 1020.33L1917.81 959.874L1757.66 931.415L1583.13 830.65L1610.85 814.65L1471.22 734.038L1464.03 697.885L1429.12 677.732L1157.07 552.661L738.194 310.825L528.758 189.907L416.847 93.2951L381.941 73.1421L395.267 -15.1639L290.549 -75.6229L227.93 -79.7759L193.024 -99.9289L178.638 -172.235L-51.3181 -241M1891.15 1136.49L1856.25 1116.33L1849.05 1080.18L1639.62 959.262L1597.52 902.956L1527.71 862.65L1548.23 810.497L1443.51 750.038L1345.98 725.732L1129.35 568.661L710.482 326.825L543.145 262.213L438.426 201.754L431.233 165.601L326.515 105.142L312.129 32.8361L277.223 12.6831L124.272 20.3771L-15.352 -60.235L-79.0309 -225M1808.01 1184.49L1703.3 1124.03L1605.77 1099.72L1431.24 998.956L1451.76 946.803L1347.04 886.344L1374.76 870.344L1332.66 814.038L1193.03 733.426L1101.64 584.661L682.769 342.825L515.432 278.213L480.526 258.06L417.907 253.907L383.001 233.754L333.708 141.295L298.802 121.142L166.371 76.6831L-8.15873 -24.0819L-168.303 -52.5409L-112.877 -84.541L-182.689 -124.847L-356.159 -65M1617.83 1296.18L1629.2 1207.75L1524.48 1147.29L1458.27 1142.06L1355.73 1082.86L1328.02 1098.86L1295.84 1080.28L1391.76 943.66L1320.31 902.409L1255.94 896.747L1047.28 777.273L1073.93 600.661L655.056 358.825C653.747 358.069 612.765 355.742 592.437 354.672L452.813 274.06L230.05 241.448L195.144 221.295L173.565 112.836L33.9405 32.2241L-321.253 -44.8469L-376.679 -12.8469L-411.585 -33M1503.17 1360.49L1488.79 1288.18L1279.35 1167.26L1334.78 1135.26L1090.43 994.191L971.33 861.426L901.518 821.12L1046.22 616.661L627.343 374.825L564.724 370.672L425.1 290.06L369.674 322.06L154.105 325.601L84.293 285.295L153.045 164.989L-56.3912 44.0711L-146.723 55.9181L-522.436 31.0001\"\n          stroke=\"url(#paint0_radial_232_269)\"\n          stroke-opacity=\"0.72\"\n          stroke-width=\"4\"\n        />\n      </g>\n      <path\n        d=\"M2436.88 881.563 L2265.72 880.245 L2091.19 779.48 L2040.92 686.956 L2096.35 654.956 L1921.82 554.191 L1866.39 586.191 L1622.05 445.12 L1240.21 544.661 L821.333 302.825 L938.317 114.366 L833.599 53.9072 L861.312 37.9072 L686.782 -62.8579 L659.069 -46.8579 L554.351 -107.31 L623.103 -227.623 L310.681 -410 M2293.99 964.063 L2241.08 933.519 L2145.39 906.268 L2040.67 845.809 L1992.69 755.109 L1853.07 674.497 L1700.12 682.191 L1665.21 662.038 L1623.11 605.732 L1518.39 545.273 L1212.49 560.661 L793.62 318.825 L764.847 174.213 L729.941 154.06 L666.262 -10.7049 L491.732 -111.47 L359.301 -155.929 L254.583 -216.388 L198.097 -345 M2168.28 1016.49 L2043.04 1008.18 L1973.23 967.874 L1890.99 855.392 L1752.46 775.41 L1650.7 751.659 L1442.45 629.426 L1184.78 576.661 L765.907 334.825 L688.902 258.366 L584.184 197.907 L625.223 93.6011 L590.317 73.4481 L562.604 89.4481 L492.792 49.1421 L520.505 33.1421 L401.4 -99.6229 L261.776 -180.235 L31.8203 -249 M2029.72 1096.49 L2022.52 1060.33 L1917.81 999.874 L1757.66 971.415 L1583.13 870.65 L1610.85 854.65 L1471.22 774.038 L1464.03 737.885 L1429.12 717.732 L1157.07 592.661 L738.194 350.825 L528.758 229.907 L416.847 133.295 L381.941 113.142 L395.267 24.8361 L290.549 -35.6229 L227.93 -39.7759 L193.024 -59.9289 L178.638 -132.235 L-51.3181 -201 M1891.15 1176.49 L1856.25 1156.33 L1849.05 1120.18 L1639.62 999.262 L1597.52 942.956 L1527.71 902.65 L1548.23 850.497 L1443.51 790.038 L1345.98 765.732 L1129.35 608.661 L710.482 366.825 L543.145 302.213 L438.426 241.754 L431.233 205.601 L326.515 145.142 L312.129 72.8361 L277.223 52.6831 L124.272 60.3771 L-15.352 -20.235 L-79.0309 -185 M1808.01 1224.49 L1703.3 1164.03 L1605.77 1139.72 L1431.24 1038.96 L1451.76 986.803 L1347.04 926.344 L1374.76 910.344 L1332.66 854.038 L1193.03 773.426 L1101.64 624.661 L682.769 382.825 L515.432 318.213 L480.526 298.06 L417.907 293.907 L383.001 273.754 L333.708 181.295 L298.802 161.142 L166.371 116.683 L-8.15873 15.9181 L-168.303 -12.5409 L-112.877 -44.541 L-182.689 -84.847 L-356.159 -25 M1617.83 1336.18 L1629.2 1247.75 L1524.48 1187.29 L1458.27 1182.06 L1355.73 1122.86 L1328.02 1138.86 L1295.84 1120.28 L1391.76 983.66 L1320.31 942.409 L1255.94 936.747 L1047.28 817.273 L1073.93 640.661 L655.056 398.825 C653.747 398.069 612.765 395.742 592.437 394.672 L452.813 314.06 L230.05 281.448 L195.144 261.295 L173.565 152.836 L33.9405 72.2241 L-321.253 -4.84695 L-376.679 27.1531 L-411.585 7.00005 M1503.17 1400.49 L1488.79 1328.18 L1279.35 1207.26 L1334.78 1175.26 L1090.43 1034.19 L971.33 901.426 L901.518 861.12 L1046.22 656.661 L627.343 414.825 L564.724 410.672 L425.1 330.06 L369.674 362.06 L154.105 365.601 L84.293 325.29 L153.045 204.989 L-56.3912 84.0711 L-146.723 95.9181 L-522.436 71.0001\"\n        stroke=\"url(#lineGradient)\"\n      />\n\n      <!-- Line 1 -->\n      <g #lineGroup>\n        <path\n          d=\"M686.782 -62.8579 L861.312 37.9072 L833.599 53.9072 L938.317 114.366 L821.333 302.825\"\n        />\n        <path d=\"M821.333 302.825 L1240.21 544.661\" />\n        <path d=\"M1240.21 544.661 L1622.05 445.12 L1866.39 586.191\" />\n        <ellipse\n          cx=\"821.333\"\n          cy=\"302.825\"\n          rx=\"3\"\n          ry=\"6\"\n          filter=\"url(#ellipseFilter)\"\n        />\n        <circle cx=\"821.333\" cy=\"302.825\" r=\"3\" />\n      </g>\n\n      <!-- Line 2 -->\n      <g #lineGroup>\n        <path\n          d=\"M666.262 -10.7049 L729.941 154.06 L764.847 174.213 L793.62 318.825\"\n        />\n        <path d=\"M793.62 318.825  L1212.49 560.661\" />\n        <path\n          d=\"M1212.49 560.661 L1518.39 545.273 L1623.11 605.732 L1665.21 662.038 L1700.12 682.191 L1853.07 674.497 L2293.99 964.063\"\n        />\n        <ellipse\n          cx=\"821.333\"\n          cy=\"302.825\"\n          rx=\"3\"\n          ry=\"6\"\n          filter=\"url(#ellipseFilter)\"\n        />\n        <circle cx=\"729.941\" cy=\"154.06\" r=\"3\" />\n      </g>\n\n      <!-- Line 3 -->\n      <g #lineGroup>\n        <path\n          d=\"M401.4 -99.6229 L520.505 33.1421 L492.792 49.1421 L562.604 89.4481 L590.317 73.4481 L625.223 93.6011 L584.184 197.907 L688.902 258.366 L765.907 334.825\"\n        />\n        <path d=\"M765.907 334.825 L1184.78 576.661\" />\n        <path\n          d=\"M1184.78 576.661 L1442.45 629.426 L1650.7 751.659 L1752.46 775.41 L1890.99 855.392\"\n        />\n        <ellipse\n          cx=\"821.333\"\n          cy=\"302.825\"\n          rx=\"3\"\n          ry=\"6\"\n          filter=\"url(#ellipseFilter)\"\n        />\n        <circle cx=\"520.505\" cy=\"33.1421\" r=\"3\" />\n      </g>\n\n      <!-- Line 4 -->\n      <g #lineGroup>\n        <path\n          d=\"M290.549 -35.6229 L395.267 24.8361 L381.941 113.142 L416.847 133.295 L528.758 229.907 L738.194 350.825\"\n        />\n        <path d=\"M738.194 350.825 L1157.07 592.661\" />\n        <path\n          d=\"M1157.07 592.661 L1429.12 717.732 L1464.03 737.885 L1471.22 774.038 L1610.85 854.65 L1583.13 870.65 L1757.66 971.415 L1917.81 999.874\"\n        />\n        <ellipse\n          cx=\"821.333\"\n          cy=\"302.825\"\n          rx=\"3\"\n          ry=\"6\"\n          filter=\"url(#ellipseFilter)\"\n        />\n        <circle cx=\"395.267\" cy=\"24.8361\" r=\"3\" />\n      </g>\n\n      <!-- Line 5 -->\n      <g #lineGroup>\n        <path\n          d=\"M-15.352 -20.235 L124.272 60.3771 L277.223 52.6831 L312.129 72.8361 L326.515 145.142 L431.233 205.601 L438.426 241.754 L543.145 302.213 L710.482 366.825\"\n        />\n        <path d=\"M710.482 366.825 L1129.35 608.661\" />\n        <path\n          d=\"M1129.35 608.661 L1345.98 765.732 L1443.51 790.038 L1548.23 850.497 L1527.71 902.65 L1597.52 942.956 L1639.62 999.262 L1849.05 1120.18\"\n        />\n        <ellipse\n          cx=\"821.333\"\n          cy=\"302.825\"\n          rx=\"3\"\n          ry=\"6\"\n          filter=\"url(#ellipseFilter)\"\n        />\n        <circle cx=\"1129.35\" cy=\"608.661\" r=\"3\" />\n      </g>\n\n      <!-- Line 6 -->\n      <g #lineGroup>\n        <path\n          d=\"M-8.15873 15.9181 L166.371 116.683 L298.802 161.142 L333.708 181.295 L383.001 273.754 L417.907 293.907 L480.526 298.06 L515.432 318.213 L682.769 382.825\"\n        />\n        <path d=\"M682.769 382.825 L1101.64 624.661\" />\n        <path\n          d=\"M1101.64 624.661 L1193.03 773.426 L1332.66 854.038 L1374.76 910.344 L1347.04 926.344 L1451.76 986.803 L1431.24 1038.96 L1605.77 1139.72\"\n        />\n        <ellipse\n          cx=\"821.333\"\n          cy=\"302.825\"\n          rx=\"3\"\n          ry=\"6\"\n          filter=\"url(#ellipseFilter)\"\n        />\n        <circle cx=\"166.371\" cy=\"116.683\" r=\"3\" />\n      </g>\n\n      <!-- Line 7 -->\n      <g #lineGroup>\n        <path\n          d=\"M-321.253 -4.84695 L33.9405 72.2241 L173.565 152.836 L195.144 261.295 L230.05 281.448 L452.813 314.06 L592.437 394.672 C612.765 395.742 653.747 398.069 655.056 398.825\"\n        />\n        <path d=\"M655.056 398.825 L1073.93 640.661\" />\n        <path\n          d=\"M1073.93 640.661 L1047.28 817.273 L1255.94 936.747 L1320.31 942.409 L1391.76 983.66 L1295.84 1120.28\"\n        />\n        <ellipse\n          cx=\"821.333\"\n          cy=\"302.825\"\n          rx=\"3\"\n          ry=\"6\"\n          filter=\"url(#ellipseFilter)\"\n        />\n        <circle cx=\"1073.93\" cy=\"640.661\" r=\"3\" />\n      </g>\n\n      <!-- Line 8 -->\n      <g #lineGroup>\n        <path\n          d=\"M-56.3912 84.0711 L153.045 204.989 L84.293 325.29 L154.105 365.601 L369.674 362.06 L425.1 330.06 L564.724 410.672 L627.343 414.825\"\n        />\n        <path d=\"M627.343 414.825 L1046.22 656.661\" />\n        <path\n          d=\"M1046.22 656.661 L901.518 861.12 L971.33 901.426 L1090.43 1034.19 L1334.78 1175.26\"\n        />\n        <ellipse\n          cx=\"821.333\"\n          cy=\"302.825\"\n          rx=\"3\"\n          ry=\"6\"\n          filter=\"url(#ellipseFilter)\"\n        />\n        <circle cx=\"153.045\" cy=\"204.989\" r=\"3\" />\n      </g>\n\n      <defs>\n        <radialGradient\n          id=\"lineGradient\"\n          cx=\"0\"\n          cy=\"0\"\n          r=\"1\"\n          gradientUnits=\"userSpaceOnUse\"\n          gradientTransform=\"translate(915.654 500.281) rotate(33.9338) scale(1416.97 2755.16)\"\n        >\n          <stop stop-color=\"#B1219B\" stop-opacity=\"0.24\" />\n          <stop offset=\"0.385\" stop-color=\"#8A1A79\" stop-opacity=\"0.48\" />\n          <stop offset=\"1\" stop-color=\"#FB9C2D\" />\n        </radialGradient>\n        <filter\n          id=\"ellipseFilter\"\n          x=\"-30px\"\n          y=\"-30px\"\n          width=\"60px\"\n          height=\"60px\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"6\" />\n        </filter>\n\n        <filter\n          id=\"filter0_f_232_269\"\n          x=\"-570.715\"\n          y=\"-498.995\"\n          width=\"3055.63\"\n          height=\"1907.64\"\n          filterUnits=\"userSpaceOnUse\"\n          color-interpolation-filters=\"sRGB\"\n        >\n          <feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\" />\n          <feBlend\n            mode=\"normal\"\n            in=\"SourceGraphic\"\n            in2=\"BackgroundImageFix\"\n            result=\"shape\"\n          />\n          <feGaussianBlur\n            stdDeviation=\"24\"\n            result=\"effect1_foregroundBlur_232_269\"\n          />\n        </filter>\n        <radialGradient\n          id=\"paint0_radial_232_269\"\n          cx=\"0\"\n          cy=\"0\"\n          r=\"1\"\n          gradientUnits=\"userSpaceOnUse\"\n          gradientTransform=\"translate(915.654 460.281) rotate(33.9338) scale(1416.97 2755.16)\"\n        >\n          <stop stop-color=\"#B1219B\" stop-opacity=\"0\" />\n          <stop offset=\"0.385\" stop-color=\"#8A1A79\" stop-opacity=\"0.3\" />\n          <stop offset=\"1\" stop-color=\"#FB9C2D\" />\n        </radialGradient>\n      </defs>\n    </svg>\n  `,\n})\nexport class BannerAnimationComponent implements AfterViewInit, OnDestroy {\n  WIDTH = 1832;\n  HEIGHT = 1000;\n  viewBox = `0 0 ${this.WIDTH} ${this.HEIGHT}`;\n  animateRef?: number;\n  setTimeoutRef?: number;\n  hostRef = inject(ElementRef);\n  platformId = inject(PLATFORM_ID);\n  svgRef = viewChild.required<ElementRef<SVGElement>>('svg');\n  lineGroupRefs = viewChildren<ElementRef<SVGPathElement>>('lineGroup');\n  lineGroups = computed(() => {\n    return this.lineGroupRefs().map((ref, index): ComputedLineGroup => {\n      const group = ref.nativeElement;\n      const path1 = group.querySelector<SVGPathElement>('path:nth-child(1)');\n      const path2 = group.querySelector<SVGPathElement>('path:nth-child(2)');\n      const path3 = group.querySelector<SVGPathElement>('path:nth-child(3)');\n      const circle = group.querySelector('circle');\n      const ellipse = group.querySelector('ellipse');\n\n      if (!path1 || !path2 || !path3) {\n        throw new Error(`Missing path elements in line group ${index}`);\n      }\n\n      if (!circle) {\n        throw new Error(`Missing circle element in line group ${index}`);\n      }\n\n      if (!ellipse) {\n        throw new Error(`Missing ellipse element in line group ${index}`);\n      }\n\n      const path1Length = this.getLengthOfPath(path1);\n      const path2Length = this.getLengthOfPath(path2);\n      const path3Length = this.getLengthOfPath(path3);\n\n      return {\n        group,\n        path1,\n        path2,\n        path3,\n        path1Length,\n        path2Length,\n        path3Length,\n        circle,\n        ellipse,\n      };\n    });\n  });\n\n  moveCircleInLineGroup(\n    {\n      path1,\n      path2,\n      path3,\n      path1Length,\n      path2Length,\n      path3Length,\n      circle,\n      ellipse,\n    }: ComputedLineGroup,\n    progress: number\n  ) {\n    const LINE_1_DURATION = 0.4;\n    const LINE_2_DURATION = 0.2;\n    const LINE_3_DURATION = 0.4;\n\n    if (progress < LINE_1_DURATION) {\n      const innerProgress = progress / LINE_1_DURATION;\n      const distance = path1Length * innerProgress;\n      const point = path1.getPointAtLength(distance);\n      circle.setAttribute('cx', `${point.x}`);\n      circle.setAttribute('cy', `${point.y}`);\n      ellipse.setAttribute('cx', `${point.x}`);\n      ellipse.setAttribute('cy', `${point.y}`);\n    } else if (progress < LINE_1_DURATION + LINE_2_DURATION) {\n      const innerProgress = (progress - LINE_1_DURATION) / LINE_2_DURATION;\n      const distance = path2Length * innerProgress;\n      const point = path2.getPointAtLength(distance);\n      circle.setAttribute('cx', `${point.x}`);\n      circle.setAttribute('cy', `${point.y}`);\n      ellipse.setAttribute('cx', `${point.x}`);\n      ellipse.setAttribute('cy', `${point.y}`);\n    } else {\n      const innerProgress =\n        (progress - (LINE_1_DURATION + LINE_2_DURATION)) / LINE_3_DURATION;\n      const distance = path3Length * innerProgress;\n      const point = path3.getPointAtLength(distance);\n      circle.setAttribute('cx', `${point.x}`);\n      circle.setAttribute('cy', `${point.y}`);\n      ellipse.setAttribute('cx', `${point.x}`);\n      ellipse.setAttribute('cy', `${point.y}`);\n    }\n  }\n\n  startCircleAnimation() {\n    if (isPlatformServer(this.platformId)) {\n      return;\n    }\n\n    const CIRCLE_ANIMATION_DURATION = 7_500;\n    const CIRCLE_ANIMATION_DELAY = 2_500;\n\n    const circleAnimationStart = Date.now();\n\n    for (const { circle, ellipse } of this.lineGroups()) {\n      const [dark, light] =\n        COLOR_PAIRS[Math.floor(Math.random() * COLOR_PAIRS.length)];\n      circle.setAttribute('fill', `#${dark}`);\n      ellipse.setAttribute('fill', `#${light}`);\n    }\n\n    const animate = () => {\n      const now = Date.now();\n      const elapsed = now - circleAnimationStart;\n\n      const progress = elapsed / CIRCLE_ANIMATION_DURATION;\n\n      this.lineGroups().forEach((lineGroup) => {\n        this.moveCircleInLineGroup(lineGroup, progress);\n      });\n\n      if (progress < 1) {\n        this.animateRef = requestAnimationFrame(animate);\n      } else {\n        this.setTimeoutRef = setTimeout(() => {\n          this.startCircleAnimation();\n        }, CIRCLE_ANIMATION_DELAY) as unknown as number;\n      }\n    };\n\n    this.animateRef = requestAnimationFrame(animate);\n  }\n\n  resizeSvgToCoverSelf() {\n    if (isPlatformServer(this.platformId)) {\n      return;\n    }\n\n    const resizeObserver = new ResizeObserver((entries) => {\n      for (const entry of entries) {\n        const { width, height } = entry.contentRect;\n        this.svgRef().nativeElement.setAttribute('width', `${width}`);\n        this.svgRef().nativeElement.setAttribute('height', `${height}`);\n      }\n    });\n\n    resizeObserver.observe(this.hostRef.nativeElement);\n  }\n\n  ngAfterViewInit() {\n    this.startCircleAnimation();\n    this.resizeSvgToCoverSelf();\n  }\n\n  ngOnDestroy() {\n    if (this.animateRef) {\n      cancelAnimationFrame(this.animateRef);\n    }\n    if (this.setTimeoutRef) {\n      clearTimeout(this.setTimeoutRef);\n    }\n  }\n\n  private getLengthOfPath(pth: SVGPathElement) {\n    if (isPlatformServer(this.platformId)) {\n      return 0;\n    }\n\n    return pth.getTotalLength();\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/contributor-card.component.ts",
    "content": "import { Component, input, signal } from '@angular/core';\nimport { Contributor } from '../services/contributors.service';\n\n@Component({\n  selector: 'ngrx-contributor-card',\n  template: `\n    <div class=\"contributor\">\n      <div class=\"contributor-photo\">\n        <img\n          [src]=\"'/images/bios/' + contributor().picture\"\n          alt=\"{{ contributor().name }}\"\n        />\n      </div>\n\n      <div class=\"contributor-info\">\n        <h3>{{ contributor().name }}</h3>\n\n        <div class=\"contributor-links\">\n          @if (contributor().twitter) {\n            <a\n              [href]=\"'https://twitter.com/' + contributor().twitter\"\n              target=\"_blank\"\n            >\n              <img src=\"/images/bios/card-icons/twitter.svg\" alt=\"Twitter\" />\n            </a>\n          }\n          @if (contributor().website) {\n            <a [href]=\"contributor().website\" target=\"_blank\">\n              <img src=\"/images/bios/card-icons/link.svg\" alt=\"Website\" />\n            </a>\n          }\n        </div>\n\n        <p (click)=\"toggleBio()\" class=\"view-bio\">View Bio</p>\n      </div>\n\n      <div\n        class=\"contributor-bio-preview\"\n        [class.show]=\"bioVisible()\"\n        (click)=\"toggleBio()\"\n      >\n        <p>{{ contributor().bio }}</p>\n      </div>\n    </div>\n  `,\n  styles: [\n    `\n      .contributor {\n        overflow: hidden;\n        position: relative;\n        width: fit-content;\n        align-items: center;\n        min-height: 240px;\n        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);\n      }\n      .contributor-photo {\n        width: 200px;\n        height: 210px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        overflow: hidden;\n        background: var(--ngrx-text);\n        border-bottom: solid 4px var(--ngrx-accent);\n      }\n      .contributor-photo img {\n        height: 100%;\n        width: auto;\n        object-fit: cover;\n        display: block;\n      }\n      .contributor-info {\n        width: 200px;\n        padding: 15px 0 0;\n        text-align: center;\n        background: var(--ngrx-bg-overlay);\n      }\n      .contributor-info h3 {\n        margin-top: 0;\n        margin-bottom: 8px;\n        font-size: 16px;\n        font-weight: 500;\n      }\n      .view-bio {\n        margin: 10px 0 0;\n        color: var(--ngrx-accent);\n        font-size: 14px;\n        cursor: pointer;\n        background: var(--ngrx-bg-elevated);\n        padding: 6px 0;\n      }\n      .view-bio:hover {\n        color: var(--ngrx-text);\n      }\n      .contributor-links {\n        display: flex;\n        justify-content: center;\n        gap: 10px;\n      }\n      .contributor-links img {\n        opacity: 0.8;\n      }\n      .contributor-links a:hover img {\n        opacity: 1;\n      }\n      .contributor-bio-preview {\n        position: absolute;\n        top: 0;\n        right: 0;\n        width: 100%;\n        height: 100%;\n        background: var(--ngrx-bg-overlay);\n        color: var(--ngrx-text);\n        padding: 15px;\n        overflow: hidden;\n        display: flex;\n        flex-direction: column;\n        justify-content: space-between;\n        transform: translateX(100%);\n        transition: transform 0.3s ease-in-out;\n        pointer-events: none;\n        z-index: 2;\n        cursor: pointer;\n        background-image: url('/images/bios/card-icons/back.svg');\n        background-repeat: no-repeat;\n        background-position: right 12px bottom 12px;\n      }\n      .contributor-bio-preview.show {\n        transform: translateX(0);\n        pointer-events: auto;\n      }\n      .contributor-bio-preview p {\n        margin: 0;\n        color: var(--ngrx-text);\n        font-size: 12px;\n        line-height: 16px;\n      }\n    `,\n  ],\n})\nexport class ContributorCardComponent {\n  contributor = input.required<Contributor>();\n  bioVisible = signal(false);\n\n  toggleBio() {\n    this.bioVisible.set(!this.bioVisible());\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/contributor-list.component.ts",
    "content": "import { Component, input } from '@angular/core';\nimport { ContributorCardComponent } from './contributor-card.component';\nimport { Contributor } from '../services/contributors.service';\n\n@Component({\n  selector: 'ngrx-contributor-list',\n  imports: [ContributorCardComponent],\n  template: `\n    @if (contributors() && contributors().length > 0) {\n    <div class=\"contributor-list\">\n      @for (contributor of contributors(); track contributor.name) {\n      <ngrx-contributor-card\n        [contributor]=\"contributor\"\n      ></ngrx-contributor-card>\n      }\n    </div>\n    } @else {\n    <p>No contributors found</p>\n    }\n  `,\n  styles: [\n    `\n      .contributor-list {\n        margin-top: 30px;\n        display: grid;\n        grid-template-columns: repeat(4, 1fr);\n        gap: 24px;\n        margin-bottom: 50px;\n        justify-items: center;\n        width: 900px;\n        max-width: 100%;\n        @media only screen and (max-width: 900px) {\n          grid-template-columns: repeat(3, 1fr);\n        }\n        @media only screen and (max-width: 600px) {\n          grid-template-columns: repeat(2, 1fr);\n        }\n        @media only screen and (max-width: 480px) {\n          grid-template-columns: repeat(1, 1fr);\n        }\n      }\n    `,\n  ],\n})\nexport class ContributorListComponent {\n  contributors = input<Contributor[]>([]);\n}\n"
  },
  {
    "path": "projects/www/src/app/components/contributor-navigation.component.ts",
    "content": "import { Component, input, output } from '@angular/core';\nimport { GroupNav } from '../services/contributors.service';\n\n@Component({\n  selector: 'ngrx-contributor-navigation',\n  template: `\n    <div class=\"groups-navigation\">\n      @for (group of groupNames(); track group.name) {\n        <p\n          (click)=\"selectGroup(group.name)\"\n          [class.selected]=\"selectedGroup() === group.name\"\n        >\n          {{ group.name }}\n        </p>\n      }\n    </div>\n  `,\n  styles: [\n    `\n      .groups-navigation {\n        display: flex;\n        background: var(--ngrx-bg-elevated);\n        width: fit-content;\n        padding: 5px;\n        gap: 5px;\n        margin-top: 50px;\n        margin-bottom: 50px;\n        border-radius: 5px;\n      }\n      .groups-navigation p {\n        color: var(--ngrx-text-muted);\n        padding: 2px 10px;\n        cursor: pointer;\n        margin: 0;\n        border-radius: 2px;\n        font-size: 18px;\n      }\n      .groups-navigation p.selected {\n        background: var(--ngrx-bg-overlay);\n        color: var(--ngrx-text);\n      }\n    `,\n  ],\n})\nexport class ContributorNavigationComponent {\n  groupNames = input<GroupNav[]>();\n  selectedGroup = input<string>();\n  groupSelected = output<string>();\n\n  selectGroup(name: string) {\n    this.groupSelected.emit(name);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/alert.component.ts",
    "content": "import { Component, Input } from '@angular/core';\n\nexport type AlertType = 'inform' | 'warn' | 'error' | 'help';\n\n@Component({\n  selector: 'ngrx-alert',\n  standalone: true,\n  template: ` <ng-content></ng-content> `,\n  host: {\n    '[class.inform]': 'isInform',\n    '[class.warn]': 'isWarn',\n    '[class.error]': 'isError',\n    '[class.help]': 'isHelp',\n  },\n  styles: [\n    `\n      :host {\n        display: block;\n        padding: 0px 16px 16px;\n        margin: 14px 0;\n        border-left: 8px solid;\n        border-top: 1px solid;\n        border-bottom: 1px solid;\n        border-right: 1px solid;\n        border-color: var(--ngrx-border-color);\n      }\n\n      :host p {\n        margin: 0;\n      }\n\n      :host(.inform) {\n        border-color: rgb(97, 174, 238);\n        background-color: rgba(97, 174, 238, 0.12);\n      }\n\n      :host(.warn) {\n        border-color: rgb(255, 184, 113);\n        background-color: rgba(255, 184, 113, 0.12);\n      }\n\n      :host(.error) {\n        border-color: rgb(220, 53, 69);\n        background-color: rgba(220, 53, 69, 0.12);\n      }\n\n      :host(.help) {\n        border-color: var(--ngrx-link);\n        background-color: rgba(255, 172, 230, 0.08);\n      }\n\n      :host + h2 {\n        margin-top: 0;\n      }\n    `,\n  ],\n})\nexport class AlertComponent {\n  #type: AlertType = 'inform';\n\n  @Input() set type(type: AlertType) {\n    this.#type = type;\n\n    if (\n      type !== 'inform' &&\n      type !== 'warn' &&\n      type !== 'error' &&\n      type !== 'help'\n    ) {\n      throw new Error(\n        `Invalid alert type: ${type}. Must be: 'inform', 'warn', 'error', or 'help'.`\n      );\n    }\n  }\n\n  get isInform() {\n    return this.#type === 'inform';\n  }\n\n  get isWarn() {\n    return this.#type === 'warn';\n  }\n\n  get isError() {\n    return this.#type === 'error';\n  }\n\n  get isHelp() {\n    return this.#type === 'help';\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/code-example.component.ts",
    "content": "import {\n  AfterViewInit,\n  Component,\n  ElementRef,\n  inject,\n  Input,\n  PLATFORM_ID,\n  signal,\n  viewChild,\n} from '@angular/core';\nimport { isPlatformServer } from '@angular/common';\nimport { MatIcon } from '@angular/material/icon';\nimport { CodeHighlightPipe } from './code-highlight.pipe';\nimport { ExamplesService } from '@ngrx-io/app/examples/examples.service';\n\n@Component({\n  selector: 'ngrx-code-example',\n  standalone: true,\n  imports: [MatIcon, CodeHighlightPipe],\n  template: `\n    @if (header) {\n      <div class=\"header\">{{ header }}</div>\n    }\n\n    <div class=\"body\">\n      <button\n        class=\"copy-button\"\n        [class.copied]=\"copied()\"\n        (click)=\"copyCode()\"\n        (animationend)=\"onAnimationEnd($event)\"\n        [title]=\"copied() ? 'Copied!' : 'Copy code'\"\n        [attr.aria-label]=\"\n          copied() ? 'Code copied to clipboard' : 'Copy code to clipboard'\n        \"\n      >\n        @if (copied()) {\n          <mat-icon>done</mat-icon>\n        } @else {\n          <mat-icon>content_copy</mat-icon>\n        }\n      </button>\n      <div #codeBody>\n        @if (snippet || path) {\n          <div [innerHTML]=\"codeContent() | ngrxCodeHighlight: language\"></div>\n        } @else {\n          <ng-content />\n        }\n      </div>\n    </div>\n  `,\n  styles: [\n    `\n      :host {\n        display: flex;\n        flex-direction: column;\n        border: 1px solid var(--ngrx-border-color);\n        margin: 14px 0 24px;\n      }\n\n      :host ::ng-deep pre:has(code) {\n        margin: 0;\n        border: 0;\n      }\n\n      .header {\n        padding: 8px 16px;\n        background-color: var(--ngrx-bg-elevated);\n        border-bottom: 1px solid var(--ngrx-border-color);\n        font-size: 12px;\n        font-weight: 500;\n      }\n\n      .body {\n        padding: 0 0px;\n        overflow-x: wrap;\n        position: relative;\n      }\n\n      .copy-button {\n        position: absolute;\n        top: 8px;\n        right: 8px;\n        cursor: pointer;\n        padding: 3px;\n        transition: all 0.2s ease;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        border: none;\n        background: none;\n      }\n\n      .copy-button.copied {\n        animation: copyFeedback 1s ease-in-out;\n      }\n\n      @keyframes copyFeedback {\n        0% {\n          background: rgba(207, 143, 197, 0.6);\n          border-color: rgba(207, 143, 197, 0.4);\n          color: var(--ngrx-text);\n          transform: scale(1);\n        }\n        15% {\n          background: rgba(207, 143, 197, 0.6);\n          border-color: rgba(207, 143, 197, 0.4);\n          color: var(--ngrx-text);\n          transform: scale(1.1);\n        }\n        30% {\n          background: rgba(207, 143, 197, 0.6);\n          border-color: rgba(207, 143, 197, 0.4);\n          color: var(--ngrx-text);\n          transform: scale(1);\n        }\n        80% {\n          background: rgba(207, 143, 197, 0.6);\n          border-color: rgba(207, 143, 197, 0.4);\n          color: var(--ngrx-text);\n          transform: scale(1);\n        }\n        100% {\n          background: var(--ngrx-bg-elevated);\n          border-color: var(--ngrx-border-color);\n          color: var(--ngrx-text-secondary);\n          transform: scale(1);\n        }\n      }\n\n      .copy-button:active {\n        transform: scale(0.95);\n      }\n    `,\n  ],\n})\nexport class CodeExampleComponent implements AfterViewInit {\n  @Input() header = '';\n  @Input() path = '';\n  @Input() region = '';\n  @Input() language = 'typescript';\n  @Input() snippet = '';\n\n  codeBody = viewChild.required<ElementRef>('codeBody');\n  copied = signal(false);\n\n  copyCode() {\n    if (navigator.clipboard && window.isSecureContext) {\n      const codeText = this.codeBody().nativeElement.textContent?.trim() || '';\n      navigator.clipboard.writeText(codeText);\n      this.copied.set(true);\n    }\n  }\n\n  onAnimationEnd(_event: AnimationEvent) {\n    this.copied.set(false);\n  }\n\n  private exampleService = inject(ExamplesService);\n  private platformId = inject(PLATFORM_ID);\n  protected codeContent = signal('');\n\n  async ngAfterViewInit() {\n    if (isPlatformServer(this.platformId)) return;\n    if (this.snippet) {\n      this.codeContent.set(this.snippet);\n      return;\n    }\n\n    if (!this.path) return;\n\n    const content = await this.exampleService.extractSnippet(\n      this.path,\n      this.region\n    );\n    this.codeContent.set(content);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/code-highlight.pipe.ts",
    "content": "import { inject, Pipe, PipeTransform } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { ngrxTheme } from '@ngrx-io/shared/ngrx-shiki-theme';\nimport {\n  BundledLanguage,\n  BundledTheme,\n  getHighlighter,\n  HighlighterGeneric,\n} from 'shiki';\n\nlet highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;\ngetHighlighter({\n  langs: ['typescript', 'sh', 'html'],\n  themes: [ngrxTheme],\n}).then((h) => (highlighter = h));\n\n@Pipe({\n  name: 'ngrxCodeHighlight',\n  standalone: true,\n  pure: true,\n})\nexport class CodeHighlightPipe implements PipeTransform {\n  private sanitizer = inject(DomSanitizer);\n\n  transform(code: string, language = 'typescript'): SafeHtml {\n    const html = highlighter?.codeToHtml(code, {\n      lang: language,\n      theme: 'ngrx-theme',\n    });\n\n    return this.sanitizer.bypassSecurityTrustHtml(html);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/code-tabs.component.ts",
    "content": "import {\n  Component,\n  ElementRef,\n  inject,\n  signal,\n  viewChild,\n  AfterContentInit,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { MatTabsModule } from '@angular/material/tabs';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { CodeExampleComponent } from './code-example.component';\n@Component({\n  selector: 'ngrx-code-tabs',\n  standalone: true,\n  imports: [CommonModule, MatTabsModule, CodeExampleComponent],\n  template: `\n    <div #content style=\"display: none\"><ng-content></ng-content></div>\n\n    <mat-tab-group [preserveContent]=\"true\">\n      @for (tab of tabs(); track tab) {\n        <mat-tab [label]=\"tab.header\">\n          <ngrx-code-example [innerHTML]=\"tab.code\"> </ngrx-code-example>\n        </mat-tab>\n      }\n    </mat-tab-group>\n  `,\n  styles: [\n    `\n      ngrx-code-example {\n        margin: 0;\n      }\n    `,\n  ],\n})\nexport class CodeTabsComponent implements AfterContentInit {\n  private domSanitizer = inject(DomSanitizer);\n  private content = viewChild.required<ElementRef>('content');\n  protected tabs = signal<TabInfo[]>([]);\n\n  async ngAfterContentInit() {\n    // Wait a short period for content projection to complete because the content is read asynchronously\n    await new Promise((res) => setTimeout(res, 1000));\n\n    const codeExamples =\n      this.content().nativeElement.querySelectorAll('ngrx-code-example') ?? [];\n    const examples: TabInfo[] = [...codeExamples].map((example) =>\n      this.extractTabInfo(example)\n    );\n    this.tabs.set(examples);\n  }\n\n  private extractTabInfo(tabContent: HTMLElement): TabInfo {\n    return {\n      code: this.domSanitizer.bypassSecurityTrustHtml(\n        tabContent.querySelector('pre')?.parentElement?.innerHTML ?? ''\n      ),\n      header: tabContent.getAttribute('header') || '',\n    };\n  }\n}\n\nexport interface TabInfo {\n  code: SafeHtml;\n  header: string;\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/deprecated-chip.component.ts",
    "content": "import { Component, input } from '@angular/core';\nimport { MatTooltipModule } from '@angular/material/tooltip';\n\n@Component({\n  selector: 'ngrx-deprecated-chip',\n  standalone: true,\n  imports: [MatTooltipModule],\n  template: ` <span [matTooltip]=\"reason()\">Deprecated</span> `,\n  styles: [\n    `\n      :host {\n        display: flex;\n        width: 84px;\n        height: 24px;\n        justify-content: center;\n        align-items: center;\n        flex-grow: 0;\n        flex-shrink: 0;\n        font-family: 'Oxanium', monospace;\n        text-transform: uppercase;\n        font-weight: 700;\n        font-size: 10px;\n        background-color: rgba(255, 0, 0, 0.36);\n        padding: 4px 8px;\n        border-radius: 4px;\n      }\n    `,\n  ],\n})\nexport class DeprecatedChipComponent {\n  reason = input.required<string>();\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/inline-markdown.pipe.ts",
    "content": "import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { Marked } from 'marked';\n\n@Pipe({\n  name: 'ngrxInlineMarkdown',\n  standalone: true,\n  pure: true,\n})\nexport class InlineMarkdownPipe implements PipeTransform {\n  marked = new Marked();\n  domSanitizer = inject(DomSanitizer);\n\n  transform(value: string): SafeHtml {\n    return this.domSanitizer.bypassSecurityTrustHtml(\n      this.marked.parseInline(value) as string\n    );\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/install-instructions.component.ts",
    "content": "import { Component, computed, input } from '@angular/core';\nimport { MatTabsModule } from '@angular/material/tabs';\nimport { CodeExampleComponent } from './code-example.component';\n\n@Component({\n  selector: 'ngrx-install-instructions',\n  standalone: true,\n  imports: [MatTabsModule, CodeExampleComponent],\n  template: `\n    <mat-tab-group [preserveContent]=\"true\">\n      <mat-tab label=\"npm\">\n        <p>\n          For more information on using <code>npm</code> check out the docs\n          <a\n            href=\"https://docs.npmjs.com/cli/install\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            >here</a\n          >.\n        </p>\n        <ngrx-code-example [snippet]=\"npmCommand()\" language=\"sh\" />\n      </mat-tab>\n      <mat-tab label=\"pnpm\">\n        <p>\n          For more information on using <code>pnpm</code> check out the docs\n          <a\n            href=\"https://pnpm.io/cli/add\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            >here</a\n          >.\n        </p>\n        <ngrx-code-example [snippet]=\"pnpmCommand()\" language=\"sh\" />\n      </mat-tab>\n      <mat-tab label=\"yarn\">\n        <p>\n          For more information on using <code>yarn</code> check out the docs\n          <a\n            href=\"https://yarnpkg.com/getting-started/usage#installing-all-the-dependencies\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            >here</a\n          >.\n        </p>\n        <ngrx-code-example [snippet]=\"yarnCommand()\" language=\"sh\" />\n      </mat-tab>\n    </mat-tab-group>\n  `,\n})\nexport class InstallInstructionsComponent {\n  packageName = input('');\n  devDependency = input(false);\n\n  npmCommand = computed(\n    () =>\n      `npm install ${this.packageName()}${this.devDependency() ? ' --save-dev' : ''}`\n  );\n\n  pnpmCommand = computed(\n    () =>\n      `pnpm add ${this.packageName()}${this.devDependency() ? ' --save-dev' : ''}`\n  );\n\n  yarnCommand = computed(\n    () =>\n      `yarn add ${this.packageName()}${this.devDependency() ? ' --dev' : ''}`\n  );\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/markdown-article.component.ts",
    "content": "import {\n  Component,\n  ElementRef,\n  OnDestroy,\n  Signal,\n  afterNextRender,\n  inject,\n  signal,\n  viewChild,\n} from '@angular/core';\nimport { MatIcon } from '@angular/material/icon';\nimport { Router } from '@angular/router';\n\ntype Heading = { level: number; text: string; id: string; url: string };\n\n@Component({\n  selector: 'ngrx-markdown-article',\n  standalone: true,\n  imports: [MatIcon],\n  template: `\n    <article #article>\n      <ng-content></ng-content>\n    </article>\n    <menu>\n      <div class=\"content-menu\" (click)=\"isMenuOpen.set(!isMenuOpen())\">\n        <mat-icon>library_books</mat-icon>\n        @if (isMenuOpen()) {\n          <mat-icon>keyboard_arrow_up</mat-icon>\n        } @else {\n          <mat-icon>keyboard_arrow_down</mat-icon>\n        }\n      </div>\n      <div class=\"content-menu-holder\" [class.open]=\"isMenuOpen()\">\n        @for (heading of headings(); track $index) {\n          <a\n            [href]=\"heading.url\"\n            [style]=\"{ paddingLeft: 24 + (heading.level - 2) * 8 + 'px' }\"\n            [class.active]=\"activeHeadingId() === heading.id\"\n            (click)=\"navigateToHeading($event, heading)\"\n          >\n            {{ heading.text }}\n          </a>\n        }\n      </div>\n    </menu>\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n        width: 100%;\n        position: relative;\n        padding-right: 240px;\n        @media only screen and (max-width: 1280px) {\n          padding-top: 40px;\n          padding-right: 0px;\n          display: flex;\n          flex-direction: column-reverse;\n        }\n      }\n\n      menu {\n        display: flex;\n        width: 240px;\n        flex-direction: column;\n        gap: 6px;\n        position: fixed;\n        top: calc(var(--top-banner-height, 0px) + 24px);\n        right: 24px;\n        margin: 0;\n        padding: 0;\n        border-left: 1px solid var(--ngrx-border-color);\n        @media only screen and (max-width: 1280px) {\n          position: relative;\n          top: 0;\n          width: 100%;\n          padding: 24px 24px 0;\n          right: 0px;\n        }\n      }\n\n      menu a {\n        color: var(--ngrx-text-muted);\n        font-size: 13px;\n        border-left: 2px solid transparent;\n      }\n\n      menu a:hover {\n        color: var(--ngrx-text-primary);\n      }\n\n      menu a.active {\n        color: var(--ngrx-text-primary);\n        border-color: var(--ngrx-accent);\n      }\n\n      .content-menu-holder {\n        display: flex;\n        flex-direction: column;\n        gap: 6px;\n        @media only screen and (max-width: 1280px) {\n          display: none;\n          &.open {\n            display: flex;\n          }\n        }\n      }\n\n      .content-menu {\n        align-items: center;\n        justify-content: space-between;\n        padding: 5px 10px;\n        background: var(--ngrx-bg-content-menu);\n        border-radius: 5px;\n        display: flex;\n        margin-bottom: 10px;\n        cursor: pointer;\n        display: none;\n        @media only screen and (max-width: 1280px) {\n          display: flex;\n        }\n        &:hover {\n          background: var(--ngrx-bg-content-menu-hover);\n        }\n      }\n\n      article {\n        max-width: 960px;\n        width: calc(100% - 120px);\n        padding: 24px;\n        margin: 0 auto;\n        @media only screen and (max-width: 1500px) {\n          margin: 0px;\n        }\n        @media only screen and (max-width: 1280px) {\n          max-width: 100%;\n          width: 100%;\n          margin: 0px;\n        }\n      }\n\n      article ::ng-deep h1 {\n        font-size: 32px;\n      }\n\n      article ::ng-deep p:not(ngrx-alert p),\n      article ::ng-deep li {\n        opacity: 0.8;\n      }\n\n      article ::ng-deep code:not(pre code) {\n        font-weight: 600;\n      }\n\n      article ::ng-deep table {\n        border-collapse: collapse;\n        border-top: 1px solid var(--ngrx-border-color);\n        border-left: 1px solid var(--ngrx-border-color);\n        border-right: 1px solid var(--ngrx-border-color);\n        margin: 14px 0;\n        @media only screen and (max-width: 1280px) {\n          display: block;\n          overflow-x: scroll;\n        }\n      }\n\n      article ::ng-deep table thead {\n        background-color: var(--ngrx-table-header-bg);\n        font-family: 'Oxanium', sans-serif;\n      }\n\n      article ::ng-deep table tr {\n        border-bottom: 1px solid var(--ngrx-border-color);\n      }\n\n      article ::ng-deep table th,\n      article ::ng-deep table td {\n        padding: 16px;\n        text-align: left;\n      }\n\n      article ::ng-deep table td code {\n        white-space: nowrap;\n      }\n\n      article ::ng-deep pre:has(code) {\n        padding: 16px 20px;\n      }\n    `,\n  ],\n})\nexport class MarkdownArticleComponent implements OnDestroy {\n  router = inject(Router);\n  articleRef: Signal<ElementRef<HTMLElement>> = viewChild.required('article');\n  headings = signal<Heading[]>([]);\n  activeHeadingId = signal<string | null>(null);\n  isMenuOpen = signal(false);\n  mutationObserver?: MutationObserver;\n  intersectionObserver?: IntersectionObserver;\n\n  constructor() {\n    afterNextRender(() => {\n      this.mutationObserver = new MutationObserver(() =>\n        this.collectHeadings()\n      );\n      this.mutationObserver.observe(this.articleRef().nativeElement, {\n        childList: true,\n        subtree: true,\n      });\n\n      this.collectHeadings();\n    });\n  }\n\n  ngOnDestroy(): void {\n    this.mutationObserver?.disconnect();\n    this.intersectionObserver?.disconnect();\n  }\n\n  navigateToHeading($event: MouseEvent, heading: Heading) {\n    $event.preventDefault();\n\n    this.router.navigate([], { fragment: heading.id }).then(() => {\n      const element = document.getElementById(heading.id);\n      if (element) {\n        element.scrollIntoView({ behavior: 'smooth' });\n      }\n    });\n  }\n\n  private collectHeadings() {\n    const headingElements = this.articleRef().nativeElement.querySelectorAll(\n      'h1, h2, h3, h4, h5, h6'\n    );\n    const headings: Heading[] = [];\n\n    for (const heading of Array.from(headingElements)) {\n      const textContent = heading.textContent ?? '';\n      const id = textContent\n        .toLowerCase()\n        .replaceAll(' ', '-')\n        .replaceAll(':', '')\n        .replaceAll('@', '')\n        .replaceAll('/', '-');\n      heading.id = id;\n\n      const currentUrl = this.router.url;\n      const currentUrlWithoutHash = currentUrl.split('#')[0];\n      const url = `${currentUrlWithoutHash}#${id}`;\n\n      headings.push({\n        level: parseInt(heading.tagName[1]),\n        text: textContent,\n        id,\n        url,\n      });\n    }\n\n    this.headings.set(headings);\n    this.watchHeadings();\n  }\n\n  private watchHeadings() {\n    if (this.intersectionObserver) {\n      this.intersectionObserver.disconnect();\n      this.intersectionObserver = undefined;\n    }\n\n    this.intersectionObserver = new IntersectionObserver(\n      (entries) => {\n        const intersectingEntries = entries.filter(\n          (entry) => entry.isIntersecting\n        );\n        const firstHeadingIntersecting = this.headings().find((heading) =>\n          intersectingEntries.some((e) => e.target.id === heading.id)\n        );\n\n        if (firstHeadingIntersecting) {\n          this.activeHeadingId.set(firstHeadingIntersecting.id);\n        }\n      },\n      { threshold: 1 }\n    );\n\n    const headingElements = this.articleRef().nativeElement.querySelectorAll(\n      'h1, h2, h3, h4, h5, h6'\n    );\n\n    for (const heading of Array.from(headingElements)) {\n      this.intersectionObserver.observe(heading);\n    }\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/markdown-symbol-link.component.ts",
    "content": "import { Component, Input, ViewEncapsulation } from '@angular/core';\nimport { SymbolLinkComponent } from './symbol-link.component';\nimport { CanonicalReference } from '@ngrx-io/shared';\n\n@Component({\n  selector: 'ngrx-markdown-symbol-link',\n  standalone: true,\n  imports: [SymbolLinkComponent],\n  template: ` <ngrx-symbol-link [reference]=\"reference\"></ngrx-symbol-link> `,\n  encapsulation: ViewEncapsulation.None,\n  styles: [\n    `\n      ngrx-docs-symbol-link {\n        color: var(--ngrx-link);\n      }\n\n      ngrx-docs-symbol-link a {\n        font-family: 'Space Mono', monospace;\n        font-variant-ligatures: none;\n        font-weight: 600;\n        text-decoration: none;\n      }\n    `,\n  ],\n})\nexport class MarkdownSymbolLinkComponent {\n  @Input() reference: CanonicalReference = '@ngrx/store!Store:class';\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/markdown.pipe.ts",
    "content": "import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { Marked } from 'marked';\nimport { markedHighlight } from 'marked-highlight';\nimport hljs from 'highlight.js/lib/core';\nimport { CanonicalReference, ParsedCanonicalReference } from '@ngrx-io/shared';\n\nexport const CanonicalReferenceExtension = {\n  name: 'canonicalReference',\n  level: 'inline',\n  tokenizer(src: string): any {\n    const rule = /@?[\\w/]+![\\w]+:[\\w]+/;\n    const match = rule.exec(src);\n    console.log({ src, match });\n    if (match) {\n      const parsed = new ParsedCanonicalReference(\n        match[0] as CanonicalReference\n      );\n      const [before, after] = src.split(match[0]);\n      return {\n        type: 'canonicalReference',\n        raw: src,\n        name: parsed.name,\n        canonicalReference: parsed.referenceString,\n        before,\n        after,\n        tokens: [],\n      };\n    }\n  },\n  renderer(this: any, token: any) {\n    return `${token.before}<ngrx-docs-symbol-link reference=\"${token.canonicalReference}\" />${token.after}`;\n  },\n  childTokens: [],\n};\n\n@Pipe({\n  name: 'ngrxMarkdown',\n  standalone: true,\n  pure: true,\n})\nexport class MarkdownPipe implements PipeTransform {\n  marked = new Marked(\n    markedHighlight({\n      langPrefix: 'hljs language-',\n      highlight(code, lang, _info) {\n        const language = hljs.getLanguage(lang) ? lang : 'plaintext';\n        return hljs.highlight(code, { language }).value;\n      },\n    })\n  );\n  domSanitizer = inject(DomSanitizer);\n\n  transform(value: string): SafeHtml {\n    this.marked.use({ extensions: [CanonicalReferenceExtension] });\n    return this.domSanitizer.bypassSecurityTrustHtml(\n      this.marked.parse(value) as string\n    );\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/stackblitz.component.ts",
    "content": "import { isPlatformServer } from '@angular/common';\nimport {\n  AfterViewInit,\n  Component,\n  ElementRef,\n  Input,\n  PLATFORM_ID,\n  ViewEncapsulation,\n  inject,\n  viewChild,\n} from '@angular/core';\nimport { ExamplesService } from '@ngrx-io/app/examples/examples.service';\n\n@Component({\n  selector: 'ngrx-docs-stackblitz',\n  standalone: true,\n  template: `\n    @if (isEmbedded) {\n      <div [attr.title]=\"name\" #example></div>\n    } @else {\n      <a (click)=\"openStackblitz()\" [attr.title]=\"name\"\n        ><ng-content>StackBlitz example</ng-content></a\n      >\n    }\n  `,\n  encapsulation: ViewEncapsulation.None,\n  styles: [\n    `\n      ngrx-docs-stackblitz iframe {\n        display: block;\n        width: 100%;\n        height: 800px;\n        border: none;\n      }\n    `,\n  ],\n})\nexport class StackblitzComponent implements AfterViewInit {\n  examplesService = inject(ExamplesService);\n  platformId = inject(PLATFORM_ID);\n  @Input() name = '__base';\n  @Input() embedded = 'false';\n\n  exampleRef = viewChild.required<ElementRef<HTMLDivElement>>('example');\n\n  ngAfterViewInit(): void {\n    if (isPlatformServer(this.platformId)) return;\n    if (!this.isEmbedded) return;\n\n    this.examplesService.load(this.exampleRef().nativeElement, this.name);\n  }\n\n  openStackblitz(): void {\n    this.examplesService.open(this.name);\n  }\n\n  get isEmbedded(): boolean {\n    return this.embedded !== 'false';\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-api.component.ts",
    "content": "import { Component, computed, input } from '@angular/core';\nimport {\n  ApiExcerptToken,\n  ApiExcerptTokenKind,\n  ApiMember,\n  ApiMemberKind,\n} from '@ngrx-io/shared';\nimport { SymbolExcerptComponent } from './symbol-excerpt.component';\nimport { SymbolExcerptGroupComponent } from './symbol-excerpt-group.component';\n\n@Component({\n  selector: 'ngrx-symbol-api',\n  standalone: true,\n  imports: [SymbolExcerptComponent, SymbolExcerptGroupComponent],\n  template: `\n    <h4>{{ '@api' }}</h4>\n    <ngrx-symbol-excerpt-group>\n      <ngrx-symbol-excerpt [excerptTokens]=\"headerExcerptTokens()\" />\n      @for (member of bodyMembers(); track $index) {\n        <ngrx-symbol-excerpt\n          [excerptTokens]=\"member.excerptTokens\"\n          [deprecated]=\"!!member.docs.deprecated\"\n          class=\"memberExcerpt\"\n        />\n      }\n      @if (footerExcerptTokens().length) {\n        <ngrx-symbol-excerpt [excerptTokens]=\"footerExcerptTokens()\" />\n      }\n    </ngrx-symbol-excerpt-group>\n  `,\n  styles: [\n    `\n      :host {\n        display: flex;\n        padding: 8px;\n        flex-direction: column;\n        border-top: 1px solid var(--ngrx-border-color);\n      }\n\n      h4 {\n        font-family: 'Oxanium', monospace;\n        font-size: 15px;\n        font-weight: 700;\n        margin: 0;\n        padding: 8px;\n        color: var(--ngrx-link);\n      }\n\n      .memberExcerpt {\n        margin-left: 16px;\n      }\n    `,\n  ],\n})\nexport class SymbolApiComponent {\n  symbol = input.required<ApiMember>();\n  headerExcerptTokens = computed((): ApiExcerptToken[] => {\n    const symbol = this.symbol();\n\n    if (\n      symbol.kind === ApiMemberKind.Class ||\n      symbol.kind === ApiMemberKind.Interface ||\n      symbol.kind === ApiMemberKind.Enum\n    ) {\n      return [\n        ...symbol.excerptTokens,\n        {\n          kind: ApiExcerptTokenKind.Content,\n          text: ' {',\n        },\n      ];\n    }\n\n    return symbol.excerptTokens;\n  });\n  bodyMembers = computed((): ApiMember[] => {\n    const symbol = this.symbol();\n\n    if (\n      symbol.kind === ApiMemberKind.Class ||\n      symbol.kind === ApiMemberKind.Interface ||\n      symbol.kind === ApiMemberKind.Enum\n    ) {\n      const members = symbol.members ?? [];\n      const [nonDeprecatedMembers, deprecatedMembers] = members.reduce(\n        (acc, member) => {\n          if (member.docs.deprecated) {\n            acc[1].push(member);\n          } else {\n            acc[0].push(member);\n          }\n\n          return acc;\n        },\n        [[], []] as [ApiMember[], ApiMember[]]\n      );\n\n      return [...nonDeprecatedMembers, ...deprecatedMembers];\n    }\n\n    return [];\n  });\n  footerExcerptTokens = computed((): ApiExcerptToken[] => {\n    const symbol = this.symbol();\n\n    if (\n      symbol.kind === ApiMemberKind.Class ||\n      symbol.kind === ApiMemberKind.Interface ||\n      symbol.kind === ApiMemberKind.Enum\n    ) {\n      return [\n        {\n          kind: ApiExcerptTokenKind.Content,\n          text: '}',\n        },\n      ];\n    }\n\n    return [];\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-chip.component.ts",
    "content": "import { NgClass } from '@angular/common';\nimport { Component, computed, input } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport {\n  MinimizedApiMemberSummary,\n  ParsedCanonicalReference,\n} from '@ngrx-io/shared';\n\n@Component({\n  selector: 'ngrx-symbol-chip',\n  template: `\n    <a [routerLink]=\"url()\">\n      <span [ngClass]=\"kind()\">{{ firstLetterOfKind() }}</span>\n      {{ symbol().name }}\n    </a>\n  `,\n  styles: [\n    `\n      a {\n        font-family: 'Oxanium', sans-serif;\n        display: flex;\n        align-items: center;\n        text-decoration: none;\n        cursor: pointer;\n        color: var(--ngrx-text-secondary);\n        transition: color 0.2s;\n      }\n\n      a:hover {\n        color: var(--ngrx-accent);\n      }\n\n      span {\n        display: inline-block;\n        width: 1.5em;\n        text-align: center;\n        margin: 0 0.5em 0 0;\n        border-radius: 2px;\n        font-weight: bold;\n        font-size: 12px;\n      }\n\n      .EntryPoint {\n        background-color: #f8d7da;\n        color: #721c24;\n      }\n\n      .Function {\n        background-color: #d4edda;\n        color: #155724;\n      }\n\n      .Class {\n        background-color: #cce5ff;\n        color: #004085;\n      }\n\n      .TypeAlias {\n        background-color: #fff3cd;\n        color: #856404;\n      }\n\n      .Interface {\n        background-color: #d1ecf1;\n        color: #0c5460;\n      }\n\n      .Enum {\n        background-color: #f8d7da;\n        color: #721c24;\n      }\n\n      .Variable {\n        background-color: #f8d7da;\n        color: #721c24;\n      }\n\n      .Property {\n        background-color: #f8d7da;\n        color: #721c24;\n      }\n\n      .Method {\n        background-color: #d4edda;\n        color: #155724;\n      }\n    `,\n  ],\n  imports: [RouterLink, NgClass],\n})\nexport class SymbolChipComponent {\n  symbol = input.required<MinimizedApiMemberSummary>();\n  kind = computed(() => this.symbol().kind);\n  firstLetterOfKind = computed(() => this.kind().charAt(0).toUpperCase());\n  parsedReference = computed(\n    () => new ParsedCanonicalReference(this.symbol().canonicalReference)\n  );\n  url = computed(() => {\n    const [_ngrx, ...rest] = this.parsedReference().package.split('/');\n    return `/api/${rest.join('/')}/${this.parsedReference().name}`;\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-code-link.component.ts",
    "content": "import { Component, computed, input } from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\n\n@Component({\n  selector: 'ngrx-symbol-code-link',\n  standalone: true,\n  imports: [MatIconModule, MatButtonModule],\n  template: `\n    <a mat-stroked-button [href]=\"url()\" target=\"_blank\">\n      <mat-icon>code</mat-icon>\n      View Source on Github\n    </a>\n  `,\n})\nexport class SymbolCodeLinkComponent {\n  fileUrlPath = input.required<string>();\n  url = computed(() => {\n    const [, fileName] = this.fileUrlPath().split('dist/');\n    const actualFileName = fileName.replace('.d.ts', '.ts');\n\n    return `https://github.com/ngrx/platform/blob/main/${actualFileName}`;\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-excerpt-group.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'ngrx-symbol-excerpt-group',\n  standalone: true,\n  template: `<ng-content></ng-content>`,\n  styles: [\n    `\n      :host {\n        display: flex;\n        flex-direction: column;\n        padding: 8px;\n        gap: 2px;\n        overflow-x: auto;\n      }\n    `,\n  ],\n})\nexport class SymbolExcerptGroupComponent {}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-excerpt.component.ts",
    "content": "import { Component, computed, forwardRef, input } from '@angular/core';\nimport { NgClass } from '@angular/common';\nimport { ApiExcerptToken } from '@ngrx-io/shared';\nimport { SymbolLinkComponent } from './symbol-link.component';\nimport { CodeHighlightPipe } from './code-highlight.pipe';\n\n@Component({\n  selector: 'ngrx-symbol-excerpt',\n  standalone: true,\n  imports: [NgClass, forwardRef(() => SymbolLinkComponent), CodeHighlightPipe],\n  /**\n   * The lack of spacing in the template is intentional. The template is\n   * formatted this way to ensure that the generated code is as compact as\n   * possible, with no spaces between blocks.\n   */\n  template: `\n    <div class=\"links\">\n      @for (excerpt of simplifiedExcerptTokens(); track $index) {\n        @if (excerpt.kind === 'Content') {\n          {{ excerpt.text }}\n        } @else if (excerpt.kind === 'Reference') {\n          <ngrx-symbol-link [reference]=\"excerpt.canonicalReference\" />\n        }\n      }\n    </div>\n    <pre><code [ngClass]=\"{deprecated: deprecated()}\" [innerHTML]=\"joinedContent() | ngrxCodeHighlight\"></code></pre>\n  `,\n  styles: [\n    `\n      :host {\n        position: relative;\n        display: block;\n        padding: 8px;\n        overflow-x: auto;\n      }\n\n      ngrx-symbol-excerpt-group > :host {\n        padding: 0;\n        overflow-x: initial;\n      }\n\n      .links,\n      code {\n        font-family: 'Space Mono', monospace;\n        font-variant-ligatures: none;\n        font-size: 12px;\n      }\n\n      .links {\n        position: absolute;\n        top: 8px;\n        left: 8px;\n        color: transparent;\n        white-space: pre;\n      }\n\n      ngrx-symbol-excerpt-group > :host .links {\n        top: 0;\n        left: 0;\n      }\n\n      pre {\n        margin: 0;\n        overflow-x: initial;\n      }\n\n      .deprecated {\n        text-decoration: line-through;\n        font-style: italic;\n        opacity: 0.72;\n      }\n    `,\n  ],\n})\nexport class SymbolExcerptComponent {\n  excerptTokens = input.required<ApiExcerptToken[]>();\n  deprecated = input<boolean>(false);\n  simplifiedExcerptTokens = computed(() => {\n    return this.excerptTokens().map((token, index) => {\n      if (index !== 0) return token;\n\n      return {\n        ...token,\n        text: token.text\n          .replace('export declare ', '')\n          .replace('export type', 'type')\n          .replace('export interface', 'interface'),\n      };\n    });\n  });\n  joinedContent = computed(() => {\n    return this.simplifiedExcerptTokens()\n      .map((token) => token.text)\n      .join('');\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-header.component.ts",
    "content": "import { Component, computed, input } from '@angular/core';\nimport { ApiMember, ParsedCanonicalReference } from '@ngrx-io/shared';\nimport { SymbolExcerptComponent } from './symbol-excerpt.component';\nimport { NgClass } from '@angular/common';\nimport { DeprecatedChipComponent } from './deprecated-chip.component';\n\n@Component({\n  selector: 'ngrx-symbol-header',\n  standalone: true,\n  imports: [SymbolExcerptComponent, NgClass, DeprecatedChipComponent],\n  template: `\n    <h3 [ngClass]=\"{ deprecated: symbol().docs.deprecated }\">\n      <span class=\"symbolImport\">{{ symbolImport() }}</span>\n      <span class=\"symbolName\">{{ symbol().name }}</span>\n    </h3>\n\n    @if (typeTokenRange().length) {\n      <ngrx-symbol-excerpt [excerptTokens]=\"typeTokenRange()\" />\n    }\n    @if (symbol().docs.deprecated) {\n      <ngrx-deprecated-chip [reason]=\"symbol().docs.deprecated\" />\n    }\n  `,\n  styles: [\n    `\n      :host {\n        display: grid;\n        grid-template-columns: 1fr max-content max-content;\n        align-items: center;\n        padding: 16px;\n        gap: 16px;\n        @media only screen and (max-width: 600px) {\n          grid-template-columns: 1fr;\n        }\n      }\n\n      h3 {\n        margin: 0;\n        display: flex;\n        flex-direction: column;\n        gap: 6px;\n      }\n\n      h3 .symbolImport {\n        font-size: 12px;\n        font-family: 'Oxanium', monospace;\n        font-weight: 700;\n        color: var(--ngrx-link);\n      }\n\n      h3 .symbolName {\n        font-size: 18px;\n        font-family: 'Oxanium', monospace;\n        font-weight: 500;\n      }\n\n      h3.deprecated {\n        text-decoration: line-through;\n      }\n    `,\n  ],\n})\nexport class SymbolHeaderComponent {\n  symbol = input.required<ApiMember>();\n  symbolImport = computed(() => {\n    const parsedRef = new ParsedCanonicalReference(\n      this.symbol().canonicalReference\n    );\n\n    return parsedRef.package;\n  });\n  typeTokenRange = computed(() => {\n    const typeTokenRange = this.symbol().typeTokenRange;\n    const returnTypeTokenRange = this.symbol().returnTypeTokenRange;\n    const variableTypeTokenRange = this.symbol().variableTypeTokenRange;\n\n    if (typeTokenRange) {\n      return this.symbol().excerptTokens.slice(\n        typeTokenRange.startIndex,\n        typeTokenRange.endIndex\n      );\n    }\n\n    if (returnTypeTokenRange) {\n      return this.symbol().excerptTokens.slice(\n        returnTypeTokenRange.startIndex,\n        returnTypeTokenRange.endIndex\n      );\n    }\n\n    if (variableTypeTokenRange) {\n      return this.symbol().excerptTokens.slice(\n        variableTypeTokenRange.startIndex,\n        variableTypeTokenRange.endIndex\n      );\n    }\n\n    return [];\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-link.component.ts",
    "content": "import { Overlay } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport {\n  Component,\n  ElementRef,\n  Injector,\n  Input,\n  inject,\n  viewChild,\n} from '@angular/core';\nimport { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';\nimport { RouterLink } from '@angular/router';\nimport { CanonicalReference, ParsedCanonicalReference } from '@ngrx-io/shared';\nimport { EMPTY, Observable, fromEvent, switchMap, takeUntil } from 'rxjs';\nimport {\n  SYMBOl_POPOVER_REF,\n  SymbolPopoverComponent,\n} from './symbol-popover.component';\nimport { ReferenceService } from '@ngrx-io/app/reference/reference.service';\n\n@Component({\n  selector: 'ngrx-symbol-link',\n  imports: [RouterLink, SymbolPopoverComponent],\n  // Spacing is intentional to avoid unnecessary whitespace in the output\n  template: `@if (isPrivate) {\n      {{ name }}\n    } @else if (shouldUseExternalLink) {\n      <a [href]=\"url\" target=\"_blank\">{{ name }}</a>\n    } @else {\n      <a [routerLink]=\"url\" #internalSymbolLink>{{ name }}</a>\n    }`,\n  styles: [\n    `\n      a {\n        color: inherit;\n        text-decoration: none;\n      }\n    `,\n  ],\n})\nexport class SymbolLinkComponent {\n  injector = inject(Injector);\n  overlay = inject(Overlay);\n  referenceService = inject(ReferenceService);\n  internalSymbolLink =\n    viewChild<ElementRef<HTMLAnchorElement>>('internalSymbolLink');\n  url = '';\n  isPrivate = true;\n  parsedReference: ParsedCanonicalReference = new ParsedCanonicalReference(\n    '@ngrx/store!Store:class'\n  );\n  shouldUseExternalLink = false;\n\n  /**\n   * Signal inputs aren't supported by @angular/elements, so we need\n   * to use a traditional input to set the reference.\n   */\n  @Input({ required: true }) set reference(ref: CanonicalReference) {\n    const parsed = new ParsedCanonicalReference(ref);\n    this.isPrivate = parsed.isPrivate;\n    this.shouldUseExternalLink =\n      parsed.package.startsWith('@angular') || parsed.package === 'rxjs';\n    this.parsedReference = parsed;\n\n    if (parsed.isPrivate) {\n      this.url = '';\n    } else if (parsed.package.startsWith('@ngrx')) {\n      const [_ngrx, ...rest] = parsed.package.split('/');\n      this.url = `/api/${rest.join('/')}/${parsed.name}`;\n    } else if (parsed.package.startsWith('@angular')) {\n      const [, packageName] = parsed.package.split('/');\n\n      this.url = `https://angular.dev/api/${packageName}/${parsed.name}`;\n    } else if (parsed.package === 'rxjs') {\n      this.url = `https://rxjs.dev/api/index/${parsed.kind}/${parsed.name}`;\n    } else {\n      throw new Error(`Unknown package: ${parsed.package}`);\n    }\n  }\n\n  get name() {\n    if (this.parsedReference.isPrivate) {\n      return this.parsedReference.name.slice(1);\n    }\n\n    return this.parsedReference.name;\n  }\n\n  constructor() {\n    toObservable(this.internalSymbolLink)\n      .pipe(\n        switchMap((linkRef) => {\n          if (!linkRef) return EMPTY;\n\n          const link = linkRef.nativeElement;\n\n          return fromEvent(link, 'mouseenter').pipe(\n            switchMap(() =>\n              this.referenceService.loadFromCanonicalReference(\n                this.parsedReference.referenceString\n              )\n            ),\n            switchMap((apiMemberSummary) => {\n              const overlay = this.overlay.create({\n                positionStrategy: this.overlay\n                  .position()\n                  .flexibleConnectedTo(link)\n                  .withPositions([\n                    {\n                      originX: 'center',\n                      originY: 'bottom',\n                      overlayX: 'center',\n                      overlayY: 'top',\n                    },\n                  ]),\n                hasBackdrop: false,\n                scrollStrategy: this.overlay.scrollStrategies.close(),\n              });\n              const injector = Injector.create({\n                parent: this.injector,\n                providers: [\n                  {\n                    provide: SYMBOl_POPOVER_REF,\n                    useValue: apiMemberSummary,\n                  },\n                ],\n              });\n              const componentPortal = new ComponentPortal(\n                SymbolPopoverComponent,\n                null,\n                injector\n              );\n\n              return new Observable(() => {\n                overlay.attach(componentPortal);\n\n                return () => overlay.detach();\n              }).pipe(takeUntil(fromEvent(link, 'mouseleave')));\n            })\n          );\n        }),\n        takeUntilDestroyed()\n      )\n      .subscribe({\n        error: console.error,\n      });\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-methods.component.ts",
    "content": "import { Component, computed, input } from '@angular/core';\nimport { NgClass } from '@angular/common';\nimport { ApiMember, ApiMemberKind } from '@ngrx-io/shared';\nimport { SymbolExcerptComponent } from './symbol-excerpt.component';\nimport { InlineMarkdownPipe } from './inline-markdown.pipe';\nimport { SymbolTypeParamsComponent } from './symbol-type-params.component';\nimport { SymbolParamsComponent } from './symbol-params.component';\nimport { SymbolReturnsComponent } from './symbol-returns.component';\nimport { SymbolApiComponent } from './symbol-api.component';\nimport { DeprecatedChipComponent } from './deprecated-chip.component';\nimport { CodeHighlightPipe } from './code-highlight.pipe';\nimport { SymbolUsageNotesComponent } from './symbol-usage-notes.component';\n\n@Component({\n  selector: 'ngrx-symbol-methods',\n  standalone: true,\n  imports: [\n    NgClass,\n    SymbolApiComponent,\n    SymbolExcerptComponent,\n    SymbolParamsComponent,\n    SymbolTypeParamsComponent,\n    SymbolReturnsComponent,\n    SymbolUsageNotesComponent,\n    InlineMarkdownPipe,\n    DeprecatedChipComponent,\n    CodeHighlightPipe,\n  ],\n  template: `\n    @if (methods().length) {\n      <div class=\"methods\">\n        <h3>{{ '@methods' }}</h3>\n\n        @for (method of methods(); track $index) {\n          <div class=\"method\">\n            <div class=\"header\">\n              <div\n                class=\"methodName\"\n                [ngClass]=\"{ deprecated: method.docs.deprecated }\"\n              >\n                <code\n                  [innerHTML]=\"getMethodSignature(method) | ngrxCodeHighlight\"\n                ></code>\n              </div>\n              @if (method.docs.deprecated) {\n                <ngrx-deprecated-chip [reason]=\"method.docs.deprecated\" />\n              }\n              @if (method.docs.summary; as summary) {\n                <p\n                  class=\"summary\"\n                  [innerHtml]=\"summary | ngrxInlineMarkdown\"\n                ></p>\n              }\n            </div>\n\n            <ngrx-symbol-api [symbol]=\"method\" />\n            <ngrx-symbol-returns [symbol]=\"method\" />\n            <ngrx-symbol-params [symbol]=\"method\" />\n            <ngrx-symbol-type-params [symbol]=\"method\" />\n            <ngrx-symbol-usage-notes [symbol]=\"method\" />\n          </div>\n        }\n      </div>\n    }\n  `,\n  styles: [\n    `\n      .methods {\n        display: block;\n        border-top: 1px solid var(--ngrx-border-color);\n      }\n\n      h3 {\n        font-family: 'Oxanium', monospace;\n        font-size: 15px;\n        font-weight: 700;\n        padding: 8px 16px;\n        color: var(--ngrx-link);\n      }\n\n      .method {\n        display: flex;\n        flex-direction: column;\n        padding: 8px;\n      }\n\n      .header {\n        display: grid;\n        align-items: center;\n        grid-template-columns: 1fr max-content;\n        grid-template-areas:\n          'name deprecated'\n          'summary summary';\n      }\n\n      .summary {\n        font-size: 16px;\n        margin: 0;\n        padding: 8px;\n        grid-area: summary;\n      }\n\n      .methodSymbol {\n        font-weight: 700;\n      }\n\n      .methodName {\n        padding: 8px;\n        display: flex;\n        flex-direction: row;\n        grid-area: name;\n      }\n\n      .methodName.deprecated {\n        text-decoration: line-through;\n        font-style: italic;\n      }\n\n      code {\n        font-family: 'Oxanium', sans-serif;\n        font-size: 18px;\n      }\n\n      ngrx-deprecated-chip {\n        grid-area: deprecated;\n      }\n\n      .returns {\n        grid-area: returns;\n      }\n\n      .summary {\n        grid-area: summary;\n      }\n\n      p {\n        font-size: 13px;\n        padding: 0;\n        margin: 0;\n      }\n\n      ngrx-symbol-api,\n      ngrx-symbol-returns,\n      ngrx-symbol-params,\n      ngrx-symbol-type-params,\n      ngrx-symbol-usage-notes {\n        /* border-top: none; */\n        margin-left: 8px;\n        border-left: 16px solid var(--ngrx-border-color);\n        border-right: 1px solid var(--ngrx-border-color);\n      }\n    `,\n  ],\n})\nexport class SymbolMethodsComponent {\n  symbol = input.required<ApiMember>();\n  methods = computed(() => {\n    const members = this.symbol().members ?? [];\n    const methods = members.filter(\n      (member) => member.kind === ApiMemberKind.Method\n    );\n    const [nonDeprecatedMethods, deprecatedMethods] = methods.reduce(\n      (acc, method) => {\n        if (method.docs.deprecated) {\n          acc[1].push(method);\n        } else {\n          acc[0].push(method);\n        }\n\n        return acc;\n      },\n      [[], []] as [ApiMember[], ApiMember[]]\n    );\n\n    return [...nonDeprecatedMethods, ...deprecatedMethods].map((member) => {\n      const parameters = member.parameters ?? [];\n\n      return {\n        ...member,\n        simpleParameters: parameters.map((param, index) => {\n          return {\n            name: param.parameterName,\n            isLast: index === parameters.length - 1,\n          };\n        }),\n      };\n    });\n  });\n\n  getMethodSeparater(method: ApiMember) {\n    return method.isStatic ? '.' : '#';\n  }\n\n  getMethodSignature(method: ApiMember) {\n    const symbolName = this.symbol().name;\n    const instanceName = method.isStatic\n      ? symbolName\n      : `${symbolName[0].toLowerCase()}${symbolName.slice(1)}`;\n    const parameters = method.parameters ?? [];\n    const simpleParameters = parameters.map((param) => param.parameterName);\n\n    return `${instanceName}.${method.name}(${simpleParameters.join(', ')})`;\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-params.component.ts",
    "content": "import { Component, computed, input } from '@angular/core';\nimport { ApiMember } from '@ngrx-io/shared';\nimport { SymbolExcerptComponent } from './symbol-excerpt.component';\nimport { InlineMarkdownPipe } from './inline-markdown.pipe';\n\n@Component({\n  selector: 'ngrx-symbol-params',\n  standalone: true,\n  imports: [SymbolExcerptComponent, InlineMarkdownPipe],\n  template: `\n    @for (param of params(); track $index) {\n      <div class=\"param\">\n        <div class=\"header\">\n          @if (param.required) {\n            <code class=\"paramSymbol\">{{ '@param' }}</code>\n          } @else {\n            <code class=\"paramSymbol\">{{ '@optional' }}</code>\n          }\n          <code class=\"name\">{{ param.name }}</code>\n          @if (param.description) {\n            <p [innerHtml]=\"param.description | ngrxInlineMarkdown\"></p>\n          }\n        </div>\n\n        <ngrx-symbol-excerpt [excerptTokens]=\"param.excerptTokens\" />\n      </div>\n    }\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n        border-top: 1px solid var(--ngrx-border-color);\n      }\n\n      .param {\n        display: flex;\n        flex-direction: column;\n        padding: 8px;\n      }\n\n      .header {\n        display: grid;\n        align-items: center;\n        grid-template-columns: 96px 112px 1fr;\n        gap: 16px;\n      }\n\n      .paramSymbol {\n        font-weight: 700;\n        color: var(--ngrx-link);\n      }\n\n      code {\n        font-family: 'Oxanium', sans-serif;\n        padding: 0.25em 0.5em;\n        border-radius: 4px;\n        font-size: 15px;\n      }\n\n      p {\n        font-size: 13px;\n        padding: 0;\n        margin: 0;\n      }\n    `,\n  ],\n})\nexport class SymbolParamsComponent {\n  symbol = input.required<ApiMember>();\n  params = computed(() => {\n    const parameters = this.symbol().parameters || [];\n    return parameters.map((param) => {\n      const docs = this.symbol().docs.params.find(\n        (p) => p.name === param.parameterName\n      );\n\n      return {\n        name: param.parameterName,\n        required: !param.isOptional,\n        excerptTokens: this.symbol().excerptTokens.slice(\n          param.parameterTypeTokenRange.startIndex,\n          param.parameterTypeTokenRange.endIndex\n        ),\n        description: docs?.description || '',\n      };\n    });\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-popover.component.ts",
    "content": "import { Component, InjectionToken, inject } from '@angular/core';\nimport { ApiMemberSummary } from '@ngrx-io/shared';\nimport { SymbolHeaderComponent } from './symbol-header.component';\nimport { SymbolApiComponent } from './symbol-api.component';\nimport { SymbolSummaryComponent } from './symbol-summary.component';\n\nexport const SYMBOl_POPOVER_REF = new InjectionToken<ApiMemberSummary>(\n  'SYMBOl_POPOVER_REF'\n);\n\n@Component({\n  selector: 'ngrx-symbol-popover',\n  standalone: true,\n  imports: [SymbolHeaderComponent, SymbolApiComponent, SymbolSummaryComponent],\n  template: `\n    <div class=\"popover\">\n      <ngrx-symbol-header [symbol]=\"symbol\" />\n      <ngrx-symbol-summary [symbol]=\"symbol\" />\n      <ngrx-symbol-api [symbol]=\"symbol\" />\n    </div>\n  `,\n  styles: [\n    `\n      .popover {\n        display: flex;\n        flex-direction: column;\n        width: 500px;\n        background-color: var(--ngrx-bg-overlay);\n        border-radius: 4px;\n        box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);\n        border: 1px solid var(--ngrx-border-color);\n        backdrop-filter: blur(8px);\n        overflow-y: hidden;\n      }\n    `,\n  ],\n})\nexport class SymbolPopoverComponent {\n  summary = inject(SYMBOl_POPOVER_REF);\n  symbol = this.summary.members[0];\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-returns.component.ts",
    "content": "import { Component, computed, input } from '@angular/core';\nimport { SymbolExcerptComponent } from './symbol-excerpt.component';\nimport { InlineMarkdownPipe } from './inline-markdown.pipe';\nimport { ApiMember } from '@ngrx-io/shared';\n\n@Component({\n  selector: 'ngrx-symbol-returns',\n  standalone: true,\n  imports: [SymbolExcerptComponent, InlineMarkdownPipe],\n  template: `\n    @if (returns(); as returns) {\n      <div class=\"returns\">\n        <h4>{{ '@returns' }}</h4>\n        <div [innerHtml]=\"returns.description | ngrxInlineMarkdown\"></div>\n        <ngrx-symbol-excerpt [excerptTokens]=\"returns.excerptTokens\" />\n      </div>\n    }\n  `,\n  styles: [\n    `\n      .returns {\n        display: grid;\n        column-gap: 16px;\n        grid-template-areas:\n          'h4 description'\n          'excerpt excerpt';\n        grid-template-columns: 104px 1fr;\n        padding: 8px;\n        align-items: center;\n        border-bottom: 1px solid var(--ngrx-border-color);\n      }\n\n      h4 {\n        grid-area: h4;\n        margin: 0;\n        font-family: 'Oxanium', sans-serif;\n        font-size: 15px;\n        font-weight: 700;\n        padding: 8px;\n        color: var(--ngrx-link);\n      }\n\n      div {\n        grid-area: description;\n        font-size: 13px;\n      }\n\n      ngrx-symbol-excerpt {\n        grid-area: excerpt;\n      }\n    `,\n  ],\n})\nexport class SymbolReturnsComponent {\n  symbol = input.required<ApiMember>();\n  returns = computed(() => {\n    const symbol = this.symbol();\n    const returnTypeTokenRange = symbol.returnTypeTokenRange;\n\n    if (!returnTypeTokenRange) {\n      return;\n    }\n\n    return {\n      description: symbol.docs.returns,\n      excerptTokens: symbol.excerptTokens.slice(\n        returnTypeTokenRange.startIndex,\n        returnTypeTokenRange.endIndex\n      ),\n    };\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-summary.component.ts",
    "content": "import { Component, ViewEncapsulation, input } from '@angular/core';\nimport { ApiMember } from '@ngrx-io/shared';\nimport { MarkdownPipe } from './markdown.pipe';\n\n@Component({\n  selector: 'ngrx-symbol-summary',\n  standalone: true,\n  imports: [MarkdownPipe],\n  template: `\n    @if (symbol().docs.summary; as summary) {\n      <div class=\"summary\" [innerHtml]=\"summary | ngrxMarkdown\"></div>\n    }\n  `,\n  encapsulation: ViewEncapsulation.None,\n  styles: [\n    `\n      ngrx-symbol-summary .summary {\n        display: block;\n        padding: 16px;\n        border-top: 1px solid var(--ngrx-border-color);\n      }\n\n      ngrx-symbol-summary p:first-of-type {\n        margin: 0;\n      }\n    `,\n  ],\n})\nexport class SymbolSummaryComponent {\n  symbol = input.required<ApiMember>();\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-type-params.component.ts",
    "content": "import { Component, computed, input } from '@angular/core';\nimport { ApiMember } from '@ngrx-io/shared';\nimport { SymbolExcerptComponent } from './symbol-excerpt.component';\n\n@Component({\n  selector: 'ngrx-symbol-type-params',\n  standalone: true,\n  imports: [SymbolExcerptComponent],\n  template: `\n    @for (param of params(); track $index) {\n      <div class=\"param\">\n        <code class=\"paramSymbol\">{{ '@type' }}</code>\n        <code class=\"name\">{{ param.name }}</code>\n        <ngrx-symbol-excerpt [excerptTokens]=\"param.excerptTokens\" />\n      </div>\n    }\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n        border-top: 1px solid var(--ngrx-border-color);\n      }\n\n      .param {\n        display: grid;\n        padding: 8px;\n        align-items: center;\n        grid-template-columns: 96px 112px 1fr;\n        gap: 16px;\n      }\n\n      .param:last-child {\n        border-bottom: 1px solid var(--ngrx-border-color);\n      }\n\n      .paramSymbol {\n        font-weight: 700;\n        color: var(--ngrx-link);\n      }\n\n      code {\n        font-family: 'Oxanium', sans-serif;\n        padding: 0.25em 0.5em;\n        border-radius: 4px;\n        font-size: 15px;\n      }\n    `,\n  ],\n})\nexport class SymbolTypeParamsComponent {\n  symbol = input.required<ApiMember>();\n  params = computed(() => {\n    const parameters = this.symbol().typeParameters || [];\n    return parameters.map((param) => {\n      return {\n        name: param.typeParameterName,\n        excerptTokens: [\n          ...this.symbol().excerptTokens.slice(\n            param.constraintTokenRange.startIndex,\n            param.constraintTokenRange.endIndex\n          ),\n        ],\n      };\n    });\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol-usage-notes.component.ts",
    "content": "import { Component, computed, input } from '@angular/core';\nimport { MarkdownPipe } from './markdown.pipe';\nimport { ApiMember } from '@ngrx-io/shared';\n\n@Component({\n  selector: 'ngrx-symbol-usage-notes',\n  standalone: true,\n  imports: [MarkdownPipe],\n  template: `\n    @if (notes()) {\n      <div class=\"notes\">\n        <h3>{{ '@usageNotes' }}</h3>\n        <div [innerHTML]=\"notes() | ngrxMarkdown\"></div>\n      </div>\n    }\n  `,\n  styles: [\n    `\n      .notes {\n        display: block;\n        padding: 16px;\n        border-bottom: 1px solid var(--ngrx-border-color);\n      }\n\n      h3 {\n        font-family: 'Oxanium', monospace;\n        font-size: 15px;\n        font-weight: 700;\n        margin: 0;\n        color: var(--ngrx-link);\n      }\n\n      code {\n        background: transparent;\n      }\n    `,\n  ],\n})\nexport class SymbolUsageNotesComponent {\n  symbol = input.required<ApiMember>();\n  notes = computed(() => this.symbol().docs.usageNotes);\n}\n"
  },
  {
    "path": "projects/www/src/app/components/docs/symbol.component.ts",
    "content": "import { AsyncPipe, JsonPipe } from '@angular/common';\nimport { Component, input } from '@angular/core';\nimport { ApiMemberSummary } from '@ngrx-io/shared';\nimport { SymbolApiComponent } from './symbol-api.component';\nimport { SymbolCodeLinkComponent } from './symbol-code-link.component';\nimport { SymbolExcerptComponent } from './symbol-excerpt.component';\nimport { SymbolHeaderComponent } from './symbol-header.component';\nimport { SymbolMethodsComponent } from './symbol-methods.component';\nimport { SymbolParamsComponent } from './symbol-params.component';\nimport { SymbolReturnsComponent } from './symbol-returns.component';\nimport { SymbolSummaryComponent } from './symbol-summary.component';\nimport { SymbolTypeParamsComponent } from './symbol-type-params.component';\nimport { SymbolUsageNotesComponent } from './symbol-usage-notes.component';\n\n@Component({\n  selector: 'ngrx-symbol',\n  standalone: true,\n  template: `\n    <div class=\"header\">\n      <h1>{{ summary().name }}</h1>\n      <ngrx-symbol-code-link [fileUrlPath]=\"summary().fileUrlPath\" />\n    </div>\n\n    @for (symbol of summary().members; track $index) {\n      <div class=\"symbol-call-signature\">\n        <ngrx-symbol-header [symbol]=\"symbol\" />\n        <ngrx-symbol-summary [symbol]=\"symbol\" />\n        <ngrx-symbol-api [symbol]=\"symbol\" />\n        <ngrx-symbol-params [symbol]=\"symbol\" />\n        <ngrx-symbol-type-params [symbol]=\"symbol\" />\n        <ngrx-symbol-returns [symbol]=\"symbol\" />\n        <ngrx-symbol-usage-notes [symbol]=\"symbol\" />\n        <ngrx-symbol-methods [symbol]=\"symbol\" />\n\n        <!-- <pre>{{ symbol | json }}</pre> -->\n      </div>\n    }\n  `,\n  styles: [\n    `\n      :host {\n        display: flex;\n        flex-direction: column;\n        max-width: 960px;\n        margin: 0 auto;\n        padding: 54px 0 24px;\n        @media only screen and (max-width: 1280px) {\n          padding: 90px 30px 24px;\n          max-width: 100%;\n        }\n      }\n\n      .header {\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n        @media only screen and (max-width: 600px) {\n          flex-wrap: wrap;\n        }\n      }\n\n      h1 {\n        margin: 0;\n        @media only screen and (max-width: 600px) {\n          margin-bottom: 10px;\n        }\n      }\n\n      .symbol-call-signature {\n        border: 1px solid var(--ngrx-border-color);\n        border-radius: 8px;\n        margin: 16px 0;\n        max-width: 100%;\n        overflow-y: auto;\n      }\n    `,\n  ],\n  imports: [\n    SymbolExcerptComponent,\n    SymbolParamsComponent,\n    SymbolTypeParamsComponent,\n    SymbolHeaderComponent,\n    SymbolCodeLinkComponent,\n    SymbolApiComponent,\n    SymbolSummaryComponent,\n    SymbolMethodsComponent,\n    SymbolReturnsComponent,\n    SymbolUsageNotesComponent,\n    AsyncPipe,\n    JsonPipe,\n  ],\n})\nexport class SymbolComponent {\n  summary = input.required<ApiMemberSummary>();\n}\n"
  },
  {
    "path": "projects/www/src/app/components/footer.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\n\n@Component({\n  selector: 'ngrx-footer',\n  standalone: true,\n  imports: [RouterLink],\n  template: `\n    <div class=\"logo-and-copyright\">\n      <a routerLink=\"/\" class=\"logo\">\n        <img src=\"/ngrx-logo.svg\" alt=\"NGRX Logo\" />\n      </a>\n      <span>&copy; {{ currentYear }} NgRx</span>\n      <span>Released under the MIT License</span>\n    </div>\n\n    <nav class=\"learn-ngrx\">\n      <h4>Learn NgRx</h4>\n      <!--      <a routerLink=\"/workshops\">Workshops</a>-->\n      <a routerLink=\"/api\">API Reference</a>\n      <a href=\"https://github.com/sponsors/ngrx\" target=\"_blank\">Sponsor</a>\n      <a routerLink=\"/about\">About</a>\n    </nav>\n\n    <nav class=\"packages\">\n      <h4>Packages</h4>\n      <a routerLink=\"/guide/store\">Store</a>\n      <a routerLink=\"/guide/effects\">Effects</a>\n      <a routerLink=\"/guide/signals\">Signals</a>\n      <a routerLink=\"/guide/operators\">Operators</a>\n    </nav>\n\n    <nav class=\"community\">\n      <h4>Community</h4>\n      <a href=\"https://dev.to/ngrx\" target=\"_blank\">Blog</a>\n      <a href=\"https://github.com/ngrx/platform\" target=\"_blank\">GitHub</a>\n      <a href=\"https://x.com/ngrx_io\" target=\"_blank\">X/Twitter</a>\n      <a href=\"https://discord.com/invite/ngrx\" target=\"_blank\">Discord</a>\n    </nav>\n  `,\n  styles: [\n    `\n      :host {\n        display: grid;\n        grid-template-columns: 280px 1fr 1fr 1fr;\n        gap: 32px;\n        padding: 32px;\n        border-top: 1px solid var(--ngrx-border-color);\n        @media only screen and (max-width: 1280px) {\n          grid-template-columns: repeat(2, 1fr);\n        }\n      }\n\n      img {\n        width: 80px;\n        height: auto;\n      }\n\n      span {\n        opacity: 0.64;\n      }\n\n      .logo-and-copyright,\n      nav {\n        display: flex;\n        flex-direction: column;\n        gap: 16px;\n      }\n    `,\n  ],\n})\nexport class FooterComponent {\n  currentYear = new Date().getFullYear();\n}\n"
  },
  {
    "path": "projects/www/src/app/components/guide-footer.component.ts",
    "content": "import { Component, input } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { MatIcon } from '@angular/material/icon';\nimport { FlattenedLink } from '../services/guide-menu.service';\n\n@Component({\n  selector: 'ngrx-guide-footer',\n  standalone: true,\n  imports: [RouterLink, MatIcon],\n  template: `\n    <div class=\"linkWrapper previous\">\n      @if (previousLink(); as previousLink) {\n        <a [routerLink]=\"previousLink.url\">\n          <mat-icon>chevron_left</mat-icon>\n          <div class=\"parents\">\n            @for (parent of previousLink.parents; track $index) {\n              <span>{{ parent }}</span>\n              @if ($index < previousLink.parents.length - 1) {\n                <span> > </span>\n              }\n            }\n          </div>\n          <span class=\"linkText\">{{ previousLink.text }}</span>\n        </a>\n      }\n    </div>\n    <div class=\"linkWrapper next\">\n      @if (nextLink(); as nextLink) {\n        <a [routerLink]=\"nextLink.url\">\n          <div class=\"parents\">\n            @for (parent of nextLink.parents; track $index) {\n              <span>{{ parent }}</span>\n              @if ($index < nextLink.parents.length - 1) {\n                <span> > </span>\n              }\n            }\n          </div>\n          <span class=\"linkText\">{{ nextLink.text }}</span>\n          <mat-icon>chevron_right</mat-icon>\n        </a>\n      }\n    </div>\n  `,\n  styles: [\n    `\n      :host {\n        display: grid;\n        grid-template-columns: 1fr 1fr;\n        gap: 24px;\n        width: 960px;\n        padding-top: 24px;\n        margin-top: 24px;\n        border-top: 1px solid var(--ngrx-border-color);\n        @media only screen and (max-width: 1280px) {\n          width: 100%;\n        }\n        @media only screen and (max-width: 480px) {\n          grid-template-columns: 1fr;\n        }\n      }\n\n      .linkWrapper a {\n        display: grid;\n        column-gap: 16px;\n        width: 100%;\n        padding: 16px;\n        border-radius: 8px;\n        border: 1px solid var(--ngrx-border-color);\n        align-items: center;\n        transition: border-color 200ms;\n        height: 100%;\n      }\n\n      .linkWrapper.previous a {\n        grid-template-areas:\n          'icon parents'\n          'icon linkText';\n        grid-template-columns: 32px 1fr;\n      }\n\n      .linkWrapper.next a {\n        grid-template-areas:\n          'parents icon'\n          'linkText icon';\n        grid-template-columns: 1fr 32px;\n        text-align: right;\n      }\n\n      .parents {\n        grid-area: parents;\n        color: var(--ngrx-text-faint);\n        font-weight: 600;\n        font-size: 12px;\n      }\n\n      mat-icon {\n        grid-area: icon;\n        font-size: 32px;\n        position: relative;\n        top: -2px;\n        color: var(--ngrx-text-faint);\n        transition: color 200ms;\n      }\n\n      .linkText {\n        grid-area: linkText;\n      }\n\n      .linkWrapper a:hover {\n        border-color: var(--ngrx-accent);\n      }\n\n      .linkWrapper a:hover mat-icon {\n        color: var(--ngrx-accent);\n      }\n    `,\n  ],\n})\nexport class GuideFooterComponent {\n  previousLink = input.required<FlattenedLink | null>();\n  nextLink = input.required<FlattenedLink | null>();\n}\n"
  },
  {
    "path": "projects/www/src/app/components/guide-link.component.ts",
    "content": "import { Component, input } from '@angular/core';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\n\n@Component({\n  selector: 'ngrx-guide-menu-link',\n  standalone: true,\n  imports: [RouterLink, RouterLinkActive],\n  template: `\n    <a\n      [routerLink]=\"url()\"\n      routerLinkActive=\"active\"\n      [routerLinkActiveOptions]=\"{ exact: true }\"\n    >\n      <ng-content></ng-content>\n    </a>\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n      }\n\n      a {\n        display: block;\n        color: var(--ngrx-text-muted);\n        transition: color 0.2s;\n        font-size: 14px;\n      }\n\n      a.active,\n      a:hover {\n        color: var(--ngrx-text-primary);\n      }\n    `,\n  ],\n})\nexport class GuideMenuLinkComponent {\n  url = input.required<string>();\n}\n"
  },
  {
    "path": "projects/www/src/app/components/guide-section.component.ts",
    "content": "import { Component, input, signal, computed, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { Location } from '@angular/common';\nimport { MatIcon } from '@angular/material/icon';\nimport { Router, NavigationEnd } from '@angular/router';\nimport { filter, map, startWith, tap } from 'rxjs';\nimport { GuideMenuLinkComponent } from './guide-link.component';\nimport { Section } from '../services/guide-menu.service';\n\n@Component({\n  selector: 'ngrx-guide-section',\n  standalone: true,\n  imports: [GuideMenuLinkComponent, MatIcon],\n  template: `\n    <section>\n      @if (collapsible()) {\n        <header>\n          <button (click)=\"toggleSection()\">\n            <mat-icon>chevron_right</mat-icon>\n            <span>{{ section().title }}</span>\n          </button>\n        </header>\n      }\n      @if (!collapsible() || isOpen()) {\n        <div class=\"section-content\">\n          @for (child of section().children; track $index) {\n            @if (child.kind === 'link') {\n              <ngrx-guide-menu-link [url]=\"child.url\">{{\n                child.text\n              }}</ngrx-guide-menu-link>\n            } @else if (child.kind === 'break') {\n              <hr />\n            } @else {\n              <ngrx-guide-section [section]=\"child\"></ngrx-guide-section>\n            }\n          }\n        </div>\n      }\n    </section>\n  `,\n  host: {\n    '[class.open]': 'isOpen()',\n    '[class.collapsible]': 'collapsible()',\n    '[class.hasActiveUrl]': 'hasActiveUrl()',\n  },\n  styles: [\n    `\n      .section-content {\n        display: flex;\n        flex-direction: column;\n        gap: 4px;\n        margin-bottom: 3px;\n      }\n\n      :host(.collapsible) .section-content {\n        border-left: 1px solid var(--ngrx-border-color);\n        padding: 4px 16px;\n        margin-left: 12px;\n      }\n\n      :host(.open) > section > header mat-icon {\n        transform: rotate(90deg);\n      }\n\n      :host(.hasActiveUrl) > section > .section-content {\n        border-color: var(--ngrx-active-border);\n      }\n\n      section header button {\n        font-family: 'Oxanium', sans-serif;\n        font-weight: 600;\n        margin: 6px 0 3px;\n        font-size: 16px;\n        display: flex;\n        gap: 4px;\n        align-items: center;\n        padding: 0;\n        outline: none;\n        border: none;\n        background: none;\n        color: var(--ngrx-text-secondary);\n        cursor: pointer;\n      }\n\n      :host(.hasActiveUrl) > section > header > button {\n        color: var(--ngrx-accent, #cf8fc5);\n      }\n\n      section header mat-icon {\n        opacity: 0.56;\n        position: relative;\n        top: -2px;\n      }\n\n      section :host {\n        display: flex;\n        flex-direction: column;\n      }\n\n      section :host header button {\n        font-size: 14px;\n      }\n\n      hr {\n        border: none;\n        border-top: 1px solid var(--ngrx-border-color);\n        margin: 16px 0;\n        width: 100%;\n      }\n    `,\n  ],\n})\nexport class GuideSectionComponent {\n  router = inject(Router);\n  location = inject(Location);\n  section = input.required<Section>();\n  collapsible = input<boolean>(true);\n  isToggledOpen = signal(false);\n  path = toSignal(\n    this.router.events.pipe(\n      filter((event) => event instanceof NavigationEnd),\n      tap(() => this.isToggledOpen.set(false)),\n      map(() => {\n        return this.location.path(false);\n      }),\n      startWith(this.location.path(false))\n    )\n  );\n  urls = computed(() => {\n    const collectUrls = (section: Section): string[] => {\n      const urls: string[] = [];\n      for (const child of section.children) {\n        if (child.kind === 'link') {\n          urls.push(child.url);\n        } else if (child.kind === 'section') {\n          urls.push(...collectUrls(child));\n        }\n      }\n      return urls;\n    };\n\n    return collectUrls(this.section());\n  });\n  hasActiveUrl = computed(() => {\n    const path = this.path();\n\n    if (!path) {\n      return false;\n    }\n\n    return this.urls().some((url) => path === url);\n  });\n  isOpen = computed(() => this.isToggledOpen() || this.hasActiveUrl());\n\n  toggleSection() {\n    this.isToggledOpen.set(!this.isToggledOpen());\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/menu.component.ts",
    "content": "import {\n  Component,\n  effect,\n  ElementRef,\n  HostListener,\n  inject,\n  signal,\n  viewChild,\n} from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\nimport { GuideSectionComponent } from './guide-section.component';\nimport { GuideMenuService } from '../services/guide-menu.service';\nimport { DOCUMENT } from '@angular/common';\nimport { VersionNavigationComponent } from './version-navigation.component';\nimport { ThemeToggleComponent } from './theme-toggle.component';\n\n@Component({\n  selector: 'ngrx-menu',\n  standalone: true,\n  imports: [\n    MatIconModule,\n    RouterLink,\n    RouterLinkActive,\n    GuideSectionComponent,\n    VersionNavigationComponent,\n    ThemeToggleComponent,\n  ],\n  template: `\n    <div class=\"mobile-nav-bar\">\n      <button class=\"menu-toggle\" #toggleBtnRef (click)=\"toggleMenu()\">\n        <img src=\"/ngrx-logo-pink.svg\" alt=\"ngrx logo\" />\n        <mat-icon>menu</mat-icon>\n      </button>\n    </div>\n    <nav class=\"sidebar\" #sidebarRef [class.open]=\"isMenuOpen()\">\n      <button class=\"close-menu\" (click)=\"closeMenu()\">\n        <mat-icon class=\"close-menu-icon\">close</mat-icon>\n      </button>\n      <div class=\"sidebar-header\">\n        <a routerLink=\"\" class=\"logoLink\" (click)=\"closeMenu()\">\n          <img src=\"/ngrx-logo-pink.svg\" alt=\"ngrx logo\" />\n          NgRx\n        </a>\n        <ngrx-theme-toggle />\n      </div>\n      <ngrx-version-navigation />\n      <hr />\n      <a\n        routerLink=\"/api\"\n        routerLinkActive=\"active\"\n        class=\"menu-link\"\n        (click)=\"closeMenu()\"\n      >\n        <mat-icon>description</mat-icon>\n        API Reference\n      </a>\n      <a\n        routerLink=\"/workshops\"\n        routerLinkActive=\"active\"\n        class=\"menu-link\"\n        (click)=\"closeMenu()\"\n      >\n        <mat-icon>co_present</mat-icon>\n        Workshops\n      </a>\n      <a\n        href=\"https://github.com/sponsors/ngrx\"\n        target=\"_blank\"\n        class=\"menu-link\"\n      >\n        <mat-icon>volunteer_activism</mat-icon>\n        Sponsor\n      </a>\n      <a\n        href=\"https://github.com/ngrx/platform\"\n        target=\"__blank\"\n        class=\"menu-link\"\n      >\n        <mat-icon>code</mat-icon>\n        GitHub\n      </a>\n      <hr />\n      <span class=\"guideHeader\">Guide</span>\n      <ngrx-guide-section\n        [section]=\"guideMenu.getMenu()\"\n        [collapsible]=\"false\"\n      ></ngrx-guide-section>\n    </nav>\n  `,\n  styles: [\n    `\n      .mobile-nav-bar {\n        position: fixed;\n        top: var(--top-banner-height, 0px);\n        display: none;\n        background-color: var(--ngrx-bg-surface);\n        width: 100%;\n        padding: 15px 20px;\n\n        .menu-toggle {\n          display: flex;\n          align-items: center;\n          background-color: transparent;\n          border: none;\n          cursor: pointer;\n\n          img {\n            width: 30px;\n            margin-right: 8px;\n          }\n        }\n\n        @media only screen and (max-width: 1280px) {\n          display: block;\n        }\n      }\n\n      .sidebar {\n        display: flex;\n        flex-direction: column;\n        gap: 16px;\n        padding: 32px 24px;\n        border-right: 1px solid var(--ngrx-border-color);\n        @media only screen and (max-width: 1280px) {\n          background-color: var(--ngrx-bg-surface);\n          position: fixed;\n          top: 0;\n          left: -270px;\n          transition: left 0.3s ease-in-out;\n          width: 270px;\n          height: 100lvh;\n          overflow-y: scroll;\n        }\n\n        &.open {\n          @media only screen and (max-width: 1280px) {\n            display: flex;\n            left: 0;\n          }\n        }\n\n        .close-menu {\n          display: none;\n          background-color: transparent;\n          border: none;\n          width: 25px;\n          padding: 0;\n          @media only screen and (max-width: 1280px) {\n            display: block;\n          }\n\n          .close-menu-icon {\n            cursor: pointer;\n          }\n        }\n      }\n\n      .logoLink {\n        font-family: 'Oxanium', sans-serif;\n        font-weight: 600;\n        font-size: 18px;\n        color: var(--ngrx-text);\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        gap: 12px;\n      }\n\n      .logoLink img {\n        width: 24px;\n      }\n\n      .sidebar-header {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: space-between;\n      }\n\n      :host {\n        z-index: 2;\n\n        @media only screen and (max-width: 1280px) {\n          z-index: 4;\n        }\n\n        width: 270px;\n        height: 100lvh;\n        background-color: var(--ngrx-bg-surface);\n        overflow-y: scroll;\n        border-right: 1px solid var(--ngrx-border-color);\n        @media only screen and (max-width: 1280px) {\n          border-right: none;\n          width: 0px;\n          padding: 0px;\n        }\n      }\n\n      .menu-link {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        gap: 12px;\n        text-decoration: none;\n        color: var(--ngrx-text-muted);\n        font-family: 'Oxanium', sans-serif;\n        font-size: 14px;\n        transition: color 0.2s;\n      }\n\n      .menu-link mat-icon {\n        color: var(--ngrx-text-faint);\n        font-size: 20px;\n        transition: color 0.2s;\n      }\n\n      .menu-link:hover,\n      .menu-link.active {\n        color: var(--ngrx-text);\n      }\n\n      .menu-link:hover mat-icon,\n      .menu-link.active mat-icon {\n        color: var(--ngrx-accent);\n      }\n\n      hr {\n        border: none;\n        border-top: 1px solid var(--ngrx-border-color);\n        width: 100%;\n      }\n\n      .guideHeader {\n        display: block;\n        font-size: 12px;\n        font-weight: 700;\n        text-transform: uppercase;\n        padding: 0 0 0 8px;\n        margin: -8px;\n        color: var(--ngrx-text-muted);\n      }\n    `,\n  ],\n})\nexport class MenuComponent {\n  guideMenu = inject(GuideMenuService);\n  isMenuOpen = signal(false);\n\n  sidebarRef = viewChild.required<ElementRef>('sidebarRef');\n  toggleBtnRef = viewChild.required<ElementRef>('toggleBtnRef');\n\n  document = inject(DOCUMENT);\n\n  constructor() {\n    effect(() => {\n      if (this.isMenuOpen()) {\n        this.document.body.classList.add('no-scroll');\n      } else {\n        this.document.body.classList.remove('no-scroll');\n      }\n    });\n  }\n\n  toggleMenu() {\n    this.isMenuOpen.set(!this.isMenuOpen());\n  }\n\n  closeMenu() {\n    this.isMenuOpen.set(false);\n  }\n\n  @HostListener('document:click', ['$event'])\n  closeSidebarWhenClickedOutside(event: MouseEvent) {\n    const target = event.target as HTMLElement;\n    const isSidebarClicked = this.sidebarRef().nativeElement.contains(target);\n    const isToggleBtnClicked =\n      this.toggleBtnRef().nativeElement.contains(target);\n\n    if (!isSidebarClicked && !isToggleBtnClicked && this.isMenuOpen()) {\n      this.closeMenu();\n    }\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/styled-box.component.ts",
    "content": "import { isPlatformServer } from '@angular/common';\nimport {\n  AfterViewInit,\n  Component,\n  ElementRef,\n  HostBinding,\n  PLATFORM_ID,\n  computed,\n  inject,\n  signal,\n} from '@angular/core';\n\n@Component({\n  selector: 'ngrx-styled-box',\n  template: `\n    <svg\n      [attr.viewBox]=\"viewBox()\"\n      [attr.width]=\"width()\"\n      [attr.height]=\"height()\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <rect\n        [attr.width]=\"width()\"\n        [attr.height]=\"height()\"\n        fill=\"white\"\n        fill-opacity=\"0.04\"\n      />\n      <ellipse\n        cx=\"285.5\"\n        cy=\"207\"\n        rx=\"114.5\"\n        ry=\"47\"\n        filter=\"url(#styledBoxEllipseBlur)\"\n        fill=\"url(#styledBoxEllipseGradient)\"\n        class=\"backgroundBlurShapes\"\n      />\n      <path\n        d=\"M90.4445 99.6809C79.1814 108.189 54.1311 204.578 17.3753 130.24C-19.3804 55.9033 63.3118 92.6761 91.3154 85.1726C119.319 77.669 135.912 69.9287 150.175 56.2242C164.438 42.5197 177.06 -48.3425 230.735 42.6908C284.409 133.724 163.166 63.7242 150.188 67.2014C137.211 70.6787 101.708 91.1726 90.4445 99.6809Z\"\n        filter=\"url(#styledBoxBlobBlur)\"\n        fill=\"#FB9C2D\"\n        class=\"backgroundBlurShapes\"\n      />\n      <defs>\n        <filter\n          id=\"styledBoxEllipseBlur\"\n          x=\"-77\"\n          y=\"-88\"\n          width=\"725\"\n          height=\"590\"\n          filterUnits=\"userSpaceOnUse\"\n          color-interpolation-filters=\"sRGB\"\n        >\n          <feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\" />\n          <feBlend\n            mode=\"normal\"\n            in=\"SourceGraphic\"\n            in2=\"BackgroundImageFix\"\n            result=\"shape\"\n          />\n          <feGaussianBlur stdDeviation=\"124\" />\n        </filter>\n        <filter\n          id=\"styledBoxBlobBlur\"\n          x=\"-239.881\"\n          y=\"-245.322\"\n          width=\"732.25\"\n          height=\"651.535\"\n          filterUnits=\"userSpaceOnUse\"\n          color-interpolation-filters=\"sRGB\"\n        >\n          <feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\" />\n          <feBlend\n            mode=\"normal\"\n            in=\"SourceGraphic\"\n            in2=\"BackgroundImageFix\"\n            result=\"shape\"\n          />\n          <feGaussianBlur stdDeviation=\"124\" />\n        </filter>\n        <linearGradient\n          id=\"styledBoxEllipseGradient\"\n          x1=\"285.5\"\n          y1=\"160\"\n          x2=\"285.5\"\n          y2=\"254\"\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <stop stop-color=\"#B1219B\" />\n          <stop offset=\"1\" stop-color=\"#5306A1\" />\n        </linearGradient>\n      </defs>\n    </svg>\n    <ng-content></ng-content>\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n        --background-blur-shapes-rotation: 27deg;\n        border-radius: 32px;\n        overflow: hidden;\n        border: 1px solid var(--ngrx-border-color);\n        position: relative;\n      }\n\n      svg {\n        position: absolute;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n      }\n\n      .backgroundBlurShapes {\n        transform: rotate(var(--background-blur-shapes-rotation));\n        transform-origin: center;\n      }\n    `,\n  ],\n})\nexport class StyledBoxComponent implements AfterViewInit {\n  @HostBinding('style.--background-blur-shapes-rotation')\n  readonly rotation = `${Math.round(Math.random() * 360)}deg`;\n\n  hostRef = inject(ElementRef);\n  platformId = inject(PLATFORM_ID);\n  width = signal<number>(0);\n  height = signal<number>(0);\n  viewBox = computed(() => `0 0 ${this.width()} ${this.height()}`);\n\n  ngAfterViewInit() {\n    if (isPlatformServer(this.platformId)) {\n      return;\n    }\n\n    const resizeObserver = new ResizeObserver((entries) => {\n      for (const _entry of entries) {\n        const { width, height } =\n          this.hostRef.nativeElement.getBoundingClientRect();\n        this.width.set(width);\n        this.height.set(height);\n      }\n    });\n\n    resizeObserver.observe(this.hostRef.nativeElement);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/components/theme-toggle.component.ts",
    "content": "import { Component, inject } from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { ThemeService } from '../services/theme.service';\n\n@Component({\n  selector: 'ngrx-theme-toggle',\n  standalone: true,\n  imports: [MatButtonModule, MatIconModule],\n  template: `\n    <button\n      mat-icon-button\n      type=\"button\"\n      (click)=\"themeService.toggle()\"\n      [title]=\"\n        themeService.theme() === 'dark'\n          ? 'Switch to light mode'\n          : 'Switch to dark mode'\n      \"\n    >\n      <mat-icon>\n        {{ themeService.theme() === 'dark' ? 'light' : 'dark' }}_mode\n      </mat-icon>\n    </button>\n  `,\n})\nexport class ThemeToggleComponent {\n  themeService = inject(ThemeService);\n}\n"
  },
  {
    "path": "projects/www/src/app/components/top-banner.component.ts",
    "content": "import { Component, output } from '@angular/core';\nimport { RouterLink } from '@angular/router';\n\nexport const TOP_BANNER_DISMISSED_STORAGE_KEY = 'ngrx-top-banner-dismissed';\n\n@Component({\n  selector: 'ngrx-top-banner',\n  imports: [RouterLink],\n  template: `\n    <div class=\"banner\">\n      <div class=\"banner-content\">\n        <span class=\"banner-label\">Upcoming Workshops</span>\n        <span class=\"banner-text\">\n          New NgRx workshops are coming up!\n          <span class=\"banner-text-extended\">\n            Join us online for 3 days of in-depth Angular and NgRx training.\n          </span>\n        </span>\n        <a routerLink=\"/workshops\" class=\"banner-link\"> Learn more &rarr; </a>\n      </div>\n      <button\n        class=\"banner-dismiss\"\n        aria-label=\"Dismiss banner\"\n        (click)=\"dismiss.emit()\"\n      >\n        &#x2715;\n      </button>\n    </div>\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n        margin-left: 270px;\n\n        @media only screen and (max-width: 1280px) {\n          position: fixed;\n          top: 0;\n          left: 0;\n          right: 0;\n          margin-left: 0;\n          z-index: 3;\n        }\n      }\n\n      .banner {\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n        gap: 16px;\n        padding: 0 20px;\n        height: 44px;\n        box-sizing: border-box;\n        background-color: rgba(170, 27, 182, 0.12);\n        border-bottom: 1px solid rgba(170, 27, 182, 0.25);\n        backdrop-filter: blur(4px);\n\n        @media only screen and (max-width: 1280px) {\n          height: auto;\n          padding: 10px 20px;\n          align-items: flex-start;\n        }\n      }\n\n      .banner-content {\n        display: flex;\n        align-items: center;\n        gap: 16px;\n        min-width: 0;\n        overflow: hidden;\n\n        @media only screen and (max-width: 1280px) {\n          flex-direction: column;\n          align-items: flex-start;\n          gap: 6px;\n        }\n      }\n\n      .banner-label {\n        flex-shrink: 0;\n        font-size: 0.75rem;\n        font-weight: 700;\n        text-transform: uppercase;\n        letter-spacing: 0.07em;\n        padding: 2px 10px;\n        border-radius: 20px;\n        background-color: rgba(170, 27, 182, 0.18);\n        border: 1px solid rgba(170, 27, 182, 0.3);\n        color: var(--ngrx-link);\n\n        @media only screen and (max-width: 1280px) {\n          display: none;\n        }\n      }\n\n      .banner-text {\n        font-size: 0.9rem;\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n\n        @media only screen and (max-width: 1280px) {\n          white-space: normal;\n          overflow: visible;\n          text-overflow: unset;\n        }\n      }\n\n      .banner-text-extended {\n        @media only screen and (max-width: 1280px) {\n          display: none;\n        }\n        min-width: 0;\n      }\n\n      .banner-link {\n        flex-shrink: 0;\n        font-size: 0.9rem;\n        font-weight: 600;\n        color: var(--ngrx-link);\n        text-decoration: none;\n        white-space: nowrap;\n      }\n\n      .banner-link:hover {\n        text-decoration: underline;\n      }\n\n      .banner-dismiss {\n        flex-shrink: 0;\n        background: none;\n        border: none;\n        cursor: pointer;\n        font-size: 1rem;\n        color: inherit;\n        opacity: 0.5;\n        padding: 4px 8px;\n        line-height: 1;\n        border-radius: 4px;\n      }\n\n      .banner-dismiss:hover {\n        opacity: 1;\n        background-color: rgba(170, 27, 182, 0.1);\n      }\n    `,\n  ],\n})\nexport class TopBannerComponent {\n  readonly dismiss = output<void>();\n}\n"
  },
  {
    "path": "projects/www/src/app/components/version-navigation.component.ts",
    "content": "import { Component, inject, signal, ElementRef } from '@angular/core';\nimport { VersionInfoService } from '../services/versionInfo.service';\n\n@Component({\n  selector: 'ngrx-version-navigation',\n  template: `\n    @if (previousVersions && previousVersions.length > 0) {\n      <div class=\"version-navigation\">\n        <button\n          (click)=\"toggleDropdown()\"\n          class=\"version-toggle-btn\"\n          type=\"button\"\n        >\n          <span>v{{ currentVersion }}</span>\n          <span class=\"arrow-icon\" [class.rotated]=\"isDropdownVisible()\"\n            >▼</span\n          >\n        </button>\n\n        <div class=\"version-list\" [class.hidden]=\"!isDropdownVisible()\">\n          <ul>\n            @for (version of previousVersions; track version.title) {\n              <li>\n                <a [href]=\"version.url\">\n                  {{ version.title }}\n                </a>\n              </li>\n            }\n          </ul>\n        </div>\n      </div>\n    }\n  `,\n  styles: [\n    `\n      .version-toggle-btn {\n        width: 100%;\n        padding: 0.5rem 1rem;\n        background-color: var(--ngrx-bg-elevated);\n        color: var(--ngrx-text-faint);\n        border: none;\n        cursor: pointer;\n        font-size: 0.9rem;\n        transition: background-color 0.2s ease;\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n      }\n\n      .arrow-icon {\n        display: inline-block;\n        transition: transform 0.3s ease;\n        font-size: 0.6rem;\n      }\n\n      .arrow-icon.rotated {\n        transform: rotate(180deg);\n      }\n\n      .version-toggle-btn:hover {\n        background-color: var(--ngrx-bg-elevated-hover);\n      }\n\n      .version-list {\n        background-color: var(--ngrx-bg-overlay);\n      }\n\n      .version-list.hidden {\n        display: none;\n      }\n\n      .version-list ul {\n        list-style: none;\n        padding: 0;\n        margin: 0;\n      }\n\n      .version-list li {\n        border-bottom: 1px solid var(--ngrx-bg);\n        font-size: 0.9rem;\n      }\n\n      .version-list li:last-child {\n        border-bottom: none;\n      }\n\n      .version-list a {\n        display: block;\n        padding: 0.25rem 1rem;\n        text-decoration: none;\n        color: var(--ngrx-text-faint);\n        transition: background-color 0.2s ease;\n      }\n\n      .version-list a:hover {\n        background-color: var(--ngrx-bg-elevated-hover);\n      }\n    `,\n  ],\n  host: {\n    '(document:click)': 'closeDropdownOnOutsideClick($event)',\n  },\n})\nexport class VersionNavigationComponent {\n  readonly #versionInfoService = inject(VersionInfoService);\n  readonly #elementRef = inject(ElementRef);\n  readonly isDropdownVisible = signal(false);\n  readonly currentVersion = this.#versionInfoService.currentVersion;\n  readonly previousVersions = this.#versionInfoService.previousVersions;\n\n  closeDropdownOnOutsideClick(event: Event): void {\n    if (!this.#elementRef.nativeElement.contains(event.target as Node)) {\n      this.isDropdownVisible.set(false);\n    }\n  }\n\n  toggleDropdown(): void {\n    this.isDropdownVisible.update((visible) => !visible);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/data/contributors.json",
    "content": "{\n  \"robwormald\": {\n    \"name\": \"Rob Wormald\",\n    \"picture\": \"rob-wormald.jpg\",\n    \"twitter\": \"robwormald\",\n    \"website\": \"http://github.com/robwormald\",\n    \"bio\": \"Rob is a Developer Programs Engineer for Angular at Google. He's the Angular team's resident reactive programming geek and founder of NgRx.\",\n    \"group\": \"Alumni\"\n  },\n\n  \"brandonroberts\": {\n    \"name\": \"Brandon Roberts\",\n    \"picture\": \"brandonroberts.jpg\",\n    \"twitter\": \"brandontroberts\",\n    \"website\": \"http://github.com/brandonroberts\",\n    \"bio\": \"Brandon is member of the Angular Team working on documentation and development. He is also a former GDE\",\n    \"group\": \"Core\"\n  },\n\n  \"MikeRyanDev\": {\n    \"name\": \"Mike Ryan\",\n    \"picture\": \"mike-ryan.jpg\",\n    \"twitter\": \"mikeryandev\",\n    \"website\": \"http://github.com/mikeryandev\",\n    \"bio\": \"Mike Ryan is a Principal Architect at LiveLoveApp and one of the original co-creators of NgRx and a Google Developer Expert in Angular\",\n    \"group\": \"Core\"\n  },\n\n  \"vsavkin\": {\n    \"name\": \"Victor Savkin\",\n    \"picture\": \"victor.png\",\n    \"twitter\": \"victorsavkin\",\n    \"website\": \"http://blog.nrwl.io\",\n    \"bio\": \"Victor is co-founder of Nrwl. Victor developed Angular dependency injection, change detection, forms, and the router.\",\n    \"group\": \"Alumni\"\n  },\n\n  \"wardbell\": {\n    \"name\": \"Ward Bell\",\n    \"picture\": \"wardbell.jpg\",\n    \"website\": \"https://github.com/wardbell\",\n    \"twitter\": \"wardbell\",\n    \"bio\": \"Ward is an all-around developer with JavaScript, Node.js®, and .net chops. He's a frequent conference speaker and podcaster, trainer, Google Developer Expert for Angular, Microsoft MVP, and PluralSight author. He is also president of IdeaBlade, an enterprise software consulting firm and the makers of breeze.js. He would like to get more sleep and spend more time in the mountains.\",\n    \"group\": \"Alumni\"\n  },\n\n  \"johnpapa\": {\n    \"name\": \"John Papa\",\n    \"picture\": \"john-papa.jpg\",\n    \"website\": \"https://github.com/johnpapa\",\n    \"twitter\": \"john_papa\",\n    \"bio\": \"John Papa is a dedicated father and husband, a Principal Developer Advocate with Microsoft, and an alumnus of the Google Developer Expert, Microsoft RD, and MVP programs.\",\n    \"group\": \"Alumni\"\n  },\n\n  \"timdeschryver\": {\n    \"name\": \"Tim Deschryver\",\n    \"picture\": \"timd.jpg\",\n    \"twitter\": \"tim_deschryver\",\n    \"website\": \"https://timdeschryver.dev\",\n    \"bio\": \"Tim is a Microsoft MVP, and likes to share his knowledge on his blog.\",\n    \"group\": \"Core\"\n  },\n\n  \"wesleygrimes\": {\n    \"name\": \"Wes Grimes\",\n    \"picture\": \"wesgrimes.jpg\",\n    \"website\": \"https://github.com/wesleygrimes\",\n    \"bio\": \"Wes is a software engineer in the insurance industry, and writer for Angular in Depth and Ultimate Courses.\",\n    \"group\": \"Alumni\"\n  },\n\n  \"alex-okrushko\": {\n    \"name\": \"Alex Okrushko\",\n    \"picture\": \"alex-okrushko.jpg\",\n    \"twitter\": \"alexokrushko\",\n    \"website\": \"https://github.com/alex-okrushko\",\n    \"bio\": \"Alex Okrushko is a Principal Architect at Cisco. He is part of the NgRx team, Google Developer Expert in Angular and @AngularToronto organizer\",\n    \"group\": \"Core\"\n  },\n\n  \"markostanimirovic\": {\n    \"name\": \"Marko Stanimirović\",\n    \"picture\": \"marko.jpg\",\n    \"twitter\": \"markostdev\",\n    \"website\": \"https://github.com/markostanimirovic\",\n    \"bio\": \"Marko is a core member of the NgRx team, a Google Developer Expert in Angular, and an organizer of the Angular Belgrade group. He enjoys contributing to open source software, sharing knowledge through technical articles and talks, and playing guitar. He is also a Master of Science in Software Engineering from the University of Belgrade.\",\n    \"group\": \"Core\"\n  },\n\n  \"rainerhahnekamp\": {\n    \"name\": \"Rainer Hahnekamp\",\n    \"picture\": \"rainer.jpg\",\n    \"twitter\": \"rainerhahnekamp\",\n    \"website\": \"https://www.rainerhahnekamp.com/\",\n    \"bio\": \"Passionate Angular and Spring developer. Google Developer Expert in Angular. Trainer and Consultant at AngularArchitects.\",\n    \"group\": \"Core\"\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/data/versionInfo.json",
    "content": "{\n  \"currentVersion\": \"21\",\n  \"docVersions\": [\n    {\n      \"title\": \"v20\",\n      \"url\": \"https://v20.ngrx.io\"\n    },\n    {\n      \"title\": \"v19\",\n      \"url\": \"https://v19.ngrx.io\"\n    },\n    {\n      \"title\": \"v18\",\n      \"url\": \"https://v18.ngrx.io\"\n    },\n    {\n      \"title\": \"v17\",\n      \"url\": \"https://v17.ngrx.io\"\n    },\n    {\n      \"title\": \"v16\",\n      \"url\": \"https://v16.ngrx.io\"\n    },\n    {\n      \"title\": \"v15\",\n      \"url\": \"https://v15.ngrx.io\"\n    },\n    {\n      \"title\": \"v14\",\n      \"url\": \"https://v14.ngrx.io\"\n    },\n    {\n      \"title\": \"v13\",\n      \"url\": \"https://v13.ngrx.io\"\n    },\n    {\n      \"title\": \"v12\",\n      \"url\": \"https://v12.ngrx.io\"\n    },\n    {\n      \"title\": \"v11\",\n      \"url\": \"https://v11.ngrx.io\"\n    },\n    {\n      \"title\": \"v10\",\n      \"url\": \"https://v10.ngrx.io\"\n    },\n    {\n      \"title\": \"v9\",\n      \"url\": \"https://v9.ngrx.io\"\n    },\n    {\n      \"title\": \"v8\",\n      \"url\": \"https://v8.ngrx.io\"\n    },\n    {\n      \"title\": \"v7\",\n      \"url\": \"https://v7.ngrx.io\"\n    },\n    {\n      \"title\": \"v6\",\n      \"url\": \"https://github.com/ngrx/platform/tree/6.1.2/docs\"\n    },\n    {\n      \"title\": \"v5\",\n      \"url\": \"https://github.com/ngrx/platform/tree/v5.2.0/docs\"\n    },\n    {\n      \"title\": \"v4\",\n      \"url\": \"https://github.com/ngrx/platform/tree/v4.1.1/docs\"\n    }\n  ]\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>NgRx Example</title>\n    <base href=\"/\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"stylesheet\" href=\"/src/styles.scss\" />\n  </head>\n  <body class=\"mat-typography mat-app-background\">\n    <ngrx-root>Loading...</ngrx-root>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/package.json",
    "content": "{\n  \"name\": \"ngrx-examples\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"vite\"\n  },\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@angular/animations\": \"<angular-version>\",\n    \"@angular/cdk\": \"<angular-version>\",\n    \"@angular/common\": \"<angular-version>\",\n    \"@angular/compiler\": \"<angular-version>\",\n    \"@angular/core\": \"<angular-version>\",\n    \"@angular/elements\": \"<angular-version>\",\n    \"@angular/forms\": \"<angular-version>\",\n    \"@angular/material\": \"<angular-version>\",\n    \"@angular/platform-browser\": \"<angular-version>\",\n    \"@angular/platform-browser-dynamic\": \"<angular-version>\",\n    \"@angular/platform-server\": \"<angular-version>\",\n    \"@angular/router\": \"<angular-version>\",\n    \"@ngrx/store\": \"<ngrx-version>\",\n    \"@ngrx/store-devtools\": \"<ngrx-version>\",\n    \"@ngrx/effects\": \"<ngrx-version>\",\n    \"@ngrx/entity\": \"<ngrx-version>\",\n    \"@ngrx/router-store\": \"<ngrx-version>\",\n    \"@ngrx/signals\": \"<ngrx-version>\",\n    \"@ngrx/component-store\": \"<ngrx-version>\",\n    \"rxjs\": \"<rxjs-version>\",\n    \"zone.js\": \"<zone-js-version>\"\n  },\n  \"devDependencies\": {\n    \"@angular/language-service\": \"<angular-version>\",\n    \"typescript\": \"<typescript-version>\",\n    \"sass\": \"1.77.5\",\n    \"vite\": \"^5.0.0\",\n    \"vitest\": \"^1.3.1\"\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/src/_theme.scss",
    "content": "// This file was generated by running 'ng generate @angular/material:m3-theme'.\n// Proceed with caution if making changes to this file.\n\n@use 'sass:map';\n@use '@angular/material' as mat;\n\n// Note: Color palettes are generated from primary: B1219B, secondary: FB9C2D\n$_palettes: (\n  primary: (\n    0: #000000,\n    10: #3a0032,\n    20: #5e0052,\n    25: #710063,\n    30: #850074,\n    35: #990086,\n    40: #a91794,\n    50: #c839b0,\n    60: #e856cc,\n    70: #ff7be1,\n    80: #fface6,\n    90: #ffd7f0,\n    95: #ffecf5,\n    98: #fff7f9,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n  secondary: (\n    0: #000000,\n    10: #2d1600,\n    20: #4a2800,\n    25: #5a3200,\n    30: #6a3c00,\n    35: #7a4600,\n    40: #8b5000,\n    50: #ae6500,\n    60: #d37c00,\n    70: #f39526,\n    80: #ffb871,\n    90: #ffdcbe,\n    95: #ffeee1,\n    98: #fff8f5,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n  tertiary: (\n    0: #000000,\n    10: #321205,\n    20: #4b2717,\n    25: #583121,\n    30: #663c2b,\n    35: #734836,\n    40: #815341,\n    50: #9d6b57,\n    60: #ba846f,\n    70: #d79e88,\n    80: #f5b9a2,\n    90: #ffdbce,\n    95: #ffede7,\n    98: #fff8f6,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n  neutral: (\n    0: #000000,\n    10: #1f1a1d,\n    20: #342f32,\n    25: #3f3a3d,\n    30: #4b4548,\n    35: #575154,\n    40: #635d60,\n    50: #7c7578,\n    60: #978f92,\n    70: #b2a9ac,\n    80: #cdc4c7,\n    90: #eae0e3,\n    95: #f8eef1,\n    98: #fff7f9,\n    99: #fffbff,\n    100: #ffffff,\n    4: #110d0f,\n    6: #161215,\n    12: #231e21,\n    17: #2e292b,\n    22: #393336,\n    24: #3d383a,\n    87: #e1d7db,\n    92: #f0e6e9,\n    94: #f5ebef,\n    96: #fbf1f4,\n  ),\n  neutral-variant: (\n    0: #000000,\n    10: #22191f,\n    20: #382e34,\n    25: #43383f,\n    30: #4f444a,\n    35: #5b4f56,\n    40: #675b62,\n    50: #81737b,\n    60: #9b8d94,\n    70: #b6a7af,\n    80: #d2c2ca,\n    90: #efdee6,\n    95: #feecf4,\n    98: #fff7f9,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n  error: (\n    0: #000000,\n    10: #410002,\n    20: #690005,\n    25: #7e0007,\n    30: #93000a,\n    35: #a80710,\n    40: #ba1a1a,\n    50: #de3730,\n    60: #ff5449,\n    70: #ff897d,\n    80: #ffb4ab,\n    90: #ffdad6,\n    95: #ffedea,\n    98: #fff8f7,\n    99: #fffbff,\n    100: #ffffff,\n  ),\n);\n\n$_rest: (\n  secondary: map.get($_palettes, secondary),\n  neutral: map.get($_palettes, neutral),\n  neutral-variant: map.get($_palettes, neutral-variant),\n  error: map.get($_palettes, error),\n);\n$_primary: map.merge(map.get($_palettes, primary), $_rest);\n$_tertiary: map.merge(map.get($_palettes, tertiary), $_rest);\n\n$light-theme: mat.define-theme(\n  (\n    color: (\n      theme-type: light,\n      primary: $_primary,\n      tertiary: $_tertiary,\n    ),\n    density: (\n      scale: -1,\n    ),\n  )\n);\n$dark-theme: mat.define-theme(\n  (\n    color: (\n      theme-type: dark,\n      primary: $_primary,\n      tertiary: $_tertiary,\n    ),\n    density: (\n      scale: -1,\n    ),\n  )\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/src/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'ngrx-root',\n  template: `\n    <h1>Hello from {{ name }}!</h1>\n    <a target=\"_blank\" href=\"https://angular.dev/overview\">\n      Learn more about Angular\n    </a>\n  `,\n})\nexport class AppComponent {\n  name = 'Angular';\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/src/app.config.ts",
    "content": "import { ApplicationConfig } from '@angular/core';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [],\n};\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/src/main.ts",
    "content": "import '@angular/compiler';\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport {\n  mergeApplicationConfig,\n  provideZonelessChangeDetection,\n} from '@angular/core';\nimport { AppComponent } from './app.component';\nimport { appConfig } from './app.config';\n\nconst config = mergeApplicationConfig(appConfig, {\n  providers: [provideZonelessChangeDetection()],\n});\n\nbootstrapApplication(AppComponent, config);\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/src/styles.scss",
    "content": "@use '@angular/material' as mat;\n@use './theme.scss' as theme;\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\n/* Prevent font size inflation */\nhtml {\n  -moz-text-size-adjust: none;\n  -webkit-text-size-adjust: none;\n  text-size-adjust: none;\n}\n\n/* Remove default margin in favour of better control in authored CSS */\nbody,\nh1,\nh2,\nh3,\nh4,\np,\nfigure,\nblockquote,\ndl,\ndd {\n  margin-block-end: 0;\n}\n\n/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */\nul[role='list'],\nol[role='list'] {\n  list-style: none;\n}\n\n/* Set core body defaults */\nbody {\n  min-height: 100vh;\n  line-height: 1.5;\n}\n\n/* Set shorter line heights on headings and interactive elements */\nh1,\nh2,\nh3,\nh4,\nbutton,\ninput,\nlabel {\n  line-height: 1.1;\n}\n\n/* Balance text wrapping on headings */\nh1,\nh2,\nh3,\nh4 {\n  text-wrap: balance;\n}\n\n/* A elements that don't have a class get default styles */\na:not([class]) {\n  text-decoration-skip-ink: auto;\n  color: currentColor;\n}\n\n/* Make images easier to work with */\nimg,\npicture {\n  max-width: 100%;\n  display: block;\n}\n\n/* Inherit fonts for inputs and buttons */\ninput,\nbutton,\ntextarea,\nselect {\n  font-family: inherit;\n  font-size: inherit;\n}\n\n/* Make sure textareas without a rows attribute are not tiny */\ntextarea:not([rows]) {\n  min-height: 10em;\n}\n\n/* Anything that has been anchored to should have extra scroll margin */\n:target {\n  scroll-margin-block: 5ex;\n}\n\nhtml,\nbody {\n  height: 100%;\n}\nbody.mat-app-background {\n  margin: 0;\n  font-family: Roboto, 'Helvetica Neue', sans-serif;\n}\n\n// Set up Angular Material\n@include mat.core();\n\n:root {\n  @include mat.all-component-themes(theme.$dark-theme);\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/stackblitz-empty.yml",
    "content": "name: Base withouth main and app files\ndescription: Base template for NgRx examples\nfiles:\n  src/_theme.scss: './src/_theme.scss'\n  src/styles.scss: './src/styles.scss'\n  package.json: './package.json'\n  tsconfig.json: './tsconfig.json'\n  vite.config.js: './vite.config.js'\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/stackblitz.yml",
    "content": "name: Base\ndescription: Base template for NgRx examples\nopen: src/app.component.ts\nfiles:\n  src/_theme.scss: './src/_theme.scss'\n  src/styles.scss: './src/styles.scss'\n  src/app.component.ts: './src/app.component.ts'\n  src/app.config.ts: './src/app.config.ts'\n  src/main.ts: './src/main.ts'\n  index.html: './index.html'\n  package.json: './package.json'\n  tsconfig.json: './tsconfig.json'\n  vite.config.js: './vite.config.js'\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/tsconfig.json",
    "content": "{\n  \"files\": [\"src/main.ts\"],\n  \"include\": [\"src/**/*.d.ts\"],\n  \"exclude\": [\"**/*.test.ts\", \"**/*.spec.ts\"],\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"moduleResolution\": \"node\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"module\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"types\": [],\n    \"experimentalDecorators\": true\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/__base/vite.config.js",
    "content": "import { defineConfig } from 'vite';\n\nexport default defineConfig(({ mode }) => {\n  return {\n    root: __dirname,\n    plugins: [],\n  };\n});\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator/src/app/app.component.ts",
    "content": "import { Component, VERSION } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  template: `\n    <paginator\n      [length]=\"100\"\n      [pageSize]=\"10\"\n      [pageSizeOptions]=\"[5, 10, 25, 100]\"\n      (page)=\"log($event)\"\n    >\n    </paginator>\n  `,\n})\nexport class AppComponent {\n  log(obj: unknown) {\n    console.log(obj);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTooltipModule } from '@angular/material/tooltip';\n\nimport { AppComponent } from './app.component';\nimport { PaginatorComponent } from './paginator.component';\n\n@NgModule({\n  imports: [\n    BrowserAnimationsModule,\n    MatNativeDateModule,\n    ReactiveFormsModule,\n    MatFormFieldModule,\n    MatSelectModule,\n    MatTooltipModule,\n  ],\n  declarations: [AppComponent, PaginatorComponent],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator/src/app/paginator.component.ts",
    "content": "import {\n  Component,\n  Input,\n  ChangeDetectionStrategy,\n  Output,\n  EventEmitter,\n  ViewEncapsulation,\n} from '@angular/core';\nimport { ComponentStore } from '@ngrx/component-store';\nimport {\n  filter,\n  tap,\n  withLatestFrom,\n  map,\n  pairwise,\n  skip,\n} from 'rxjs/operators';\n\nexport interface PaginatorState {\n  /** The current page index. */\n  pageIndex: number;\n  /** The current page size */\n  pageSize: number;\n  /** The current total number of items being paged */\n  length: number;\n  /** The set of provided page size options to display to the user. */\n  pageSizeOptions: ReadonlySet<number>;\n}\n\n/**\n * Change event object that is emitted when the user selects a\n * different page size or navigates to another page.\n */\nexport interface PageEvent\n  extends Pick<PaginatorState, 'pageIndex' | 'pageSize' | 'length'> {\n  /**\n   * Index of the page that was selected previously.\n   */\n  previousPageIndex?: number;\n}\n\n@Component({\n  selector: 'paginator',\n  templateUrl: 'paginator.html',\n  host: {\n    class: 'mat-paginator',\n  },\n  styleUrls: ['./paginator.scss'],\n  encapsulation: ViewEncapsulation.None,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  providers: [ComponentStore],\n})\nexport class PaginatorComponent {\n  @Input() set pageIndex(value: string | number) {\n    this.setPageIndex(value);\n  }\n\n  @Input() set length(value: string | number) {\n    this.componentStore.setState((state) => ({\n      ...state,\n      length: Number(value) || 0,\n    }));\n  }\n\n  @Input() set pageSize(value: string | number) {\n    this.componentStore.setState((state) => ({\n      ...state,\n      pageSize: Number(value) || 0,\n    }));\n  }\n\n  @Input() set pageSizeOptions(value: readonly number[]) {\n    this.componentStore.setState((state) => {\n      // Making sure that the pageSize is included and sorted\n      const pageSizeOptions = new Set<number>(\n        [...value, state.pageSize].sort((a, b) => a - b)\n      );\n      return { ...state, pageSizeOptions };\n    });\n  }\n\n  private readonly pageIndexChanges$ = this.componentStore.state$.pipe(\n    // map instead of select, so that non-distinct value could go through\n    map((state) => state.pageIndex),\n    pairwise()\n  );\n\n  @Output() readonly page = this.componentStore\n    .select(\n      // first Observable 👇\n      this.pageIndexChanges$,\n      // second Observable 👇\n      this.componentStore.select((state) => [state.pageSize, state.length]),\n      // Now combining the results from both of these Observables into a PageEvent object\n      ([previousPageIndex, pageIndex], [pageSize, length]) => ({\n        pageIndex,\n        previousPageIndex,\n        pageSize,\n        length,\n      }),\n      // debounce, so that we let the state \"settle\" before emitting a value\n      { debounce: true }\n    )\n    .pipe(\n      // Skip the emission of the initial state values\n      skip(1)\n    );\n\n  // *********** Updaters *********** //\n\n  readonly setPageIndex = this.componentStore.updater(\n    (state, value: string | number) => ({\n      ...state,\n      pageIndex: Number(value) || 0,\n    })\n  );\n\n  readonly changePageSize = this.componentStore.updater(\n    (state, newPageSize: number) => {\n      const startIndex = state.pageIndex * state.pageSize;\n      return {\n        ...state,\n        pageSize: newPageSize,\n        pageIndex: Math.floor(startIndex / newPageSize),\n      };\n    }\n  );\n\n  // *********** Selectors *********** //\n\n  readonly hasPreviousPage$ = this.componentStore.select(\n    ({ pageIndex, pageSize }) => pageIndex >= 1 && pageSize != 0\n  );\n\n  readonly numberOfPages$ = this.componentStore.select(\n    ({ pageSize, length }) => {\n      if (!pageSize) return 0;\n      return Math.ceil(length / pageSize);\n    }\n  );\n\n  readonly hasNextPage$ = this.componentStore.select(\n    this.componentStore.state$,\n    this.numberOfPages$,\n    ({ pageIndex, pageSize }, numberOfPages) => {\n      const maxPageIndex = numberOfPages - 1;\n      return pageIndex < maxPageIndex && pageSize != 0;\n    }\n  );\n\n  readonly rangeLabel$ = this.componentStore.select(\n    ({ pageIndex, pageSize, length }) => {\n      if (length == 0 || pageSize == 0) {\n        return `0 of ${length}`;\n      }\n      length = Math.max(length, 0);\n\n      const startIndex = pageIndex * pageSize;\n\n      // If the start index exceeds the list length, do not try and fix the end index to the end.\n      const endIndex =\n        startIndex < length\n          ? Math.min(startIndex + pageSize, length)\n          : startIndex + pageSize;\n\n      return `${startIndex + 1} – ${endIndex} of ${length}`;\n    }\n  );\n\n  // ViewModel of Paginator component\n  readonly vm$ = this.componentStore.select(\n    this.componentStore.state$,\n    this.hasPreviousPage$,\n    this.hasNextPage$,\n    this.rangeLabel$,\n    (state, hasPreviousPage, hasNextPage, rangeLabel) => ({\n      pageSize: state.pageSize,\n      pageSizeOptions: Array.from(state.pageSizeOptions),\n      pageIndex: state.pageIndex,\n      hasPreviousPage,\n      hasNextPage,\n      rangeLabel,\n    })\n  );\n\n  // *********** Effects *********** //\n\n  readonly lastPage = this.componentStore.effect((trigger$) => {\n    return trigger$.pipe(\n      withLatestFrom(this.numberOfPages$),\n      tap(([, numberOfPages]) => {\n        this.setPageIndex(numberOfPages - 1);\n      })\n    );\n  });\n\n  constructor(private readonly componentStore: ComponentStore<PaginatorState>) {\n    // set defaults\n    this.componentStore.setState({\n      pageIndex: 0,\n      pageSize: 50,\n      length: 0,\n      pageSizeOptions: new Set<number>([50]),\n    });\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator/src/app/paginator.html",
    "content": "<div class=\"mat-paginator-outer-container\" *ngIf=\"vm$ | async as vm\">\n  <div class=\"mat-paginator-container\">\n    <div class=\"mat-paginator-page-size\">\n      <div class=\"mat-paginator-page-size-label\">Items per page</div>\n\n      <mat-form-field\n        *ngIf=\"vm.pageSizeOptions.length > 1\"\n        class=\"mat-paginator-page-size-select\"\n      >\n        <mat-select\n          [value]=\"vm.pageSize\"\n          (selectionChange)=\"changePageSize($any($event).value)\"\n        >\n          <mat-option\n            *ngFor=\"let pageSizeOption of vm.pageSizeOptions\"\n            [value]=\"pageSizeOption\"\n          >\n            {{pageSizeOption}}\n          </mat-option>\n        </mat-select>\n      </mat-form-field>\n\n      <div\n        class=\"mat-paginator-page-size-value\"\n        *ngIf=\"vm.pageSizeOptions.length <= 1\"\n      >\n        {{vm.pageSize}}\n      </div>\n    </div>\n\n    <div class=\"mat-paginator-range-actions\">\n      <div class=\"mat-paginator-range-label\">{{vm.rangeLabel}}</div>\n\n      <button\n        mat-icon-button\n        type=\"button\"\n        class=\"mat-paginator-navigation-first\"\n        (click)=\"setPageIndex(0)\"\n        matTooltip=\"First page\"\n        [matTooltipDisabled]=\"!vm.hasPreviousPage\"\n        [matTooltipPosition]=\"'above'\"\n        [disabled]=\"!vm.hasPreviousPage\"\n      >\n        <svg class=\"mat-paginator-icon\" viewBox=\"0 0 24 24\" focusable=\"false\">\n          <path\n            d=\"M18.41 16.59L13.82 12l4.59-4.59L17 6l-6 6 6 6zM6 6h2v12H6z\"\n          />\n        </svg>\n      </button>\n      <button\n        mat-icon-button\n        type=\"button\"\n        class=\"mat-paginator-navigation-previous\"\n        (click)=\"setPageIndex(vm.pageIndex - 1)\"\n        [attr.aria-label]=\"'Previous Page'\"\n        [matTooltip]=\"'Previous Page'\"\n        [matTooltipDisabled]=\"!vm.hasPreviousPage\"\n        [matTooltipPosition]=\"'above'\"\n        [disabled]=\"!vm.hasPreviousPage\"\n      >\n        <svg class=\"mat-paginator-icon\" viewBox=\"0 0 24 24\" focusable=\"false\">\n          <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n        </svg>\n      </button>\n      <button\n        mat-icon-button\n        type=\"button\"\n        class=\"mat-paginator-navigation-next\"\n        (click)=\"setPageIndex(vm.pageIndex + 1)\"\n        [attr.aria-label]=\"'Next Page'\"\n        [matTooltip]=\"'Next Page'\"\n        [matTooltipDisabled]=\"!vm.hasNextPage\"\n        [matTooltipPosition]=\"'above'\"\n        [disabled]=\"!vm.hasNextPage\"\n      >\n        <svg class=\"mat-paginator-icon\" viewBox=\"0 0 24 24\" focusable=\"false\">\n          <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n        </svg>\n      </button>\n      <button\n        mat-icon-button\n        type=\"button\"\n        class=\"mat-paginator-navigation-last\"\n        (click)=\"lastPage()\"\n        [attr.aria-label]=\"'Last Page'\"\n        [matTooltip]=\"'Last Page'\"\n        [matTooltipDisabled]=\"!vm.hasNextPage\"\n        [matTooltipPosition]=\"'above'\"\n        [disabled]=\"!vm.hasNextPage\"\n      >\n        <svg class=\"mat-paginator-icon\" viewBox=\"0 0 24 24\" focusable=\"false\">\n          <path\n            d=\"M5.59 7.41L10.18 12l-4.59 4.59L7 18l6-6-6-6zM16 6h2v12h-2z\"\n          />\n        </svg>\n      </button>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator/src/app/paginator.scss",
    "content": "@import '~@angular/material/prebuilt-themes/indigo-pink.css';\n/* Add application styles & imports to this file! */\n\n$mat-paginator-padding: 0 8px;\n$mat-paginator-page-size-margin-right: 8px;\n\n$mat-paginator-items-per-page-label-margin: 0 4px;\n$mat-paginator-selector-margin: 6px 4px 0 4px;\n$mat-paginator-selector-trigger-width: 56px;\n$mat-paginator-selector-trigger-outline-width: 64px;\n$mat-paginator-selector-trigger-fill-width: 64px;\n\n$mat-paginator-range-label-margin: 0 32px 0 24px;\n$mat-paginator-button-icon-size: 28px;\n\n.mat-paginator {\n  display: block;\n}\n\n// Note: this wrapper element is only used to get the flexbox vertical centering to work\n// with the `min-height` on IE11. It can be removed if we drop support for IE.\n.mat-paginator-outer-container {\n  display: flex;\n}\n\n.mat-paginator-container {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  padding: $mat-paginator-padding;\n  flex-wrap: wrap-reverse;\n  width: 100%;\n}\n\n.mat-paginator-page-size {\n  display: flex;\n  align-items: baseline;\n  margin-right: $mat-paginator-page-size-margin-right;\n\n  [dir='rtl'] & {\n    margin-right: 0;\n    margin-left: $mat-paginator-page-size-margin-right;\n  }\n}\n\n.mat-paginator-page-size-label {\n  margin: $mat-paginator-items-per-page-label-margin;\n}\n\n.mat-paginator-page-size-select {\n  margin: $mat-paginator-selector-margin;\n  width: $mat-paginator-selector-trigger-width;\n\n  &.mat-form-field-appearance-outline {\n    width: $mat-paginator-selector-trigger-outline-width;\n  }\n\n  &.mat-form-field-appearance-fill {\n    width: $mat-paginator-selector-trigger-fill-width;\n  }\n}\n\n.mat-paginator-range-label {\n  margin: $mat-paginator-range-label-margin;\n}\n\n.mat-paginator-range-actions {\n  display: flex;\n  align-items: center;\n}\n\n.mat-paginator-icon {\n  width: $mat-paginator-button-icon-size;\n  fill: currentColor;\n\n  [dir='rtl'] & {\n    transform: rotate(180deg);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator/src/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Paginator ComponentStore Example</title>\n    <base href=\"/\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator/src/main.ts",
    "content": "import './polyfills';\n\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .then((ref) => {\n    // Ensure Angular destroys itself on hot reloads.\n    if (window['ngRef']) {\n      window['ngRef'].destroy();\n    }\n    window['ngRef'] = ref;\n\n    // Otherwise, log the boot error\n  })\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator/src/styles.scss",
    "content": "@import '~@angular/material/prebuilt-themes/indigo-pink.css';\n/* Add application styles & imports to this file! */\n\n$mat-paginator-padding: 0 8px;\n$mat-paginator-page-size-margin-right: 8px;\n\n$mat-paginator-items-per-page-label-margin: 0 4px;\n$mat-paginator-selector-margin: 6px 4px 0 4px;\n$mat-paginator-selector-trigger-width: 56px;\n$mat-paginator-selector-trigger-outline-width: 64px;\n$mat-paginator-selector-trigger-fill-width: 64px;\n\n$mat-paginator-range-label-margin: 0 32px 0 24px;\n$mat-paginator-button-icon-size: 28px;\n\n.mat-paginator {\n  display: block;\n}\n\n// Note: this wrapper element is only used to get the flexbox vertical centering to work\n// with the `min-height` on IE11. It can be removed if we drop support for IE.\n.mat-paginator-outer-container {\n  display: flex;\n}\n\n.mat-paginator-container {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  padding: $mat-paginator-padding;\n  flex-wrap: wrap-reverse;\n  width: 100%;\n}\n\n.mat-paginator-page-size {\n  display: flex;\n  align-items: baseline;\n  margin-right: $mat-paginator-page-size-margin-right;\n\n  [dir='rtl'] & {\n    margin-right: 0;\n    margin-left: $mat-paginator-page-size-margin-right;\n  }\n}\n\n.mat-paginator-page-size-label {\n  margin: $mat-paginator-items-per-page-label-margin;\n}\n\n.mat-paginator-page-size-select {\n  margin: $mat-paginator-selector-margin;\n  width: $mat-paginator-selector-trigger-width;\n\n  &.mat-form-field-appearance-outline {\n    width: $mat-paginator-selector-trigger-outline-width;\n  }\n\n  &.mat-form-field-appearance-fill {\n    width: $mat-paginator-selector-trigger-fill-width;\n  }\n}\n\n.mat-paginator-range-label {\n  margin: $mat-paginator-range-label-margin;\n}\n\n.mat-paginator-range-actions {\n  display: flex;\n  align-items: center;\n}\n\n.mat-paginator-icon {\n  width: $mat-paginator-button-icon-size;\n  fill: currentColor;\n\n  [dir='rtl'] & {\n    transform: rotate(180deg);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator-service/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  template: `\n    <paginator\n      [length]=\"100\"\n      [pageSize]=\"10\"\n      [pageSizeOptions]=\"[5, 10, 25, 100]\"\n      (page)=\"log($event)\"\n    >\n    </paginator>\n  `,\n})\nexport class AppComponent {\n  log(obj: unknown) {\n    console.log(obj);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator-service/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTooltipModule } from '@angular/material/tooltip';\n\nimport { AppComponent } from './app.component';\nimport { PaginatorComponent } from './paginator.component';\n\n@NgModule({\n  imports: [\n    BrowserAnimationsModule,\n    MatNativeDateModule,\n    ReactiveFormsModule,\n    MatFormFieldModule,\n    MatSelectModule,\n    MatTooltipModule,\n  ],\n  declarations: [AppComponent, PaginatorComponent],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator-service/src/app/paginator.component.ts",
    "content": "import {\n  Component,\n  Input,\n  ChangeDetectionStrategy,\n  Output,\n  ViewEncapsulation,\n} from '@angular/core';\nimport { PaginatorStore } from './paginator.store';\n\n@Component({\n  selector: 'paginator',\n  templateUrl: 'paginator.html',\n  host: {\n    class: 'mat-paginator',\n  },\n  styleUrls: ['./paginator.scss'],\n  encapsulation: ViewEncapsulation.None,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  providers: [PaginatorStore],\n})\nexport class PaginatorComponent {\n  // #docregion inputs\n  @Input() set pageIndex(value: string | number) {\n    this.paginatorStore.setPageIndex(value);\n  }\n\n  @Input() set length(value: string | number) {\n    this.paginatorStore.setLength(value);\n  }\n\n  @Input() set pageSize(value: string | number) {\n    this.paginatorStore.setPageSize(value);\n  }\n\n  @Input() set pageSizeOptions(value: readonly number[]) {\n    this.paginatorStore.setPageSizeOptions(value);\n  }\n  // #enddocregion inputs\n\n  // #docregion selectors\n  // Outputing the event directly from the page$ Observable<PageEvent> property.\n  /** Event emitted when the paginator changes the page size or page index. */\n  @Output() readonly page = this.paginatorStore.page$;\n\n  // ViewModel for the PaginatorComponent\n  readonly vm$ = this.paginatorStore.vm$;\n  // #enddocregion selectors\n\n  constructor(private readonly paginatorStore: PaginatorStore) {}\n\n  // #docregion updating-state\n  changePageSize(newPageSize: number) {\n    this.paginatorStore.changePageSize(newPageSize);\n  }\n  nextPage() {\n    this.paginatorStore.nextPage();\n  }\n  firstPage() {\n    this.paginatorStore.firstPage();\n  }\n  previousPage() {\n    this.paginatorStore.previousPage();\n  }\n  lastPage() {\n    this.paginatorStore.lastPage();\n  }\n  // #enddocregion updating-state\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator-service/src/app/paginator.html",
    "content": "<div class=\"mat-paginator-outer-container\" *ngIf=\"vm$ | async as vm\">\n  <div class=\"mat-paginator-container\">\n    <div class=\"mat-paginator-page-size\">\n      <div class=\"mat-paginator-page-size-label\">Items per page</div>\n\n      <mat-form-field\n        *ngIf=\"vm.pageSizeOptions.length > 1\"\n        class=\"mat-paginator-page-size-select\"\n      >\n        <mat-select\n          [value]=\"vm.pageSize\"\n          (selectionChange)=\"changePageSize($any($event).value)\"\n        >\n          <mat-option\n            *ngFor=\"let pageSizeOption of vm.pageSizeOptions\"\n            [value]=\"pageSizeOption\"\n          >\n            {{pageSizeOption}}\n          </mat-option>\n        </mat-select>\n      </mat-form-field>\n\n      <div\n        class=\"mat-paginator-page-size-value\"\n        *ngIf=\"vm.pageSizeOptions.length <= 1\"\n      >\n        {{vm.pageSize}}\n      </div>\n    </div>\n\n    <div class=\"mat-paginator-range-actions\">\n      <div class=\"mat-paginator-range-label\">{{vm.rangeLabel}}</div>\n\n      <button\n        mat-icon-button\n        type=\"button\"\n        class=\"mat-paginator-navigation-first\"\n        (click)=\"firstPage()\"\n        matTooltip=\"First page\"\n        [matTooltipDisabled]=\"!vm.hasPreviousPage\"\n        [matTooltipPosition]=\"'above'\"\n        [disabled]=\"!vm.hasPreviousPage\"\n      >\n        <svg class=\"mat-paginator-icon\" viewBox=\"0 0 24 24\" focusable=\"false\">\n          <path\n            d=\"M18.41 16.59L13.82 12l4.59-4.59L17 6l-6 6 6 6zM6 6h2v12H6z\"\n          />\n        </svg>\n      </button>\n      <button\n        mat-icon-button\n        type=\"button\"\n        class=\"mat-paginator-navigation-previous\"\n        (click)=\"previousPage()\"\n        [attr.aria-label]=\"'Previous Page'\"\n        [matTooltip]=\"'Previous Page'\"\n        [matTooltipDisabled]=\"!vm.hasPreviousPage\"\n        [matTooltipPosition]=\"'above'\"\n        [disabled]=\"!vm.hasPreviousPage\"\n      >\n        <svg class=\"mat-paginator-icon\" viewBox=\"0 0 24 24\" focusable=\"false\">\n          <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\n        </svg>\n      </button>\n      <button\n        mat-icon-button\n        type=\"button\"\n        class=\"mat-paginator-navigation-next\"\n        (click)=\"nextPage()\"\n        [attr.aria-label]=\"'Next Page'\"\n        [matTooltip]=\"'Next Page'\"\n        [matTooltipDisabled]=\"!vm.hasNextPage\"\n        [matTooltipPosition]=\"'above'\"\n        [disabled]=\"!vm.hasNextPage\"\n      >\n        <svg class=\"mat-paginator-icon\" viewBox=\"0 0 24 24\" focusable=\"false\">\n          <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\n        </svg>\n      </button>\n      <button\n        mat-icon-button\n        type=\"button\"\n        class=\"mat-paginator-navigation-last\"\n        (click)=\"lastPage()\"\n        [attr.aria-label]=\"'Last Page'\"\n        [matTooltip]=\"'Last Page'\"\n        [matTooltipDisabled]=\"!vm.hasNextPage\"\n        [matTooltipPosition]=\"'above'\"\n        [disabled]=\"!vm.hasNextPage\"\n      >\n        <svg class=\"mat-paginator-icon\" viewBox=\"0 0 24 24\" focusable=\"false\">\n          <path\n            d=\"M5.59 7.41L10.18 12l-4.59 4.59L7 18l6-6-6-6zM16 6h2v12h-2z\"\n          />\n        </svg>\n      </button>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator-service/src/app/paginator.scss",
    "content": "@import '~@angular/material/prebuilt-themes/indigo-pink.css';\n/* Add application styles & imports to this file! */\n\n$mat-paginator-padding: 0 8px;\n$mat-paginator-page-size-margin-right: 8px;\n\n$mat-paginator-items-per-page-label-margin: 0 4px;\n$mat-paginator-selector-margin: 6px 4px 0 4px;\n$mat-paginator-selector-trigger-width: 56px;\n$mat-paginator-selector-trigger-outline-width: 64px;\n$mat-paginator-selector-trigger-fill-width: 64px;\n\n$mat-paginator-range-label-margin: 0 32px 0 24px;\n$mat-paginator-button-icon-size: 28px;\n\n.mat-paginator {\n  display: block;\n}\n\n// Note: this wrapper element is only used to get the flexbox vertical centering to work\n// with the `min-height` on IE11. It can be removed if we drop support for IE.\n.mat-paginator-outer-container {\n  display: flex;\n}\n\n.mat-paginator-container {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  padding: $mat-paginator-padding;\n  flex-wrap: wrap-reverse;\n  width: 100%;\n}\n\n.mat-paginator-page-size {\n  display: flex;\n  align-items: baseline;\n  margin-right: $mat-paginator-page-size-margin-right;\n\n  [dir='rtl'] & {\n    margin-right: 0;\n    margin-left: $mat-paginator-page-size-margin-right;\n  }\n}\n\n.mat-paginator-page-size-label {\n  margin: $mat-paginator-items-per-page-label-margin;\n}\n\n.mat-paginator-page-size-select {\n  margin: $mat-paginator-selector-margin;\n  width: $mat-paginator-selector-trigger-width;\n\n  &.mat-form-field-appearance-outline {\n    width: $mat-paginator-selector-trigger-outline-width;\n  }\n\n  &.mat-form-field-appearance-fill {\n    width: $mat-paginator-selector-trigger-fill-width;\n  }\n}\n\n.mat-paginator-range-label {\n  margin: $mat-paginator-range-label-margin;\n}\n\n.mat-paginator-range-actions {\n  display: flex;\n  align-items: center;\n}\n\n.mat-paginator-icon {\n  width: $mat-paginator-button-icon-size;\n  fill: currentColor;\n\n  [dir='rtl'] & {\n    transform: rotate(180deg);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator-service/src/app/paginator.store.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { ComponentStore } from '@ngrx/component-store';\nimport {\n  filter,\n  tap,\n  map,\n  withLatestFrom,\n  pairwise,\n  skip,\n} from 'rxjs/operators';\nimport { Observable } from 'rxjs';\n\nexport interface PaginatorState {\n  /** The current page index. */\n  pageIndex: number;\n  /** The current page size */\n  pageSize: number;\n  /** The current total number of items being paged */\n  length: number;\n  /** The set of provided page size options to display to the user. */\n  pageSizeOptions: ReadonlySet<number>;\n}\n\n/**\n * Change event object that is emitted when the user selects a\n * different page size or navigates to another page.\n */\nexport interface PageEvent\n  extends Pick<PaginatorState, 'pageIndex' | 'pageSize' | 'length'> {\n  /**\n   * Index of the page that was selected previously.\n   */\n  previousPageIndex?: number;\n}\n\n@Injectable()\nexport class PaginatorStore extends ComponentStore<PaginatorState> {\n  constructor() {\n    // set defaults\n    super({\n      pageIndex: 0,\n      pageSize: 50,\n      length: 0,\n      pageSizeOptions: new Set<number>([50]),\n    });\n  }\n  // *********** Updaters *********** //\n\n  readonly setPageIndex = this.updater((state, value: string | number) => ({\n    ...state,\n    pageIndex: Number(value) || 0,\n  }));\n\n  readonly setPageSize = this.updater((state, value: string | number) => ({\n    ...state,\n    pageSize: Number(value) || 0,\n  }));\n\n  readonly setLength = this.updater((state, value: string | number) => ({\n    ...state,\n    length: Number(value) || 0,\n  }));\n\n  readonly setPageSizeOptions = this.updater(\n    (state, value: readonly number[]) => {\n      // Making sure that the pageSize is included and sorted\n      const pageSizeOptions = new Set<number>(\n        [...value, state.pageSize].sort((a, b) => a - b)\n      );\n      return { ...state, pageSizeOptions };\n    }\n  );\n\n  readonly changePageSize = this.updater((state, newPageSize: number) => {\n    const startIndex = state.pageIndex * state.pageSize;\n    return {\n      ...state,\n      pageSize: newPageSize,\n      pageIndex: Math.floor(startIndex / newPageSize),\n    };\n  });\n\n  // *********** Selectors *********** //\n\n  readonly hasPreviousPage$ = this.select(\n    ({ pageIndex, pageSize }) => pageIndex >= 1 && pageSize != 0\n  );\n\n  readonly numberOfPages$ = this.select(({ pageSize, length }) => {\n    if (!pageSize) return 0;\n    return Math.ceil(length / pageSize);\n  });\n\n  readonly hasNextPage$ = this.select(\n    this.state$,\n    this.numberOfPages$,\n    ({ pageIndex, pageSize }, numberOfPages) => {\n      const maxPageIndex = numberOfPages - 1;\n      return pageIndex < maxPageIndex && pageSize != 0;\n    }\n  );\n\n  readonly rangeLabel$ = this.select(({ pageIndex, pageSize, length }) => {\n    if (length === 0 || pageSize === 0) return `0 of ${length}`;\n\n    length = Math.max(length, 0);\n    const startIndex = pageIndex * pageSize;\n\n    // If the start index exceeds the list length, do not try and fix the end index to the end.\n    const endIndex =\n      startIndex < length\n        ? Math.min(startIndex + pageSize, length)\n        : startIndex + pageSize;\n\n    return `${startIndex + 1} – ${endIndex} of ${length}`;\n  });\n\n  // #docregion selectors\n  // ViewModel of Paginator component\n  readonly vm$ = this.select(\n    this.state$,\n    this.hasPreviousPage$,\n    this.hasNextPage$,\n    this.rangeLabel$,\n    (state, hasPreviousPage, hasNextPage, rangeLabel) => ({\n      pageSize: state.pageSize,\n      pageSizeOptions: Array.from(state.pageSizeOptions),\n      pageIndex: state.pageIndex,\n      hasPreviousPage,\n      hasNextPage,\n      rangeLabel,\n    })\n  );\n\n  private readonly pageIndexChanges$ = this.state$.pipe(\n    // map instead of select, so that non-distinct value could go through\n    map((state) => state.pageIndex),\n    pairwise()\n  );\n\n  readonly page$: Observable<PageEvent> = this.select(\n    // first Observable 👇\n    this.pageIndexChanges$,\n    // second Observable 👇\n    this.select((state) => [state.pageSize, state.length]),\n    // Now combining the results from both of these Observables into a PageEvent object\n    ([previousPageIndex, pageIndex], [pageSize, length]) => ({\n      pageIndex,\n      previousPageIndex,\n      pageSize,\n      length,\n    }),\n    // debounce, so that we let the state \"settle\"\n    { debounce: true }\n  ).pipe(\n    // Skip the emission of the initial state values\n    skip(1)\n  );\n  // #enddocregion selectors\n\n  readonly nextPage = this.effect((trigger$) => {\n    return trigger$.pipe(\n      withLatestFrom(this.hasNextPage$),\n      filter(([, hasNextPage]) => hasNextPage),\n      tap(() => {\n        this.setPageIndex(this.get().pageIndex + 1);\n      })\n    );\n  });\n\n  readonly firstPage = this.effect((trigger$) => {\n    return trigger$.pipe(\n      withLatestFrom(this.hasPreviousPage$),\n      filter(([, hasPreviousPage]) => hasPreviousPage),\n      tap(() => {\n        this.setPageIndex(0);\n      })\n    );\n  });\n\n  readonly previousPage = this.effect((trigger$) => {\n    return trigger$.pipe(\n      withLatestFrom(this.hasPreviousPage$),\n      filter(([, hasPreviousPage]) => hasPreviousPage),\n      tap(() => {\n        this.setPageIndex(this.get().pageIndex - 1);\n      })\n    );\n  });\n\n  readonly lastPage = this.effect((trigger$) => {\n    return trigger$.pipe(\n      withLatestFrom(this.hasNextPage$, this.numberOfPages$),\n      filter(([, hasNextPage]) => hasNextPage),\n      tap(([, , numberOfPages]) => {\n        this.setPageIndex(numberOfPages - 1);\n      })\n    );\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator-service/src/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Paginator ComponentStore Example</title>\n    <base href=\"/\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator-service/src/main.ts",
    "content": "import './polyfills';\n\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .then((ref) => {\n    // Ensure Angular destroys itself on hot reloads.\n    if (window['ngRef']) {\n      window['ngRef'].destroy();\n    }\n    window['ngRef'] = ref;\n\n    // Otherwise, log the boot error\n  })\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-paginator-service/src/styles.scss",
    "content": "@import '~@angular/material/prebuilt-themes/indigo-pink.css';\n/* Add application styles & imports to this file! */\n\n$mat-paginator-padding: 0 8px;\n$mat-paginator-page-size-margin-right: 8px;\n\n$mat-paginator-items-per-page-label-margin: 0 4px;\n$mat-paginator-selector-margin: 6px 4px 0 4px;\n$mat-paginator-selector-trigger-width: 56px;\n$mat-paginator-selector-trigger-outline-width: 64px;\n$mat-paginator-selector-trigger-fill-width: 64px;\n\n$mat-paginator-range-label-margin: 0 32px 0 24px;\n$mat-paginator-button-icon-size: 28px;\n\n.mat-paginator {\n  display: block;\n}\n\n// Note: this wrapper element is only used to get the flexbox vertical centering to work\n// with the `min-height` on IE11. It can be removed if we drop support for IE.\n.mat-paginator-outer-container {\n  display: flex;\n}\n\n.mat-paginator-container {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  padding: $mat-paginator-padding;\n  flex-wrap: wrap-reverse;\n  width: 100%;\n}\n\n.mat-paginator-page-size {\n  display: flex;\n  align-items: baseline;\n  margin-right: $mat-paginator-page-size-margin-right;\n\n  [dir='rtl'] & {\n    margin-right: 0;\n    margin-left: $mat-paginator-page-size-margin-right;\n  }\n}\n\n.mat-paginator-page-size-label {\n  margin: $mat-paginator-items-per-page-label-margin;\n}\n\n.mat-paginator-page-size-select {\n  margin: $mat-paginator-selector-margin;\n  width: $mat-paginator-selector-trigger-width;\n\n  &.mat-form-field-appearance-outline {\n    width: $mat-paginator-selector-trigger-outline-width;\n  }\n\n  &.mat-form-field-appearance-fill {\n    width: $mat-paginator-selector-trigger-fill-width;\n  }\n}\n\n.mat-paginator-range-label {\n  margin: $mat-paginator-range-label-margin;\n}\n\n.mat-paginator-range-actions {\n  display: flex;\n  align-items: center;\n}\n\n.mat-paginator-icon {\n  width: $mat-paginator-button-icon-size;\n  fill: currentColor;\n\n  [dir='rtl'] & {\n    transform: rotate(180deg);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-slide-toggle/src/app/app.component.css",
    "content": "p {\n  font-family: Lato;\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-slide-toggle/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  template: `\n    <mat-slide-toggle (change)=\"logFirst($event)\">Slide me!</mat-slide-toggle>\n    <br />\n    <mat-slide-toggle [checked]=\"true\" (change)=\"logSecond($event)\"\n      >I'm ON initially</mat-slide-toggle\n    >\n  `,\n})\nexport class AppComponent {\n  logFirst(obj: { checked: boolean }) {\n    console.log('first toggle:', obj.checked);\n  }\n\n  logSecond(obj: { checked: boolean }) {\n    console.log('second toggle:', obj.checked);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-slide-toggle/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { ReactiveFormsModule } from '@angular/forms';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTooltipModule } from '@angular/material/tooltip';\nimport { MatRippleModule } from '@angular/material/core';\n\nimport { AppComponent } from './app.component';\nimport { SlideToggleComponent } from './slide-toggle.component';\n\n@NgModule({\n  imports: [\n    BrowserAnimationsModule,\n    MatNativeDateModule,\n    ReactiveFormsModule,\n    MatFormFieldModule,\n    MatSelectModule,\n    MatTooltipModule,\n    MatRippleModule,\n  ],\n  declarations: [AppComponent, SlideToggleComponent],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-slide-toggle/src/app/slide-toggle.component.ts",
    "content": "import {\n  Component,\n  Input,\n  ChangeDetectionStrategy,\n  Output,\n  ViewEncapsulation,\n} from '@angular/core';\nimport { ComponentStore } from '@ngrx/component-store';\nimport { tap } from 'rxjs/operators';\n\n// #docregion state\nexport interface SlideToggleState {\n  checked: boolean;\n}\n// #enddocregion state\n\n/** Change event object emitted by a SlideToggleComponent. */\nexport interface MatSlideToggleChange {\n  /** The source MatSlideToggle of the event. */\n  readonly source: SlideToggleComponent;\n  /** The new `checked` value of the MatSlideToggle. */\n  readonly checked: boolean;\n}\n\n// #docregion providers\n@Component({\n  selector: 'mat-slide-toggle',\n  templateUrl: 'slide-toggle.html',\n  // #enddocregion providers\n  styleUrls: ['./slide-toggle.scss'],\n  encapsulation: ViewEncapsulation.None,\n  // #docregion providers\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  providers: [ComponentStore],\n})\nexport class SlideToggleComponent {\n  // #enddocregion providers\n  // #docregion updater\n  @Input() set checked(value: boolean) {\n    this.setChecked(value);\n  }\n  // #enddocregion updater\n  // #docregion selector\n  // Observable<MatSlideToggleChange> used instead of EventEmitter\n  @Output() readonly change = this.componentStore.select((state) => ({\n    source: this,\n    checked: state.checked,\n  }));\n  // #enddocregion selector\n\n  // #docregion updater\n  readonly setChecked = this.componentStore.updater(\n    (state, value: boolean) => ({ ...state, checked: value })\n  );\n  // #enddocregion updater\n\n  // #docregion selector\n  // ViewModel for the component\n  readonly vm$ = this.componentStore.select((state) => ({\n    checked: state.checked,\n  }));\n  // #enddocregion selector\n\n  // #docregion providers, init\n  constructor(\n    private readonly componentStore: ComponentStore<SlideToggleState>\n  ) {\n    // #enddocregion providers\n    // set defaults\n    this.componentStore.setState({\n      checked: false,\n    });\n  }\n  // #enddocregion init\n\n  // #docregion updater\n  onChangeEvent = this.componentStore.effect<{\n    source: Event;\n    checked: boolean;\n  }>((event$) => {\n    return event$.pipe(\n      tap<{ source: Event; checked: boolean }>((event) => {\n        event.source.stopPropagation();\n        this.setChecked(!event.checked);\n      })\n    );\n  });\n  // #enddocregion updater\n  // #docregion providers\n}\n// #enddocregion providers\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-slide-toggle/src/app/slide-toggle.html",
    "content": "<div *ngIf=\"vm$ | async as vm\" class=\"mat-mdc-slide-toggle mat-accent\">\n  <div class=\"mdc-form-field mdc-form-field--align-end\">\n    <button\n      class=\"mdc-switch\"\n      role=\"switch\"\n      type=\"button\"\n      [class.mdc-switch--selected]=\"vm.checked\"\n      [class.mdc-switch--unselected]=\"!vm.checked\"\n      [class.mdc-switch--checked]=\"vm.checked\"\n      (click)=\"onChangeEvent({ source: $event, checked: vm.checked })\"\n      #switch\n    >\n      <div class=\"mdc-switch__track\"></div>\n      <div class=\"mdc-switch__handle-track\">\n        <div class=\"mdc-switch__handle\">\n          <div class=\"mdc-switch__shadow\">\n            <div class=\"mdc-elevation-overlay\"></div>\n          </div>\n          <div class=\"mdc-switch__ripple\">\n            <div\n              class=\"mat-mdc-slide-toggle-ripple mat-mdc-focus-indicator\"\n              mat-ripple\n              [matRippleTrigger]=\"switch\"\n              [matRippleCentered]=\"true\"\n            ></div>\n          </div>\n          <div class=\"mdc-switch__icons\">\n            <svg\n              class=\"mdc-switch__icon mdc-switch__icon--on\"\n              viewBox=\"0 0 24 24\"\n            >\n              <path\n                d=\"M19.69,5.23L8.96,15.96l-4.23-4.23L2.96,13.5l6,6L21.46,7L19.69,5.23z\"\n              />\n            </svg>\n            <svg\n              class=\"mdc-switch__icon mdc-switch__icon--off\"\n              viewBox=\"0 0 24 24\"\n            >\n              <path d=\"M20 13H4v-2h16v2z\" />\n            </svg>\n          </div>\n        </div>\n      </div>\n    </button>\n\n    <label>\n      <ng-content></ng-content>\n    </label>\n  </div>\n</div>\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-slide-toggle/src/app/slide-toggle.scss",
    "content": "@use 'sass:map';\n@use '@material/animation' as mdc-animation;\n@use '@material/switch/switch' as mdc-switch;\n@use '@material/switch/switch-theme' as mdc-switch-theme;\n@use '@material/form-field' as mdc-form-field;\n@use '@material/ripple' as mdc-ripple;\n@use '@material/theme/css' as mdc-theme-css;\n@use '@material/feature-targeting' as mdc-feature-targeting;\n@import '~@angular/material/prebuilt-themes/indigo-pink.css';\n\n$mdc-base-styles-query: mdc-feature-targeting.without(\n  mdc-feature-targeting.any(color, typography)\n);\n\n@mixin disable-mdc-fallback-declarations {\n  $previous-value: mdc-theme-css.$enable-fallback-declarations;\n  mdc-theme-css.$enable-fallback-declarations: false;\n  @content;\n  mdc-theme-css.$enable-fallback-declarations: $previous-value;\n}\n\n@include disable-mdc-fallback-declarations {\n  @include mdc-form-field.core-styles($query: $mdc-base-styles-query);\n  @include mdc-switch.static-styles-without-ripple;\n}\n\n@mixin fill {\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  position: absolute;\n}\n\n.mat-mdc-slide-toggle {\n  display: inline-block;\n  -webkit-tap-highlight-color: transparent;\n\n  // Remove the native outline since we use the ripple for focus indication.\n  outline: 0;\n\n  .mdc-switch {\n    // MDC theme styles also include structural styles so we have to include the theme at least\n    // once here. The values will be overwritten by our own theme file afterwards.\n    @include disable-mdc-fallback-declarations {\n      @include mdc-switch-theme.theme-styles(mdc-switch-theme.$light-theme);\n    }\n  }\n\n  // The ripple needs extra specificity so the base ripple styling doesn't override its `position`.\n  .mat-mdc-slide-toggle-ripple,\n  #{mdc-switch.$ripple-target}::after {\n    @include fill();\n    border-radius: 50%;\n    // Disable pointer events for the ripple container so that it doesn't eat the mouse events meant\n    // for the input. Pointer events can be safely disabled because the ripple trigger element is\n    // the host element.\n    pointer-events: none;\n    // Fixes the ripples not clipping to the border radius on Safari. Uses `:not(:empty)`\n    // in order to avoid creating extra layers when there aren't any ripples.\n    &:not(:empty) {\n      transform: translateZ(0);\n    }\n  }\n\n  #{mdc-switch.$ripple-target}::after {\n    content: '';\n    opacity: 0;\n  }\n\n  .mdc-switch:hover #{mdc-switch.$ripple-target}::after {\n    opacity: map.get(mdc-ripple.$dark-ink-opacities, hover);\n    transition: mdc-animation.enter(opacity, 75ms);\n  }\n\n  // Needs a little more specificity so the :hover styles don't override it.\n  &.mat-mdc-slide-toggle-focused {\n    .mdc-switch #{mdc-switch.$ripple-target}::after {\n      opacity: map.get(mdc-ripple.$dark-ink-opacities, focus);\n    }\n\n    // For slide-toggles render the focus indicator when we know\n    // the hidden input is focused (slightly different for each control).\n    .mat-mdc-focus-indicator::before {\n      content: '';\n    }\n  }\n\n  // We use an Angular Material ripple rather than an MDC ripple due to size concerns, so we need to\n  // style it appropriately.\n  .mat-ripple-element {\n    opacity: map.get(mdc-ripple.$dark-ink-opacities, press);\n  }\n\n  // Slide-toggle components have to set `border-radius: 50%` in order to support density scaling\n  // which will clip a square focus indicator so we have to turn it into a circle.\n  .mat-mdc-focus-indicator::before {\n    border-radius: 50%;\n  }\n\n  &._mat-animation-noopable {\n    .mdc-switch__handle-track,\n    .mdc-elevation-overlay,\n    .mdc-switch__icon,\n    .mdc-switch__handle::before,\n    .mdc-switch__handle::after,\n    .mdc-switch__track::before,\n    .mdc-switch__track::after {\n      transition: none;\n    }\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-slide-toggle/src/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Slide-Toggle ComponentStore Example</title>\n    <base href=\"/\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/www/src/app/examples/component-store-slide-toggle/src/main.ts",
    "content": "import './polyfills';\n\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .then((ref) => {\n    // Ensure Angular destroys itself on hot reloads.\n    if (window['ngRef']) {\n      window['ngRef'].destroy();\n    }\n    window['ngRef'] = ref;\n\n    // Otherwise, log the boot error\n  })\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "projects/www/src/app/examples/examples.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport sdk from '@stackblitz/sdk';\nimport type { StackblitzConfig } from '@ngrx-io/tools/vite-ngrx-stackblitz.plugin';\n\nconst stackblitzProjectFiles = import.meta.glob(['./**/stackblitz.yml'], {\n  import: 'default',\n});\nconst exampleFiles = import.meta.glob(['./**/*.html', './**/*.txt'], {\n  import: 'default',\n  query: '?raw',\n});\n\n@Injectable({ providedIn: 'root' })\nexport class ExamplesService {\n  async getConfig(exampleName: string): Promise<StackblitzConfig> {\n    return (await stackblitzProjectFiles[\n      `./${exampleName}/stackblitz.yml`\n    ]()) as StackblitzConfig;\n  }\n\n  async load(element: HTMLElement, exampleName: string) {\n    const config = await this.getConfig(exampleName);\n\n    return sdk.embedProject(\n      element,\n      {\n        title: config.name,\n        description: config.description,\n        template: 'node',\n        files: {\n          ...config.files,\n        },\n      },\n      {\n        clickToLoad: false,\n        openFile: config.open,\n      }\n    );\n  }\n\n  async open(exampleName: string) {\n    const config = await this.getConfig(exampleName);\n\n    return sdk.openProject(\n      {\n        title: config.name,\n        description: config.description,\n        template: 'node',\n        files: {\n          ...config.files,\n        },\n      },\n      {\n        openFile: config.open,\n      }\n    );\n  }\n\n  async extractSnippet(path: string, region?: string): Promise<string> {\n    try {\n      // The raw content of a typescript file doesn't seem to be imported correctly\n      // As a hack, we convert the .ts file to .txt (during the build)\n      const exampleFilePath = path.endsWith('.ts')\n        ? `./${path.replace('.ts', '.txt')}`\n        : `./${path}`;\n      const fileLoader = exampleFiles[exampleFilePath];\n      if (!fileLoader) {\n        return `// File not found: ${exampleFilePath}`;\n      }\n\n      const content = (await fileLoader()) as string;\n      if (region) {\n        const regionContent = this.extractRegion(content, region);\n        return this.normalizeIndentation(regionContent);\n      }\n\n      return this.normalizeIndentation(content);\n    } catch (error) {\n      return `// Error loading code from ${path}: ${\n        error instanceof Error ? error.message : 'Unknown error'\n      }`;\n    }\n  }\n\n  private extractRegion(content: string, region: string): string {\n    if (!content) {\n      return '';\n    }\n    const lines = content.split('\\n');\n    const startMarker = `#region ${region}`;\n    const endMarker = `#endregion`;\n\n    // Also support docregion markers like in Angular docs, across TS/JS/HTML comment styles\n    // Examples:\n    // // #docregion foo\n    // /* #docregion foo */\n    // <!-- #docregion foo -->\n    const docRegionStartRegexes = [\n      /\\/\\/\\s*#docregion(?:\\s+(.*))?\\s*$/, // line comment\n      /\\/\\*\\s*#docregion(?:\\s+(.*?))?\\s*\\*\\/\\s*$/, // block comment on one line\n      /<!--\\s*#docregion(?:\\s+(.*?))?\\s*-->/, // HTML comment\n    ];\n    const docRegionEndRegexes = [\n      /\\/\\/\\s*#enddocregion\\b(?:\\s+.*)?$/, // line comment with optional names\n      /\\/\\*\\s*#enddocregion(?:\\s+.*?)?\\s*\\*\\//, // block comment with optional names\n      /<!--\\s*#enddocregion(?:\\s+.*?)?\\s*-->/, // HTML comment with optional names\n    ];\n\n    let startIndex = -1;\n    let endIndex = -1;\n\n    // Look for region markers\n    for (let i = 0; i < lines.length; i++) {\n      const line = lines[i].trim();\n\n      // Handle //#region style\n      if (line.includes(startMarker)) {\n        startIndex = i + 1;\n        continue;\n      }\n\n      // Enhanced handling of #docregion lines (supports //, /* */, and <!-- -->)\n      if (line.includes('#docregion')) {\n        for (const rx of docRegionStartRegexes) {\n          const match = line.match(rx);\n          if (match) {\n            const declaredList = (match[1] ?? '')\n              .split(',')\n              .map((r) => r.trim())\n              .filter((r) => r.length > 0);\n            if (\n              (declaredList.length === 0 && region === '') ||\n              declaredList.includes(region)\n            ) {\n              startIndex = i + 1;\n              break;\n            }\n          }\n        }\n        if (startIndex === i + 1) {\n          continue;\n        }\n      }\n\n      // End markers\n      if (\n        (line.includes(endMarker) ||\n          docRegionEndRegexes.some((rx) => rx.test(line))) &&\n        startIndex !== -1\n      ) {\n        endIndex = i;\n        break;\n      }\n    }\n\n    if (startIndex === -1) {\n      return `// Region '${region}' not found in file`;\n    }\n\n    if (endIndex === -1) {\n      endIndex = lines.length;\n    }\n\n    return lines.slice(startIndex, endIndex).join('\\n');\n  }\n\n  private normalizeIndentation(code: string): string {\n    const lines = code.split('\\n');\n    const firstNonEmpty = lines.find((l) => l.trim().length > 0) ?? '';\n    const leadingSpacesMatch = firstNonEmpty.match(/^( +)/);\n\n    if (leadingSpacesMatch) {\n      const indent = leadingSpacesMatch[1];\n      const indentLen = indent.length;\n      // Normalize indentation: if the first non-empty line starts with spaces, remove that exact number from all lines starting with them\n      for (let i = 0; i < lines.length; i++) {\n        if (lines[i].startsWith(indent)) {\n          lines[i] = lines[i].slice(indentLen);\n        }\n      }\n    }\n\n    return lines.join('\\n').trim();\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/ngrx-start/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>NgRx Example</title>\n    <base href=\"/\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"stylesheet\" href=\"/src/styles.scss\" />\n  </head>\n  <body class=\"mat-typography mat-app-background\">\n    <app-root>Loading...</app-root>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/www/src/app/examples/ngrx-start/src/app.config.ts",
    "content": "import {\n  ApplicationConfig,\n  provideZonelessChangeDetection,\n} from '@angular/core';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [provideZonelessChangeDetection()],\n};\n"
  },
  {
    "path": "projects/www/src/app/examples/ngrx-start/src/app.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  template: ` <h1>NgRx base</h1> `,\n})\nexport class App {}\n"
  },
  {
    "path": "projects/www/src/app/examples/ngrx-start/src/main.ts",
    "content": "import '@angular/compiler';\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { App } from './app';\nimport { appConfig } from './app.config';\n\nbootstrapApplication(App, appConfig);\n"
  },
  {
    "path": "projects/www/src/app/examples/ngrx-start/stackblitz.yml",
    "content": "name: 'Starter project with NgRx dependencies'\ndescription: 'A simple Angular project with NgRx dependencies'\nextends: '../__base/stackblitz-empty.yml'\nopen: src/app.ts\nfiles:\n  src/main.ts: './src/main.ts'\n  src/app.ts: './src/app.ts'\n  src/app.config.ts: './src/app.config.ts'\n  index.html: './index.html'\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/app.component.css",
    "content": "p {\n  font-family: Lato;\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/app.component.html",
    "content": "<ol *ngIf=\"cars$ | async as cars\">\n  <a *ngFor=\"let car of cars\" [routerLink]=\"car.id\">\n    <li>{{ car.make }} | {{ car.model }} | {{ car.year }}</li></a\n  >\n</ol>\n\n<router-outlet></router-outlet>\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/app.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { appInit } from './car/car.actions';\nimport { selectCars } from './car/car.selectors';\nimport { Store } from '@ngrx/store';\nimport { RouterLink, RouterOutlet } from '@angular/router';\nimport { AsyncPipe, NgFor, NgIf } from '@angular/common';\n\n@Component({\n  standalone: true,\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.css'],\n  imports: [RouterOutlet, NgIf, NgFor, AsyncPipe, RouterLink],\n})\nexport class AppComponent implements OnInit {\n  cars$ = this.store.select(selectCars);\n\n  constructor(private store: Store) {}\n\n  ngOnInit() {\n    this.store.dispatch(\n      appInit({\n        cars: [\n          { id: '1', make: 'ford', model: 'mustang', year: '2005' },\n          { id: '2', make: 'ford', model: 'mustang', year: '1987' },\n          { id: '3', make: 'ford', model: 'mustang', year: '1976' },\n        ],\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/app.config.ts",
    "content": "import { ApplicationConfig } from '@angular/core';\nimport { provideStore } from '@ngrx/store';\nimport { provideStoreDevtools } from '@ngrx/store-devtools';\nimport { provideRouterStore, routerReducer } from '@ngrx/router-store';\nimport {\n  provideRouter,\n  withEnabledBlockingInitialNavigation,\n} from '@angular/router';\nimport { isDevMode } from '@angular/core';\nimport { reducer } from './car/car.reducer';\nimport { CarComponent } from './car/car.component';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideStore({ cars: reducer, router: routerReducer }),\n    provideRouter(\n      [\n        {\n          path: ':carId',\n          component: CarComponent,\n        },\n      ],\n      withEnabledBlockingInitialNavigation()\n    ),\n    provideStoreDevtools({\n      maxAge: 25,\n      logOnly: !isDevMode(),\n      name: 'NgRx Standalone App',\n    }),\n    provideRouterStore(),\n  ],\n};\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/car/car.actions.ts",
    "content": "import { createAction, props } from '@ngrx/store';\nimport { Car } from './car.reducer';\n\n// for our example, we'll only populate cars in the store on app init\nexport const appInit = createAction('[App] Init', props<{ cars: Car[] }>());\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/car/car.component.css",
    "content": ".container {\n  border-style: solid;\n  border-width: 4px;\n  border-radius: 4px;\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/car/car.component.html",
    "content": "<div class=\"container\">\n  <h2>Car Component</h2>\n\n  <pre>{{ car$ | async | json }}</pre>\n</div>\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/car/car.component.ts",
    "content": "// #docregion carComponent\nimport { Component, inject } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { selectCar } from './car.selectors';\nimport { AsyncPipe, JsonPipe } from '@angular/common';\n\n@Component({\n  standalone: true,\n  selector: 'app-car',\n  templateUrl: './car.component.html',\n  styleUrls: ['./car.component.css'],\n  imports: [AsyncPipe, JsonPipe],\n})\nexport class CarComponent {\n  private store = inject(Store);\n  car$ = this.store.select(selectCar);\n}\n// #enddocregion carComponent\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/car/car.reducer.ts",
    "content": "// #docregion carReducer\nimport { createReducer, on } from '@ngrx/store';\nimport { EntityState, createEntityAdapter } from '@ngrx/entity';\nimport { appInit } from './car.actions';\n\nexport interface Car {\n  id: string;\n  year: string;\n  make: string;\n  model: string;\n}\n\nexport type CarState = EntityState<Car>;\n\nexport const carAdapter = createEntityAdapter<Car>({\n  selectId: (car) => car.id,\n});\n\nconst initialState = carAdapter.getInitialState();\n\nexport const reducer = createReducer<CarState>(\n  initialState,\n  on(appInit, (state, { cars }) => carAdapter.addMany(cars, state))\n);\n// #enddocregion carReducer\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/car/car.selectors.ts",
    "content": "// #docregion carSelectors\nimport { createFeatureSelector, createSelector } from '@ngrx/store';\nimport { selectRouteParams } from '../router.selectors';\nimport { carAdapter, CarState } from './car.reducer';\n\nexport const carsFeatureSelector = createFeatureSelector<CarState>('cars');\n\nconst { selectEntities, selectAll } = carAdapter.getSelectors();\n\nexport const selectCarEntities = createSelector(\n  carsFeatureSelector,\n  selectEntities\n);\n\nexport const selectCars = createSelector(carsFeatureSelector, selectAll);\n\n// you can combine the `selectRouteParams` with `selectCarEntities`\n// to get a selector for the active car for this component based\n// on the route\nexport const selectCar = createSelector(\n  selectCarEntities,\n  selectRouteParams,\n  (cars, { carId }) => cars[carId]\n);\n// #enddocregion carSelectors\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/app/router.selectors.ts",
    "content": "// #docregion routerSelectors\nimport { getRouterSelectors, RouterReducerState } from '@ngrx/router-store';\n\n// `router` is used as the default feature name. You can use the feature name\n// of your choice by creating a feature selector and pass it to the `getRouterSelectors` function\n// export const selectRouter = createFeatureSelector<RouterReducerState>('yourFeatureName');\n\nexport const {\n  selectCurrentRoute, // select the current route\n  selectFragment, // select the current route fragment\n  selectQueryParams, // select the current route query params\n  selectQueryParam, // factory function to select a query param\n  selectRouteParams, // select the current route params\n  selectRouteParam, // factory function to select a route param\n  selectRouteData, // select the current route data\n  selectRouteDataParam, // factory function to select a route data param\n  selectUrl, // select the current url\n  selectTitle, // select the title if available\n} = getRouterSelectors();\n// #enddocregion routerSelectors\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>NgRx Tutorial</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n  </head>\n  <body>\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/www/src/app/examples/router-store-selectors/src/main.ts",
    "content": "import { bootstrapApplication } from '@angular/platform-browser';\nimport { AppComponent } from './app/app.component';\nimport { appConfig } from './app/app.config';\n\nbootstrapApplication(AppComponent, appConfig);\n"
  },
  {
    "path": "projects/www/src/app/examples/signals-01/src/app.component.ts",
    "content": "import { Component, inject } from '@angular/core';\nimport { patchState, signalStore, withMethods, withState } from '@ngrx/signals';\n\nconst Store = signalStore(\n  withState({ count: 0 }),\n  withMethods((store) => ({\n    increment() {\n      patchState(store, (state) => ({ count: state.count + 1 }));\n    },\n    decrement() {\n      patchState(store, (state) => ({ count: state.count - 1 }));\n    },\n  }))\n);\n\n@Component({\n  selector: 'ngrx-root',\n  template: `\n    <h1>Count: {{ store.count() }}</h1>\n    <button (click)=\"store.increment()\">Increment</button>\n    <button (click)=\"store.decrement()\">Decrement</button>\n  `,\n  providers: [Store],\n})\nexport class AppComponent {\n  store = inject(Store);\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/signals-01/stackblitz.yml",
    "content": "name: Signals Example\ndescription: Demonstrates signalStore\nextends: ../__base/stackblitz.yml\nfiles:\n  src/app.component.ts: './src/app.component.ts'\n"
  },
  {
    "path": "projects/www/src/app/examples/store/src/app.component.ts",
    "content": "import { Component } from '@angular/core';\n// 👇 Import the counter component\nimport { MyCounterComponent } from './my-counter/my-counter.component';\n\n@Component({\n  selector: 'ngrx-root',\n  // 👇 Add the counter component to the imports\n  imports: [MyCounterComponent],\n  template: `\n    <h1>NgRx Tutorial</h1>\n\n    <!-- 👇 add the counter component -->\n    <ngrx-my-counter></ngrx-my-counter>\n  `,\n})\nexport class AppComponent {}\n"
  },
  {
    "path": "projects/www/src/app/examples/store/src/app.config.ts",
    "content": "import { ApplicationConfig } from '@angular/core';\nimport { provideStore } from '@ngrx/store';\nimport { counterReducer } from './counter.reducer';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [provideStore({ count: counterReducer })],\n};\n"
  },
  {
    "path": "projects/www/src/app/examples/store/src/counter.actions.ts",
    "content": "import { createAction } from '@ngrx/store';\n\nexport const increment = createAction('[Counter Component] Increment');\nexport const decrement = createAction('[Counter Component] Decrement');\nexport const reset = createAction('[Counter Component] Reset');\n"
  },
  {
    "path": "projects/www/src/app/examples/store/src/counter.reducer.ts",
    "content": "import { createReducer, on } from '@ngrx/store';\nimport { increment, decrement, reset } from './counter.actions';\n\nexport const initialState = 0;\n\nexport const counterReducer = createReducer(\n  initialState,\n  on(increment, (state) => state + 1),\n  on(decrement, (state) => state - 1),\n  on(reset, () => 0)\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/store/src/my-counter/my-counter.component.todo.ts",
    "content": "import { Component } from '@angular/core';\nimport { Observable } from 'rxjs';\n\n@Component({\n  selector: 'ngrx-my-counter',\n  template: `\n    <button (click)=\"increment()\">Increment</button>\n\n    <div>Current Count: {{ count$ | async }}</div>\n\n    <button (click)=\"decrement()\">Decrement</button>\n\n    <button (click)=\"reset()\">Reset Counter</button>\n  `,\n})\nexport class MyCounterComponent {\n  count$: Observable<number>;\n\n  constructor() {\n    // TODO: Connect `this.count$` stream to the current store `count` state\n  }\n\n  increment() {\n    // TODO: Dispatch an increment action\n  }\n\n  decrement() {\n    // TODO: Dispatch a decrement action\n  }\n\n  reset() {\n    // TODO: Dispatch a reset action\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store/src/my-counter/my-counter.component.ts",
    "content": "import { Component, Signal, inject } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { increment, decrement, reset } from '../counter.actions';\n\n@Component({\n  selector: 'ngrx-my-counter',\n  template: `\n    <button id=\"increment\" (click)=\"increment()\">Increment</button>\n\n    <div>Current Count: {{ count() }}</div>\n\n    <button id=\"decrement\" (click)=\"decrement()\">Decrement</button>\n\n    <button id=\"reset\" (click)=\"reset()\">Reset Counter</button>\n  `,\n})\nexport class MyCounterComponent {\n  private readonly store: Store<{ count: number }> = inject(Store);\n  count: Signal<number> = this.store.selectSignal((state) => state.count);\n\n  increment() {\n    this.store.dispatch(increment());\n  }\n\n  decrement() {\n    this.store.dispatch(decrement());\n  }\n\n  reset() {\n    this.store.dispatch(reset());\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store/stackblitz.yml",
    "content": "name: 'Store Example'\ndescription: 'Basic demonstration of NgRx Store'\nextends: '../__base/stackblitz.yml'\nfiles:\n  src/my-counter/my-counter.component.ts: './src/my-counter/my-counter.component.ts'\n  src/app.component.ts: './src/app.component.ts'\n  src/app.config.ts: './src/app.config.ts'\n  src/counter.actions.ts: './src/counter.actions.ts'\n  src/counter.reducer.ts: './src/counter.reducer.ts'\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>NgRx Example</title>\n    <base href=\"/\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"stylesheet\" href=\"/src/styles.scss\" />\n  </head>\n  <body class=\"mat-typography mat-app-background\">\n    <app-root>Loading...</app-root>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/app.config.ts",
    "content": "import {\n  ApplicationConfig,\n  provideZonelessChangeDetection,\n} from '@angular/core';\nimport { provideHttpClient } from '@angular/common/http';\nimport { provideStore } from '@ngrx/store';\n\nimport { booksReducer } from './state/books.reducer';\nimport { collectionReducer } from './state/collection.reducer';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideZonelessChangeDetection(),\n    provideHttpClient(),\n    provideStore({\n      books: booksReducer,\n      collection: collectionReducer,\n    }),\n  ],\n};\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/app.ts",
    "content": "import { Component, inject, OnInit } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\nimport { selectBookCollection, selectBooks } from './state/books.selectors';\nimport { BooksActions, BooksApiActions } from './state/books.actions';\nimport { GoogleBooksService } from './book-list/books-service';\nimport { BookList } from './book-list/book-list';\nimport { BookCollection } from './book-collection/book-collection';\n\n@Component({\n  selector: 'app-root',\n  template: `\n    <h1>Oliver Sacks Books Collection</h1>\n\n    <h2>Books</h2>\n    <app-book-list [books]=\"books()!\" (add)=\"onAdd($event)\" />\n\n    <h2>My Collection</h2>\n    <app-book-collection\n      [books]=\"bookCollection()!\"\n      (remove)=\"onRemove($event)\"\n    />\n  `,\n  imports: [BookList, BookCollection],\n})\nexport class App implements OnInit {\n  private readonly booksService = inject(GoogleBooksService);\n  private readonly store = inject(Store);\n\n  protected books = this.store.selectSignal(selectBooks);\n  protected bookCollection = this.store.selectSignal(selectBookCollection);\n\n  protected onAdd(bookId: string) {\n    this.store.dispatch(BooksActions.addBook({ bookId }));\n  }\n\n  protected onRemove(bookId: string) {\n    this.store.dispatch(BooksActions.removeBook({ bookId }));\n  }\n\n  ngOnInit() {\n    this.booksService\n      .getBooks()\n      .subscribe((books) =>\n        this.store.dispatch(BooksApiActions.retrievedBookList({ books }))\n      );\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/book-collection/book-collection.css",
    "content": "div {\n  padding: 10px;\n}\nspan {\n  margin: 0 10px 0 2px;\n}\np {\n  display: inline-block;\n  font-style: italic;\n  margin: 0 0 5px;\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/book-collection/book-collection.ts",
    "content": "import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { Book } from '../book-list/book';\n\n@Component({\n  selector: 'app-book-collection',\n  template: `\n    @for (book of books; track book) {\n      <div class=\"book-item\">\n        <p>{{ book.volumeInfo.title }}</p>\n        <span> by {{ book.volumeInfo.authors }}</span>\n        <button (click)=\"remove.emit(book.id)\">Remove from Collection</button>\n      </div>\n    }\n  `,\n})\nexport class BookCollection {\n  @Input() books: ReadonlyArray<Book> = [];\n  @Output() remove = new EventEmitter<string>();\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/book-list/book-list.css",
    "content": "div {\n  padding: 10px;\n}\nspan {\n  margin: 0 10px 0 2px;\n}\np {\n  display: inline-block;\n  font-style: italic;\n  margin: 0;\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/book-list/book-list.ts",
    "content": "import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { Book } from './book';\n\n@Component({\n  selector: 'app-book-list',\n  template: `\n    @for (book of books; track book) {\n      <div class=\"book-item\">\n        <p>{{ book.volumeInfo.title }}</p>\n        <span> by {{ book.volumeInfo.authors }}</span>\n        <button (click)=\"add.emit(book.id)\">Add to Collection</button>\n      </div>\n    }\n  `,\n})\nexport class BookList {\n  @Input() books: ReadonlyArray<Book> = [];\n  @Output() add = new EventEmitter<string>();\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/book-list/book.ts",
    "content": "export interface Book {\n  id: string;\n  volumeInfo: {\n    title: string;\n    authors: Array<string>;\n  };\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/book-list/books-service.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { inject, Injectable } from '@angular/core';\n\nimport { Observable, map } from 'rxjs';\nimport { Book } from './book';\n\n@Injectable({ providedIn: 'root' })\nexport class GoogleBooksService {\n  private readonly http = inject(HttpClient);\n\n  getBooks(): Observable<Array<Book>> {\n    return this.http\n      .get<{\n        items: Book[];\n      }>(\n        'https://www.googleapis.com/books/v1/volumes?maxResults=5&orderBy=relevance&q=oliver%20sacks'\n      )\n      .pipe(map((books) => books.items || []));\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/main.ts",
    "content": "import '@angular/compiler';\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { App } from './app';\nimport { appConfig } from './app.config';\n\nbootstrapApplication(App, appConfig);\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/state/app.state.ts",
    "content": "import { Book } from '../book-list/book';\n\nexport interface AppState {\n  books: ReadonlyArray<Book>;\n  collection: ReadonlyArray<string>;\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/state/books.actions.ts",
    "content": "import { createActionGroup, props } from '@ngrx/store';\nimport { Book } from '../book-list/book';\n\nexport const BooksActions = createActionGroup({\n  source: 'Books',\n  events: {\n    'Add Book': props<{ bookId: string }>(),\n    'Remove Book': props<{ bookId: string }>(),\n  },\n});\n\nexport const BooksApiActions = createActionGroup({\n  source: 'Books API',\n  events: {\n    'Retrieved Book List': props<{ books: ReadonlyArray<Book> }>(),\n  },\n});\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/state/books.reducer.ts",
    "content": "import { createReducer, on } from '@ngrx/store';\n\nimport { BooksApiActions } from './books.actions';\nimport { Book } from '../book-list/book';\n\nexport const initialState: ReadonlyArray<Book> = [];\n\nexport const booksReducer = createReducer(\n  initialState,\n  on(BooksApiActions.retrievedBookList, (_state, { books }) => books)\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/state/books.selectors.ts",
    "content": "import { createSelector, createFeatureSelector } from '@ngrx/store';\nimport { Book } from '../book-list/book';\n\nexport const selectBooks = createFeatureSelector<ReadonlyArray<Book>>('books');\n\nexport const selectCollectionState =\n  createFeatureSelector<ReadonlyArray<string>>('collection');\n\nexport const selectBookCollection = createSelector(\n  selectBooks,\n  selectCollectionState,\n  (books, collection) => {\n    return collection.map((id) => books.find((book) => book.id === id)!);\n  }\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/src/state/collection.reducer.ts",
    "content": "import { createReducer, on } from '@ngrx/store';\nimport { BooksActions } from './books.actions';\n\nexport const initialState: ReadonlyArray<string> = [];\n\nexport const collectionReducer = createReducer(\n  initialState,\n  on(BooksActions.removeBook, (state, { bookId }) =>\n    state.filter((id) => id !== bookId)\n  ),\n  on(BooksActions.addBook, (state, { bookId }) => {\n    if (state.indexOf(bookId) > -1) return state;\n\n    return [...state, bookId];\n  })\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/store-walkthrough/stackblitz.yml",
    "content": "name: 'Starter project with NgRx dependencies'\ndescription: 'A simple Angular project with NgRx dependencies'\nextends: '../__base/stackblitz-empty.yml'\nopen: src/app.ts\nfiles:\n  src/main.ts: './src/main.ts'\n  src/app.ts: './src/app.ts'\n  src/app.config.ts: './src/app.config.ts'\n  src/state/app.state.ts: './src/state/app.state.ts'\n  src/state/books.actions.ts: './src/state/books.actions.ts'\n  src/state/books.reducer.ts: './src/state/books.reducer.ts'\n  src/state/books.selectors.ts: './src/state/books.selectors.ts'\n  src/state/collection.reducer.ts: './src/state/collection.reducer.ts'\n  src/book-list/book-list.ts: './src/book-list/book-list.ts'\n  src/book-list/book-list.css: './src/book-list/book-list.css'\n  src/book-list/book.ts: './src/book-list/book.ts'\n  src/book-list/books-service.ts: './src/book-list/books-service.ts'\n  src/book-collection/book-collection.ts: './src/book-collection/book-collection.ts'\n  src/book-collection/book-collection.css: './src/book-collection/book-collection.css'\n  index.html: './index.html'\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/.browserslistrc",
    "content": "> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\nIE 9-11\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/actions/auth.actions.ts",
    "content": "import { createAction, props } from '@ngrx/store';\n\nexport const login = createAction(\n  '[Auth] Login',\n  props<{ username: string }>()\n);\nexport const logout = createAction('[Auth] Logout');\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/actions/index.ts",
    "content": "import * as AuthActions from './auth.actions';\n\nexport { AuthActions };\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/app.component.html",
    "content": "<h1>Oliver Sacks Books Collection</h1>\n\n<h2>Books</h2>\n<app-book-list\n  class=\"book-list\"\n  [books]=\"books$ | async\"\n  (add)=\"onAdd($event)\"\n></app-book-list>\n\n<h2>My Collection</h2>\n<app-book-collection\n  class=\"book-collection\"\n  [books]=\"bookCollection$ | async\"\n  (remove)=\"onRemove($event)\"\n></app-book-collection>\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/app.component.spec.ts",
    "content": "import { provideMockStore, MockStore } from '@ngrx/store/testing';\nimport { TestBed, ComponentFixture } from '@angular/core/testing';\nimport { HttpClientTestingModule } from '@angular/common/http/testing';\n\nimport { AppState } from './state/app.state';\nimport { AppComponent } from './app.component';\nimport { addBook, removeBook } from './state/books.actions';\nimport { BookListComponent } from './book-list/book-list.component';\nimport { BookCollectionComponent } from './book-collection/book-collection.component';\nimport { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { By } from '@angular/platform-browser';\nimport { selectBooks, selectBookCollection } from './state/books.selectors';\n\ndescribe('AppComponent', () => {\n  let fixture: ComponentFixture<AppComponent>;\n  let component: AppComponent;\n  let store: MockStore<AppState>;\n  let mockBookCollectionSelector;\n  let mockBooksSelector;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [provideMockStore()],\n      imports: [HttpClientTestingModule],\n      declarations: [BookListComponent, BookCollectionComponent, AppComponent],\n      schemas: [CUSTOM_ELEMENTS_SCHEMA],\n    });\n\n    store = TestBed.inject(MockStore);\n    fixture = TestBed.createComponent(AppComponent);\n    component = fixture.componentInstance;\n\n    mockBooksSelector = store.overrideSelector(selectBooks, [\n      {\n        id: 'firstId',\n        volumeInfo: {\n          title: 'First Title',\n          authors: ['First Author'],\n        },\n      },\n      {\n        id: 'secondId',\n        volumeInfo: {\n          title: 'Second Title',\n          authors: ['Second Author'],\n        },\n      },\n      {\n        id: 'thirdId',\n        volumeInfo: {\n          title: 'Third Title',\n          authors: ['Third Author'],\n        },\n      },\n      {\n        id: 'fourthId',\n        volumeInfo: {\n          title: 'Fourth Title',\n          authors: ['Fourth Author'],\n        },\n      },\n    ]);\n\n    mockBookCollectionSelector = store.overrideSelector(selectBookCollection, [\n      {\n        id: 'thirdId',\n        volumeInfo: {\n          title: 'Third Title',\n          authors: ['Third Author'],\n        },\n      },\n    ]);\n\n    fixture.detectChanges();\n    spyOn(store, 'dispatch').and.callFake(() => {});\n  });\n\n  afterEach(() => {\n    TestBed.inject(MockStore)?.resetSelectors();\n  });\n\n  it('add method should dispatch add action', () => {\n    component.onAdd('firstId');\n    expect(store.dispatch).toHaveBeenCalledWith(addBook({ bookId: 'firstId' }));\n  });\n\n  it('remove method should dispatch remove action', () => {\n    component.onRemove('thirdId');\n    expect(store.dispatch).toHaveBeenCalledWith(\n      removeBook({ bookId: 'thirdId' })\n    );\n  });\n\n  it('should render a book list and a book collection', () => {\n    expect(\n      fixture.debugElement.queryAll(By.css('.book-list .book-item')).length\n    ).toBe(4);\n    expect(\n      fixture.debugElement.queryAll(By.css('.book-collection .book-item'))\n        .length\n    ).toBe(1);\n  });\n\n  it('should update the UI when the store changes', () => {\n    mockBooksSelector.setResult([\n      {\n        id: 'firstId',\n        volumeInfo: {\n          title: 'First Title',\n          authors: ['First Author'],\n        },\n      },\n      {\n        id: 'secondId',\n        volumeInfo: {\n          title: 'Second Title',\n          authors: ['Second Author'],\n        },\n      },\n      {\n        id: 'thirdId',\n        volumeInfo: {\n          title: 'Third Title',\n          authors: ['Third Author'],\n        },\n      },\n    ]);\n\n    mockBookCollectionSelector.setResult([\n      {\n        id: 'firstId',\n        volumeInfo: {\n          title: 'First Title',\n          authors: ['First Author'],\n        },\n      },\n      {\n        id: 'secondId',\n        volumeInfo: {\n          title: 'Second Title',\n          authors: ['Second Author'],\n        },\n      },\n    ]);\n\n    store.refreshState();\n    fixture.detectChanges();\n\n    expect(\n      fixture.debugElement.queryAll(By.css('.book-list .book-item')).length\n    ).toBe(3);\n\n    expect(\n      fixture.debugElement.queryAll(By.css('.book-collection .book-item'))\n        .length\n    ).toBe(2);\n  });\n});\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/app.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Store, select } from '@ngrx/store';\n\nimport { selectBookCollection, selectBooks } from './state/books.selectors';\nimport { retrievedBookList, addBook, removeBook } from './state/books.actions';\nimport { GoogleBooksService } from './book-list/books.service';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n})\nexport class AppComponent implements OnInit {\n  books$ = this.store.pipe(select(selectBooks));\n  bookCollection$ = this.store.pipe(select(selectBookCollection));\n\n  onAdd(bookId) {\n    this.store.dispatch(addBook({ bookId }));\n  }\n\n  onRemove(bookId) {\n    this.store.dispatch(removeBook({ bookId }));\n  }\n\n  constructor(\n    private booksService: GoogleBooksService,\n    private store: Store\n  ) {}\n\n  ngOnInit() {\n    this.booksService\n      .getBooks()\n      .subscribe((Book) => this.store.dispatch(retrievedBookList({ Book })));\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { HttpClient, HttpClientModule } from '@angular/common/http';\n\n// #docregion imports\nimport { booksReducer } from './state/books.reducer';\nimport { collectionReducer } from './state/collection.reducer';\nimport { StoreModule } from '@ngrx/store';\n// #enddocregion imports\n\nimport { AppComponent } from './app.component';\nimport { BookListComponent } from './book-list/book-list.component';\nimport { BookCollectionComponent } from './book-collection/book-collection.component';\n\n@NgModule({\n  imports: [\n    BrowserModule,\n    StoreModule.forRoot({ books: booksReducer, collection: collectionReducer }),\n    HttpClientModule,\n  ],\n  declarations: [AppComponent, BookListComponent, BookCollectionComponent],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/book-collection/book-collection.component.css",
    "content": "div {\n  padding: 10px;\n}\nspan {\n  margin: 0 10px 0 2px;\n}\np {\n  display: inline-block;\n  font-style: italic;\n  margin: 0 0 5px;\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/book-collection/book-collection.component.html",
    "content": "<div class=\"book-item\" *ngFor=\"let book of books\">\n  <p>{{ book.volumeInfo.title }}</p>\n  <span> by {{ book.volumeInfo.authors }}</span>\n  <button (click)=\"remove.emit(book.id)\" data-test=\"remove-button\">\n    Remove from Collection\n  </button>\n</div>\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/book-collection/book-collection.component.ts",
    "content": "import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { Book } from '../book-list/books.model';\n\n@Component({\n  selector: 'app-book-collection',\n  templateUrl: './book-collection.component.html',\n  styleUrls: ['./book-collection.component.css'],\n})\nexport class BookCollectionComponent {\n  @Input() books: Array<Book>;\n  @Output() remove = new EventEmitter();\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/book-list/book-list.component.css",
    "content": "div {\n  padding: 10px;\n}\nspan {\n  margin: 0 10px 0 2px;\n}\np {\n  display: inline-block;\n  font-style: italic;\n  margin: 0;\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/book-list/book-list.component.html",
    "content": "<div class=\"book-item\" *ngFor=\"let book of books\">\n  <p>{{ book.volumeInfo.title }}</p>\n  <span> by {{ book.volumeInfo.authors }}</span>\n  <button (click)=\"add.emit(book.id)\" data-test=\"add-button\">\n    Add to Collection\n  </button>\n</div>\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/book-list/book-list.component.ts",
    "content": "import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { Book } from './books.model';\n\n@Component({\n  selector: 'app-book-list',\n  templateUrl: './book-list.component.html',\n  styleUrls: ['./book-list.component.css'],\n})\nexport class BookListComponent {\n  @Input() books: Array<Book>;\n  @Output() add = new EventEmitter();\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/book-list/books.model.ts",
    "content": "export interface Book {\n  id: string;\n  volumeInfo: {\n    title: string;\n    authors: Array<string>;\n  };\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/book-list/books.service.ts",
    "content": "import { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\n\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { Book } from './books.model';\n\n@Injectable({ providedIn: 'root' })\nexport class GoogleBooksService {\n  constructor(private http: HttpClient) {}\n\n  getBooks(): Observable<Array<Book>> {\n    return this.http\n      .get<{\n        items: Book[];\n      }>(\n        'https://www.googleapis.com/books/v1/volumes?maxResults=5&orderBy=relevance&q=oliver%20sacks'\n      )\n      .pipe(map((books) => books.items || []));\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/integration.spec.ts",
    "content": "import { TestBed, ComponentFixture, waitForAsync } from '@angular/core/testing';\nimport { By } from '@angular/platform-browser';\nimport { StoreModule } from '@ngrx/store';\nimport {\n  HttpClientTestingModule,\n  HttpTestingController,\n} from '@angular/common/http/testing';\n\nimport { BookListComponent } from './book-list/book-list.component';\nimport { GoogleBooksService } from './book-list/books.service';\nimport { BookCollectionComponent } from './book-collection/book-collection.component';\nimport { AppComponent } from './app.component';\nimport { collectionReducer } from './state/collection.reducer';\nimport { booksReducer } from './state/books.reducer';\n\ndescribe('AppComponent Integration Test', () => {\n  let component: AppComponent;\n  let fixture: ComponentFixture<AppComponent>;\n  let httpMock: HttpTestingController;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [AppComponent, BookListComponent, BookCollectionComponent],\n      imports: [\n        HttpClientTestingModule,\n        StoreModule.forRoot({\n          books: booksReducer,\n          collection: collectionReducer,\n        }),\n      ],\n      providers: [GoogleBooksService],\n    }).compileComponents();\n\n    httpMock = TestBed.inject(HttpTestingController);\n\n    fixture = TestBed.createComponent(AppComponent);\n    component = fixture.debugElement.componentInstance;\n\n    fixture.detectChanges();\n\n    const req = httpMock.expectOne(\n      'https://www.googleapis.com/books/v1/volumes?maxResults=5&orderBy=relevance&q=oliver%20sacks'\n    );\n    req.flush({\n      items: [\n        {\n          id: 'firstId',\n          volumeInfo: {\n            title: 'First Title',\n            authors: ['First Author'],\n          },\n        },\n        {\n          id: 'secondId',\n          volumeInfo: {\n            title: 'Second Title',\n            authors: ['Second Author'],\n          },\n        },\n      ],\n    });\n\n    fixture.detectChanges();\n  }));\n\n  afterEach(() => {\n    httpMock.verify();\n  });\n\n  it('should create the component', () => {\n    expect(component).toBeTruthy();\n  });\n\n  describe('buttons should work as expected', () => {\n    it('should add to collection when add button is clicked and remove from collection when remove button is clicked', () => {\n      const addButton = getBookList()[1].query(\n        By.css('[data-test=add-button]')\n      );\n\n      click(addButton);\n\n      expect(getBookTitle(getCollection()[0])).toBe('Second Title');\n\n      const removeButton = getCollection()[0].query(\n        By.css('[data-test=remove-button]')\n      );\n\n      click(removeButton);\n\n      expect(getCollection().length).toBe(0);\n    });\n  });\n\n  function getCollection() {\n    return fixture.debugElement.queryAll(By.css('.book-collection .book-item'));\n  }\n\n  function getBookList() {\n    return fixture.debugElement.queryAll(By.css('.book-list .book-item'));\n  }\n\n  function getBookTitle(element) {\n    return element.query(By.css('p')).nativeElement.textContent;\n  }\n\n  function click(element) {\n    const el: HTMLElement = element.nativeElement;\n    el.click();\n    fixture.detectChanges();\n  }\n});\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/reducers/auth.reducer.ts",
    "content": "import { createReducer, on } from '@ngrx/store';\nimport { AuthActions } from '../actions';\n\nexport interface State {\n  username: string;\n}\n\nexport const initialState: State = {\n  username: '',\n};\n\nexport const reducer = createReducer<State>(\n  initialState,\n  on(AuthActions.login, ({ username }): State => ({ username })),\n  on(AuthActions.logout, (): State => ({ username: initialState.username }))\n);\n\nexport const getUsername = (state: State) => state.username;\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/reducers/index.ts",
    "content": "import { createSelector, createFeatureSelector } from '@ngrx/store';\nimport * as fromAuth from './auth.reducer';\n\nexport interface AuthState {\n  status: fromAuth.State;\n}\n\nexport interface State {\n  auth: AuthState;\n}\n\nexport const selectAuthState = createFeatureSelector<AuthState>('auth');\n\nexport const selectAuthStatusState = createSelector(\n  selectAuthState,\n  (state: AuthState) => state.status\n);\n\nexport const getUsername = createSelector(\n  selectAuthStatusState,\n  fromAuth.getUsername\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/state/app.state.ts",
    "content": "import { Book } from '../book-list/books.model';\n\nexport interface AppState {\n  books: ReadonlyArray<Book>;\n  collection: ReadonlyArray<string>;\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/state/books.actions.ts",
    "content": "import { createAction, props } from '@ngrx/store';\n\nexport const addBook = createAction(\n  '[Book List] Add Book',\n  props<{ bookId }>()\n);\n\nexport const removeBook = createAction(\n  '[Book Collection] Remove Book',\n  props<{ bookId }>()\n);\n\nexport const retrievedBookList = createAction(\n  '[Book List/API] Retrieve Books Success',\n  props<{ Book }>()\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/state/books.reducer.spec.ts",
    "content": "import * as fromReducer from './books.reducer';\nimport { retrievedBookList } from './books.actions';\nimport { Book } from '../book-list/books.model';\n\ndescribe('BooksReducer', () => {\n  describe('unknown action', () => {\n    it('should return the default state', () => {\n      const { initialState } = fromReducer;\n      const action = {\n        type: 'Unknown',\n      };\n      const state = fromReducer.booksReducer(initialState, action);\n\n      expect(state).toBe(initialState);\n    });\n  });\n\n  describe('retrievedBookList action', () => {\n    it('should retrieve all books and update the state in an immutable way', () => {\n      const { initialState } = fromReducer;\n      const newState: Array<Book> = [\n        {\n          id: 'firstId',\n          volumeInfo: {\n            title: 'First Title',\n            authors: ['First Author'],\n          },\n        },\n      ];\n      const action = retrievedBookList({ Book: newState });\n      const state = fromReducer.booksReducer(initialState, action);\n\n      expect(state).toEqual(newState);\n      expect(state).not.toBe(initialState);\n    });\n  });\n});\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/state/books.reducer.ts",
    "content": "import { createReducer, on, Action } from '@ngrx/store';\n\nimport { retrievedBookList } from './books.actions';\nimport { Book } from '../book-list/books.model';\n\nexport const initialState: ReadonlyArray<Book> = [];\n\nexport const booksReducer = createReducer(\n  initialState,\n  on(retrievedBookList, (state, { Book }) => [...Book])\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/state/books.selectors.spec.ts",
    "content": "import { selectBooks, selectBookCollection } from './books.selectors';\nimport { AppState } from './app.state';\n\ndescribe('Selectors', () => {\n  const initialState: AppState = {\n    books: [\n      {\n        id: 'firstId',\n        volumeInfo: {\n          title: 'First Title',\n          authors: ['First Author'],\n        },\n      },\n      {\n        id: 'secondId',\n        volumeInfo: {\n          title: 'Second Title',\n          authors: ['Second Author'],\n        },\n      },\n    ],\n    collection: ['firstId'],\n  };\n\n  it('should select the book list', () => {\n    const result = selectBooks.projector(initialState.books);\n    expect(result.length).toEqual(2);\n    expect(result[1].id).toEqual('secondId');\n  });\n\n  it('should select the book collection', () => {\n    const result = selectBookCollection.projector(\n      initialState.books,\n      initialState.collection\n    );\n    expect(result.length).toEqual(1);\n    expect(result[0].id).toEqual('firstId');\n  });\n});\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/state/books.selectors.ts",
    "content": "import { createSelector, createFeatureSelector } from '@ngrx/store';\nimport { Book } from '../book-list/books.model';\n\nexport const selectBooks = createFeatureSelector<ReadonlyArray<Book>>('books');\n\nexport const selectCollectionState =\n  createFeatureSelector<ReadonlyArray<string>>('collection');\n\nexport const selectBookCollection = createSelector(\n  selectBooks,\n  selectCollectionState,\n  (books, collection) => {\n    return collection.map((id) => books.find((book) => book.id === id)!);\n  }\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/state/collection.reducer.spec.ts",
    "content": "import * as fromReducer from './collection.reducer';\nimport { addBook, removeBook } from './books.actions';\n\ndescribe('CollectionReducer', () => {\n  describe('unknown action', () => {\n    it('should return the previous state', () => {\n      const { initialState } = fromReducer;\n      const action = {\n        type: 'Unknown',\n      };\n      const state = fromReducer.collectionReducer(initialState, action);\n\n      expect(state).toBe(initialState);\n    });\n  });\n\n  describe('add action', () => {\n    it('should add an item from the book list and update the state in an immutable way', () => {\n      const initialState: Array<string> = ['firstId', 'secondId'];\n\n      const action = addBook({ bookId: 'thirdId' });\n      const state = fromReducer.collectionReducer(initialState, action);\n\n      expect(state[2]).toBe('thirdId');\n    });\n\n    it('should not add a bookId to collection when that bookId is already in the collection', () => {\n      const initialState: Array<string> = ['firstId', 'secondId'];\n\n      const action = addBook({ bookId: 'secondId' });\n      const state = fromReducer.collectionReducer(initialState, action);\n\n      expect(state[2]).toEqual(undefined);\n      expect(state[1]).toBe('secondId');\n    });\n  });\n\n  describe('remove action', () => {\n    it('should remove the selected book from the collection update the state in an immutable way', () => {\n      const initialState: Array<string> = ['firstId', 'secondId'];\n      const action = removeBook({ bookId: 'secondId' });\n      const state = fromReducer.collectionReducer(initialState, action);\n\n      expect(state[1]).toEqual(undefined);\n    });\n  });\n});\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/state/collection.reducer.ts",
    "content": "import { createReducer, on, Action } from '@ngrx/store';\nimport { addBook, removeBook } from './books.actions';\n\nexport const initialState: ReadonlyArray<string> = [];\n\nexport const collectionReducer = createReducer(\n  initialState,\n  on(removeBook, (state, { bookId }) => state.filter((id) => id !== bookId)),\n  on(addBook, (state, { bookId }) => {\n    if (state.indexOf(bookId) > -1) return state;\n\n    return [...state, bookId];\n  })\n);\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/user-greeting.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { By } from '@angular/platform-browser';\nimport { MemoizedSelector } from '@ngrx/store';\nimport { provideMockStore, MockStore } from '@ngrx/store/testing';\nimport { UserGreetingComponent } from './user-greeting.component';\nimport * as fromAuth from './reducers';\n\ndescribe('User Greeting Component', () => {\n  let fixture: ComponentFixture<UserGreetingComponent>;\n  let mockStore: MockStore;\n  let mockUsernameSelector: MemoizedSelector<fromAuth.State, string>;\n  const queryDivText = () =>\n    fixture.debugElement.queryAll(By.css('div'))[0].nativeElement.textContent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [provideMockStore()],\n      declarations: [UserGreetingComponent],\n    });\n\n    fixture = TestBed.createComponent(UserGreetingComponent);\n    mockStore = TestBed.inject(MockStore);\n    mockUsernameSelector = mockStore.overrideSelector(\n      fromAuth.getUsername,\n      'John'\n    );\n    fixture.detectChanges();\n  });\n\n  it('should greet John when the username is John', () => {\n    expect(queryDivText()).toBe('Greetings, John!');\n  });\n\n  it('should greet Brandon when the username is Brandon', () => {\n    mockUsernameSelector.setResult('Brandon');\n    mockStore.refreshState();\n    fixture.detectChanges();\n    expect(queryDivText()).toBe('Greetings, Brandon!');\n  });\n});\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/app/user-greeting.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport * as fromAuth from './reducers';\n\n@Component({\n  selector: 'user-greeting',\n  template: ` <div>Greetings, {{ username$ | async }}!</div> `,\n})\nexport class UserGreetingComponent {\n  username$ = this.store.select(fromAuth.getUsername);\n\n  constructor(private store: Store<fromAuth.State>) {}\n}\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>NgRx Tutorial</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n  </head>\n  <body>\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/main-test.ts",
    "content": "import './polyfills';\n\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .then((ref) => {\n    // Ensure Angular destroys itself on hot reloads.\n    if (window['ngRef']) {\n      window['ngRef'].destroy();\n    }\n    window['ngRef'] = ref;\n\n    // Otherwise, log the boot error\n  })\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/main.ts",
    "content": "// main app entry point\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic().bootstrapModule(AppModule);\n"
  },
  {
    "path": "projects/www/src/app/examples/testing-store/src/styles.css",
    "content": "/* Master Styles */\n* {\n  font-family: Arial, Helvetica, sans-serif;\n}\n"
  },
  {
    "path": "projects/www/src/app/pages/(home).page.ts",
    "content": "import { Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { MatButtonModule } from '@angular/material/button';\nimport { BannerAnimationComponent } from '../components/banner-animation.component';\nimport { StyledBoxComponent } from '../components/styled-box.component';\nimport { MatIconModule } from '@angular/material/icon';\n\n@Component({\n  selector: 'ngrx-home-page',\n  standalone: true,\n  imports: [\n    BannerAnimationComponent,\n    MatButtonModule,\n    StyledBoxComponent,\n    MatIconModule,\n    RouterLink,\n  ],\n  template: `\n    <div class=\"banner\">\n      <ngrx-banner-animation></ngrx-banner-animation>\n      <img src=\"/ngrx-logo.svg\" alt=\"ngrx logo\" width=\"260\" />\n      <h1 class=\"mat-display-large\">Reactive State for Angular</h1>\n      <div class=\"cta-container\">\n        <a routerLink=\"/guide/store/walkthrough\" mat-flat-button class=\"cta\"\n          >Learn Global Store</a\n        >\n        <a routerLink=\"/guide/signals/signal-store\" mat-flat-button class=\"cta\"\n          >Learn SignalStore</a\n        >\n      </div>\n    </div>\n    <div class=\"content\">\n      <ngrx-styled-box>\n        <mat-icon inline>school</mat-icon>\n        <h3>Learn</h3>\n        <p>\n          Dive into NgRx with our getting started guide. You will learn how to\n          think reactively and architect your Angular apps for success.\n        </p>\n        <a routerLink=\"/guide/store/walkthrough\" mat-flat-button\n          >Learn Global Store</a\n        >\n        <a routerLink=\"/guide/signals/signal-store\" mat-flat-button\n          >Learn SignalStore</a\n        >\n      </ngrx-styled-box>\n      <!--      <ngrx-styled-box>-->\n      <!--        <mat-icon inline>co_present</mat-icon>-->\n      <!--        <h3>Workshops</h3>-->\n      <!--        <p>-->\n      <!--          Attend an NgRx workshop from the creators of NgRx. Our three day-->\n      <!--          workshops cover the entire NgRx reactive state framework, from Store-->\n      <!--          to Effects to SignalStore.-->\n      <!--        </p>-->\n      <!--        <a routerLink=\"/workshops\" mat-flat-button>Attend a Workshop</a>-->\n      <!--      </ngrx-styled-box>-->\n      <ngrx-styled-box>\n        <mat-icon inline>volunteer_activism</mat-icon>\n        <h3>Support the team</h3>\n        <p>Support the development of NgRx by sponsoring us.</p>\n        <a\n          href=\"https://github.com/sponsors/ngrx\"\n          target=\"_blank\"\n          mat-flat-button\n          >Sponsor</a\n        >\n      </ngrx-styled-box>\n    </div>\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n        position: relative;\n        @media only screen and (max-width: 1280px) {\n          z-index: 1;\n        }\n      }\n\n      .banner {\n        display: flex;\n        flex-direction: column;\n        justify-content: center;\n        align-items: center;\n        width: 100%;\n        height: 100lvh;\n        border-bottom: 1px solid var(--ngrx-border-color);\n        position: relative;\n      }\n\n      img {\n        margin-bottom: 16px;\n      }\n\n      h1 {\n        font-weight: 200;\n        font-size: 32px;\n        font-family: 'Oxanium', sans-serif;\n        margin-bottom: 24px;\n        text-align: center;\n      }\n\n      .cta-container {\n        display: flex;\n        gap: 2rem;\n        justify-content: center;\n        flex-wrap: wrap;\n      }\n\n      .cta {\n        font-size: 1.1rem;\n        padding: 1.5rem 2rem;\n      }\n\n      ngrx-banner-animation {\n        position: absolute;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        z-index: 0;\n      }\n\n      img,\n      h1,\n      button {\n        z-index: 1;\n      }\n\n      .content {\n        display: grid;\n        width: 100%;\n        /*grid-template-columns: repeat(3, minmax(300px, 1fr));*/\n        /* (===) TODO: remove after reintroducing Workshops page */\n        grid-template-columns: repeat(2, minmax(0, 500px));\n        justify-content: center;\n        /* (===) */\n        gap: 24px;\n        padding: 32px 32px;\n        @media only screen and (max-width: 1280px) {\n          grid-template-columns: 1fr;\n        }\n      }\n\n      ngrx-styled-box {\n        display: grid;\n        grid-template-rows: 40px 24px 1fr 40px;\n        gap: 8px;\n        padding: 24px;\n      }\n\n      ngrx-styled-box mat-icon {\n        color: var(--ngrx-accent);\n        font-size: 32px;\n      }\n\n      ngrx-styled-box h3 {\n        font-weight: 500;\n        font-family: 'Oxanium', sans-serif;\n      }\n\n      ngrx-styled-box p {\n        padding-bottom: 16px;\n        color: var(--ngrx-text-secondary);\n      }\n\n      ngrx-styled-box button {\n        width: max-content;\n      }\n    `,\n  ],\n})\nexport default class HomeComponent {}\n"
  },
  {
    "path": "projects/www/src/app/pages/about.page.ts",
    "content": "import { Component, inject, computed } from '@angular/core';\nimport { Router, ActivatedRoute } from '@angular/router';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport {\n  ContributorGroup,\n  ContributorsService,\n} from '../services/contributors.service';\nimport { ContributorNavigationComponent } from '../components/contributor-navigation.component';\nimport { ContributorListComponent } from '../components/contributor-list.component';\nimport { map, shareReplay, startWith } from 'rxjs';\n\n@Component({\n  selector: 'ngrx-about-page',\n  imports: [ContributorNavigationComponent, ContributorListComponent],\n  template: `\n    <div class=\"page\">\n      <h1>NgRx Team</h1>\n\n      <ngrx-contributor-navigation\n        [groupNames]=\"groupNames()\"\n        [selectedGroup]=\"filterTerm()\"\n        (groupSelected)=\"setGroup($event)\"\n      />\n\n      <ngrx-contributor-list [contributors]=\"selectedGroup()\" />\n    </div>\n  `,\n  styles: [\n    `\n      .page {\n        padding: 32px;\n      }\n      .page h1 {\n        margin-top: 35px;\n      }\n    `,\n  ],\n})\nexport default class AboutPageComponent {\n  private contributorService = inject(ContributorsService);\n  private route = inject(ActivatedRoute);\n  private router = inject(Router);\n\n  private contributors = toSignal(\n    this.contributorService.getContributors().pipe(shareReplay(1)),\n    { initialValue: [] as ContributorGroup[] }\n  );\n\n  filterTerm = toSignal(\n    this.route.queryParamMap.pipe(\n      map((params) => params.get('group') || 'Core'),\n      startWith('Core')\n    ),\n    { initialValue: 'Core' }\n  );\n\n  groupNames = computed(() =>\n    this.contributors().map((g) => ({ name: g.name, order: g.order }))\n  );\n\n  selectedGroup = computed(() => {\n    const groups = this.contributors();\n    const term = this.filterTerm();\n    const group = groups.find((g) => g.name === term);\n    return group ? group.contributors : [];\n  });\n\n  setGroup(name: string) {\n    this.router.navigate([], {\n      relativeTo: this.route,\n      queryParams: { group: name },\n      queryParamsHandling: 'merge',\n    });\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/pages/api/[package]/[subpackage]/[symbol].page.ts",
    "content": "import { Component, computed, inject, input } from '@angular/core';\nimport {\n  takeUntilDestroyed,\n  toObservable,\n  toSignal,\n} from '@angular/core/rxjs-interop';\nimport { switchMap } from 'rxjs';\nimport { SymbolComponent } from '@ngrx-io/app/components/docs/symbol.component';\nimport { ReferenceService } from '@ngrx-io/app/reference/reference.service';\n\n@Component({\n  selector: 'ngrx-reference-symbol-page',\n  standalone: true,\n  template: `\n    @if (resolvedSymbol(); as summary) {\n      <ngrx-symbol [summary]=\"summary\" />\n    }\n  `,\n  imports: [SymbolComponent],\n})\nexport default class SubpackageSymbolPageComponent {\n  referenceService = inject(ReferenceService);\n  package = input.required<string>();\n  subpackage = input.required<string>();\n  symbol = input.required<string>();\n  inputs = computed(() => ({\n    package: this.package(),\n    subpackage: this.subpackage(),\n    symbol: this.symbol(),\n  }));\n  resolvedSymbol = toSignal(\n    toObservable(this.inputs).pipe(\n      switchMap((inputs) =>\n        this.referenceService.loadReferenceData(\n          `${inputs.package}/${inputs.subpackage}`,\n          inputs.symbol\n        )\n      ),\n      takeUntilDestroyed()\n    )\n  );\n}\n"
  },
  {
    "path": "projects/www/src/app/pages/api/[package]/[symbol].page.ts",
    "content": "import {\n  Component,\n  computed,\n  input,\n  ChangeDetectionStrategy,\n  inject,\n} from '@angular/core';\nimport { toObservable, toSignal } from '@angular/core/rxjs-interop';\nimport { SymbolComponent } from '@ngrx-io/app/components/docs/symbol.component';\nimport { ReferenceService } from '@ngrx-io/app/reference/reference.service';\nimport { switchMap } from 'rxjs';\n\n@Component({\n  selector: 'ngrx-reference-symbol-page',\n  standalone: true,\n  template: `\n    @if (resolvedSymbol(); as summary) {\n      <ngrx-symbol [summary]=\"summary\" />\n    }\n  `,\n  imports: [SymbolComponent],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class PackageSymbolPageComponent {\n  referenceService = inject(ReferenceService);\n  package = input.required<string>();\n  symbol = input.required<string>();\n  inputs = computed(() => ({\n    package: this.package(),\n    symbol: this.symbol(),\n  }));\n  resolvedSymbol = toSignal(\n    toObservable(this.inputs).pipe(\n      switchMap((inputs) => {\n        return this.referenceService.loadReferenceData(\n          inputs.package,\n          inputs.symbol\n        );\n      })\n    )\n  );\n}\n"
  },
  {
    "path": "projects/www/src/app/pages/api/index.page.ts",
    "content": "import { Component, computed, inject, signal } from '@angular/core';\nimport { MatIcon } from '@angular/material/icon';\nimport { MatFormField, MatInput, MatPrefix } from '@angular/material/input';\nimport { MatSlideToggle } from '@angular/material/slide-toggle';\nimport { SymbolChipComponent } from '@ngrx-io/app/components/docs/symbol-chip.component';\nimport { ReferenceService } from '@ngrx-io/app/reference/reference.service';\nimport { MinimizedApiMemberSummary } from '@ngrx-io/shared';\n\n@Component({\n  selector: 'ngrx-reference-index-page',\n  standalone: true,\n  imports: [\n    SymbolChipComponent,\n    MatFormField,\n    MatInput,\n    MatPrefix,\n    MatIcon,\n    MatSlideToggle,\n  ],\n  template: `\n    <div class=\"controls\">\n      <h2>API Reference</h2>\n      <!--<div class=\"deprecated\">\n        <mat-slide-toggle>Hide Deprecated</mat-slide-toggle>\n      </div>-->\n      <div class=\"filter\">\n        <mat-icon matPrefix>search</mat-icon>\n        <input\n          matInput\n          placeholder=\"Search\"\n          [value]=\"searchTerm()\"\n          (input)=\"onSearch($event)\"\n        />\n      </div>\n    </div>\n\n    @for (pkg of filteredPackages(); track pkg.packageName) {\n      <h3>{{ pkg.packageName }}</h3>\n      <div class=\"packageSymbols\">\n        @for (symbol of pkg.symbols; track symbol.canonicalReference) {\n          <ngrx-symbol-chip [symbol]=\"symbol\" />\n        }\n      </div>\n    }\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n        padding: 0 24px 24px;\n        @media only screen and (max-width: 1280px) {\n          padding-top: 62px;\n        }\n      }\n\n      .controls {\n        display: flex;\n        width: 100%;\n        height: 84px;\n        padding: 8px 0 24px;\n        justify-content: space-between;\n        align-items: flex-end;\n        border-bottom: 1px solid var(--ngrx-border-color);\n        position: sticky;\n        top: 0;\n        z-index: 1;\n        background-color: var(--ngrx-bg);\n\n        @media only screen and (max-width: 1280px) {\n          top: calc(var(--top-banner-height, 0px) + 62px);\n        }\n      }\n\n      h3 {\n        margin: 32px 0 24px;\n      }\n\n      .filter {\n        position: relative;\n      }\n\n      .filter mat-icon {\n        position: absolute;\n        top: 6px;\n        left: 6px;\n        bottom: 0;\n        display: flex;\n        align-items: center;\n        color: var(--ngrx-text-faint);\n      }\n\n      .filter input {\n        background-color: transparent;\n        border: none;\n        color: var(--ngrx-text);\n        font-size: 16px;\n        padding: 8px 0 8px 38px;\n        width: 100%;\n        border: 1px solid var(--ngrx-border-color);\n        border-radius: 4px;\n      }\n\n      .packageSymbols {\n        width: 100%;\n        padding: 0 24px;\n        display: grid;\n        grid-template-columns: repeat(3, 1fr);\n        gap: 16px;\n        border-left: 1px solid var(--ngrx-border-color);\n        @media only screen and (max-width: 1280px) {\n          grid-template-columns: repeat(2, 1fr);\n        }\n        @media only screen and (max-width: 700px) {\n          grid-template-columns: 1fr;\n        }\n      }\n    `,\n  ],\n})\nexport default class ApiIndexPageComponent {\n  referenceService = inject(ReferenceService);\n  searchTerm = signal<string>('');\n  filteredPackages = computed(() => {\n    const packageReport = this.referenceService.getMinifiedApiReport();\n    const term = this.searchTerm();\n\n    if (!packageReport) return [];\n\n    return packageReport.packageNames.reduce(\n      (packages, packageName) => {\n        const pkg = packageReport.packages[packageName];\n        const symbols = pkg.symbolNames.map(\n          (symbolName) => pkg.symbols[symbolName]\n        );\n        const filteredSymbols = symbols.filter(\n          (symbol) =>\n            !symbol.isDeprecated &&\n            symbol.name.toLocaleLowerCase().includes(term.toLocaleLowerCase())\n        );\n\n        return filteredSymbols.length > 0\n          ? [...packages, { packageName, symbols }]\n          : packages;\n      },\n      [] as { packageName: string; symbols: MinimizedApiMemberSummary[] }[]\n    );\n  });\n\n  onSearch(event: Event) {\n    const input = event.target as HTMLInputElement;\n\n    this.searchTerm.set(input.value);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component/index.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/component` package is in <a href=\"https://github.com/ngrx/platform/issues/4872\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# @ngrx/component\n\nComponent is a library for building reactive Angular templates.\nIt provides a set of declarables that can work with or without `zone.js`.\nThey give more control over rendering and provide further reactivity for Angular applications.\n\n## Key Concepts\n\n- Rendering observable events in a performant way.\n- Displaying different content based on the current state of an observable.\n- Combining multiple observables in the template.\n- Creating readable templates by using aliases for nested properties.\n- Building fully reactive Angular applications regardless of whether `zone.js` is present or not.\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/component/install) page.\n\n## Available Features\n\nLearn more about features provided by the `@ngrx/component` package through the [`*ngrxLet` directive](guide/component/let)\nand [`ngrxPush` pipe](guide/component/push) docs.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component/install.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/component` package is in <a href=\"https://github.com/ngrx/platform/issues/4872\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Installation\n\n## Installing with `ng add`\n\nYou can install the Component package to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/component@latest\n```\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/component`.\n2. Run the package manager to install the added dependency.\n\n## Manual Installation\n\nYou can also install `@ngrx/component` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/component\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component/let.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/component` package is in <a href=\"https://github.com/ngrx/platform/issues/4872\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Let Directive\n\nThe `*ngrxLet` directive serves a convenient way of binding observables to a view context\n(DOM element's scope). It also helps with several internal processing under the hood.\n\n## Usage\n\nThe `*ngrxLet` directive is a standalone directive.\nTo use it, add the `LetDirective` to the `imports` of your standalone component or NgModule:\n\n<ngrx-code-example>\n\n```ts\nimport { Component } from '@angular/core';\nimport { LetDirective } from '@ngrx/component';\n\n@Component({\n  // ... other metadata\n  imports: [\n    // ... other imports\n    LetDirective,\n  ],\n})\nexport class MyStandaloneComponent {}\n```\n\n</ngrx-code-example>\n\n## Comparison with `*ngIf` and `async`\n\nThe current way of binding an observable to the view looks like this:\n\n```html\n<ng-container *ngIf=\"number$ | async as n\">\n  <app-number [number]=\"n\"></app-number>\n\n  <app-number-special [number]=\"n\"></app-number-special>\n</ng-container>\n```\n\nThe problem is that `*ngIf` is interfering with rendering.\nIn case of `0` (falsy value), the component would be hidden.\n\nThe `*ngrxLet` directive takes over several things and makes it more convenient\nand safe to work with streams in the template:\n\n```html\n<ng-container *ngrxLet=\"number$ as n\">\n  <app-number [number]=\"n\"></app-number>\n</ng-container>\n\n<ng-container *ngrxLet=\"number$; let n\">\n  <app-number [number]=\"n\"></app-number>\n</ng-container>\n```\n\n## Tracking Different Observable Events\n\nIn addition to that it provides us information from the whole observable context.\nWe can track next, error, and complete events:\n\n```html\n<ng-container *ngrxLet=\"number$ as n; error as e; complete as c\">\n  <app-number [number]=\"n\" *ngIf=\"!e && !c\"> </app-number>\n\n  <p *ngIf=\"e\">There is an error: {{ e }}</p>\n  <p *ngIf=\"c\">Observable is completed.</p>\n</ng-container>\n```\n\n## Combining Multiple Observables\n\nThe `*ngrxLet` directive can be also used with a dictionary of observables.\nThis feature provides the ability to create a view model object in the template:\n\n```html\n<ng-container *ngrxLet=\"{ users: users$, query: query$ } as vm\">\n  <app-search-bar [query]=\"vm.query\"></app-search-bar>\n  <app-user-list [users]=\"vm.users\"></app-user-list>\n</ng-container>\n```\n\n## Using Suspense Template\n\nThere is an option to pass the suspense template that will be displayed\nwhen an observable is in a suspense state:\n\n```html\n<ng-container *ngrxLet=\"number$ as n; suspenseTpl: loading\">\n  <app-number [number]=\"n\"></app-number>\n</ng-container>\n\n<ng-template #loading>\n  <p>Loading...</p>\n</ng-template>\n```\n\n<ngrx-docs-alert type=\"help\">\n\nAn observable is in a suspense state until it emits the first event (next, error, or complete).\n\n</ngrx-docs-alert>\n\nIn case a new observable is passed to the `*ngrxLet` directive at runtime,\nthe suspense template will be displayed again until the new observable emits the first event.\n\n## Using Aliases for Non-Observable Values\n\nThe `*ngrxLet` directive can also accept static (non-observable) values as input argument.\nThis feature provides the ability to create readable templates by using aliases for deeply nested properties:\n\n```html\n<ng-container *ngrxLet=\"userForm.controls.email as email\">\n  <input type=\"text\" [formControl]=\"email\" />\n\n  <ng-container\n    *ngIf=\"email.errors && (email.touched || email.dirty)\"\n  >\n    <p *ngIf=\"email.errors.required\">This field is required.</p>\n    <p *ngIf=\"email.errors.email\">This field must be an email.</p>\n  </ng-container>\n</ng-container>\n```\n\n## Included Features\n\n- Binding is present even for falsy values.\n  (See [\"Comparison with `*ngIf` and `async`\"](#comparison-with-ngif-and-async) section)\n- Takes away the multiple usages of the `async` or `ngrxPush` pipe.\n- Allows displaying different content based on the current state of an observable.\n- Allows combining multiple observables in the template.\n- Provides a unified/structured way of handling `null` and `undefined`.\n- Provides the ability to create readable templates by using aliases for nested properties.\n- Triggers change detection using the `RenderScheduler` that behaves differently in\n  zone-full and zone-less mode.\n- Distinct the same values in a row for better performance.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component/push.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/component` package is in <a href=\"https://github.com/ngrx/platform/issues/4872\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Push Pipe\n\nThe `ngrxPush` pipe serves as a drop-in replacement for the `async` pipe.\nIt contains intelligent handling of change detection to enable us\nrunning in zone-full as well as zone-less mode without any changes to the code.\n\n## Usage\n\nThe `ngrxPush` pipe is a standalone pipe.\nTo use it, add the `PushPipe` to the `imports` of your standalone component or NgModule:\n\n<ngrx-code-example>\n\n```ts\nimport { Component } from '@angular/core';\nimport { PushPipe } from '@ngrx/component';\n\n@Component({\n  // ... other metadata\n  imports: [\n    // ... other imports\n    PushPipe,\n  ],\n})\nexport class MyStandaloneComponent {}\n```\n\n</ngrx-code-example>\n\n## Comparison with `async` Pipe\n\nThe current way of binding an observable to the view looks like this:\n\n```html\n<p>{{ number$ | async }}</p>\n\n<ng-container *ngIf=\"number$ | async as n\">{{ n }}</ng-container>\n\n<app-number [number]=\"number$ | async\"></app-number>\n```\n\nThe `async` pipe marks the component and all its ancestors as dirty, but does not trigger the change detection mechanism.\nIt needs the `zone.js` microtask queue to exhaust until `ApplicationRef.tick` is called to render all dirty marked components.\nTo use the `async` pipe in zone-less mode, we have to manually trigger the change detection each time an observable\nemits a new value.\n\nFortunately, the `ngrxPush` pipe solves this problem by scheduling a new change detection cycle in zone-less mode when\nan observable emits a new value. It can be used as follows:\n\n```html\n<p>{{ number$ | ngrxPush }}</p>\n\n<ng-container *ngIf=\"number$ | ngrxPush as n\">{{ n }}</ng-container>\n\n<app-number [number]=\"number$ | ngrxPush\"></app-number>\n```\n\n## Combining Multiple Observables\n\nThe `ngrxPush` pipe can be also used with a dictionary of observables in the\nfollowing way:\n\n```html\n<code>\n  {{ { users: users$, query: query$ } | ngrxPush | json }}\n</code>\n```\n\n## Included Features\n\n- Takes observables or promises, retrieves their values, and passes the value to the template.\n- Allows combining multiple observables in the template.\n- Handles `null` and `undefined` values in a clean unified/structured way.\n- Triggers change detection using the `RenderScheduler` that behaves differently in\n  zone-full and zone-less mode.\n- Distinct the same values in a row for better performance.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component-store/comparison.md",
    "content": "<ngrx-docs-alert type=\"help\">\n\n**<a href=\"/guide/signals\"><b>NgRx Signals</b></a> is the new default.**\n\nThe NgRx team recommends using the `@ngrx/signals` library for local state management in Angular.\nWhile ComponentStore remains supported, we encourage using `@ngrx/signals` for new projects and considering migration for existing ones.\n\n</ngrx-docs-alert>\n\n# Comparison of ComponentStore and Store\n\nBoth ComponentStore and Store are designed to manage the state in an Angular application, however\nthey are addressing different problems. These libraries are independent of each other, and,\ndepending on many factors, one or the other or even both in tandem would be recommended.\n\nAmong the factors that would influence which of the libraries (or both) should be used are the following:\n\n- **Size of the application**. How many components does it have?\n- **Interconnection of the app**. Are these components tied together, or are they independent\n  groups of components sub-trees?\n- **Depth of component tree**. How many levels of depth does the component tree have?\n- **State ownership**. Could there be a clear separation of state ownership at different\n  nodes of the components tree?\n- **State lifespan**. Is the state needed throughout the lifespan of the application, or only when\n  some pages are displayed and we want to automatically clean it up when the user navigates somewhere\n  else?\n- **Business Requirements**. How well are all of the business requirements understood and finalized before the\n  implementation of the app starts? Would it be changing frequently?\n- **Lifespan of the app**. Is this a short-lived Minimum Viable Product (MVP) that would be discarded, a solution that won't need much support or changes once it's released, or is it a long-term product\n  that would be constantly changing, based on changing business needs?\n- **Backend APIs**. Does the team have influence over backend(s) and the APIs that they provide?\n\nThe longer the lifespan of the app and the larger it is, the greater the need to separate how the\ndata is retrieved and how components are displaying it. That drives earlier separation of\n_\"Data Transfer Objects\"_ (aka _\"Network Models\"_) - the models used to communicate with backend(s) - and _\"View Models\"_ - the models\nused by components in the templates.\n\nComponentStore is responsible for managing smaller, more local state. While it's possible to have\nmultiple ComponentStores, one has to have a clear understanding of state ownership of each one of\nthem.\n\n## Benefits and Trade-offs\n\nComponentStore and Global Store have many benefits, some of which are listed in the [introduction](guide/store/why#why-use-ngrx-store-for-state-management). They help organize state, make migrations to new APIs easier,\nencapsulate changes and side-effects, make our components smaller, easier to test and more\nperformant, but they are also introducing code complexity with **indirections**.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** It's important to understand what the cost is and why we are adding it.\n\n</ngrx-docs-alert>\n\nBoth of them bring [push-based architecture](https://medium.com/@thomasburlesonIA/push-based-architectures-with-rxjs-81b327d7c32d), which is the first indirection. The developer can no longer\nget the result of a service method call, instead they would be listening for Observable values\nexposed by that service. The benefit, on the other side, is that the developer no longer has to worry what\nis changing the state - all the component needs to know is that something has changed it. If the\ncomponent wants to change the state itself, it sends the message about it (either dispatches an\nAction in Store, or calls ComponentStore's `updater` or `effect`).\n\nActions are the second indirection. They are present in the Global Store only. There are many benefits\nof this indirection, such as:\n\n- ability to trigger multiple effects/reducers at the same time\n- greater scalability\n- useful DevTools\n\nComponentStore doesn't have that indirection, however it also loses the above-mentioned benefits.\n\nThe scale of state that it works with has to be smaller, which brings another set of benefits, such as:\n\n- ComponentStore that is tied to the specific node in the components tree, will be automatically cleaned up when that node is destroyed\n- state is fully self-contained with each ComponentStore, and thus allows to have multiple\n  independent instances of the same component\n- provides simpler state management to small/medium sized apps\n\n<ngrx-docs-alert type=\"help\">\n\nThe difference between the benefits and trade-offs of Stores make Global Store better suited for\nmanaging global shared state, where ComponentStore shines managing more local, encapsulated state,\nas well as component UI state.\n\n</ngrx-docs-alert>\n\nDepending on the needs of the application, the developer might choose Store or ComponentStore, or,\nin cases of the larger apps, both Store **and** ComponentStore.\n\n## State ownership\n\nThe Global Store works with the **single** immutable object, that contains all of the shared state throughout\nthe application. There are multiple reducers, each one responsible for a particular **slice** of\nstate.\n\nEach ComponentStore is fully responsible for its own state. There could be **many** different\nComponentStores, but each one should store its own distinct state.\n\n<figure>\n  <img src=\"images/guide/component-store/state-structure.png\" alt=\"Comparison of NgRx Store and Component Store state ownership or placement\" width=\"100%\" height=\"100%\" />\n</figure>\n\n## File structure\n\nComponentStore is focused on a smaller part of the state, and thus should contain not only the state\nitself, but also every \"prescription\" of how it could be changed. All \"`updater`s\" and \"`effect`s\"\nshould be part of the ComponentStore, responsible for the specific state.\n\nIt makes ComponentStore less scalable - if there are too many updaters and effects in a single class,\nthen it quickly becomes unreadable.\n\nShared `select`ors should also be part of the ComponentStore, however downstream components might\nhave their component-specific details, such as aggregating all the info needed for their _\"View Model\"_.\nIn such cases, it's acceptable to create `ComponentStore<object>` that won't be managing state\nand would contain a number of selectors.\n\n<figure>\n  <img src=\"images/guide/component-store/file-structure.png\" alt=\"Comparison of NgRx Store and Component Store file structures\" width=\"100%\" height=\"100%\" />\n</figure>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component-store/effect.md",
    "content": "<ngrx-docs-alert type=\"help\">\n\n**<a href=\"/guide/signals\"><b>NgRx Signals</b></a> is the new default.**\n\nThe NgRx team recommends using the `@ngrx/signals` library for local state management in Angular.\nWhile ComponentStore remains supported, we encourage using `@ngrx/signals` for new projects and considering migration for existing ones.\n\n</ngrx-docs-alert>\n\n# Effects\n\nEffects are designed to extract any side-effects (such as Network calls) from components and\nhandle potential race conditions.\n\n## Key Concepts\n\n- Effects isolate side effects from components, allowing for more pure components that select state and trigger updates and/or effects in ComponentStore(s).\n- Effects are Observables listening for the inputs and piping them through the \"prescription\".\n- Those inputs can either be values or Observables of values.\n- Effects perform tasks, which are synchronous or asynchronous.\n\n## `effect` method\n\nThe `effect` method takes a callback with an Observable of values, that describes HOW new\nincoming values should be handled. Each new call of the effect would push the value into that\nObservable.\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\n@Injectable()\nexport class MoviesStore extends ComponentStore<MoviesState> {\n  constructor(private readonly moviesService: MoviesService) {\n    super({ movies: [] });\n  }\n\n  // Each new call of getMovie(id) pushed that id into movieId$ stream.\n  readonly getMovie = this.effect((movieId$: Observable<string>) => {\n    return movieId$.pipe(\n      // 👇 Handle race condition with the proper choice of the flattening operator.\n      switchMap((id) =>\n        this.moviesService.fetchMovie(id).pipe(\n          //👇 Act on the result within inner pipe.\n          tap({\n            next: (movie) => this.addMovie(movie),\n            error: (e) => this.logError(e),\n          }),\n          // 👇 Handle potential error within inner pipe.\n          catchError(() => EMPTY)\n        )\n      )\n    );\n  });\n\n  readonly addMovie = this.updater((state, movie: Movie) => ({\n    movies: [...state.movies, movie],\n  }));\n\n  selectMovie(movieId: string) {\n    return this.select((state) =>\n      state.movies.find((m) => m.id === movieId)\n    );\n  }\n}\n```\n\n</ngrx-code-example>\n\nThe `getMovie` effect could then be used within a component.\n\n<ngrx-code-example header=\"movie.component.ts\">\n\n```ts\n@Component({\n  template: `...`,\n  // ❗️MoviesStore is provided higher up the component tree\n})\nexport class MovieComponent {\n  movie$: Observable<Movie>;\n\n  @Input()\n  set movieId(value: string) {\n    // calls effect with value. 👇 Notice it's a single string value.\n    this.moviesStore.getMovie(value);\n    this.movie$ = this.moviesStore.selectMovie(value);\n  }\n\n  constructor(private readonly moviesStore: MoviesStore) {}\n}\n```\n\n</ngrx-code-example>\n\n## Calling an `effect` without parameters\n\nA common use case is to call the `effect` method without any parameters.\nTo make this possible set the generic type of the `effect` method to `void`.\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\nreadonly getAllMovies = this.effect<void>(\n    // The name of the source stream doesn't matter: `trigger$`, `source$` or `$` are good\n    // names. We encourage to choose one of these and use them consistently in your codebase.\n    (trigger$) => trigger$.pipe(\n      exhaustMap(() =>\n        this.moviesService.fetchAllMovies().pipe(\n          tapResponse({\n            next: (movies) => this.addAllMovies(movies),\n            error: (error: HttpErrorResponse) => this.logError(error),\n          })\n        )\n      )\n    )\n  );\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component-store/index.md",
    "content": "<ngrx-docs-alert type=\"help\">\n\n**<a href=\"/guide/signals\"><b>NgRx Signals</b></a> is the new default.**\n\nThe NgRx team recommends using the `@ngrx/signals` library for local state management in Angular.\nWhile ComponentStore remains supported, we encourage using `@ngrx/signals` for new projects and considering migration for existing ones.\n\n</ngrx-docs-alert>\n\n# @ngrx/component-store\n\nComponentStore is a stand-alone library that helps to manage local/component state. It's an alternative to reactive push-based \"Service with a Subject\" approach.\n\n## Key Concepts\n\n- Local state has to be initialized, but it can be done lazily.\n- Local state is typically tied to the life-cycle of a particular component and is cleaned up when that component is destroyed.\n- Users of ComponentStore can update the state through `setState` or `updater`, either imperatively or by providing an Observable.\n- Users of ComponentStore can read the state through `select` or a top-level `state$`. Selectors are very performant.\n- Users of ComponentStore may start side-effects with `effect`, both sync and async, and feed the data both imperatively or reactively.\n\nThe details about [initialization](guide/component-store/initialization), [writing](guide/component-store/write) and [reading](guide/component-store/read) from state,\nand [side-effects](guide/component-store/effect) management can be found in the corresponding sections of the Architecture.\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/component-store/install) page.\n\n## @ngrx/store or @ngrx/component-store?\n\nThe Global Store and Component Store are designed to solve different problems and can be used independently from each other. The detailed comparison can\nbe found at [Store vs ComponentStore](guide/component-store/comparison) section.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component-store/initialization.md",
    "content": "<ngrx-docs-alert type=\"help\">\n\n**<a href=\"/guide/signals\"><b>NgRx Signals</b></a> is the new default.**\n\nThe NgRx team recommends using the `@ngrx/signals` library for local state management in Angular.\nWhile ComponentStore remains supported, we encourage using `@ngrx/signals` for new projects and considering migration for existing ones.\n\n</ngrx-docs-alert>\n\n# Initialization\n\nComponentStore can be initialized in 2 ways:\n\n- through the constructor - it would have the initial state\n- by calling `setState` and passing an object that matches the state interface.\n\n## Initialization through the constructor\n\nInitializing through the constructor makes the state immediately available to the ComponentStore consumers.\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\nexport interface MoviesState {\n  movies: Movie[];\n}\n\n@Injectable()\nexport class MoviesStore extends ComponentStore<MoviesState> {\n  constructor() {\n    super({ movies: [] });\n  }\n}\n```\n\n</ngrx-code-example>\n\n## Lazy initialization\n\nIn some cases, developers do not want selectors to return any state until there's meaningful data in the ComponentStore. The solution\nwould be to initialize the state lazily by calling [`setState`](guide/component-store/write#setstate-method) and passing the full state to it. The same approach can be taken to reset the state.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** Initialization has to be done before updating the state, otherwise an error would be thrown.\n\n</ngrx-docs-alert>\n\n<ngrx-code-example header=\"movies-page.component.ts\">\n\n```ts\n@Component({\n  template: `\n    <li *ngFor=\"let movie of movies$ | async\">\n      {{ movie.name }}\n    </li>\n  `,\n  providers: [ComponentStore],\n})\nexport class MoviesPageComponent {\n  readonly movies$ = this.componentStore.select(\n    (state) => state.movies\n  );\n\n  constructor(\n    private readonly componentStore: ComponentStore<{\n      movies: Movie[];\n    }>\n  ) {}\n\n  ngOnInit() {\n    this.componentStore.setState({ movies: [] });\n  }\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component-store/install.md",
    "content": "<ngrx-docs-alert type=\"help\">\n\n**<a href=\"/guide/signals\"><b>NgRx Signals</b></a> is the new default.**\n\nThe NgRx team recommends using the `@ngrx/signals` library for local state management in Angular.\nWhile ComponentStore remains supported, we encourage using `@ngrx/signals` for new projects and considering migration for existing ones.\n\n</ngrx-docs-alert>\n\n# Installation\n\n## Installing with `ng add`\n\nYou can install the ComponentStore to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/component-store@latest\n```\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/component-store`.\n2. Run the package manager to install the added dependency.\n\n## Manual Installation\n\nYou can also install `@ngrx/component-store` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/component-store\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component-store/lifecycle.md",
    "content": "<ngrx-docs-alert type=\"help\">\n\n**<a href=\"/guide/signals\"><b>NgRx Signals</b></a> is the new default.**\n\nThe NgRx team recommends using the `@ngrx/signals` library for local state management in Angular.\nWhile ComponentStore remains supported, we encourage using `@ngrx/signals` for new projects and considering migration for existing ones.\n\n</ngrx-docs-alert>\n\n# Lifecycle\n\nNgRx ComponentStore comes with lifecycle hooks and observables for performing tasks after the ComponentStore is initially instantiated, after the initial state is first set, and when the ComponentStore is destroyed. You can use these lifecycle hooks to set up long-running effects, wire up additional logic, and other tasks outside the constructor of the ComponentStore.\n\n## Setup\n\nBoth lifecycle hooks are enabled by providing the ComponentStore through the `provideComponentStore()` function. This function registers the ComponentStore as a provider, sets up a factory provider to instantiate the ComponentStore instance, and calls the implemented lifecycle hooks.\n\nCurrently, Angular provides initializer tokens in a few areas. The `APP_INITIALIZER` and `BOOTSTRAP_INITIALIZER` for application/bootstrap init logic, and the `ENVIRONMENT_INITIALIZER` for environment injector init logic. The `provideComponentStore()` mimics this behavior to run the lifecycle hooks. The function is required because there aren't any provided tokens at the component level injector to allow initialization tasks.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** If you implement the lifecycle hooks in the ComponentStore, and register it with `providers` without using `provideComponentStore()`, in development mode, a warning is logged to the browser console.\n\n</ngrx-docs-alert>\n\n## OnStoreInit\n\nThe `OnStoreInit` interface is used to implement the `ngrxOnStoreInit` method in the ComponentStore class. This lifecycle method is called immediately after the ComponentStore class is instantiated.\n\n<ngrx-code-example header=\"books.store.ts\">\n\n```ts\nexport interface BooksState {\n  collection: Book[];\n}\n\nexport const initialState: BooksState = {\n  collection: [],\n};\n\n@Injectable()\nexport class BooksStore\n  extends ComponentStore<BooksState>\n  implements OnStoreInit\n{\n  constructor() {\n    super(initialState);\n  }\n\n  ngrxOnStoreInit() {\n    // called after store has been instantiated\n  }\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"books-page.component.ts\">\n\n```ts\n@Component({\n  // ... other metadata\n  providers: [provideComponentStore(BooksStore)],\n})\nexport class BooksPageComponent {\n  constructor(private booksStore: BooksStore) {}\n}\n```\n\n</ngrx-code-example>\n\n## OnStateInit\n\nThe `OnStateInit` interface is used to implement the `ngrxOnStateInit` method in the ComponentStore class. This lifecycle method is called **only once** after the ComponentStore state is initially set. ComponentStore supports eager and lazy initialization of state, and the lifecycle hook is called appropriately in either scenario.\n\n### Eager State Init\n\n<ngrx-code-example header=\"books.store.ts\">\n\n```ts\nexport interface BooksState {\n  collection: Book[];\n}\n\nexport const initialState: BooksState = {\n  collection: [],\n};\n\n@Injectable()\nexport class BooksStore\n  extends ComponentStore<BooksState>\n  implements OnStateInit\n{\n  constructor() {\n    // eager state initialization\n    super(initialState);\n  }\n\n  ngrxOnStateInit() {\n    // called once after state has been first initialized\n  }\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"books-page.component.ts\">\n\n```ts\n@Component({\n  // ... other metadata\n  providers: [provideComponentStore(BooksStore)],\n})\nexport class BooksPageComponent {\n  constructor(private booksStore: BooksStore) {}\n}\n```\n\n</ngrx-code-example>\n\n### Lazy State Init\n\n<ngrx-code-example header=\"books.store.ts\">\n\n```ts\nexport interface BooksState {\n  collection: Book[];\n}\n\n@Injectable()\nexport class BooksStore\n  extends ComponentStore<BooksState>\n  implements OnStateInit\n{\n  constructor() {\n    super();\n  }\n\n  ngrxOnStateInit() {\n    // called once after state has been first initialized\n  }\n}\n\nexport const initialState: BooksState = {\n  collection: [],\n};\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"books-page.component.ts\">\n\n```ts\n@Component({\n  // ... other metadata\n  providers: [provideComponentStore(BooksStore)],\n})\nexport class BooksPageComponent implements OnInit {\n  constructor(private booksStore: BooksStore) {}\n\n  ngOnInit() {\n    // lazy state initialization\n    this.booksStore.setState(initialState);\n  }\n}\n```\n\n</ngrx-code-example>\n\n## OnDestroy\n\nComponentStore also implements the `OnDestroy` interface from `@angular/core` to complete any internally created observables.\n\nIt also exposes a `destroy$` property on the ComponentStore class that can be used instead of manually creating a `Subject` to unsubscribe from any observables created in the component.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** If you override the `ngOnDestroy` method in your component store, you need to call `super.ngOnDestroy()`. Otherwise a memory leak may occur.\n\n</ngrx-docs-alert>\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\n@Injectable()\nexport class MoviesStore\n  extends ComponentStore<MoviesState>\n  implements OnDestroy\n{\n  constructor() {\n    super({ movies: [] });\n  }\n\n  override ngOnDestroy(): void {\n    // 👇 add this line\n    super.ngOnDestroy();\n  }\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"books-page.component.ts\">\n\n```ts\n@Component({\n  // ... other metadata\n  providers: [ComponentStore],\n})\nexport class BooksPageComponent implements OnInit {\n  constructor(private cs: ComponentStore) {}\n\n  ngOnInit() {\n    const timer = interval(1000)\n      .pipe(takeUntil(this.cs.destroy$))\n      .subscribe(() => {\n        // listen until ComponentStore is destroyed\n      });\n  }\n}\n```\n\n</ngrx-code-example>\n\nThe `provideComponentStore()` function is not required to listen to the `destroy$` property on the ComponentStore.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component-store/read.md",
    "content": "<ngrx-docs-alert type=\"help\">\n\n**<a href=\"/guide/signals\"><b>NgRx Signals</b></a> is the new default.**\n\nThe NgRx team recommends using the `@ngrx/signals` library for local state management in Angular.\nWhile ComponentStore remains supported, we encourage using `@ngrx/signals` for new projects and considering migration for existing ones.\n\n</ngrx-docs-alert>\n\n# Reading state\n\n## `select` method\n\nReading state is done with the `select` method, which takes a projector that describes HOW the state is retrieved and/or transformed.\nSelectors emit new values when those values \"change\" - the new value is no longer distinct by comparison from the previous value.\n\nAnother performance benefit of selectors is that they are \"shared\" - they multicast the value to each subscriber.\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\nexport interface MoviesState {\n  movies: Movie[];\n}\n\n@Injectable()\nexport class MoviesStore extends ComponentStore<MoviesState> {\n  constructor() {\n    super({ movies: [] });\n  }\n\n  readonly movies$: Observable<Movie[]> = this.select(\n    (state) => state.movies\n  );\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"movies-page.component.ts\">\n\n```ts\n@Component({\n  template: `\n    <li *ngFor=\"let movie of movies$ | async\">\n      {{ movie.name }}\n    </li>\n  `,\n  providers: [MoviesStore],\n})\nexport class MoviesPageComponent {\n  movies$ = this.moviesStore.movies$;\n\n  constructor(private readonly moviesStore: MoviesStore) {}\n}\n```\n\n</ngrx-code-example>\n\n## Combining selectors\n\nSelectors can be used to combine other Selectors or Observables.\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\nexport interface MoviesState {\n  movies: Movie[];\n  userPreferredMoviesIds: string[];\n}\n\n@Injectable()\nexport class MoviesStore extends ComponentStore<MoviesState> {\n  constructor() {\n    super({ movies: [], userPreferredMoviesIds: [] });\n  }\n\n  readonly movies$ = this.select((state) => state.movies);\n  readonly userPreferredMovieIds$ = this.select(\n    (state) => state.userPreferredMoviesIds\n  );\n\n  readonly userPreferredMovies$ = this.select(\n    this.movies$,\n    this.userPreferredMovieIds$,\n    (movies, ids) => movies.filter((movie) => ids.includes(movie.id))\n  );\n}\n```\n\n</ngrx-code-example>\n\n## Creating a View Model\n\nCreating view models is a recommended way to consolidate multiple streams in a clean API to provide to your component template.\nThe `select` method accepts a dictionary of observables as input and returns an observable of the dictionary of values as output. View models can be written in the following way:\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\nprivate readonly vm$ = this.select({\n    movies: this.movies$,\n    userPreferredMovieIds: this.userPreferredMovieIds$,\n    userPreferredMovies: this.userPreferredMovies$\n  });\n```\n\n</ngrx-code-example>\n\n## Debounce selectors\n\nSelectors are synchronous by default, meaning that they emit the value immediately when subscribed to, and on every state change.\nSometimes the preferred behavior would be to wait (or debounce) until the state \"settles\" (meaning all the changes within the current microtask occur)\nand only then emit the final value.\nIn many cases, this would be the most performant way to read data from the ComponentStore, however its behavior might be surprising sometimes, as it won't emit a value until later on.\nThis makes it harder to test such selectors.\n\nAdding the debounce to a selector is done by passing `{debounce: true}` as the last argument.\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\n@Injectable()\nexport class MoviesStore extends ComponentStore<MoviesState> {\n  constructor(private movieService: MovieService) {\n    super({ movies: [], moviesPerPage: 10, currentPageIndex: 0 });\n\n    // 👇 effect is triggered whenever debounced data is changed\n    this.fetchMovies(this.fetchMoviesData$);\n  }\n\n  // Updates how many movies per page should be displayed\n  readonly updateMoviesPerPage = this.updater(\n    (state, moviesPerPage: number) => ({\n      ...state,\n      moviesPerPage, // updates with new value\n    })\n  );\n\n  // Updates which page of movies that the user is currently on\n  readonly updateCurrentPageIndex = this.updater(\n    (state, currentPageIndex: number) => ({\n      ...state,\n      currentPageIndex, // updates with new page index\n    })\n  );\n\n  readonly moviesPerPage$ = this.select(\n    (state) => state.moviesPerPage\n  );\n\n  readonly currentPageIndex$ = this.select(\n    (state) => state.currentPageIndex\n  );\n\n  private readonly fetchMoviesData$ = this.select(\n    {\n      moviesPerPage: this.moviesPerPage$,\n      currentPageIndex: this.currentPageIndex$,\n    },\n    { debounce: true } // 👈 setting this selector to debounce\n  );\n\n  private readonly fetchMovies = this.effect(\n    (\n      moviePageData$: Observable<{\n        moviesPerPage: number;\n        currentPageIndex: number;\n      }>\n    ) => {\n      return moviePageData$.pipe(\n        concatMap(({ moviesPerPage, currentPageIndex }) => {\n          return this.movieService\n            .loadMovies(moviesPerPage, currentPageIndex)\n            .pipe(tap((results) => this.updateMovieResults(results)));\n        })\n      );\n    }\n  );\n}\n```\n\n</ngrx-code-example>\n\n## Using a custom equality function\n\nThe observable created by the `select` method compares the newly emitted value with the previous one using the default equality check (`===`) and emits only if the value has changed. However, the default behavior can be overridden by passing a custom equality function to the `select` method config.\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\nexport interface MoviesState {\n  movies: Movie[];\n}\n\n@Injectable()\nexport class MoviesStore extends ComponentStore<MoviesState> {\n  constructor() {\n    super({ movies: [] });\n  }\n\n  readonly movies$: Observable<Movie[]> = this.select(\n    (state) => state.movies,\n    { equal: (prev, curr) => prev.length === curr.length } // 👈 custom equality function\n  );\n}\n```\n\n</ngrx-code-example>\n\n## Selecting from global `@ngrx/store`\n\nComponentStore is an independent library, however it can easily consume data from `@ngrx/store` or from any other global state management library.\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\nprivate readonly fetchMoviesData$ = this.select(\n  this.store.select(getUserId), // 👈 store.select returns an Observable, which is easily mixed within selector\n  moviesPerPage$,\n  currentPageIndex$,\n  (userId, moviesPerPage, currentPageIndex) => ({userId, moviesPerPage, currentPageIndex}),\n);\n```\n\n</ngrx-code-example>\n\n## `selectSignal` method\n\nComponentStore also provides the `selectSignal` method, which has two signatures.\nThe first signature creates a signal from the provided state projector function.\nThe second signature creates a signal by combining the provided signals, this is similar to the `select` method that combines the provided observables.\n\n<ngrx-code-example header=\"users.store.ts\">\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { ComponentStore } from '@ngrx/component-store';\n\nimport { User } from './user.model';\n\ntype UsersState = { users: User[]; query: string };\n\n@Injectable()\nexport class UsersStore extends ComponentStore<UsersState> {\n  // type: Signal<User[]>\n  readonly users = this.selectSignal((s) => s.users);\n  // type: Signal<string>\n  readonly query = this.selectSignal((s) => s.query);\n  // type: Signal<User[]>\n  readonly filteredUsers = this.selectSignal(\n    this.users,\n    this.query,\n    (users, query) => users.filter(({ name }) => name.includes(query))\n  );\n}\n```\n\n</ngrx-code-example>\n\nThe `selectSignal` method also accepts an equality function to stop the recomputation of the deeper dependency chain if two values are determined to be equal.\n\n## `state` signal\n\nThe `state` signal returns the entire state of the ComponentStore.\n\nUse the `state` signal to create computed signals that derives its value from the state.\n\n<ngrx-code-example header=\"users.store.ts\">\n\n```ts\nimport { computed, Injectable } from '@angular/core';\nimport { ComponentStore } from '@ngrx/component-store';\n\nimport { User } from './user.model';\n\ntype UsersState = { users: User[]; query: string };\n\n@Injectable()\nexport class UsersStore extends ComponentStore<UsersState> {\n  readonly users = computed(() => this.state().users);\n  readonly query = computed(() => this.state().query);\n\n  readonly filteredUsers = computed(() =>\n    this.users().filter(({ name }) => name.includes(this.query()))\n  );\n}\n```\n\n</ngrx-code-example>\n\n## `get` method\n\nWhile a selector provides a reactive way to read the state from ComponentStore via Observable, sometimes an imperative read is needed.\nOne of such use cases is accessing the state within an `effect`s and that's where `get` method could be used.\n\n<ngrx-docs-alert type=\"error\">\n\nThe `get` method is ComponentStore-private, meaning it's accessible only within the ComponentStore. It's done to discourage frequent imperative reads\nfrom the state as the NgRx team is in a consensus that such reads promote further potentially harmful architectural decisions.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component-store/usage.md",
    "content": "<ngrx-docs-alert type=\"help\">\n\n**<a href=\"/guide/signals\"><b>NgRx Signals</b></a> is the new default.**\n\nThe NgRx team recommends using the `@ngrx/signals` library for local state management in Angular.\nWhile ComponentStore remains supported, we encourage using `@ngrx/signals` for new projects and considering migration for existing ones.\n\n</ngrx-docs-alert>\n\n# Usage\n\n## Types of State\n\nThere are multiple types of state that exist in the application, and state management libraries are there to help manage/synchronize/update them. The topic of which one to choose, ComponentStore or the Global Store, or maybe both would be helpful, is described at [ComponentStore vs Store](guide/component-store/comparison) section.\n\nThe types of state that developers typically deal with in applications are:\n\n- **Server/Backend(s) State**. This is the ultimate source of truth of all the data.\n- **Persisted State**. The \"snapshots\" of backend data transferred _to_ and _from_ application. E.g. Movies data passed as a JSON response, or user's rating for a particular Movie passed as an update request.\n- **URL State**. This is the state of the URL itself. Depending on which URL the user navigates to, the app would open specific pages and thus might request for _Persisted State_.\n- **Client State**. The state within the application that is not persisted to the backend. E.g. The info about which Tab is open in the application.\n- **Local UI State**. The state within a component itself. E.g. `isEnabled` toggle state of Toggle Component.\n\n<figure>\n  <img src=\"images/guide/component-store/types-of-state.png\" alt=\"Types of state in a typical SPA\" width=\"100%\" height=\"100%\" />\n</figure>\n\nThere are more types of states, but these are the most important ones in the context of state management.\n\n<ngrx-docs-alert type=\"inform\">\n\nSynchronizing these states is one of the most complex tasks that developers have to solve.\n\n</ngrx-docs-alert>\n\nHere is a small example to demonstrate how even a simple task might involve all of them:\n\n1. The user opens the page at a specific URL, \"https://www.TheBestMoviesOfAllTimeEver.com/favorites\". That changes the **_URL State_**.\n1. The URL has a path for a specific tab, \"favorites\". That selection becomes part of the **_Client State_**.\n1. This results in API calls to the backend to get the data of the movies that the user marked as \"favorites\". We receive a snapshot of **_Persisted State_**.\n1. The Toggle Component that lives next to the _\"is favorite\"_ label is turned ON. The \"ON\" state is derived from the data that the application received and passed to the Toggle Component through `@Input() isEnabled: boolean`. The component itself is not aware of _Persisted State_ or what it even means to be ON in the context of the rest of the application. All it knows is that it needs to be visually displayed as ON. The `isEnabled` state is **_Local UI State_**.\n1. The user might decide that this movie is no longer their favorite and would click the Toggle button to turn it OFF. The _Local UI State_ of the component is then changed, the `@Output() changed` event is emitted and picked up by a container component which would then call the backend to update the _Persisted State_.\n\nThis was a very simple example. Typically developers have to solve many problems that are more interconnected. What if the user is not logged in? Should we wait until the new favorite state is persisted to the backend and only then show disabled state or should we do this optimistically? and so on.\n\n<ngrx-docs-alert type=\"help\">\n\nUnderstanding these types of state helps us define our usage of ComponentStore.\n\n</ngrx-docs-alert>\n\n## Use Case 1: Local UI State\n\n### Example 1: ComponentStore as part of the component\n\nThe simplest example usage of `ComponentStore` is **reactive _Local UI State_**.\n\n<div class=\"callout is-helpful\">\n<header>A note about component reactivity</header>\n\nOne of the ways to improve the performance of the application is to use the `OnPush` change detection strategy. However, contrary to the popular belief, we do not always need to tell Angular's change detection to `markForCheck()` or `detectChanges()` (or the Angular Ivy alternatives). As pointed out in [this article on change detection](https://indepth.dev/the-last-guide-for-angular-change-detection-youll-ever-need/), if the event originates from the component itself, the component will be dirty checked.\nThis means that common presentational (aka dumb) components that interact with the rest of the application with Input(s)/Output(s) do not have to be overcomplicated with reactive state, even though we did it to the Toggle Component mentioned above.\n\n</div>\n\nHaving said that, in most cases making _Local UI State_ **reactive** is beneficial:\n\n- For Zoneless application, the `async` pipe can easily be substituted with a Zoneless alternative such as the [`ngrxPush` pipe](guide/component/push)\n- For components with non-trivial business logic, reactivity can organize the state better by clearly separating actual state from derived values and identifying side-effects.\n\n<ngrx-docs-alert type=\"help\">\n\nComponentStore is not the only reactive _Local UI State_ holder - sometimes `FormControl`s are good enough. They contain the state and they have reactive APIs.\n\n</ngrx-docs-alert>\n\nHere's the simplified `SlideToggleComponent` example which uses `ComponentStore` for _Local UI State_. In this example, the `ComponentStore` is provided directly by the component itself, which might not be the best choice for most of the use cases of `ComponentStore`. Instead, consider a service that `extends ComponentStore`.\n\n<ngrx-docs-alert type=\"help\">\n\nYou can see the full example at StackBlitz: <ngrx-docs-stackblitz name=\"component-store-slide-toggle\"></ngrx-docs-stackblitz>\n\n</ngrx-docs-alert>\n\n<ngrx-code-tabs>\n  <ngrx-code-example header=\"slide-toggle.component.ts\" path=\"component-store-slide-toggle/src/app/slide-toggle.component.ts\">\n  </ngrx-code-example>\n\n  <ngrx-code-example header=\"slide-toggle.component.html\" path=\"component-store-slide-toggle/src/app/slide-toggle.html\">\n  </ngrx-code-example>\n</ngrx-code-tabs>\n\nBelow are the steps of integrating `ComponentStore` into a component.\n\n#### Step 1. Setting up\n\nFirst, the state for the component needs to be identified. In `SlideToggleComponent` only the state of whether the toggle is turned ON or OFF is stored.\n\n<ngrx-code-example\n  header=\"slide-toggle.component.ts\"\n  path=\"component-store-slide-toggle/src/app/slide-toggle.component.ts\"\n  region=\"state\">\n\n</ngrx-code-example>\n\nThen we need to provide `ComponentStore` in the component's providers, so that each new instance of `SlideToggleComponent` has its own `ComponentStore`. It also has to be injected into the constructor.\n\n<ngrx-docs-alert type=\"inform\">\n\nIn this example `ComponentStore` is provided directly in the component. This works for simple cases, however in real-world cases it is recommended to create a separate Service, for example `SlideToggleStore`, that would extend `ComponentStore` and would contain all the business logic. This new Service is then provided in the component. See examples below.\n\n</ngrx-docs-alert>\n\n<ngrx-code-example\n  header=\"slide-toggle.component.ts\"\n  path=\"component-store-slide-toggle/src/app/slide-toggle.component.ts\"\n  region=\"providers\">\n\n</ngrx-code-example>\n\nNext, the default state for the component needs to be set. It could be done lazily, however it needs to be done before any of `updater`s are executed, because they rely on the state to be present and would throw an error if the state is not initialized by the time they are invoked.\n\nState is initialized by calling `setState` and passing an object that matches the interface of `SlideToggleState`.\n\n<ngrx-docs-alert type=\"inform\">\n\n`setState` could be called with either an object or a callback.\n\nWhen it is called with an object, state is initialized or reset to that object.\n\nWhen it is called with a callback, the state is updated.\n\n</ngrx-docs-alert>\n\n<ngrx-code-example\n  header=\"slide-toggle.component.ts\"\n  path=\"component-store-slide-toggle/src/app/slide-toggle.component.ts\"\n  region=\"init\">\n\n</ngrx-code-example>\n\n#### Step 2. Updating state\n\nIn the slide-toggle example, the state is updated either through `@Input` or by a user interaction, which results in a `onChangeEvent($event)` call in the template. Both of them change the same piece of state - `checked: boolean`, thus we have the `setChecked` updater that is reused in two places. This updater describes **HOW** the state changes - it takes the current state and a value and returns the new state.\n\n`@Input` here is a setter function that passes the value to the `setChecked` updater.\n\nWhen a user clicks the toggle (triggering a 'change' event), instead of calling the same updater directly, the `onChangeEvent` effect is called. This is done because we also need to have the side-effect of `event.source.stopPropagation` to prevent this event from bubbling up (slide-toggle output event in named 'change' as well) and only after that the `setChecked` updater is called with the value of the input element.\n\n<ngrx-code-example\n  header=\"slide-toggle.component.ts\"\n  path=\"component-store-slide-toggle/src/app/slide-toggle.component.ts\"\n  region=\"updater\">\n\n</ngrx-code-example>\n\n#### Step 3. Reading the state\n\nFinally, the state is aggregated with selectors into two properties:\n\n- `vm$` property collects all the data needed for the template - this is the _ViewModel_ of `SlideToggleComponent`.\n- `change` is the `@Output` of `SlideToggleComponent`. Instead of creating an `EventEmitter`, here the output is connected to the Observable source directly.\n\n<ngrx-code-example\n  header=\"slide-toggle.component.ts\"\n  path=\"component-store-slide-toggle/src/app/slide-toggle.component.ts\"\n  region=\"selector\">\n\n</ngrx-code-example>\n\nThis example does not have a lot of business logic, however it is still fully reactive.\n\n### Example 2: Service extending ComponentStore\n\n`SlideToggleComponent` is a fairly simple component and having `ComponentStore` within the component itself is still manageable. When components takes more Inputs and/or has more events within its template, it becomes larger and harder to read/maintain.\n\nExtracting the business logic of a component into a separate Service also helps reduce the cognitive load while reading the components code.\n\n<ngrx-docs-alert type=\"inform\">\n\nA Service that extends ComponentStore and contains business logic of the component brings many advantages. **This is also the recommended usage of ComponentStore**.\n\n</ngrx-docs-alert>\n\n`ComponentStore` was designed with such an approach in mind. The main APIs of `ComponentStore` (`updater`, `select` and `effect`) are meant to wrap the **HOW** state is changed, extracted or effected, and then called with arguments.\n\nBelow are the two examples of a re-implemented [Paginator component](https://material.angular.io/components/paginator/overview) from Angular Material (a UI component library). These re-implementations are very functionally close alternatives.\n\nHere's the source code of the [Material's paginator.ts](https://github.com/angular/components/blob/23d3c216c65b327e0acfb48b53302b8fda326e7f/src/material/paginator/paginator.ts#L112) as a reference.\n\nWhat we can see is that while the _\"PaginatorComponent providing ComponentStore\"_ example already makes the component a lot smaller, reactive, removes `this._changeDetectorRef.markForCheck()` and organizes it into distinct \"read\"/\"write\"/\"effect\" buckets, it still could be hard to read. The _\"PaginatorComponent with PaginatorStore\"_ example adds readability and further improves the testability of behaviors and business logic.\n\n<ngrx-docs-alert type=\"help\">\n\nYou can see the examples at StackBlitz:\n\n- \"PaginatorComponent providing ComponentStore\" <ngrx-docs-stackblitz name=\"component-store-paginator\" noDownload></ngrx-docs-stackblitz>\n- \"PaginatorComponent with PaginatorStore Service\" <ngrx-docs-stackblitz name=\"component-store-paginator-service\" noDownload></ngrx-docs-stackblitz>\n\n</ngrx-docs-alert>\n\n<ngrx-code-tabs>\n  <ngrx-code-example\n    header=\"PaginatorComponent with PaginatorStore Service\"\n    path=\"component-store-paginator-service/src/app/paginator.component.ts\">\n  </ngrx-code-example>\n  <ngrx-code-example\n    header=\"PaginatorComponent providing ComponentStore\"\n    path=\"component-store-paginator/src/app/paginator.component.ts\">\n  </ngrx-code-example>\n  <ngrx-code-example\n    header=\"paginator.store.ts\"\n    path=\"component-store-paginator-service/src/app/paginator.store.ts\">\n  </ngrx-code-example>\n</ngrx-code-tabs>\n\n#### Updating the state\n\nWith `ComponentStore` extracted into `PaginatorStore`, the developer is now using updaters and effects to update the state. `@Input` values are passed directly into `updater`s as their arguments.\n\n<ngrx-code-example\n  header=\"paginator.store.ts\"\n  path=\"component-store-paginator-service/src/app/paginator.component.ts\"\n  region=\"inputs\">\n\n</ngrx-code-example>\n\nNot all `updater`s have to be called in the `@Input`. For example, `changePageSize` is called from the template.\n\nEffects are used to perform additional validation and get extra information from sources with derived data (i.e. selectors).\n\n<ngrx-code-example\n  header=\"paginator.store.ts\"\n  path=\"component-store-paginator-service/src/app/paginator.component.ts\"\n  region=\"updating-state\">\n\n</ngrx-code-example>\n\n#### Reading the state\n\n`PaginatorStore` exposes the two properties: `vm$` for an aggregated _ViewModel_ to be used in the template and `page$` that would emit whenever data aggregated from a `PageEvent` changes.\n\n<ngrx-code-tabs>\n  <ngrx-code-example\n    header=\"paginator.component.ts\"\n    path=\"component-store-paginator-service/src/app/paginator.component.ts\"\n    region=\"selectors\"\n    >\n  </ngrx-code-example>\n  <ngrx-code-example\n    header=\"paginator.store.ts\"\n    path=\"component-store-paginator-service/src/app/paginator.store.ts\"\n    region=\"selectors\">\n  </ngrx-code-example>\n</ngrx-code-tabs>\n\n<ngrx-docs-alert type=\"help\">\n\n`page$` would emit `PageEvent` when page size or page index changes, however in some cases updater would update both of these properties at the same time. To avoid \"intermediary\" emissions, `page$` selector is using **`{debounce: true}`** configuration. More about debouncing can be found in [selector section](guide/component-store/read#debounce-selectors).\n\n</ngrx-docs-alert>\n\n### Local UI State patterns\n\nComponents that use `ComponentStore` for managing **Local UI State** are frequently calling `updater`s directly.\n\nEffects can also be used when:\n\n- side effects are required (e.g. `event.stopPropagation()`)\n- derived data (from selectors) is needed to influence the new state\n- they are orchestrating a number of well-defined updaters\n\nThe last point can sometimes be refactored into another `updater`. Use your best judgment.\n\n`@Output()`s and derived data are **reacting** to these state changes and are generated using `selector`s.\n\nA _ViewModel_ for the component is also composed from `selector`s.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/component-store/write.md",
    "content": "<ngrx-docs-alert type=\"help\">\n\n**<a href=\"/guide/signals\"><b>NgRx Signals</b></a> is the new default.**\n\nThe NgRx team recommends using the `@ngrx/signals` library for local state management in Angular.\nWhile ComponentStore remains supported, we encourage using `@ngrx/signals` for new projects and considering migration for existing ones.\n\n</ngrx-docs-alert>\n\n# Updating state\n\nComponentStore can be updated in 3 ways:\n\n- by calling `setState`.\n- by calling `patchState`.\n- by creating an `updater` and passing inputs through it.\n\n## `updater` method\n\nThe `updater` method describes HOW the state changes. It takes a pure function with the current state and the value as arguments,\nand should return the new state, updated immutably.\n\nThere could be many updaters within a ComponentStore. They are analogous to \"CASE\" statements or `on()` functions in `@ngrx/store` reducer.\n\n<ngrx-docs-alert type=\"help\">\n\nUsing the `updater` method allows developers to extract business logic out of components into services,\nwhich makes components easier to read and test.\n\n</ngrx-docs-alert>\n\n<ngrx-code-example header=\"movies.store.ts\">\n\n```ts\n@Injectable()\nexport class MoviesStore extends ComponentStore<MoviesState> {\n  constructor() {\n    super({ movies: [] });\n  }\n\n  readonly addMovie = this.updater((state, movie: Movie) => ({\n    movies: [...state.movies, movie],\n  }));\n}\n```\n\n</ngrx-code-example>\n\nUpdater then can be called with the values imperatively or could take an Observable.\n\n<ngrx-code-example header=\"movies-page.component.ts\">\n\n```ts\n@Component({\n  template: `\n    <button (click)=\"add('New Movie')\">Add a Movie</button>\n  `,\n  providers: [MoviesStore],\n})\nexport class MoviesPageComponent {\n  constructor(private readonly moviesStore: MoviesStore) {}\n\n  add(movie: string) {\n    this.moviesStore.addMovie({ name: movie, id: generateId() });\n  }\n}\n```\n\n</ngrx-code-example>\n\n## `setState` method\n\nThe `setState` method can be called by either providing the object of state type or as a callback.\n\nWhen the object is provided it resets the entire state to the provided value. This is also how lazy\ninitialization is performed.\n\nThe callback approach allows developers to change the state partially.\n\n<ngrx-code-example header=\"movies-page.component.ts\">\n\n```ts\n@Component({\n  template: `...`,\n  providers: [ComponentStore],\n})\nexport class MoviesPageComponent implements OnInit {\n  constructor(\n    private readonly componentStore: ComponentStore<MoviesState>\n  ) {}\n\n  ngOnInit() {\n    this.componentStore.setState({ movies: [] });\n  }\n\n  resetMovies() {\n    //    resets the State to empty array 👇\n    this.componentStore.setState({ movies: [] });\n  }\n\n  addMovie(movie: Movie) {\n    this.componentStore.setState((state) => {\n      return {\n        ...state,\n        movies: [...state.movies, movie],\n      };\n    });\n  }\n}\n```\n\n</ngrx-code-example>\n\n## `patchState` method\n\nThe `patchState` method can be called by providing a partial state Observable<object>, object, or a partial updater callback.\n\nWhen the partial state is provided it patches the state with the provided value.\n\nWhen the partial updater is provided it patches the state with the value returned from the callback.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** The state has to be initialized before any of `patchState` calls, otherwise \"not initialized\" error will be thrown.\n\n</ngrx-docs-alert>\n\n<ngrx-code-example header=\"movies-page.component.ts\">\n\n```ts\ninterface MoviesState {\n  movies: Movie[];\n  selectedMovieId: string | null;\n}\n\n@Component({\n  template: `...`,\n  providers: [ComponentStore],\n})\nexport class MoviesPageComponent implements OnInit {\n  constructor(\n    private readonly componentStore: ComponentStore<MoviesState>\n  ) {}\n\n  ngOnInit() {\n    this.componentStore.setState({\n      movies: [],\n      selectedMovieId: null,\n    });\n  }\n\n  updateSelectedMovie(selectedMovieId: string) {\n    this.componentStore.patchState({ selectedMovieId });\n  }\n\n  addMovie(movie: Movie) {\n    this.componentStore.patchState((state) => ({\n      movies: [...state.movies, movie],\n    }));\n  }\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/architecture-overview.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Architecture overview\n\nYou describe your entity model to NgRx Data in a few lines of [entity metadata](guide/data/entity-metadata) and let the library do the rest of the work.\n\nYour component injects an NgRx Data **`EntityCollectionService`** and calls one or more of the standard set of command methods for dispatching actions.\n\nYour component also subscribes to one or more of the service's `Observable` _selectors_ in order to reactively process and display entity state changes produced by those commands.\n\nNgRx Data is really just NgRx under the hood. The data flows in typical NgRx fashion.\nThe following diagram illustrates the journey of a persistence `EntityAction`\nsuch as `QUERY_ALL` for the `Hero` entity type.\n\n<figure>\n  <img src=\"images/guide/data/action-flow.png\" alt=\"flow diagram\">\n</figure>\n\n1.  The view/component calls [`EntityCollectionService.getAll()`](guide/data/entity-services), which dispatches the hero's `QUERY_ALL` [EntityAction](guide/data/entity-actions) to the store.\n\n2.  NgRx kicks into gear ...\n    1.  The NgRx Data [EntityReducer](guide/data/entity-reducer) reads the action's `entityName` property (`Hero` in this example) and\n        forwards the action and existing entity collection state to the `EntityCollectionReducer` for heroes.\n\n    1.  The collection reducer picks a switch-case based on the action's `entityOp` (operation) property.\n        That case processes the action and collection state into a new (updated) hero collection.\n\n    1.  The store updates the _entity cache_ in the state tree with that updated collection.\n\n    1.  _NgRx_ observable selectors detect and report the changes (if any) to subscribers in the view.\n\n3.  The original `EntityAction` then goes to the [EntityEffects](guide/data/entity-effects).\n\n4.  The effect selects an [EntityDataService](guide/data/entity-dataservice) for that entity type. The data service sends an HTTP request to the server.\n\n5.  The effect turns the HTTP response into a new _success_ action with heroes (or an _error_ action if the request failed).\n\n6.  _NgRx effects_ Dispatches that action to the store, which reiterates step #2 to update the collection with heroes and refresh the view.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/architecture.md",
    "content": "# Architecture\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/entity-actions.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Entity Actions\n\nThe [`EntityCollectionService`](guide/data/entity-services) dispatches an `EntityAction` to the _NgRx store_ when you call one of its commands to query or update entities in a cached collection.\n\n## _Action_ and _EntityAction_\n\nA vanilla\n[_NgRx `Action`_](guide/store/actions) is a message.\nThe message describes an operation that can change state in the _store_.\n\nThe _action_'s `type` identifies the operation.\nIt's optional `payload` carries the message data necessary to perform the operation.\n\nAn `EntityAction` is a super-set of the _NgRx `Action`_.\nIt has additional properties that guide NgRx Data's handling of the action. Here's the full interface.\n\n<ngrx-code-example header=\"EntityAction\">\n\n```ts\nexport interface EntityAction<P = any> extends Action {\n  readonly type: string;\n  readonly payload: EntityActionPayload<P>;\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"EntityActionPayload\">\n\n```ts\nexport interface EntityActionPayload<P = any>\n  extends EntityActionOptions {\n  readonly entityName: string;\n  readonly entityOp: EntityOp;\n  readonly data?: P;\n\n  // EntityActionOptions (also an interface)\n  readonly correlationId?: any;\n  readonly isOptimistic?: boolean;\n  readonly mergeStrategy?: MergeStrategy;\n  readonly tag?: string;\n  error?: Error;\n  skip?: boolean;\n}\n```\n\n</ngrx-code-example>\n\n- `type` - action name, typically generated from the `tag` and the `entityOp`.\n- `entityName` - the name of the entity type.\n- `entityOp` - the name of an entity operation.\n- `data?` - the message data for the action.\n- `correlationId?` - a serializable object (typically a string) for correlating related actions.\n- `isOptimistic?` - true if should perform the action optimistically (before the server responds).\n- `mergeStrategy` - how to merge an entity into the cache. See [Change Tracking](guide/data/entity-change-tracker).\n- `tag?` - the tag to use within the generated type. If not specified, the `entityName` is the tag.\n- `error?` - an unexpected action processing error.\n- `skip?` - true if downstream consumers should skip processing the action.\n\nThe `type` is the only property required by _NgRx_. It is a string that uniquely identifies the action among the set of all the types of actions that can be dispatched to the store.\n\nNgRx Data doesn't care about the `type`. It pays attention to the `entityName` and `entityOp` properties.\n\nThe `entityName` is the name of the entity type.\nIt identifies the _entity collection_ in the NgRx Data cache to which this action applies.\nThis name corresponds to [NgRx Data _metadata_](guide/data/entity-metadata) for that collection.\nAn entity interface or class name, such as `'Hero'`, is a typical `entityName`.\n\nThe `entityOp` identifies the operation to perform on the _entity collection_,\none of the `EntityOp` enumerations that correspond to one of the\nalmost _sixty_ distinct operations that NgRx Data can perform on a collection.\n\nThe `data` is conceptually the body of the message.\nIts type and content should fit the requirements of the operation to be performed.\n\nThe optional `correlationId?` is an optional serializable object (usually a GUID) that correlates two or more actions such as the action that initiates a server action (\"get all heroes\") and the subsequent actions that follow after the server action completed (\"got heroes successfully\" or \"error while getting heroes\").\n\nThe optional `mergeStrategy` tells NgRx Data how to \"merge\" the result of the action into the cache.\nMostly this is an instruction to the the [Change Tracking](guide/data/entity-change-tracker) sub-system.\n\nThe optional `tag` appears in the generated `type` text when the `EntityActionFactory` creates this `EntityAction`.\n\nThe `entityName` is the default tag that appears between brackets in the formatted `type`,\ne.g., `'[Hero] NgRx Data/query-all'`.\nYou can set this tag to identify the purpose of the operation and \"who\" dispatched it.\nNgRx Data will put your tag between the brackets in the formatted `type`.\n\nThe `error` property indicates that something went wrong while processing the action. [See more below](#action-error).\n\nThe `skip` property tells downstream action receivers that they should skip the usual action processing.\nThis flag is usually missing and is implicitly false.\n[See more below](#action-skip).\n\n## _EntityAction_ consumers\n\nThe NgRx Data library ignores the `Action.type`.\nAll NgRx Data library behaviors are determined by the `entityName` and `entityOp` properties alone.\n\nThe NgRx Data `EntityReducer` redirects an action to an `EntityCollectionReducer` based on the `entityName`\nand that reducer processes the action based on the `entityOp`.\n\n`EntityEffects` intercepts an action if its `entityOp` is among the small set of persistence `EntityAction.entityOp` names.\nThe effect picks the right _data service_ for that action's `entityName`, then tells the service to make the appropriate HTTP request and handle the response.\n\n## Creating an _EntityAction_\n\nYou can create an `EntityAction` by hand if you wish.\nThe NgRx Data library considers _any action_ with an `entityName` and `entityOp` properties to be an `EntityAction`.\n\nThe `EntityActionFactory.create()` method helps you create a consistently well-formed `EntityAction` instance\nwhose `type` is a string composed from the `tag` (the `entityName` by default) and the `entityOp`.\n\nFor example, the default generated `Action.type` for the operation that queries the server for all heroes is `'[Hero] NgRx Data/query-all'`.\n\n<ngrx-docs-alert type=\"help\">\n\nThe `EntityActionFactory.create()` method calls the factory's `formatActionType()` method\nto produce the `Action.type` string.\n\nBecause NgRx Data ignores the `type`, you can replace `formatActionType()` with your own method if you prefer a different format\nor provide and inject your own `EntityActionFactory`.\n\n</ngrx-docs-alert>\n\nNote that **_each entity type has its own \\_unique_ `Action` for each operation\\_**, as if you had created them individually by hand.\n\n## Tagging the EntityAction\n\nA well-formed action `type` can tell the reader what changed and\nwho changed it.\n\nThe NgRx Data library doesn't look at the type of an `EntityAction`,\nonly its `entityName` and `entityOp`.\nSo you can get the same behavior from several different actions,\neach with its own informative `type`, as long as they share the same\n`entityName` and `entityOp`.\n\nThe optional `tag` parameter of the `EntityActionFactory.create()` method makes\nit easy to produce meaningful _EntityActions_.\n\nYou don't have to specify a tag. The `entityName` is the default tag that appears between brackets in the formatted `type`,\ne.g., `'[Hero] NgRx Data/query-all'`.\n\nHere's an example that uses the injectable `EntityActionFactory` to construct the default \"query all heroes\" action.\n\n```typescript\nconst action = this.entityActionFactory.create<Hero>(\n  'Hero',\n  EntityOp.QUERY_ALL\n);\n\nstore.dispatch(action);\n```\n\nThanks to the NgRx Data _Effects_, this produces _two_ actions in the log, the first to initiate the request and the second with the successful response:\n\n```typescript\n[Hero] ngrx/data/query-all\n[Hero] ngrx/data/query-all/success\n```\n\nThis default `entityName` tag identifies the action's target entity collection.\nBut you can't understand the _context_ of the action from these log entries. You don't know who dispatched the action or why.\nThe action `type` is too generic.\n\nYou can create a more informative action by providing a tag that\nbetter describes what is happening and also make it easier to find\nwhere that action is dispatched by your code.\n\nFor example,\n\n```typescript\nconst action = this.entityActionFactory.create<Hero>(\n  'Hero',\n  EntityOp.QUERY_ALL,\n  null,\n  { tag: 'Load Heroes On Start' }\n);\n\nstore.dispatch(action);\n```\n\nThe action log now looks like this:\n\n```typescript\n[Load Heroes On Start] ngrx/data/query-all\n[Load Heroes On Start] ngrx/data/query-all/success\n```\n\n### Handcrafted _EntityAction_\n\nYou don't have to create entity actions with the `EntityActionFactory`.\nAny action object with an `entityName` and `entityOp` property is\nan entity action, as explained [below](#where-are-the-entityactions).\n\nThe following example creates the initiating \"query all heroes\" action by hand.\n\n```typescript\nconst action = {\n  type: 'some/arbitrary/action/type',\n  entityName: 'Hero',\n  entityOp: EntityOp.QUERY_ALL,\n};\n\nstore.dispatch(action);\n```\n\nIt triggers the HTTP request via _NgRx Data effects_, as in the previous examples.\n\nJust be aware that _NgRx Data effects_ uses the `EntityActionFactory` to create the second, success Action.\nWithout the `tag` property, it produces a generic success action.\n\nThe log of the two action types will look like this:\n\n```sh\nsome/arbitrary/action/type\n[Hero] NgRx Data/query-all-success\n```\n\n## Where are the _EntityActions_?\n\nIn an NgRx Data app, the NgRx Data library creates and dispatches _EntityActions_ for you.\n\n_EntityActions_ are largely invisible when you call the [`EntityCollectionService`](guide/data/entity-services) API.\nYou can see them in action with the\n[NgRx store dev-tools](guide/store-devtools).\n\n## Why this matters\n\nIn an ordinary _NgRx_ application, you hand-code every `Action` for every _state_ in the store\nas well as the [reducers](guide/store/reducers)\nthat process those _actions_.\n\nIt takes many _actions_, a complex _reducer_, and the help of an NgRx [Effect](guide/effects) to manage queries and saves for a _single_ entity type.\n\nThe NgRx [Entity](guide/entity) library makes the job considerably easier.\n\n<ngrx-docs-alert type=\"help\">\n\nThe NgRx Data library internally delegates much of the heavy lifting to NgRx _Entity_.\n\n</ngrx-docs-alert>\n\nBut you must still write a lot of code for each entity type.\nYou're expected to create _eight actions_ per entity type and\nwrite a _reducer_ that responds to these eight actions by calling eight methods of an NgRx [_EntityAdapter_](guide/entity/adapter#adapter-collection-methods).\n\nThese artifacts only address the _cached_ entity collection.\n\nYou may write as many as _eighteen additional actions_ to support a typical complement of asynchronous CRUD (Create, Retrieve, Update, Delete) operations. You'll have to dispatch them to the store where you'll process them with more _reducer_ methods and _effects_ that you must also hand code.\n\nWith vanilla _NgRx_, you'll go through this exercise for _every entity type_.\nThat's a lot of code to write, test, and maintain.\n\nWith the help of NgRx Data, you don't write any of it.\nNgRx Data creates the _actions_ and the _dispatchers_, _reducers_, and _effects_ that respond to those actions.\n\n<a id=\"action-error\"></a>\n\n## _EntityAction.error_\n\nThe presence of an `EntityAction.error` property indicates that something bad happened while processing the action.\n\nAn `EntityAction` should be immutable. The `EntityAction.error` property is the _only_ exception and is strictly an internal property of the NgRx Data system.\nYou should rarely (if ever) set it yourself.\n\nThe primary use case for `error` is to catch reducer exceptions.\n_NgRx_ stops subscribing to reducers if one of them throws an exception.\nCatching reducer exceptions allows the application to continue operating.\n\nNgRx Data traps an error thrown by an `EntityCollectionReducer` and sets the `EntityAction.error` property to the caught error object.\n\nThe `error` property is important when the errant action is a _persistence action_ (such as `SAVE_ADD_ONE`).\nThe `EntityEffects` will see that such an action has an error and will return the corresponding failure action (`SAVE_ADD_ONE_ERROR`) immediately, without attempting an HTTP request.\n\n<ngrx-docs-alert type=\"inform\">\n\nThis is the only way we've found to prevent a bad action from getting through the effect and triggering an HTTP request.\n\n</ngrx-docs-alert>\n\n<a id=\"action-skip\"></a>\n\n## _EntityAction.skip_\n\nThe `skip` property tells downstream action receivers that they should skip the usual action processing.\nThis flag is usually missing and is implicitly false.\n\nThe NgRx Data sets `skip=true` when you try to delete a new entity that has not been saved.\nWhen the `EntityEffects.persist$` method sees this flag set true on the `EntityAction` envelope,\nit skips the HTTP request and dispatches an appropriate `_SUCCESS` action with the\noriginal request payload.\n\nThis feature allows NgRx Data to avoid making a DELETE request when you try to delete an entity\nthat has been added to the collection but not saved.\nSuch a request would have failed on the server because there is no such entity to delete.\n\nSee the [`EntityChangeTracker`](guide/data/entity-change-tracker) page for more about change tracking.\n\n<a id=\"entity-cache-actions\"></a>\n\n## EntityCache-level actions\n\nA few actions target the entity cache as a whole.\n\n`SET_ENTITY_CACHE` replaces the entire cache with the object in the action payload,\neffectively re-initializing the entity cache to a known state.\n\n`MERGE_ENTITY_CACHE` replaces specific entity collections in the current entity cache\nwith those collections present in the action payload.\nIt leaves the other current collections alone.\n\nLearn about them in the \"[EntityReducer](guide/data/entity-reducer#entity-cache-actions)\" document.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/entity-change-tracker.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# EntityChangeTracker\n\nNgRx Data tracks entity changes that haven't yet been saved on the server.\nIt also preserves \"original values\" for these changes and you can revert them with _undo actions_.\n\nChange-tracking and _undo_ are important for applications that make _optimistic saves_.\n\n## Optimistic versus Pessimistic save\n\nAn _optimistic save_ stores a new or changed entity in the cache _before making a save request to the server_.\nIt also removes an entity from the store _before making a delete request to the server_.\n\n<ngrx-docs-alert type=\"help\">\n\nThe `EntityAction.isOptimistic` flag is one of the `EntityActionOptions`. Set it to override the action's default optimistic or pessimistic behavior.\n\n</ngrx-docs-alert>\n\nMany apps are easier to build when saves are \"optimistic\" because\nthe changes are immediately available to application code that is watching collection selectors.\nThe app doesn't have to wait for confirmation that the entity operation succeeded on the server.\n\nA _pessimistic save_ doesn't update the store until the server confirms that the save succeeded,\nwhich NgRx Data then turns into a \"SUCCESS\" action that updates the collection.\nWith a _pessimistic_ save, the changes won't be available in the store\n\nThis confirmation cycle can (and usually will) take significant time and the app has to account for that gap somehow.\nThe app could \"freeze\" the UX (perhaps with a modal spinner and navigation guards) until the confirmation cycle completes.\nThat's tricky code to write and race conditions are inevitable.\nAnd it's difficult to hide this gap from the user and keep the user experience responsive.\n\nThis isn't a problem with optimistic saves because the changed data are immediately available in the store.\n\n<ngrx-docs-alert type=\"help\">\n\nThe developer always has the option to wait for confirmation of an optimistic save.\nBut the changed entity data will be in the store during that wait.\n\n</ngrx-docs-alert>\n\n### Save errors\n\nThe downside of optimistic save is that _the save could fail_ for many reasons including lost connection,\ntimeout, or rejection by the server.\n\nWhen the client or server rejects the save request,\nthe _nrgx_ `EntityEffect.persist$` dispatches an error action ending in `_ERROR`.\n\n**The default entity reducer methods do nothing with save errors.**\n\nThere is no issue if the operation was _pessimistic_.\nThe collection had not been updated so there is no obvious inconsistency between the state\nof the entity in the collection and on the server.\n\nIf the operation was _optimistic_, the entity in the cached collection has been added, removed, or updated.\nThe entity and the collection are no longer consistent with the state on the server.\n\nThat may be a problem for your application.\nIf the save fails, the entity in cache no longer accurately reflects the state of the entity on the server.\nWhile that can happen for other reasons (e.g., a different user changed the same data),\nwhen you get a save error, you're almost certainly out-of-sync and should be able to do something about it.\n\nChange tracking gives the developer the option to respond to a server error\nby dispatching an _undo action_ for the entity (or entities) and\nthereby reverting the entity (or entities) to the last known server state.\n\n_Undo_ is NOT automatic.\nYou may have other save error recovery strategies that preserve the user's\nunsaved changes.\nIt is up to you if and when to dispatch one of the `UNDO_...` actions.\n\n## Change Tracking\n\nThe NgRx Data tracks an entity's change-state in the collection's `changeState` property.\n\nWhen change tracking is enabled (the default), the `changeState` is a _primary key to_ `changeState` _map_.\n\n<ngrx-docs-alert type=\"help\">\n\nYou can disable change tracking for an individual action or the collection as a whole as\ndescribed [below](#disable-change-tracking).\n\n</ngrx-docs-alert>\n\n### _ChangeState_\n\nA `changeState` map adheres to the following interface\n\n<ngrx-code-example header=\"ChangeState\">\n\n```ts\nexport interface ChangeState<T> {\n  changeType: ChangeType;\n  originalValue: T | undefined;\n}\n\nexport enum ChangeType {\n  Unchanged, // the entity has not been changed.\n  Added, // the entity was added to the collection\n  Updated, // the entity in the collection was updated\n  Deleted, // the entity is scheduled for delete and was removed from collection.\n}\n```\n\n</ngrx-code-example>\n\nA _ChangeState_ describes an entity that changed since its last known server value.\nThe `changeType` property tells you how it changed.\n\n<ngrx-docs-alert type=\"help\">\n\n`Unchanged` is an _implied_ state.\nOnly changed entities are recorded in the collection's `changeState` property.\nIf an entity's key is not present, assume it is `Unchanged` and has not changed since it was last\nretrieved from or successfully saved to the server.\n\n</ngrx-docs-alert>\n\nThe _original value_ is the last known value from the server.\nThe `changeState` object holds an entity's _original value_ for _two_ of these states: _Updated_ and _Deleted_.\nFor an _Unchanged_ entity, the current value is the original value so there is no need to duplicate it.\nThere could be no original value for an entity this is added to the collection but no yet saved.\n\n## EntityActions and change tracking.\n\nThe collection is created with an empty `changeState` map.\n\n### Recording a change state\n\nMany _EntityOp_ reducer methods will record an entity's change state.\nOnce an entity is recorded in the `changeState`, its `changeType` and `originalValue` generally do not change.\nOnce \"added\", \"deleted\" or \"updated\", an entity stays\nthat way until committed or undone.\n\nDelete (remove) is a special case with special rules.\n[See below](#delete).\n\nHere are the most important `EntityOps` that record an entity in the `changeState` map:\n\n<ngrx-code-example>\n\n```ts\n// Save operations when isOptimistic flag is true\nSAVE_ADD_ONE;\nSAVE_ADD_MANY;\nSAVE_DELETE_ONE;\nSAVE_DELETE_MANY;\nSAVE_UPDATE_ONE;\nSAVE_UPDATE_MANY;\nSAVE_UPSERT_ONE;\nSAVE_UPSERT_MANY;\n\n// Cache operations\nADD_ONE;\nADD_MANY;\nREMOVE_ONE;\nREMOVE_MANY;\nUPDATE_ONE;\nUPDATE_MANY;\nUPSERT_ONE;\nUPSERT_MANY;\n```\n\n</ngrx-code-example>\n\n### Removing an entity from the _changeState_ map.\n\nAn entity which has no entry in the `ChangeState` map is presumed to be unchanged.\n\nThe _commit_ and _undo_ operations remove entries from the `ChangeState` which means, in effect, that they are \"unchanged.\"\n\nThe **commit** operations simply remove entities from the `changeState`.\nThey have no other effect on the collection.\n\nThe [**undo** operations](#undo) replace entities in the collection based on\ninformation in the `changeState` map, reverting them their last known server-side state, and removing them from the `changeState` map.\nThese entities become \"unchanged.\"\n\nAn entity ceases to be in a changed state when the server returns a new version of the entity.\nOperations that put that entity in the store also remove it from the `changeState` map.\n\nHere are the operations that remove one or more specified entities from the `changeState` map.\n\n<ngrx-code-example>\n\n```ts\nQUERY_ALL_SUCCESS;\nQUERY_BY_KEY_SUCCESS;\nQUERY_LOAD_SUCCESS;\nQUERY_MANY_SUCCESS;\nSAVE_ADD_ONE_SUCCESS;\nSAVE_ADD_MANY_SUCCESS;\nSAVE_DELETE_ONE_SUCCESS;\nSAVE_DELETE_MANY_SUCCESS;\nSAVE_UPDATE_ONE_SUCCESS;\nSAVE_UPDATE_MANY_SUCCESS;\nSAVE_UPSERT_ONE_SUCCESS;\nSAVE_UPSERT_MANY_SUCCESS;\nCOMMIT_ONE;\nCOMMIT_MANY;\nUNDO_ONE;\nUNDO_MANY;\n```\n\n</ngrx-code-example>\n\n### Operations that clear the _changeState_ map.\n\nThe `EntityOps` that replace or remove every entity in the collection also reset the `changeState` to an empty object.\nAll entities in the collection (if any) become \"unchanged\".\n\n<ngrx-code-example>\n\n```ts\nADD_ALL;\nQUERY_LOAD_SUCCESS;\nREMOVE_ALL;\nCOMMIT_ALL;\nUNDO_ALL;\n```\n\n</ngrx-code-example>\n\nTwo of these may surprise you.\n\n1.  `ADD_ALL` is interpreted as a cache load from a known state.\n    These entities are presumed _unchanged_.\n    If you have a different intent, use `ADD_MANY`.\n\n2.  `REMOVE_ALL` is interpreted as a cache clear with nothing to save. If you have a different intent, use _removeMany_.\n\nYou can (re)set the `changeState` to anything with `EntityOp.SET_CHANGE_STATE`.\n\nThis is a super-powerful operation that you should rarely perform.\nIt's most useful if you've created your own entity action and are\nmodifying the collection in some unique way.\n\n<a id=\"undo\"></a>\n\n## _Undo_ (revert) an unsaved change\n\nYou have many options for handling an optimistic save error.\nOne of them is to revert the change to the entity's last known state on the server by dispatching an _undo_ action.\n\nThere are three _undo_ `EntityOps` that revert entities:\n`UNDO_ONE`, `UNDO_MANY` and `UNDO_ALL`.\n\nFor `UNDO_ONE` and `UNDO_MANY`, the id(s) of the entities to revert are in the action payload.\n\n`UNDO_ALL` reverts every entity in the `changeState` map.\n\nEach entity is reverted as follows:\n\n- `ADDED` - Remove from the collection and discard\n\n- `DELETED` - Add the _original value_ of the removed entity to the collection.\n  If the collection is sorted, it will be moved into place.\n  If unsorted, it's added to the end of the collection.\n\n- `UPDATED` - Update the collection with the entity's _original value_.\n\nIf you try to undo/revert an entity whose id is not in the `changeState` map, the action is silently ignored.\n\n<a id=\"delete\"></a>\n\n### Deleting/removing entities\n\nThere are special change tracking rules for deleting/removing an entity from the collection\n\n#### Added entities\n\nWhen you remove or delete an \"added\" entity, the change tracker removes the entity from the `changeState` map because there is no server state to which such an entity could be restored.\n\nThe reducer methods that delete and remove entities should immediately remove an _added entity_ from the collection.\n\n<ngrx-docs-alert type=\"help\">\n\nThe default delete and remove reducer methods remove these entities immediately.\n\n</ngrx-docs-alert>\n\nThey should not send HTTP DELETE requests to the server because these entities do not exist on the server.\n\n<ngrx-docs-alert type=\"help\">\n\nThe default `EntityEffects.persist$` effect does not make HTTP DELETE requests for these entities.\n\n</ngrx-docs-alert>\n\n#### Updated entities\n\nAn entity registered in the `changeState` map as \"updated\"\nis reclassified as \"deleted\".\nIts `originalValue` stays the same.\nUndoing the change will restore the entity to the collection in its pre-update state.\n\n<a id=\"merge-strategy\"></a>\n\n### Merge Strategies\n\nYou can determine how NgRx Data will merge entities after a query, a save, or a cache operation by setting the\nentity action's optional `mergeStrategy` property to one of the three strategy enums:\n\n1. `IgnoreChanges` - Update the collection entities and ignore all change tracking for this operation. Each entity's `changeState` is untouched.\n\n2. `PreserveChanges` - Updates current values for unchanged entities.\n   For each changed entity it preserves the current value and overwrites the `originalValue` with the merge entity.\n   This is the query-success default.\n\n3. `OverwriteChanges` - Replace the current collection entities.\n   For each merged entity it discards the `changeState` and sets the `changeType` to \"unchanged\".\n   This is the save-success default.\n\nDisabling change tracking with `IgnoreChanges` is the most frequent choice.\n\n<a id=\"disable-change-tracking\"></a>\n\n### Disable change tracking\n\nYou can opt-out of change tracking for the entire collection by setting the collection's `noChangeTracking` flag to `true` in its `entityMetadata`.\nWhen `true`, NgRx Data does not track any changes for this collection\nand the `EntityCollection.changeState` property remains an empty object.\n\nYou can opt-out of change tracking for a _specific_ entity action by supplying the `mergeStrategy` in the optional `EntityActionOptions` that you can pass in the action payload.\n\n> If you don't specify a `MergeStrategy`, NgRx Data uses the default for that action.\n\nIf you are dispatching an action with `EntityDispatcher` and you don't want that action to be change-tracked, you might write something like this:\n\n```typescript\nconst hero: Hero = { id: 42, name: 'Francis' };\n\ndispatcher.addOneToCache(hero, {\n  mergeStrategy: MergeStrategy.IgnoreChanges,\n});\n```\n\nYou can also pass that option to methods of a helpful `EntityCollectionService` facade such as your custom `HeroService`\n\n```typescript\nconst hero: Hero = { id: 42, name: 'Francis' };\n\nheroService.addOneToCache(hero, {\n  mergeStrategy: MergeStrategy.IgnoreChanges,\n});\n```\n\nIf you prepare the `EntityAction` directly with an `EntityActionFactory`, it might look like this:\n\n```typescript\nconst hero: Hero = { id: 42, name: 'Francis' };\n\nconst payload: EntityActionPayload = {\n  entityName: 'Hero',\n  entityOp: EntityOp.ADD_ONE,\n  data: hero,\n  mergeStrategy: MergeStrategy.IgnoreChanges,\n  // .. other options ..\n};\n\nconst action = factory.create(payload);\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/entity-collection-service.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# EntityCollectionService\n\nAn **`EntityCollectionService`** is a facade over the NgRx Data **dispatcher** and **selectors$** that manages an entity `T` collection cached in the _NgRx store_.\n\nThe **_Dispatcher_** features **command** methods that dispatch [_entity actions_](guide/data/entity-actions) to the _NgRx store_.\nThese commands either update the entity collection directly or trigger HTTP requests to a server. When the server responds, the NgRx Data library dispatches new actions with the response data and these actions update the entity collection.\n\nThe `EntityCommands` interface lists all the commands and what they do.\n\nYour application calls these _command methods_ to update\nthe _cached entity collection_ in the _NgRx store_.\n\n**_Selectors$_** are properties returning _selector observables_.\nEach _observable_ watches for a specific change in the cached entity collection and emits the changed value.\n\nThe `EntitySelectors$` interface lists all of the pre-defined _selector observable properties_ and\nexplains which collection properties they observe.\n\nYour application subscribes to _selector observables_\nin order to process and display entities in the collection.\n\n## Examples from the demo app\n\nHere are simplified excerpts from the demo app's `HeroesComponent` showing the component calling _command methods_ and subscribing to _selector observables_.\n\n```typescript\nconstructor(EntityCollectionServiceFactory: EntityCollectionServiceFactory) {\n  this.heroService = EntityCollectionServiceFactory.create<Hero>('Hero');\n  this.filteredHeroes$ = this.heroService.filteredEntities$;\n  this.loading$ = this.heroService.loading$;\n}\n\ngetHeroes() { this.heroService.getAll(); }\nadd(hero: Hero) { this.heroService.add(hero); }\ndeleteHero(hero: Hero) { this.heroService.delete(hero.id); }\nupdate(hero: Hero) { this.heroService.update(hero); }\n```\n\n### Create the _EntityCollectionService_ with a factory\n\nThe component injects the NgRx Data `EntityCollectionServiceFactory` and\ncreates an `EntityCollectionService` for `Hero` entities.\n\n<ngrx-docs-alert type=\"help\">\n\nWe'll go inside the factory [later in this guide](#entitycollectionservicefactory).\n\n</ngrx-docs-alert>\n\n### Create the _EntityCollectionService_ as a class\n\nAlternatively, you could have created a single `HeroEntityService` elsewhere, perhaps in the `AppModule`, and injected it into the component's constructor.\n\nThere are two basic ways to create the service class.\n\n1.  Derive from `EntityCollectionServiceBase<T>`\n1.  Write a `HeroEntityService` with just the API you need.\n\nWhen `HeroEntityService` derives from `EntityCollectionServiceBase<T>` it must inject the `EntityCollectionServiceFactory` into its constructor.\nThere are examples of this approach in the demo app.\n\nWhen defining an `HeroEntityService` with a limited API,\nyou may also inject `EntityCollectionServiceFactory` as a source of the\nfunctionality that you choose to expose.\n\nLet your preferred style and app needs determine which creation technique you choose.\n\n### Set component _selector$_ properties\n\nA `selector$` property is an _observable_ that emits when a _selected_ state property changes.\n\n<ngrx-docs-alert type=\"help\">\n\nSome folks refer to such properties as **state streams**.\n\n</ngrx-docs-alert>\n\nThe example component has two such properties that expose two `EntityCollectionService` _selector observables_: `filteredEntities$` and `loading$`.\n\nThe `filteredEntities$` _observable_ produces an array of the currently cached `Hero` entities that satisfy the user's filter criteria.\nThis _observable_ produces a new array of heroes if the user\nchanges the filter or if some action changes the heroes in the cached collection.\n\nThe `loading$` _observable_ produces `true` while the\n[data service](guide/data/entity-dataservice) is waiting for heroes from the server.\nIt produces `false` when the server responds.\nThe demo app subscribes to `loading$` so that it can turn a visual loading indicator on and off.\n\n<ngrx-docs-alert type=\"help\">\n\nThese component and `EntityCollectionService` selector property names end in `'$'` which is a common convention for a property that returns an `Observable`.\nAll _selector observable_ properties of an `EntityCollectionService` follow this convention.\n\n</ngrx-docs-alert>\n\n#### The _selector observable_ versus the _selector function_\n\nThe _`selector$`_ observable (ending with an `'$'`) differs from the similarly named and\nclosely-related `selector` function (no `'$'` suffix)\n\nA `selector` is a _function_ that _selects_ a slice of state from the entity collection.\nA `selector$` observable emits that slice of state when the state changes.\n\nNgRx Data creates a `selector$` observable by passing the _selector_ function to the NgRx `select` operator and piping it onto the NgRx store, as seen in the following example:\n\n```typescript\nloading$ = this.store.select(selectLoading);\n```\n\n#### Using _selectors$_\n\nThe component _class_ does not subscribe to these `selector$` properties but the component _template_ does.\n\nThe template binds to them and forwards their _observables_ to the Angular `AsyncPipe`, which subscribes to them.\nHere's an excerpt of the `filteredHeroes$` binding.\n\n```html\n<div *ngIf=\"filteredHeroes$ | async as heroes\">...</div>\n```\n\n### Call _command methods_\n\nMost of the `HeroesComponent` methods delegate to `EntityCollectionService` command methods such as `getAll()` and `add()`.\n\nThere are two kinds of commands:\n\n1.  Commands that trigger requests to the server.\n1.  Cache-only commands that update the cached entity collection.\n\nThe server commands are simple verbs like \"add\" and \"getAll\".\nThey dispatch actions that trigger asynchronous requests to a remote server.\n\nThe cache-only command methods are longer verbs like \"addManyToCache\" and \"removeOneFromCache\"\nand their names all contain the word \"cache\".\nThey update the cached collection immediately (synchronously).\n\n<ngrx-docs-alert type=\"help\">\n\nMost applications call the server commands because they want to query and save entity data.\n\nApps rarely call the cache-only commands because direct updates to the entity collection\nare lost when the application shuts down.\n\n</ngrx-docs-alert>\n\nMany `EntityCollectionService` command methods take a value.\nThe value is _typed_ (often as `Hero`) so you won't make a mistake by passing in the wrong kind of value.\n\nInternally, an entity service method creates an\n[_entity action_](guide/data/entity-actions) that corresponds to the method's intent. The action's _payload_ is either the value passed to the method or an appropriate derivative of that value.\n\n_Immutability_ is a core principle of the _redux pattern_.\nSeveral of the command methods take an entity argument such as a `Hero`.\nAn entity argument **must never be a cached entity object**.\nIt can be a _copy_ of a cached entity object and it often is.\nThe demo application always calls these command methods with copies of the entity data.\n\nAll _command methods_ return `void` or an `Observable`.\nA core principle of the _redux pattern_ is that _commands_ never return a value. They just _do things_ that have side-effects.\nThus, the action is only dispatched when the command is invoked, and re-subscribing to a command's returned `Observable` will not\ndispatch another action.\n\nRather than expect a result from the command,\nyou subscribe to a _selector$_ property that reflects\nthe effects of the command. If the command did something you care about, a _selector$_ property should be able to tell you about it.\n\n## _EntityCollectionServiceFactory_\n\nThe `create<T>()` method of the NgRx Data `EntityCollectionServiceFactory` produces a new instance\nof the `EntityCollectionServiceBase<T>` class that implements the `EntityCollectionService` interface for the entity type `T`.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/entity-collection.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Entity Collection\n\nThe NgRx Data library maintains a _cache_ (`EntityCache`) of\n_entity collections_ for each _entity type_ in the _NgRx store_.\n\nAn _entity_collection_ implements the `EntityCollection` interface for an entity type.\n\n| Property      | Meaning                                                                                                     |\n| ------------- | ----------------------------------------------------------------------------------------------------------- |\n| `ids`         | Primary key values in default sort order                                                                    |\n| `entities`    | Map of primary key to entity data values                                                                    |\n| `filter`      | The user's filtering criteria                                                                               |\n| `loaded`      | Whether collection was filled by QueryAll; forced false after clear                                         |\n| `loading`     | Whether currently waiting for query results to arrive from the server                                       |\n| `changeState` | When [change-tracking](guide/data/entity-change-tracker) is enabled, the `ChangeStates` of unsaved entities |\n\nYou can extend an entity types with _additional properties_ via\n[entity metadata](guide/data/entity-metadata#additionalcollectionstate).\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/entity-dataservice.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Entity DataService\n\nThe NgRx Data library expects to persist entity data with calls to a REST-like web api with endpoints for each entity type.\n\nThe `EntityDataService` maintains a registry of service classes dedicated to persisting data for a specific entity type.\n\nWhen the NgRx Data library sees an action for an entity _persistence operation_, it asks the `EntityDataService` for the registered data service that makes HTTP calls for that entity type, and calls the appropriate service method.\n\nA data service is an instance of a class that implements the `EntityCollectionDataService`.\nThis interface supports a basic set of CRUD operations for an entity.\nEach that return `Observables`:\n\n| Method                                                                                               | Meaning                                   | HTTP Method with endpoint        |\n| ---------------------------------------------------------------------------------------------------- | ----------------------------------------- | -------------------------------- |\n| `add(entity: T): Observable<T>, httpOptions?: HttpOptions`                                           | Add a new entity                          | `POST` /api/hero/                |\n| `delete(id: number` &#x7c; `string, httpOptions?: HttpOptions): Observable<number` &#x7c; `string>`  | Delete an entity by primary key value     | `DELETE` /api/hero/5             |\n| `getAll(httpOptions?: HttpOptions): Observable<T[]>`                                                 | Get all instances of this entity type     | `GET` /api/heroes/               |\n| `getById(id: number` &#x7c; `string, httpOptions?: HttpOptions): Observable<T>`                      | Get an entity by its primary key          | `GET` /api/hero/5                |\n| `getWithQuery(queryParams: QueryParams` &#x7c; `string, httpOptions?: HttpOptions): Observable<T[]>` | Get entities that satisfy the query       | `GET` /api/heroes/?name=bombasto |\n| `update(update: Update<T>, httpOptions?: HttpOptions): Observable<T>`                                | Update an existing entity                 | `PUT` /api/hero/5                |\n| `upsert(entity: T, httpOptions?: HttpOptions): Observable<T>`                                        | Upsert an entity (if api supports upsert) | `POST` /api/hero/5               |\n\n<ngrx-docs-alert type=\"help\">\n\n`QueryParams` is a _parameter-name/value_ map\nYou can also supply the query string itself.\n`HttpClient` safely encodes both into an encoded query string.\n\n`Update<T>` is an object with a strict subset of the entity properties.\nIt _must_ include the properties that participate in the primary key (e.g., `id`).\nThe update property values are the _properties-to-update_;\nunmentioned properties should retain their current values.\n\n`HttpOptions` is an object containing properties that will be forwarded on\nto the Data Service's http requests. The `DefaultDataService` uses this data to create `HttpHeaders` and `HttpParams`\nto pass to `HttpClient` requests. This allows the configuration of Http Query Parameters and/or\nHttp Headers from the `EntityCollectionDataService` api.\n\n</ngrx-docs-alert>\n\nThe default data service methods return the `Observables` returned by the corresponding Angular `HttpClient` methods.\n\nYour API should return an object in the shape of the return type for each data service method. For example: when calling `.add(entity)` your API\nshould create the entity and then return the full entity matching `T` as that is the value that will be set as the record in the store for that entities primary\nkey. The one method that differs from the others is `delete`. `delete` requires a response type of the entities primary key, `string | number`, instead of the full object, `T`, that was deleted.\n\n<ngrx-docs-alert type=\"help\">\n\nIf you create your own data service alternatives, they should return similar `Observables`.\n\n</ngrx-docs-alert>\n\n## Register data services\n\nThe `EntityDataService` registry is empty by default.\n\nYou can add custom data services to it by creating instances of those classes and registering them with `EntityDataService` in one of two ways.\n\n1.  register a single data service by entity name with the `registerService()` method.\n\n2.  register several data services at the same time with by calling `registerServices` with an _entity-name/service_ map.\n\n<ngrx-docs-alert type=\"help\">\n\nYou can create and import a module that registers your custom data services as shown in the _EntityDataService_ [tests](https://github.com/ngrx/platform/blob/main/modules/data/spec/dataservices/entity-data.service.spec.ts)\n\n</ngrx-docs-alert>\n\nIf you decide to register an entity data service, be sure to do so _before_ you ask NgRx Data to perform a persistence operation for that entity.\n\nOtherwise, the NgRx Data library will create and register an instance of the default data service `DefaultDataService<T>` for that entity type.\n\n## The _DefaultDataService_\n\nThe demo app doesn't register any entity data services.\nIt relies entirely on a `DefaultDataService`, created for each entity type, by the injected `DefaultDataServiceFactory`.\n\nA `DefaultDataService<T>` makes REST-like calls to the server's web api with Angular's `HttpClient`.\n\nIt composes HTTP URLs from a _root_ path (see [\"Configuration\"](#configuration) below) and the entity name.\n\nFor example,\n\n- if the persistence action is to delete a hero with id=42 _and_\n- the root path is `'api'` _and_\n- the entity name is `'Hero'`, _then_\n- the DELETE request URL will be `'api/hero/42'`.\n\nWhen the persistence operation concerns multiple entities, the `DefaultDataService` substitutes the plural of the entity type name for the resource name.\n\nThe `QUERY_ALL` action to get all heroes would result in an HTTP GET request to the URL `'api/heroes'`.\n\nThe `DefaultDataService` doesn't know how to pluralize the entity type name.\nIt doesn't even know how to create the base resource names.\n\nIt relies on an injected `HttpUrlGenerator` service to produce the appropriate endpoints.\nAnd the default implementation of the `HttpUrlGenerator` relies on the\n`Pluralizer` service to produce the plural collection resource names.\n\nThe [_Entity Metadata_](guide/data/entity-metadata#plurals) guide\nexplains how to configure the default `Pluralizer` .\n\n<a id=\"configuration\"></a>\n\n### Configure the _DefaultDataService_\n\nThe collection-level data services construct their own URLs for HTTP calls. They typically rely on shared configuration information such as the root of every resource URL.\n\nThe shared configuration values are almost always specific to the application and may vary according the runtime environment.\n\nThe NgRx Data library defines a `DefaultDataServiceConfig` for\nconveying shared configuration to an entity collection data service.\n\nThe most important configuration property, `root`, returns the _root_ of every web api URL, the parts that come before the entity resource name. If you are using a remote API, this value can include the protocol, domain, port, and root path, such as `https://my-api-domain.com:8000/api/v1`.\n\nFor a `DefaultDataService<T>`, the default value is `'api'`, which results in URLs such as `api/heroes`.\n\nThe `timeout` property sets the maximum time (in ms) before the _ng-lib_ persistence operation abandons hope of receiving a server reply and cancels the operation. The default value is `0`, which means that requests do not timeout.\n\nThe `delete404OK` flag tells the data service what to do if the server responds to a DELETE request with a `404 - Not Found`.\n\nIn general, not finding the resource to delete is harmless and\nyou can save yourself the headache of ignoring a DELETE 404 error\nby setting this flag to `true`, which is the default for the `DefaultDataService<T>`.\n\nWhen running a demo app locally, the server may respond more quickly than it will in production. You can simulate real-world by setting the `getDelay` and `saveDelay` properties.\n\n#### Provide a custom configuration\n\nFirst, create a custom configuration object of type `DefaultDataServiceConfig` :\n\n```typescript\nconst defaultDataServiceConfig: DefaultDataServiceConfig = {\n  root: 'https://my-api-domain.com:8000/api/v1',\n  timeout: 3000, // request timeout\n};\n```\n\nProvide it in an eagerly-loaded `NgModule` such as the `EntityStoreModule` in the sample application:\n\n```typescript\n@NgModule({\n  providers: [{ provide: DefaultDataServiceConfig, useValue: defaultDataServiceConfig }]\n})\n```\n\n## Custom _EntityDataService_\n\nWhile the NgRx Data library provides a configuration object to modify certain aspects of the _DefaultDataService_,\nyou may want to further customize what happens when you save or retrieve data for a particular collection.\n\nFor example, you may need to modify fetched entities to convert strings to dates, or to add additional properties to an entity.\n\nYou could do this by creating a custom data service and registering that service with the `EntityDataService`.\n\nTo illustrate this, the sample app adds a `dateLoaded` property to the `Hero` entity to record when a hero is loaded from the server into the _NgRx-store_ entity cache.\n\n```typescript\nexport class Hero {\n  readonly id: number;\n  readonly name: string;\n  readonly saying: string;\n  readonly dateLoaded: Date;\n}\n```\n\nTo support this feature, we 'll create a `HeroDataService` class that implements the `EntityCollectionDataService<T>` interface.\n\nIn the sample app the `HeroDataService` derives from the NgRx Data `DefaultDataService<T>` in order to leverage its base functionality.\nIt only overrides what it really needs.\n\n<ngrx-code-example header=\"store/entity/hero-data-service.ts\">\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport {\n  EntityCollectionDataService,\n  DefaultDataService,\n  HttpUrlGenerator,\n  Logger,\n  QueryParams,\n} from '@ngrx/data';\n\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { Hero } from '../../core';\n\n@Injectable()\nexport class HeroDataService extends DefaultDataService<Hero> {\n  constructor(\n    http: HttpClient,\n    httpUrlGenerator: HttpUrlGenerator,\n    logger: Logger\n  ) {\n    super('Hero', http, httpUrlGenerator);\n    logger.log('Created custom Hero EntityDataService');\n  }\n\n  getAll(): Observable<Hero[]> {\n    return super\n      .getAll()\n      .pipe(\n        map((heroes) => heroes.map((hero) => this.mapHero(hero)))\n      );\n  }\n\n  getById(id: string | number): Observable<Hero> {\n    return super.getById(id).pipe(map((hero) => this.mapHero(hero)));\n  }\n\n  getWithQuery(params: string | QueryParams): Observable<Hero[]> {\n    return super\n      .getWithQuery(params)\n      .pipe(\n        map((heroes) => heroes.map((hero) => this.mapHero(hero)))\n      );\n  }\n\n  private mapHero(hero: Hero): Hero {\n    return { ...hero, dateLoaded: new Date() };\n  }\n}\n```\n\n</ngrx-code-example>\n\nThis `HeroDataService` hooks into the _get_ operations to set the `Hero.dateLoaded` on fetched hero entities.\nIt also tells the logger when it is created (see the console output of the running sample) .\n\nFinally, we must tell NgRx Data about this new data service.\n\nThe sample app provides `HeroDataService` and registers it by calling the `registerService()` method on the `EntityDataService` in the app's _entity store module_:\n\n<ngrx-code-example header=\"store/entity-store.module.ts\">\n\n```ts\nimport { EntityDataService } from '@ngrx/data'; // <-- import the NgRx Data data service registry\n\nimport { HeroDataService } from './hero-data-service';\n\n@NgModule({\n  imports: [ ... ],\n  providers: [ HeroDataService ] // <-- provide the data service\n})\nexport class EntityStoreModule {\n  constructor(\n    entityDataService: EntityDataService,\n    heroDataService: HeroDataService,\n  ) {\n    entityDataService.registerService('Hero', heroDataService); // <-- register it\n  }\n}\n```\n\n</ngrx-code-example>\n\n### A custom _DataService_\n\nYou don't have to override members of the `DefaultDataService`.\nYou could write a completely custom alternative that queries and saves\nentities by any mechanism you choose.\n\nYou can register it the same way as long as it adheres to the interface.\n\n```typescript\n// Register custom data service\nentityDataService.registerService('Hero', peculiarHeroDataService);\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/entity-effects.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Entity Effects\n\n**Work in Progress**\n\n**_Effects_** are a way to trigger _side effects_ with _actions_.\n\nA one common, desirable _side effect_ is an asynchronous HTTP call to the remote server to fetch or save entity data.\n\nYou implement one or more _effects_ with the help of the NgRx [Effects](guide/effects) library.\n\n_Actions_ dispatched to the _NgRx store_ can be detected and processed by your _effect_ method.\nAfter processing, whether synchronously or asynchronously, your method can dispatch new action(s) to the _store_\n\nThe NgRx Data library implements an effect named `persist$` in its `EntityEffects` class.\n\nThe `persist$` method filters for certain `EntityAction.op` values.\nThese values turn into HTTP GET, PUT, POST, and DELETE requests with entity data.\nWhen the server responds (whether favorably or with an error), the `persist$` method dispatches new `EntityAction`s to the _store_ with the appropriate response data.\n\n#### Cancellation\n\nYou can try to cancel a save by dispatching a `CANCEL_PERSIST` EntityAction with the\n**correlation id** of the _persistence action_ that you want to cancel.\n\nThe `EntityCache.cancel$` watches for this action and is piped into\nthe `EntityCache.persist$`, where it can try to cancel an entity collection query or save operation\nor at least prevent the server response from updating the cache.\n\n<ngrx-docs-alert type=\"help\">\n\nIt's not obvious that this is ever a great idea for a save operation.\nYou cannot tell the server to cancel this way and cannot know if the server did or did not save.\nNor can you count on processing the cancel request before the client receives the server response\nand applies the changes on the server or to the cache.\n\nIf you cancel before the server results arrive, the `EntityEffect` will not try to update\nthe cached collection with late arriving server results.\nThe effect will issue a `CANCELED_PERSIST` action instead.\nThe `EntityCollection` reducer ignores this action but you can listen for it among the store actions\nand thus know that the cancellation took effect on the client.\n\n</ngrx-docs-alert>\n\n**_More to come on the subject of effects_**\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/entity-metadata.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Entity Metadata\n\nThe NgRx Data library maintains a **_cache_** of entity collection data in the _NgRx store_.\n\nYou tell the NgRx Data library about those collections and the entities they contain with **_entity metadata_**.\n\nThe entities within a collection belong to the same **_entity type_**.\nEach _entity type_ appears as named instance of the NgRx Data [**`EntityMetadata<T>`**](#metadata-properties) interface.\n\nYou can specify metadata for several entities at the same time in an **`EntityMetadataMap`**.\n\nHere is an example `EntityMetadataMap` similar to the one in the demo app\nthat defines metadata for two entities, `Hero` and `Villain`.\n\n<ngrx-code-example header=\"app-entity-metadata.ts\">\n\n```ts\nexport const appEntityMetadata: EntityMetadataMap = {\n  Hero: {\n    /* optional settings */\n    filterFn: nameFilter,\n    sortComparer: sortByName,\n  },\n  Villain: {\n    villainSelectId, // necessary if key is not `id`\n\n    /* optional settings */\n    entityName: 'Villain', // optional because same as map key\n    filterFn: nameAndSayingFilter,\n    entityDispatcherOptions: {\n      optimisticAdd: true,\n      optimisticUpdate: true,\n    },\n  },\n};\n```\n\n</ngrx-code-example>\n\n## Register metadata\n\nYou must register the metadata with the NgRx Data `EntityDefinitionService`.\n\nThe easiest way to register metadata is to define a single `EntityMetadataMap` for the entire application and specify it in the one place where you initialize the NgRx Data library:\n\n```typescript\n    EntityDataModule.forRoot({\n      ...\n      entityMetadata: appEntityMetadata,\n      ...\n    })\n```\n\nIf you define entities in several, different _eagerly-loaded_ Angular modules, you can add the metadata for each module with the multi-provider.\n\n```typescript\n{ provide: ENTITY_METADATA_TOKEN, multi: true, useValue: someEntityMetadata }\n```\n\nThis technique won't work for a _lazy-loaded_ module.\nThe `ENTITY_METADATA_TOKEN` provider was already set and consumed by the time the _lazy-loaded_ module arrives.\n\nThe module should inject the `EntityDefinitionService`\ninstead and register metadata directly with one of the registration methods.\n\n```typescript\n@NgModule({...})\nclass LazyModule {\n  constructor(eds: EntityDefinitionService) {\n    eds.registerMetadataMap(this.lazyMetadataMap);\n  }\n  ...\n}\n```\n\n## Metadata Properties\n\nThe `EntityMetadata<T>` interface describes aspects of an entity type that tell the NgRx Data library how to manage collections of entity data of type `T`.\n\nType `T` is your application's TypeScript representation of that entity; it can be an interface or a class.\n\n### _entityName_\n\nThe `entityName` of the type is the only **required metadata property**.\nIt's the unique _key_ of the entity type's metadata in cache.\n\nIt _must_ be specified for individual `EntityMetadata` instances.\nIf you omit it in an `EntityMetadataMap`, the map _key_ becomes the `entityName` as in this example.\n\n```typescript\nconst map = {\n  Hero: {}, // \"Hero\" becomes the entityName\n};\n```\n\nThe spelling and case (typically PascalCase) of the `entityName` is important for NgRx Data conventions. It appears in the generated [_entity actions_](guide/data/entity-actions), in error messages, and in the persistence operations.\n\nImportantly, the default [_entity dataservice_](guide/data/entity-dataservice) creates HTTP resource URLs from the lowercase version of this name. For example, if the `entityName` is \"Hero\", the default data service will POST to a URL such as `'api/hero'`.\n\n<ngrx-docs-alert type=\"help\">\n\nBy default it generates the _plural_ of the entity name when preparing a _collection_ resource URL.\n\nIt isn't good at pluralization.\nIt would produce `'api/heros'` for the URL to fetch _all heroes_ because it blindly adds an `'s'` to the end of the lowercase entity name.\n\nOf course the proper plural of \"hero\" is \"hero**es**\", not \"hero**s**\".\nYou'll see how to correct this problem [below](#plurals).\n\n</ngrx-docs-alert>\n\n### _filterFn_\n\nMany applications allow the user to filter a cached entity collection.\n\nIn the accompanying demonstration app, the user can filter _heroes_ by name and can filter _villains_ by name or the villain's _saying_.\n\nWe felt this common scenario is worth building into the NgRx Data library. So every entity can have an _optional_ filter function.\n\nEach collection's `filteredEntities` selector applies the filter function to the collection, based on the user's filtering criteria, which are held in the stored entity collection's `filter` property.\n\nIf there is no filter function, the `filteredEntities` selector is the same as the `selectAll` selector, which returns all entities in the collection.\n\nA filter function (see `EntityFilterFn`) takes an entity collection and the user's filtering criteria (the filter _pattern_) and returns an array of the selected entities.\n\nHere's an example that filters for entities with a `name` property whose value contains the search string.\n\n```typescript\nexport function nameFilter(\n  entities: { name: string }[],\n  search: string\n) {\n  return entities.filter((e) => -1 < e.name.indexOf(search));\n}\n```\n\nThe NgRx Data library includes a helper function, `PropsFilterFnFactory<T>`, that creates an entity filter function which will treat the user's input\nas a case-insensitive, regular expression and apply it to one or more properties of the entity.\n\nThe demo uses this helper to create hero and villain filters. Here's how the app creates the `nameAndSayingFilter` function for villains.\n\n```typescript\n/**\n * Filter for entities whose name or saying\n * matches the case-insensitive pattern.\n */\nexport function nameAndSayingFilter(\n  entities: Villain[],\n  pattern: string\n) {\n  return (PropsFilterFnFactory<Villain>)[('name', 'saying')](\n    entities,\n    pattern\n  );\n}\n```\n\n### _selectId_\n\nEvery _entity type_ must have a _primary key_ whose value is an integer or a string.\n\nThe NgRx Data library assumes that the entity has an `id` property whose value is the primary key.\n\nNot every entity will have a primary key property named `id`. For some entities, the primary key could be the combined value of two or more properties.\n\nIn these cases, you specify a `selectId` function that, given an entity instance, returns an integer or string primary key value.\n\nIn the _EntityCollectionReducer_ [tests](https://github.com/ngrx/platform/blob/main/modules/data/spec/reducers/entity-collection-reducer.spec.ts),\nthe `Villain` type has a string primary key property named `key`.\nThe `selectorId` function is this:\n\n```typescript\nselectId: (villain: Villain) => villain.key;\n```\n\n### _sortComparer_\n\nThe NgRx Data library keeps the collection entities in a specific order.\n\n<ngrx-docs-alert type=\"help\">\n\nThis is actually a feature of the underlying NgRx Entity library.\n\n</ngrx-docs-alert>\n\nThe default order is the order in which the entities arrive from the server.\nThe entities you add are pushed to the end of the collection.\n\nYou may prefer to maintain the collection in some other order.\nWhen you provide a `sortComparer` function, the _NgRx-lib_ keeps the collection in the order prescribed by your comparer.\n\nIn the demo app, the villains metadata has no comparer so its entities are in default order.\n\nThe hero metadata have a `sortByName` comparer that keeps the collection in alphabetical order by `name`.\n\n```typescript\nexport function sortByName(\n  a: { name: string },\n  b: { name: string }\n): number {\n  return a.name.localeCompare(b.name);\n}\n```\n\nRun the demo app and try changing existing hero names or adding new heroes.\n\nYour app can call the `selectKey` selector to see the collection's `ids` property, which returns an array of the collection's primary key values in sorted order.\n\n### _entityDispatcherOptions_\n\nThese options determine the default behavior of the collection's _dispatcher_ which sends actions to the reducers and effects.\n\nA dispatcher save command will add, delete, or update\nthe collection _before_ sending a corresponding HTTP request (_optimistic_) or _after_ (_pessimistic_).\nThe caller can specify in the optional `isOptimistic` parameter.\nIf the caller doesn't specify, the dispatcher chooses based on default options.\n\nThe _defaults_ are the safe ones: _optimistic_ for delete and _pessimistic_ for add and update.\nYou can override those choices here.\n\n### _additionalCollectionState_\n\nEach NgRx Data entity collection in the store has\n[predefined properties](guide/data/entity-collection).\n\nYou can add your own collection properties by setting the `additionalCollectionState` property to an object with those custom collection properties.\n\nThe _EntitySelectors_ [tests](https://github.com/ngrx/platform/blob/main/modules/data/spec/selectors/entity-selectors.spec.ts)\nillustrate by adding `foo` and `bar` collection properties to test hero metadata.\n\n```typescript\n  additionalCollectionState: {\n    foo: 'Foo',\n    bar: 3.14\n  }\n```\n\nThe property values become the initial collection values for those properties when NgRx Data first creates the collection in the store.\n\nThe NgRx Data library generates selectors for these properties, but has no way to update them. You'll have to create or extend the existing reducers to do that yourself.\n\nIf the property you want to add comes from `backend`, you will need some additional work to make sure the property can be saved into the store from `Effects` correctly.\n\n#### Step 1: Implement `PersistenceResultHandler` to save data from backend to action.payload\n\nCreate a new class `AdditionalPersistenceResultHandler` that `extends DefaultPersistenceResultHandler` and overwrite the [handleSuccess](https://github.com/ngrx/platform/blob/main/modules/data/src/dataservices/persistence-result-handler.service.ts) method, the purpose is to parse the data received from `DataService`, retrieve the additional property, and then save this to the `action.payload`. Note that the default reducer for success actions requires that `action.payload.data` is an array of entities or an entity. This would need to be set after retrieving the additional property, not shown in the example below.\n\n```typescript\nexport class AdditionalPersistenceResultHandler extends DefaultPersistenceResultHandler {\n  handleSuccess(originalAction: EntityAction): (data: any) => Action {\n    const actionHandler = super.handleSuccess(originalAction);\n    // return a factory to get a data handler to\n    // parse data from DataService and save to action.payload\n    return function (data: any) {\n      const action = actionHandler.call(this, data);\n      if (action && data && data.foo) {\n        // save the data.foo to action.payload.foo\n        (action as any).payload.foo = data.foo;\n      }\n      return action;\n    };\n  }\n}\n```\n\n#### Step 2: Overwrite `EntityCollectionReducerMethods` to save the additional property from action.payload to the EntityCollection instance\n\nFollowing the prior step, we have added the additional property to the `action.payload`. Up next we need to set it to the instance of EntityCollection in the `reducer`. In order to accomplish that, we need to create an `AdditionalEntityCollectionReducerMethods` that `extends EntityCollectionReducerMethods`. In addition, we will need to overwrite the method to match your `action`. For example, if the additional property `foo` is only available in `queryMany action(triggered by EntityCollectionService.getWithQuery)`, we can follow this approach.\n\n```typescript\nexport class AdditionalEntityCollectionReducerMethods<\n  T,\n> extends EntityCollectionReducerMethods<T> {\n  constructor(\n    public entityName: string,\n    public definition: EntityDefinition<T>\n  ) {\n    super(entityName, definition);\n  }\n  protected queryManySuccess(\n    collection: EntityCollection<T>,\n    action: EntityAction<T[]>\n  ): EntityCollection<T> {\n    const ec = super.queryManySuccess(collection, action);\n    if ((action.payload as any).foo) {\n      // save the foo property from action.payload to entityCollection instance\n      (ec as any).foo = (action.payload as any).foo;\n    }\n    return ec;\n  }\n}\n```\n\n#### Step 3: Register customized `EntityCollectionReducerMethods` and `AdditionalPersistenceResultHandler`.\n\nFinally we need to register the `AdditionalPersistenceResultHandler` and `AdditionalEntityCollectionReducerMethods` to replace the default implementation.\n\nRegister `AdditionalPersistenceResultHandler` in `NgModule`,\n\n```typescript\n@NgModule({\n  { provide: PersistenceResultHandler, useClass: AdditionalPersistenceResultHandler },\n})\n```\n\nRegister `AdditionalEntityCollectionReducerMethods`, to do that, we need to create an `AdditionalEntityCollectionReducerMethodFactory`, for details, see [Entity Reducer](guide/data/entity-reducer)\n\n```typescript\n@Injectable()\nexport class AdditionalEntityCollectionReducerMethodsFactory {\n  constructor(\n    private entityDefinitionService: EntityDefinitionService\n  ) {}\n  /** Create the  {EntityCollectionReducerMethods} for the named entity type */\n  create<T>(entityName: string): EntityCollectionReducerMethodMap<T> {\n    const definition =\n      this.entityDefinitionService.getDefinition<T>(entityName);\n    const methodsClass = new AdditionalEntityCollectionReducerMethods(\n      entityName,\n      definition\n    );\n    return methodsClass.methods;\n  }\n}\n```\n\nRegister `AdditionalEntityCollectionReducerMethodsFactory` to `NgModule`,\n\n```typescript\n@NgModule({\n  {\n    provide: EntityCollectionReducerMethodsFactory,\n    useClass: AdditionalEntityCollectionReducerMethodsFactory\n  },\n})\n```\n\nNow you can get `foo` from `backend` just like another `EntityCollection` level property.\n\n<a id=\"plurals\"></a>\n\n## Pluralizing the entity name\n\nThe NgRx Data [`DefaultDataService`](guide/data/entity-dataservice) relies on the `HttpUrlGenerator` to create conventional HTTP resource names (URLs) for each entity type.\n\nBy convention, an HTTP request targeting a single entity item contains the lowercase, singular version of the entity type name. For example, if the entity type `entityName` is \"Hero\", the default data service will POST to a URL such as `'api/hero'`.\n\nBy convention, an HTTP request targeting multiple entities contains the lowercase, _plural_ version of the entity type name. The URL of a GET request that retrieved all heroes should be something like `'api/heroes'`.\n\nThe `HttpUrlGenerator` can't pluralize the entity type name on its own. It delegates to an injected _pluralizing class_, called `Pluralizer`.\n\nThe `Pluralizer` class has a _pluralize()_ method that takes the singular string and returns the plural string.\n\nThe default `Pluralizer` handles many of the common English pluralization rules such as appending an `'s'`.\nThat's fine for the `Villain` type (which becomes \"Villains\") and even for `Company` (which becomes \"Companies\").\n\nIt's far from perfect. For example, it incorrectly turns `Hero` into \"Heros\" instead of \"Heroes\".\n\nFortunately, the default `Pluralizer` also injects a map of singular to plural strings (with the `PLURAL_NAMES_TOKEN`).\n\nIts `pluralize()` method looks for the singular entity name in that map and uses the corresponding plural value if found.\nOtherwise, it returns the default pluralization of the entity name.\n\nIf this scheme works for you, create a map of _singular-to-plural_ entity names for the exceptional cases:\n\n```typescript\nexport const pluralNames = {\n  // Case matters. Match the case of the entity name.\n  Hero: 'Heroes',\n};\n```\n\nThen specify this map while configuring the NgRx Data library.\n\n```typescript\n    EntityDataModule.forRoot({\n      ...\n      pluralNames: pluralNames\n    })\n```\n\nIf you define your _entity model_ in separate Angular modules, you can incrementally add a plural names map with the multi-provider.\n\n```typescript\n{ provide: PLURAL_NAMES_TOKEN, multi: true, useValue: morePluralNames }\n```\n\nIf this scheme isn't working for you, replace the `Pluralizer` class with your own invention.\n\n```typescript\n{ provide: Pluralizer, useClass: MyPluralizer }\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/entity-reducer.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Entity Reducer\n\nThe _Entity Reducer_ is the _master reducer_ for all entity collections in the stored entity cache.\n\n<a id=\"reducer-factory\"></a>\n\nThe library doesn't have a named _entity reducer_ type.\nRather it relies on the **`EntityCacheReducerFactory.create()`** method to produce that reducer,\nwhich is an _NgRx_ `ActionReducer<EntityCache, EntityAction>`.\n\nSuch a reducer function takes an `EntityCache` state and an `EntityAction` action\nand returns an `EntityCache` state.\n\nThe reducer responds either to an [EntityCache-level action](#entity-cache-actions) (rare)\nor to an `EntityAction` targeting an entity collection (the usual case).\nAll other kinds of `Action` are ignored and the reducer simply returns the given `state`.\n\n<ngrx-docs-alert type=\"help\">\n\nThe reducer filters specifically for the action's `entityType` property.\nIt treats any action with an `entityType` property as an `EntityAction`.\n\n</ngrx-docs-alert>\n\nThe _entity reducer's_ primary job is to\n\n- extract the `EntityCollection` for the action's entity type from the `state`.\n- create a new, [initialized entity collection](#initialize) if necessary.\n- get or create the `EntityCollectionReducer` for that entity type.\n- call the _entity collection reducer_ with the collection and the action.\n- replace the _entity collection_ in the `EntityCache` with the new collection returned by the _entity collection reducer_.\n\n## _EntityCollectionReducers_\n\nAn `EntityCollectionReducer` applies _actions_ to an `EntityCollection` in the `EntityCache` held in the _NgRx store_.\n\nThere is always a reducer for a given entity type.\nThe `EntityCollectionReducerFactory` maintains a registry of them.\nIf it can't find a reducer for the entity type, it [creates one](#collection-reducer-factory), with the help\nof the injected `EntityCollectionReducerFactory`, and registers that reducer\nso it can use it again next time.\n\n<a id=\"register\"></a>\n\n### Register custom reducers\n\nYou can create a custom reducer for an entity type and\nregister it directly with `EntityCollectionReducerRegistry.registerReducer()`.\n\nYou can register several custom reducers at the same time\nby calling `EntityCollectionReducerRegistry.registerReducers(reducerMap)` where\nthe `reducerMap` is a hash of reducers, keyed by _entity-type-name_.\n\n<a id=\"collection-reducer-factory\"></a>\n\n## Default _EntityCollectionReducer_\n\nThe `EntityCollectionReducerFactory` creates a default reducer that leverages\nthe capabilities of the NgRx `EntityAdapter`,\nguided by the app's [_entity metadata_](guide/data/entity-metadata).\n\nThe default reducer decides what to do based on the `EntityAction.op` property,whose string value it expects will be a member of the `EntityOp` enum.\n\nMany of the `EntityOp` values are ignored; the reducer simply returns the\n_entity collection_ as given.\n\nCertain persistence-oriented ops, for example,\nare meant to be handled by the NgRx Data [`persist$` effect](guide/data/entity-effects).\nThey don't update the collection data (other than, perhaps, to flip the `loading` flag).\n\nOthers add, update, and remove entities from the collection.\n\n<ngrx-docs-alert type=\"help\">\n\nRemember that _immutable objects_ are a core principle of the _redux/NgRx_ pattern.\nThese reducers don't actually change the original collection or any of the objects in it.\nThey make a copy of the collection and only update copies of the objects within the collection.\n\n</ngrx-docs-alert>\n\nSee the NgRx Entity [`EntityAdapter` collection methods](guide/entity/adapter#adapter-collection-methods) for a basic guide to the\ncache altering operations performed by the default _entity collection reducer_.\n\nThe `EntityCollectionReducerFactory` class and its tests are the authority on how the default reducer actually works.\n\n<a id='initialize'></a>\n\n## Initializing collection state\n\nThe `NgRxDataModule` adds an empty `EntityCache` to the NgRx Data store.\nThere are no collections in this cache.\n\nIf the master `EntityReducer` can't find a collection for the _action_'s entity type,\nit creates a new, initialized collection with the help of the `EntityCollectionCreator`, which was injected into the `EntityCacheReducerFactory`.\n\nThe _creator_ returns an initialized collection from the `initialState` in the entity's `EntityDefinition`.\nIf the entity type doesn't have a _definition_ or the definition doesn't have an `initialState` property value,\nthe creator returns an `EntityCollection`.\n\nThe _entity reducer_ then passes the new collection in the `state` argument of the _entity collection reducer_.\n\n<a id=\"customizing\"></a>\n\n## Customizing entity reducer behavior\n\nYou can _replace_ any entity collection reducer by [registering a custom alternative](#register).\n\nYou can _replace_ the default _entity reducer_ by\nproviding a custom alternative to the [`EntityCollectionReducerFactory`](#collection-reducer-factory).\n\nYou could even _replace_ the master _entity reducer_ by\nproviding a custom alternative to the [`EntityCacheReducerFactory`](#reducer-factory).\n\nBut quite often you'd like to extend a _collection reducer_ with some additional reducer logic that runs before or after.\n\n<a name=\"entity-cache-actions\"></a>\n\n## EntityCache-level actions\n\nA few actions target the entity cache as a whole.\n\n`SET_ENTITY_CACHE` replaces the entire cache with the object in the action payload,\neffectively re-initializing the entity cache to a known state.\n\n`MERGE_ENTITY_CACHE` replaces specific entity collections in the current entity cache\nwith those collections present in the action payload.\nIt leaves the other current collections alone.\n\n<ngrx-docs-alert type=\"help\">\n\nSee `entity-reducer.spec.ts` for examples of these actions.\n\n</ngrx-docs-alert>\n\nThese actions might be part of your plan to support offline scenarios or rollback changes to many collections at the same time.\n\nFor example, you could subscribe to the `EntityServices.entityCache$` selector.\nWhen the cache changes, you could\nserialize the cache to browser local storage.\nYou might want to _debounce_ for a few seconds to reduce churn.\n\nLater, when relaunching the application, you could dispatch the `SET_ENTITY_CACHE` action to initialize the entity-cache even while disconnected.\nOr you could dispatch the `MERGE_ENTITY_CACHE` to rollback selected collections to a known state as\nin error-recovery or \"what-if\" scenarios.\n\n<ngrx-docs-alert type=\"error\">\n\n**Important**: `MERGE_ENTITY_CACHE` _replaces_ the currently cached collections with the entity collections in its payload.\nIt does not _merge_ the payload collection entities into the existing collections as the name might imply.\nMay reconsider and do that in the future.\n\n</ngrx-docs-alert>\n\nIf you want to create and reduce additional, cache-wide actions,\nconsider the _EntityCache MetaReducer_, described in the next section.\n\n## _MetaReducers_\n\nThe `NgRx/store` supports [**MetaReducers**](guide/store/metareducers) that can inspect and process actions flowing through the store and potentially change state in the store.\n\nA _MetaReducer_ is a function that takes a reducer and returns a reducer.\nNgRx composes these reducers with other reducers in a chain of responsibility.\n\nNgRx calls the reducer returned by a MetaReducer just as it does any reducer.\nIt calls it with a _state_ object and an _action_.\n\nThe MetaReducer can do what it wants with the state and action.\nIt can log the action, handle the action on its own, delegate to the incoming reducer, post-process the updated state, or all of the above.\n\n<ngrx-docs-alert type=\"help\">\n\nRemember that the actions themselves are immutable. Do not change the action!\n\n</ngrx-docs-alert>\n\nLike every reducer, the state passed to a MetaReducer's reducer is only\nthe section of the store that is within the reducer's scope.\n\nNgRx Data supports two levels of MetaReducer\n\n1.  _EntityCache MetaReducer_, scoped to the entire entity cache\n1.  _EntityCollection MetaReducer_, scoped to a particular collection.\n\n<a id='cache-meta-reducers'></a>\n\n### Entity Cache _MetaReducers_\n\nThe **EntityCache MetaReducer** helps you inspect and apply actions that affect the _entire entity cache_.\nYou might add custom actions and an _EntityCache MetaReducer_ to update several collections at the\nsame time.\n\nAn _EntityCache MetaReducer_ reducer must satisfy three requirements:\n\n1.  always returns the entire entity cache.\n1.  return synchronously (no waiting for server responses).\n1.  never mutate the original action; clone it to change it.\n\n<ngrx-docs-alert type=\"help\">\n\nWe intend to explain how in a documentation update.\nFor now, see the NgRx Data `entity-data.module.spec.ts` for examples.\n\n</ngrx-docs-alert>\n\n### Entity Collection _MetaReducers_\n\nAn **entity collection _MetaReducer_** takes an _entity collection reducer_ as its reducer argument and\nreturns a new _entity collection reducer_.\n\nThe new reducer receives the `EntityCollection` and `EntityAction` arguments that would have gone to the original reducer.\n\nIt can do what it wants with those arguments, such as:\n\n- log the action,\n- transform the action into a different action (for the same entity collection),\n- call the original reducer,\n- post-process the results from original reducer.\n\nThe new entity collection reducer must satisfy three requirements:\n\n1.  always returns an `EntityCollection` for the same entity.\n1.  return synchronously (no waiting for server responses).\n1.  never mutate the original action; clone it to change it.\n\n#### Compared to Store MetaReducers\n\nWhile the _entity collection MetaReducer_ is modeled on the NgRx Store `MetaReducer` (\"_Store MetaReducer_\"), it is crucially different in several respects.\n\nThe _Store MetaReducer_ broadly targets _store reducers_.\nIt wraps _store reducers_, sees _all actions_, and can update _any state within its scope_.\n\nBut a _Store MetaReducer_ neither see nor wrap an _entity collection reducer_.\nThese _entity collection reducers_ are internal to the _EntityCache Reducer_ that is registered with the NgRx Data feature.\n\nAn _entity collection MetaReducer_ is narrowly focused on manipulation of a single, target _entity collection_.\nIt wraps _all entity collection reducers_.\n\nNote that it can't access other collections, the _entity cache_, or any other state in the store.\nIf you need a cross-collection _MetaReducer_, try the [EntityCache MetaReducer](#cache-meta-reducers)\ndescribed above.\n\n#### Provide Entity _MetaReducers_ to the factory\n\nCreate one or more _entity collection MetaReducers_ and\nadd them to an array.\n\nProvide this array with the `ENTITY_COLLECTION_META_REDUCERS` injection token\nwhere you import the `NgRxDataModule`.\n\nThe `EntityCollectionReducerRegistry` injects it and composes the\narray of _MetaReducers_ into a single _meta-MetaReducer_.\nThe earlier _MetaReducers_ wrap the later ones in the array.\n\nWhen the factory register an `EntityCollectionReducer`, including the reducers it creates,\nit wraps that reducer in the _meta-MetaReducer_ before\nadding it to its registry.\n\nAll `EntityActions` dispatched to the store pass through this wrapper on their way in and out of the entity-specific reducers.\n\n<ngrx-docs-alert type=\"help\">\n\nWe intend to explain how to create and provide _entity collection MetaReducers_ in a documentation update.\nFor now, see the `entity-reducer.spec.ts` for examples.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/entity-services.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# EntityServices\n\n`EntityServices` is a facade over the NgRx Data services and the NgRx Data `EntityCache`.\n\n## Registry of _EntityCollectionServices_\n\nIt is primarily a registry of [EntityCollectionServices](guide/data/entity-collection-service).\n\nCall its `EntityServices.getEntityCollectionService(entityName)` method to get the singleton\n`EntityCollectionService` for that entity type.\n\nHere's a component doing that.\n\n<ngrx-code-example header=\"heroes-component.ts\">\n\n```ts\nimport { EntityCollectionService, EntityServices } from '@ngrx/data';\nimport { Hero } from '../../model';\n\n@Component({...})\nexport class HeroesComponent implements OnInit {\n  heroesService: EntityCollectionService<Hero>;\n\n  constructor(entityServices: EntityServices) {\n    this.heroesService = entityServices.getEntityCollectionService('Hero');\n  }\n}\n```\n\n</ngrx-code-example>\n\nIf the `EntityCollectionService` service does not yet exist,\n`EntityServices` creates a default instance of that service and registers\nthat instance for future reference.\n\n## Create a custom _EntityCollectionService_\n\nYou'll often create custom `EntityCollectionService` classes with additional capabilities and convenience members,\nas explained in the [EntityCollectionService](guide/data/entity-collection-service) doc.\n\nHere's an example.\n\n<ngrx-code-example header=\"heroes.service.ts\">\n\n```ts\nimport { Injectable } from '@angular/core';\nimport {\n  EntityCollectionServiceBase,\n  EntityCollectionServiceElementsFactory,\n} from '@ngrx/data';\n\nimport { Hero } from '../model';\n\n@Injectable()\nexport class HeroesService extends EntityCollectionServiceBase<Hero> {\n  constructor(\n    elementsFactory: EntityCollectionServiceElementsFactory\n  ) {\n    super('Hero', elementsFactory);\n  }\n\n  // ... your special sauce here\n}\n```\n\n</ngrx-code-example>\n\nOf course you must provide the custom service before you use it, typically in an Angular `NgModule`.\n\n<ngrx-code-example header=\"heroes.module.ts\">\n\n```ts\n...\nimport { HeroesService } from './heroes.service';\n\n@NgModule({\n  imports: [...],\n  declarations: [...],\n  providers: [HeroesService]\n})\nexport class HeroesModule {}\n```\n\n</ngrx-code-example>\n\nThe following alternative example uses the **preferred \"tree-shakable\" `Injectable()`**\nto provide the service in the root module.\n\n```javascript\n@Injectable({ providedIn: 'root' })\nexport class HeroesService extends EntityCollectionServiceBase<Hero> {\n  ...\n}\n```\n\nYou can inject that custom service directly into the component.\n\n<ngrx-code-example header=\"heroes.component.ts (v2)\">\n\n```ts\n@Component({...})\nexport class HeroesComponent {\n  heroes$: Observable<Hero[]>;\n  loading$: Observable<boolean>;\n\n  constructor(public heroesService: HeroesService) {\n    this.heroes$ = this.heroesService.entities$;\n    this.loading$ = this.heroesService.loading$;\n  }\n  ...\n}\n```\n\n</ngrx-code-example>\n\nNothing new so far.\nBut we want to be able to get the `HeroesService` from `EntityServices.getEntityCollectionService()`\njust as we get the default collection services.\n\nThis consistency will pay off when the app has a lot of collection services\n\n## Register the custom _EntityCollectionService_\n\nWhen you register an instance of a custom `EntityCollectionService` with `EntityServices`, other callers of\n`EntityServices.getEntityCollectionService()` get that custom service instance.\n\nYou'll want to do that before anything tries to acquire it via the `EntityServices`.\n\nOne solution is to inject custom collection services in the constructor of the module that provides them,\nand register them there.\n\nThe following example demonstrates.\n\n<ngrx-code-example header=\"app.module.ts\">\n\n```ts\n@NgModule({ ... })\nexport class AppModule {\n  // Inject the service to ensure it registers with EntityServices\n  constructor(\n    entityServices: EntityServices,\n    // custom collection services\n    hs: HeroesService,\n    vs: VillainsService\n    ){\n    entityServices.registerEntityCollectionServices([hs, vs]);\n  }\n}\n```\n\n</ngrx-code-example>\n\n## Sub-class _EntityServices_ for application class convenience\n\nAnother useful solution is to create a sub-class of `EntityServices`\nthat both injects the custom collection services\nand adds convenience members for your application.\n\nThe following `AppEntityServices` demonstrates.\n\n<ngrx-code-example header=\"app-entity-services.ts\">\n\n```ts\nimport { Injectable } from '@angular/core';\nimport {\n  EntityServicesBase,\n  EntityServicesElements,\n} from '@ngrx/data';\n\nimport { SideKick } from '../../model';\nimport { HeroService, VillainService } from '../../services';\n\n@Injectable()\nexport class AppEntityServices extends EntityServicesBase {\n  constructor(\n    elements: EntityServicesElements,\n\n    // Inject custom services, register them with the EntityServices, and expose in API.\n    readonly heroesService: HeroesService,\n    readonly villainsService: VillainsService\n  ) {\n    super(elements);\n    this.registerEntityCollectionServices([\n      heroesService,\n      villainsService,\n    ]);\n  }\n\n  /** get the (default) SideKicks service */\n  get sideKicksService() {\n    return this.getEntityCollectionService<SideKick>('SideKick');\n  }\n}\n```\n\n</ngrx-code-example>\n\n`AppEntityService` first injects the `EntityServicesElements` helper which it passes straight through to the base class constructor.\nThe \"elements\" enclose the ingredients that the base class needs to make and manage the entities you described in metadata.\n\nThen it injects your two custom collection services, `HeroesService` and `VillainsService`,\nand exposes them directly to consumers as convenience properties for accessing those services.\n\nIn this example, we don't need a custom collection service for the `SideKick` entity.\nThe default service will do.\n\nNonetheless, we add a `sideKicksService` property that gets or creates a default service for `SideKick`.\nConsumers will find this more discoverable and easier to call than `getEntityCollectionService()`.\n\nOf course the base class `EntityServices` members, such as `getEntityCollectionService()`, `entityCache$`,\nand `registerEntityCollectionService()` are all available.\n\nNext, provide `AppEntityServices` in an Angular `NgModule` both as itself (`AppEntityServices`)\nand as an alias for `EntityServices`.\n\nIn this manner, an application class references this same `AppEntityServices` service instance,\nwhether it injects `AppEntityServices` or `EntityServices`.\n\nSee it here in the sample app.\n\n<ngrx-code-example header=\"store/entity/entity-module\">\n\n```ts\n@NgModule({\n  imports: [ ... ],\n  providers: [\n    AppEntityServices,\n    { provide: EntityServices, useExisting: AppEntityServices },\n    ...\n  ]\n})\nexport class EntityStoreModule { ... }\n```\n\n</ngrx-code-example>\n\n## Access multiple _EntityCollectionServices_\n\nA complex component may need access to multiple entity collections.\nThe `EntityServices` registry makes this easy,\neven when the `EntityCollectionServices` are customized for each entity type.\n\nYou'll only need **a single injected constructor parameter**, the `EntityServices`.\n\n<ngrx-code-example header=\"character-container.component.ts\">\n\n```ts\nimport { EntityCollectionService, EntityServices } from '@ngrx/data';\nimport { SideKick } from '../../model';\nimport { HeroService, VillainService } from '../../services';\n\n@Component({...})\nexport class CharacterContainerComponent implements OnInit {\n  heroesService: HeroService;\n  sideKicksService: EntityCollectionService<SideKick>;\n  villainService: VillainService;\n\n  heroes$: Observable<Hero>;\n  ...\n  constructor(entityServices: EntityServices) {\n    this.heroesService = entityServices.getEntityCollectionService('Hero');\n    this.sidekicksService = entityServices.getEntityCollectionService('SideKick');\n    this.villainService = entityServices.getEntityCollectionService('Villain');\n\n    this.heroes$ = this.heroesService.entities$;\n    ...\n  }\n  ...\n}\n```\n\n</ngrx-code-example>\n\nAn application-specific sub-class of `EntityServices`, such as the `AppEntityServices` above,\nmakes this a little nicer.\n\n<ngrx-code-example header=\"character-container.component.ts (with AppEntityServices)\">\n\n```ts\nimport { AppEntityServices } from '../../services';\n\n@Component({...})\nexport class CharacterContainerComponent implements OnInit {\n\n  heroes$: Observable<Hero>;\n  ...\n  constructor(private appEntityServices: AppEntityServices) {\n    this.heroes$ = appEntityServices.heroesService.entities$;\n    ...\n  }\n  ...\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/extension-points.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Extension Points\n\n**Work in progress**\n\nThe `NgRx Data` library strives for the \"_it just works_\" experience.\nBut customizations are an inevitable necessity.\n\nThe `NgRx Data` library invites you to customize its behavior at many points,\nmost of them listed here.\n\n## Take control of an entity type\n\nOne day you decide that a particular entity type needs special treatment.\nYou want to take over some or all of the management of that type.\n\nYou can do that easily without abandoning NgRx Data for the rest of your entity model.\n\nYou can take it over completely simply by removing it from the entity metadata.\nCreate your own collection and add it to the store's state-tree as you would in vanilla NgRx. Create your own actions, reducers, selectors and effects.\nAs long as your actions don't have an `entityName` or `entityOp` property,\nNgRx Data will ignore them.\n\nOr you can keep the entity type in the NgRx Data system and take over the behaviors that matter to you.\n\n- Create supplemental actions for that type. Give them custom `entityOp` names that suit your purpose.\n\n- Register an alternative `EntityCollectionReducer` for that type with the `EntityCollectionReducerFactory`. Your custom reducer can respond to your custom actions and implement the standard operations in its own way.\n\n- Create your own service facade, an alternative to `EntityCollectionService`, that dispatches the actions you care about\n  and exposes the selectors that your type needs.\n\n- Add additional properties to the collection state with the `EntityMetadata.additionalCollectionState` property. Manage these properties with custom reducer actions and selectors.\n\n- By-pass the `EntityEffects` completely by never dispatching an action with an `entityOp` that it intercepts.\n  Create a custom _NgRx/effect_ that handles your custom persistence actions.\n\n## Provide alternative service implementations\n\nThe `NgRx Data` library consists of many services that perform small tasks.\n\nLook at the many providers in `NgRx Data.module.ts`.\nProvide your own version of any `NgRx Data` service, as long as it conforms to the service API and implements the expected behavior.\n\nBe sure to test your alternatives.\n\n## Custom _EntityCollectionService_\n\n## Extend the _EntityCollection_\n\n## Custom _EntityActions_\n\n### Rename the generated entity action _type_\n\nThe `EntityActionFactory.create()` method relies on the `formatActionType()` method to\nproduce the `Action.type` string.\n\nThe default implementation concatenates the entity type name with the `EntityOp`.\nFor example, querying all heroes results in the entity type, `[Hero] NgRx Data/query-all`.\n\nIf you don't like that approach you can replace the `formatActionType()` method with a generator that produces action type names that are more to your liking.\nThe NgRx Data library doesn't make decisions based on the `Action.type`.\n\n## Custom _EntityDispatcher_\n\n### Change the default save strategy\n\nThe dispatcher's `add()`, `update()`, `delete()` methods dispatch\n_optimistic_ or _pessimistic_ save actions based on settings in the `EntityDispatcherOptions`.\n\nThese options come from the `EntityDispatcherFactory` that creates the dispatcher.\nThis factory gets the options from the entity's metadata.\nBut where the metadata lack options, the factory relies on its `defaultDispatcherOptions`.\n\nYou can set these default options directly by injecting the `EntityDispatcherFactory`\nand re-setting `defaultDispatcherOptions` _before_ creating dispatchers\n(or creating an `EntityCollectionService` which creates a dispatcher).\n\n## Custom _effects_\n\nThe NgRx Data library has one NgRx `Effect`, the `EntityEffects` class.\n\nThis class detects entity persistence actions, performs the persistence operation with a\ncall to an `EntityDataService` and channels the HTTP response through a\n`PersistenceResultHandler` which produces a persistence results observable that\ngoes back to the NgRx store.\n\nThe `EntityEffects` class intercepts actions that have an `entityOp` property whose\nvalue is one of the `persistOps`. Other actions are ignored by this effect.\n\nIt tries to process any action with such an `entityOp` property by looking for a\n\n### Choose data service for the type\n\nThe [_Entity DataService_](guide/data/entity-dataservice) describes the\ndefault service, how to provide a data service for a specific entity type\nor replace the default service entirely.\n\n### Replace the generic-type effects\n\n### Handle effect for a specific type\n\n### Replace handling of the results of a data service call\n\n### Replace the EntityEffects entirely\n\n## Custom _Reducers_\n\nThe [_Entity Reducer_ guide](guide/data/entity-reducer#customizing) explains how to\ncustomize entity reducers.\n\n## Custom _Selectors_\n\n### Introduction\n\n`@ngrx/data` has several built-in selectors that are defined in the [EntitySelectors](api/data/EntitySelectors) interface. These can be used outside of a component.\n\nMany apps use `@ngrx/data` in conjunction with @ngrx/store including manually written reducers, actions, and so on. `@ngrx/data` selectors can be used to combine @ngrx/data state with the state of the entire application.\n\n### Using EntitySelectorsFactory\n\n[EntitySelectorsFactory](api/data/EntitySelectorsFactory) exposes a `create` method that can be used to create selectors outside the context of a component, such as in a `reducers/index.ts` file.\n\n#### Example\n\n<ngrx-code-example>\n\n```ts\n/* src/app/reducers/index.ts */\nimport * as fromCat from './cat.reducer';\nimport { Owner } from '~/app/models';\n\nexport const ownerSelectors =\n  new EntitySelectorsFactory().create<Owner>('Owner');\n\nexport interface State {\n  cat: fromCat.State;\n}\n\nexport const reducers: ActionReducerMap<State> = {\n  cat: fromCat.reducer,\n};\n\nexport const selectCatState = (state: State) => state.cat;\n\nexport const { selectAll: selectAllCats } =\n  fromCat.adapter.getSelectors(selectCatState);\n\nexport const selectedCatsWithOwners = createSelector(\n  selectAllCats,\n  ownerSelectors.selectEntityMap,\n  (cats, ownerEntityMap) =>\n    cats.map((c) => ({\n      ...c,\n      owner: ownerEntityMap[c.owner],\n    }))\n);\n```\n\n</ngrx-code-example>\n\n## Custom data service\n\n### Replace the generic-type data service\n\n### Replace the data service for a specific type\n\n## Custom HTTP resource URLs\n\n### Add plurals\n\n### Replace the Pluralizer\n\n### Replace the HttpUrlGenerator\n\nThis example replaces the `DefaultHttpUrlGenerator` with a customized `HttpUrlGenerator` that pluralizes both collection resource and entity resource URLs.\n\nThe implementation simply overrides `DefaultHttpUrlGenerator.getResourceUrls(string, string)`:\n\n```ts\nimport { Injectable } from '@angular/core';\nimport {\n  DefaultHttpUrlGenerator,\n  HttpResourceUrls,\n  normalizeRoot,\n  Pluralizer,\n} from '@ngrx/data';\n\n@Injectable()\nexport class PluralHttpUrlGenerator extends DefaultHttpUrlGenerator {\n  constructor(private myPluralizer: Pluralizer) {\n    super(myPluralizer);\n  }\n\n  protected getResourceUrls(\n    entityName: string,\n    root: string\n  ): HttpResourceUrls {\n    let resourceUrls = this.knownHttpResourceUrls[entityName];\n    if (!resourceUrls) {\n      const nRoot = normalizeRoot(root);\n      const url = `${nRoot}/${this.myPluralizer.pluralize(\n        entityName\n      )}/`.toLowerCase();\n      resourceUrls = {\n        entityResourceUrl: url,\n        collectionResourceUrl: url,\n      };\n      this.registerHttpResourceUrls({ [entityName]: resourceUrls });\n    }\n    return resourceUrls;\n  }\n}\n```\n\nOverride the `HttpUrlGenerator` provider in the root `AppModule` where `EntityDataModule.forRoot()` is imported:\n\n```ts\n@NgModule({\n  // ...\n  imports: [\n    // ...\n    EntityDataModule.forRoot({}),\n  ],\n  providers: [\n    // ...\n    { provide: HttpUrlGenerator, useClass: PluralHttpUrlGenerator },\n  ],\n})\nexport class AppModule {}\n```\n\nTo unit test the custom HTTP URL generator:\n\n<ngrx-code-example>\n\n```ts\nimport { PluralHttpUrlGenerator } from './plural-http-url-generator';\nimport { DefaultPluralizer } from '@ngrx/data';\n\ndescribe('PluralHttpUrlGenerator', () => {\n  let generator: PluralHttpUrlGenerator;\n\n  beforeEach(() => {\n    generator = new PluralHttpUrlGenerator(new DefaultPluralizer([]));\n  });\n\n  it('should be created', () => {\n    expect(generator).toBeTruthy();\n  });\n\n  it('should pluralize entity resource URLs', () => {\n    let url = generator.entityResource('bar', 'https://foo.com/api');\n    expect(url).toBe('https://foo.com/api/bars/');\n  });\n\n  it('should pluralize collection resource URLs', () => {\n    const url = generator.collectionResource(\n      'bar',\n      'https://foo.com/api'\n    );\n    expect(url).toBe('https://foo.com/api/bars/');\n  });\n\n  it('should cache results (needed for 100% branch coverage)', () => {\n    const url = generator.entityResource(\n      'bar',\n      'https://foo.com/api'\n    );\n    const cachedUrl = generator.entityResource(\n      'bar',\n      'https://foo.com/api'\n    );\n    expect(cachedUrl).toBe(url);\n  });\n});\n```\n\n</ngrx-code-example>\n\n## Serialization with back-end\n\nThe shape of the JSON data going over the wire to-and-from the server often\ndoesn't match the shape of the entity model(s) in the client application.\nYou may need _serialization/deserialization_ transformation functions\nto map between the client entity data and the formats expected by the web APIs.\n\nThere are no facilities for this within `NgRx Data` itself although\nthat is a [limitation](guide/data/limitations#serialization) we might address in a future version.\n\nOne option in the interim is to write such serialization functions and\ninject them into the `HttpClient` pipeline with [`HttpClient` interceptors](https://angular.dev/guide/http/interceptors).\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/faq.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# NgRx Data FAQs\n\n<a id=\"no-boilerplate-claim\"></a>\n\n## You said I'd never write an action. But what if ...\n\nHold on. We said \"you _may never_ write an action, reducer, selector, or effect.\"\n\nThat doesn’t mean you _won’t ever_.\nIn fact, a critical feature of NgRx Data is that you can add your own properties to collections, additional actions, reducer cases, selectors, etc.\n\nYou aren't locked in to the way NgRx Data does things.\nYou can customize almost anything, both at the single entity-type level and for all entity types.\n\nBut you ONLY do so when you want to do something unusual … and that, by definition, is not boilerplate.\n\n<a id=\"entity\"></a>\n\n## What is an _entity_?\n\nAn **_entity_** is an object with _long-lived data values_ that you read from and write to a database.\n\n<ngrx-docs-alert type=\"help\">\n\nOperations that access the database are called **_persistence_** operations.\n\n</ngrx-docs-alert>\n\nAn _entity_ refers to some \"thing\" in the application domain, such as a customer.\nSuch things are unique even as their values change. Accordingly each entity has a unique **_primary key_**, also called its **_identifier_**.\n\nEach _entity_ object is an instance of an **_entity type_**. That type could be represented explicitly as a class or an interface. Or it could just be a bag of data.\n\nTo manage entities with NgRx Data, you describe each entity type with [**_entity metadata_**](guide/data/entity-metadata).\n\nThe application's **_entity model_** is the set of all entity types in your application that are described with _entity metadata_.\n\nIn some definitions, the _entity type_ and _entity model_ describe both the data and the _logic_ that govern that data such as data integrity rules (e.g., validations) and behaviors (e.g., calculations). The _current version_ of NgRx Data library is unaware of entity logic beyond what is strictly necessary to persist entity data values.\n\n<a id=\"no-panacea\"></a>\n\n## Is NgRx Data the answer for everything?\n\n**_No!_**\nThe NgRx Data library is narrowly focused on a single objective:\nto simplify management of [_entity data_](#entity).\n\nEntity data are a particularly opportune target for simplification\nbecause they appear in high volume in many applications and\nthe sheer number of _similar-but-different_ NgRx code artifacts necessary to manage that data is daunting.\n\nAnything we can do to shrink entity management code and to leverage the commonalities across entity types will repay the effort handsomely.\n\nBut _entity data_ is only _one_ kind of application data.\n\nConfiguration data, user roles, the user's prior selections, the current state of screen layouts ...\nthese are all important and highly idiosyncratic data that can benefit from\ncustom coding with standard _NgRx_ techniques.\n\nData streamed from remote sources such as trading systems,\nmobile asset tracking systems, and IoT devices are not entity data\nand may not be a good fit for the NgRx Data library.\nThey are still worth managing with _NgRx_.\n\nIt bears repeating: the NgRx Data library is good for\nquerying, caching, and saving _entity data_ ... and that's it.\n\n<a id=\"ngrx\"></a>\n\n## How does NgRx Data relate to other NgRx libraries?\n\nNgRx is a collection of libraries for writing Angular applications in a \"reactive style\" that combines the\n**[redux pattern](#redux)** and tools with [RxJS Observables](#rxjs).\n\n`NgRx Data` builds upon three _NgRx_ libraries:\n[Store](guide/store),\n[Effects](guide/effects), and\n[Entity](guide/entity).\n\n<a id=\"ngrx-entity\"></a>\n\n## How is NgRx _Data_ different from NgRx _Entity_?\n\n**The NgRx Data library _extends_ [Entity](guide/entity)**.\n\nThe _Entity_ library provides the\ncore representation of a single _entity collection_ within an NgRx _Store_.\nIts `EntityAdapter` defines common operations for querying and updating individual cached entity collections.\n\nThe NgRx Data library leverages these capabilities while offering higher-level features including:\n\n- metadata-driven entity model.\n\n- actions, reducers, and selectors for all entity types in the model.\n\n- asynchronous fetch and save HTTP operations as NgRx _Effects_.\n\n- a reactive `EntityCollectionService` with a simple API that\n  encapsulates _NgRx_ interaction details.\n\nNothing is hidden from you.\nThe store, the actions, the adapter, and the entity collections remain visible and directly accessible.\n\nThe fixes and enhancements in future NgRx _Entity_ versions flow through NgRx Data to your application.\n\n<a id=\"redux\"></a>\n\n## What is _redux_?\n\n[Redux](https://redux.js.org/) is an implementation of a pattern for managing application [state](#state) in a web client application.\n\nIt is notable for:\n\n- Holding all _shared state_ as objects in a single, central _store_.\n\n- All objects in the store are [_immutable_](https://en.wikipedia.org/wiki/Immutable_object).\n  You never directly set any property of any object held in a redux store.\n\n- You update the store by _dispatching actions_ to the store.\n\n- An _action_ is like a message. It always has a _type_. It often has a _payload_ which is the data for that message.\n\n- Action instances are immutable.\n\n- Action instances are serializable (because the redux dev tools demand it and we should be able to persist them to local browser storage between user sessions).\n\n- All store values are immutable and serializable.\n\n- _actions_ sent to the store are processed by _reducers_. A reducer may update the store by replacing old objects in the store with new objects that have the updated state.\n\n- All _reducers_ are “pure” functions.\n  They have no side-effects.\n\n- The store publishes an _event_ when updated by a reducer.\n\n- Your application listens for store _events_; when it hears an event of interest, the app pulls the corresponding object(s) from the store.\n\n_NgRx_ is similar in almost all important respects.\nIt differs most significantly in replacing _events_ with _observables_.\n\n_NgRx_ relies on\n[RxJS Observables](#rxjs) to listen for store events, select those that matter, and push the selected object(s) to your application.\n\n<a id=\"state\"></a>\n\n## What is _state_?\n\n_State_ is data.\nApplications have several kinds of state including:\n\n- _application_ state is _session_ data that determine how your application works. Filter values and router configurations are examples of _application_ state.\n\n- _persistent_ state is \"permanent\" data that you store in a remote database. [Entities](#entity) are a prime example of _persistent_ state.\n\n- _shared_ state is data that are shared among application components and services.\n\nIn _NgRx_, as in the redux pattern, all stored state is (or should be) _immutable_.\nYou never change the properties of objects in the store.\nYou replace them with new objects, created through a merge of the previous property values and new property values.\n\nArrays are completely replaced when you add, remove, or replace any of their items.\n\n<a id=\"rxjs\"></a>\n\n## What are _RxJS Observables_\n\n[RxJS Observables](https://rxjs-dev.firebaseapp.com/) is a library for programming in a \"reactive style\".\n\nMany Angular APIs produce _RxJS Observables_ so programming \"reactively\" with _Observables_ is familiar to many Angular developers. Search the web for many helpful resources on _RxJS_.\n\n<a id=\"code-generation\"></a>\n\n## What's wrong with code generation?\n\nSome folks try to conquer the \"too much boilerplate\" problem by generating the code.\n\nAdding the `Foo` entity type? Run a code generator to produce _actions_, _action-creators_, _reducers_, _effects_, _dispatchers_, and _selectors_ for `Foo`.\nRun another one to produce the service that makes HTTP GET, PUT, POST, and DELETE calls for `Foo`.\n\nMaybe it generates canned tests for them too.\n\nNow you have ten (or more) new files for `Foo`. Multiply that by a 100 entity model and you have 1000 files. Cool!\n\nExcept you're responsible for everyone of those files. Overtime you're bound to modify some of them to satisfy some peculiarity of the type.\n\nThen there is a bug fix or a new feature or a new way to generate some of these files. It's your job to upgrade them. Which ones did you change? Why?\n\nGood luck!\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/index.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# @ngrx/data\n\nNgRx Data is an extension that offers a gentle introduction to NgRx by simplifying management of **entity data** while reducing the amount of explicitness.\n\n## Introduction\n\nMany applications have substantial _domain models_ with 10s or 100s of entity types.\n\nSuch applications typically create, retrieve, update, and delete entity data that are \"persisted\" in a database of some sort, hosted on a remote server.\n\nDevelopers who build these apps with the NgRx [Store](guide/store), [Effects](guide/effects), and [Entity](guide/entity) libraries alone tend to write a large number of _actions_, _action-creators_, _reducers_, _effects_, _dispatchers_, and _selectors_ as well as the HTTP GET, PUT, POST, and DELETE methods _for each entity type_.\nThere will be a lot of repetitive code to create, maintain, and test.\nThe more entities in your model, the bigger the challenge.\n\nWith NgRx Data you can develop large entity models quickly with very little code\nand without knowing much NgRx at all.\nYet all of NgRx remains accessible to you, when and if you want it.\n\nNgRx Data is an abstraction over the Store, Effects, and Entity that radically reduces\nthe amount of code you'll write.\nAs with any abstraction, while you gain simplicity,\nyou lose the explicitness of direct interaction with the supporting NgRx libraries.\n\n## Key Concepts\n\n#### NgRx Data\n\n- automates the creation of actions, reducers, effects, dispatchers, and selectors for each entity type.\n- provides default HTTP GET, PUT, POST, and DELETE methods for each entity type.\n- holds entity data as collections within a cache which is a slice of NgRx store state.\n- supports optimistic and pessimistic save strategies\n- enables transactional save of multiple entities of multiple types in the same request.\n- makes reasonable default implementation choices\n- offers numerous extension points for changing or augmenting those default behaviors.\n\nNgRx Data targets management of _persisted entity data_, like _Customers_ and _Orders_, that many apps query and save to remote storage. That's its sweet spot.\n\nIt is ill-suited to _non-entity_ data.\nValue types, enumerations, session data and highly idiosyncratic data are better managed with standard NgRx.\nReal-world apps will benefit from a combination of NgRx techniques, all sharing a common store.\n\n#### Entity\n\nAn **entity** is an object with long-lived data values that you read from and write to a database. An entity refers to some \"thing\" in the application domain. Examples include a _Customer_, _Order_, _LineItem_, _Product_, _Person_ and _User_.\n\nAn **entity** is a specific kind of data, an object defined by its _thread of continuity and identity_.\n\nWe experience its \"continuity\" by storing and retrieving (\"persisting\") entity objects in a permanent store on a server, a store such as a database. Whether we retrieve the \"Sally\" entity today or tomorrow or next week, we \"mean\" that we're getting the same conceptual \"Sally\" no matter how her data attributes have changed.\n\nIn NgRx Data we maintain the entity object's identity by means of its **primary key**. Every entity in NgRx Data must have a _primary key_. The primary key is usually a single attribute of the object. For example, that \"Sally\" entity object might be an instance of the \"Customer\" entity type, an instance whose permanent, unchanging primary key is the `id` property with a value of `42`.\n\nThe primary key doesn't have to be a single attribute. It can consist of multiple attributes of the object if you need that feature. What matters is that the primary key _uniquely_ identifies that object within a permanent collection of entities of the same type. There can be exactly one `Customer` entity with `id: 42` and that entity is \"Sally\".\n\n### Entity Collection\n\nThe notion of an _Entity Collection_ is also fundamental to NgRx Data. All entities belong to a collection of the same entity type. A `Customer` entity belongs to a `Customers` collection.\n\nEven if you have only one instance of an entity type, it must be held within an entity collection: perhaps a collection with a single element.\n\n## Defining the entities\n\nA `EntityMetadataMap` tells NgRx Data about your entities. Add a property to the set for each entity name.\n\n<ngrx-code-example header=\"entity-metadata.ts\">\n\n```ts\nimport { EntityMetadataMap } from '@ngrx/data';\n\nconst entityMetadata: EntityMetadataMap = {\n  Hero: {},\n  Villain: {},\n};\n\n// because the plural of \"hero\" is not \"heros\"\nconst pluralNames = { Hero: 'Heroes' };\n\nexport const entityConfig = {\n  entityMetadata,\n  pluralNames,\n};\n```\n\n</ngrx-code-example>\n\nExport the entity configuration to be used when registering it in your `AppModule`.\n\n## Registering the entity store\n\nOnce the entity configuration is created, you need to put it into the root store for NgRx. This is done by importing the `entityConfig` and then passing it to the `EntityDataModule.forRoot()` function.\n\n<ngrx-code-example header=\"app.module.ts\">\n\n```ts\nimport { NgModule } from '@angular/core';\nimport { HttpClientModule } from '@angular/common/http';\nimport { EffectsModule } from '@ngrx/effects';\nimport { StoreModule } from '@ngrx/store';\nimport {\n  DefaultDataServiceConfig,\n  EntityDataModule,\n} from '@ngrx/data';\nimport { entityConfig } from './entity-metadata';\n\n@NgModule({\n  imports: [\n    HttpClientModule,\n    StoreModule.forRoot({}),\n    EffectsModule.forRoot([]),\n    EntityDataModule.forRoot(entityConfig),\n  ],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n\n### Using the Standalone API\n\nRegistering the root entity data can also be done using the standalone APIs if you are bootstrapping an Angular application using standalone features.\n\n<ngrx-code-example header=\"main.ts\">\n\n```ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideHttpClient } from '@angular/common/http';\nimport { provideStore } from '@ngrx/store';\nimport { provideEffects } from '@ngrx/effects';\nimport { provideEntityData, withEffects } from '@ngrx/data';\n\nimport { AppComponent } from './app.component';\nimport { entityConfig } from './entity-metadata';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideHttpClient(),\n    provideStore(),\n    provideEffects(),\n    provideEntityData(entityConfig, withEffects()),\n  ],\n});\n```\n\n</ngrx-code-example>\n\n## Creating entity data services\n\nNgRx Data handles creating, retrieving, updating, and deleting data on your server by extending `EntityCollectionServiceBase` in your service class.\n\n<ngrx-code-example header=\"hero.service.ts\">\n\n```ts\nimport { Injectable } from '@angular/core';\nimport {\n  EntityCollectionServiceBase,\n  EntityCollectionServiceElementsFactory,\n} from '@ngrx/data';\nimport { Hero } from '../core';\n\n@Injectable({ providedIn: 'root' })\nexport class HeroService extends EntityCollectionServiceBase<Hero> {\n  constructor(\n    serviceElementsFactory: EntityCollectionServiceElementsFactory\n  ) {\n    super('Hero', serviceElementsFactory);\n  }\n}\n```\n\n</ngrx-code-example>\n\n## Using NgRx Data in components\n\nTo access the entity data, components should inject entity data services.\n\n<ngrx-code-example header=\"heroes.component.ts\">\n\n```ts\nimport { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Hero } from '../../core';\nimport { HeroService } from '../hero.service';\n\n@Component({\n  selector: 'app-heroes',\n  templateUrl: './heroes.component.html',\n  styleUrls: ['./heroes.component.scss'],\n})\nexport class HeroesComponent implements OnInit {\n  loading$: Observable<boolean>;\n  heroes$: Observable<Hero[]>;\n\n  constructor(private heroService: HeroService) {\n    this.heroes$ = heroService.entities$;\n    this.loading$ = heroService.loading$;\n  }\n\n  ngOnInit() {\n    this.getHeroes();\n  }\n\n  add(hero: Hero) {\n    this.heroService.add(hero);\n  }\n\n  delete(hero: Hero) {\n    this.heroService.delete(hero.id);\n  }\n\n  getHeroes() {\n    this.heroService.getAll();\n  }\n\n  update(hero: Hero) {\n    this.heroService.update(hero);\n  }\n}\n```\n\n</ngrx-code-example>\n\nIn this example, you need to listen for the stream of heroes. The `heroes$` property references the `heroService.entities$` Observable. When state is changed as a result of a successful HTTP request (initiated by `getAll()`, for example), the `heroes$` property will emit the latest Hero array.\n\nBy default, the service includes the `loading$` Observable to indicate whether an HTTP request is in progress. This helps applications manage loading states.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/install.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Installation\n\n## Installing with `ng add`\n\nYou can install the Data package to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/data@latest\n```\n\n### Optional `ng add` flags\n\n| flag                | description                                                                                                                                                                             | value type | default value |\n| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- |\n| `--project`         | Name of the project defined in your `angular.json` to help locating the module to add the `EntityDataModule` to.                                                                        | `string`   |\n| `--module`          | Name of file containing the module that you wish to add the import for the `EntityDataModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts`. | `string`   | `app`         |\n| `--effects`         | If `false` it will use the `EntityDataModuleWithoutEffects` module instead of the default `EntityDataModule`.                                                                           | `boolean`  | `true`        |\n| `--migrateNgRxData` | If `true` it will replace the `ngrx-data` module with the `@ngrx/data` module.                                                                                                          | `boolean`  | `false`       |\n| `--entityConfig`    | If `false` it will not create and declare the `entity-metadata` file.                                                                                                                   | `boolean`  | `true`        |\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/data`.\n2. Run `npm install` to install those dependencies.\n3. Update your `src/app/app.module.ts` > `imports` array with `EntityDataModule` or `EntityDataModuleWithoutEffects` depending on the `effects` flag.\n\nWith the `migrateNgRxData` flag the following will also take place:\n\n1. Remove `ngrx-data` from `package.json` > `dependencies`.\n2. Rename `ngrx-data` types to the matching `@ngrx/data` types.\n\n## Manual Installation\n\nYou can also install `@ngrx/data` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/data\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/limitations.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# NgRx Data Limitations\n\nThe NgRx Data library lacks many capabilities of a [full-featured entity management](#alternatives) system.\n\nYou may be able to work-around some of the limitations without too much effort,\nparticularly when the shortcomings are a problem for just a few entity types.\n\nThis page lists many of the serious limitations we've recognized ourselves.\n\nIf curing them were easy, we'd have done so already.\nSometimes there are acceptable, well-known solutions that just take a little more effort.\nSome solutions are too complicated or perform poorly.\nIn some cases, we have no good ideas at all.\n\nIf there's enough interest in this NgRx Data, we'd like to tackle some of these problems.\nWe could use your help.\n\n## Deep entity cloning\n\nThis library (like the NgRx [Entity](guide/entity) library\non which it depends) assumes that entity property values\nare simple data types such as strings, numbers, and dates.\n\nNothing enforces that assumption.\nMany web APIs return entity data with complex properties.\nA property value could be a _value type_ (e.g., a _money type_ that combines a currency indicator and an amount).\nIt could have a nested structure (e.g., an address).\n\nThis library shallow-clones the entity data in the collections.\nIt doesn't clone complex, nested, or array properties.\nYou'll have to do the deep equality tests and cloning yourself _before_ asking NgRx Data to save data.\n\n## Non-normalized server responses\n\nMany query APIs return an entity bundle with data for many different entity types.\n\nThis library only handles responses with a single entity or an array of entities of the same type.\nWhen you adopt the _redux_ pattern, you're expected to \"normalize\" the entity data\nas you would a _relational database_.\n\nThis library lacks the tools to help you disaggregate and normalize server response data.\n\n## Entity relationships and navigation\n\nEntities are often related to each other via _foreign keys_.\nThese relationships can be represented as a directed graph, often with cycles.\n\nThis library is unaware of _relationships_ and _foreign keys_ that may be implicit in the entity data.\nIt's up to you to make something out of those relationships and keys.\n\nIt's not easy to represent relationships.\n\nA `Customer` entity could have a one-to-many relationship with `Order` entities.\nThe `Order` entity has an `order.customer` property whose value is the primary key\nof its parent `Customer`.\n\nEach `Order` has related `LineItems`.\nA `LineItems` has a one-to-one relationship with `Product`.\nAnd so it goes.\n\nThere are other cardinalities to consider (one-to-zero, one-to-zero-or-many, many-to-many, etc.).\nA good solution would include an extension of the `EntityMetadata` that identified relationships, their cardinalities, and their foreign keys.\n\nIt can be convenient to construct classes for `Customer` and `Order` that have\nproperties for navigating between them (_navigation properties_).\nThe domain logic for the model may argue for unidirectional navigations in some cases\nand bi-directional navigations in others.\n\nWe have to be prepared for any load order.\nThe _orders_ could arrive before their parent _customers_.\nA good solution would tolerate that, making connections and breaking them again\nas entities enter and leave the cache.\n\nThere will be long chains of navigations (`Customer <-> Order <-> <-> LineItem <-> Product <-> Supplier`).\nHow should these be implemented?\n\nOne approach is to combine _Observable selector_ properties like this\n\n```typescript\norders$ = combineLatest([currentCustomerId$, orders$]).pipe(\n  map(([customerId, orders]) =>\n    orders.filter((o) => o.customerId === customerId)\n  )\n);\n```\n\n<ngrx-docs-alert type=\"help\">\n\nWe'll explore this and rival approaches in a future documentation update.\n\n</ngrx-docs-alert>\n\n## Client-side primary key generation\n\nYou are responsible for setting the primary key of an entity you create.\n\nIf the server supplies the key, you can send the new entity to the server\nand rely on the server to send the entity back with its assigned key.\nIt's up to you to orchestrate that cycle.\n\nIt's far better if the client assigns the key.\nYou can create new records offline or recover if your connection to the server\nbreaks inconveniently during the save.\n\nIt's easy to generate a new _guid_ (or _uuid_) key.\nIt's much harder to generate integer or semantic keys because\nyou need a foolproof way to enforce uniqueness.\n\nServer-supplied keys greatly complicate maintenance of a cache of inter-related entities.\nYou'll have to find a way to hold the related entities together until you can save them.\n\nTemporary-key generation is one approach. It requires complex key-fixup logic\nto replace the temporary keys in _foreign key properties_\nwith the server-supplied permanent keys.\n\n## Data integrity rules\n\nEntities are often governed by intra- and inter-entity validation rules.\nThe `Customer.name` property may be required.\nThe `Order.shipDate` must be after the `Order.orderDate`.\nThe parent `Order` of a `LineItem` may have to exist.\n\nYou can weave validation rules into your application logic\nbut you'll have to do so without the help of the `NgRx Data` library.\n\nIt would be great if the library knew about the rules (in `EntityMetadata`?), ran the validation rules at appropriate times, displayed validation errors on screen, and prevented the save of entities with errors.\n\nThese might be features in a future version of this library.\n\n<a name=\"serialization\"></a>\n\n## Server/client entity mapping\n\nThe representation of an entity on the server may be different than on the client.\n\nPerhaps the camelCased property names on the client-side entity are PascalCased on the server.\nMaybe a server-side property is spelled differently than on the client.\nMaybe the client entity should have some properties that don't belong on the server entity (or vice-versa).\n\nToday you could transform the data in both directions with [`HttpClient` interceptors](https://angular.dev/guide/http/interceptors).\nBut this seems like a problem that would be more easily and transparently addressed as a feature of `NgRx Data`.\n\n## No request concurrency checking\n\nThe user saves a new `Customer`, followed by a query for all customers.\nIs the new customer in the query response?\n\n`NgRx Data` does not coordinate save and query requests and does not guarantee order of responses.\n\nYou'll have to manage that yourself.\nHere's some pseudo-code that might do that for the previous example:\n\n```javascript\n// add new customer, then query all customers\ncustomerService\n  .addEntity(newCustomer)\n  .pipe(concatMap(() => customerService.queryAll()))\n  .subscribe((custs) => (this.customers = custs));\n```\n\nThe same reasoning applies to _any_ request that must follow in a precise sequence.\n\n## No update concurrency checking\n\nThere is no intrinsic mechanism to enforce concurrency checks when updating a record even if the record contains a concurrency property.\n\nFor example, the user saves a change to the customer's address from \"123 Main Street\" to \"45 Elm Avenue\".\nThen the user changes and saves the address again to \"89 Bower Road\".\nAnother user changes the same address to \"67 Maiden Lane\".\n\nWhat's the actual address in the database? What's the address in the user's cache?\n\nIt could be any of the three addresses depending on when the server saw them and when the responses arrived.\nYou cannot know.\n\nMany applications maintain a concurrency property that guards against updating an entity\nthat was updated by someone else.\nThe `NgRx Data` library is unaware of this protocol.\nYou'll have to manage concurrency yourself.\n\n## No offline capability\n\nNgRx Data lacks support for accumulating changes while the application is offline and then saving those changes to the server when connectivity is restored.\n\nThe _NgRx_ system has some of the ingredients of an offline capability.\nActions are immutable and serializable so they can be stashed in browser storage of some kind while offline and replayed later.\n\nBut there are far more difficult problems to overcome than just recording changes for playback.\nNgRx Data makes no attempt to address these problems.\n\n## Query language\n\nServers often offer a sophisticated query API for selecting entities from the server, sorting them on the server, grabbing related entities at the same time, and reducing the number of downloaded fields.\n\nThis library's `getWithQuery()` command takes a query specification in the form of a _parameter/value_ map or a URL query string.\n\nThere is no apparatus for composing queries or sending them to the server except as a query string.\n\n<a id=\"alternatives\"></a>\n\n## An alternative to NgRx Data\n\n[BreezeJS](http://www.getbreezenow.com/breezejs) is a free, open source,\nfull-featured entity management library that overcomes (almost) all of the\nlimitations described above.\nMany Angular (and AngularJS) applications use _Breeze_ today.\n\nIt's not the library for you if you **_require_** a small library that adheres to _reactive_, _immutable_, _redux-like_ principles.\n\n<ngrx-docs-alert type=\"help\">\n\nDisclosure: one of the NgRx Data authors, Ward Bell, is an original core Breeze contributor.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/data/save-entities.md",
    "content": "<ngrx-docs-alert type=\"error\">\n\nThe `@ngrx/data` package is in <a href=\"https://github.com/ngrx/platform/issues/4011\" target=\"_blank\">maintenance mode</a>.\nChanges to this package are limited to critical bug fixes.\n\n</ngrx-docs-alert>\n\n# Saving Multiple Entities\n\nMany apps must save several entities at the same time in the same transaction.\n\nMultiple entity saves are a first class feature.\nBy \"first class\" we mean that NgRx Data offers a built-in, multiple entity save solution that\nis consistent with NgRx Data itself:\n\n- defines a `ChangeSet`, describing `ChangeOperations` to be performed on multiple entities of multiple types.\n- has a set of `SAVE_ENTITIES...` cache-level actions.\n- has an `EntityCacheDispatcher` to dispatch those actions.\n- offers `EntityCacheEffects` that sends `SAVE_ENTITIES` async requests to the server and\n  returns results as `SAVE_ENTITIES_SUCCESS` or `SAVE_ENTITIES_ERROR` actions.\n- offers a default `EntityCacheDataService` to make those http server requests.\n- integrates with change tracking.\n- delegates each collection-level change to the (customizable) `entity-collection-reducer-methods`.\n\n<ngrx-docs-alert type=\"help\">\n\nYou could implement multiple-entity saves yourself by, prior to version 6.1.\nYou could define your own protocol and manipulate the `EntityCache` directly by dispatching `SET_ENTITY_CACHE`\nafter updating a copy of the current cache before and after save.\nThe collection-level reducers in `entity-collection-reducer-methods` and the NgRx `EntityAdapters` would help.\n\nIt wouldn't be easy and there are many steps that can be easily overlooked. But you could do it.\n\n</ngrx-docs-alert>\n\n### Save with _EntityCacheDispatcher.saveEntities()_\n\nThis NgRx Data version includes a new `EntityCacheDispatcher` whose\nmethods make it easier to create and dispatch all of the entity cache actions.\n\nSave a bunch of entity changes with the `saveEntities()` dispatcher method.\nCall it with a URL and a `ChangeSet` describing the entity changes that the server API (at the URL endpoint) should save.\n\nThe sample application demonstrates a simple `saveEntities` scenario.\nA button on the _Villains_ page deletes all of the villains.\n\nIn the following example, we want to add a `Hero` and delete two `Villains` in the same transaction.\nWe assume a server is ready to handle such a request.\n\nFirst create the changes (each a `ChangeSetItem`) for the `ChangeSet`.\n\n<ngrx-code-example>\n\n```ts\nimport { ChangeSetOperation } from '@ngrx/data';\n...\nconst changes: ChangeSetItem[] = [\n  {\n    op: ChangeSetOperation.Add,\n    entityName: 'Hero',\n    entities: [hero]\n  },\n  {\n    op: ChangeSetOperation.Delete,\n    entityName: 'Villain',\n    entities: [2, 3] // delete by their ids\n  }\n];\n```\n\n</ngrx-code-example>\n\nThe `changeSetItemFactory` makes it easier to write these changes.\n\n```typescript\nimport { changeSetItemFactory as cif } from '@ngrx/data';\n...\nconst changes: ChangeSetItem[] = [\n  cif.add('Hero', hero),\n  cif.delete('Villain', [2, 3])\n];\n```\n\nNow dispatch a `saveEntities` with a `ChangeSet` for those changes.\n\n```typescript\nconst changeSet: ChangeSet = { changes, tag: 'Hello World' };\n\ncacheEntityDispatcher\n  .saveEntities(changeSet, saveUrl)\n  .subscribe((result) => log('Saved ChangeSet'));\n```\n\nThe `saveEntities(changeSet, saveUrl)` returns an `Observable<ChangeSet>`,\nwhich emits a new `ChangeSet` after the server API (at the `saveUrl` endpoint) returns a successful response.\n\nThat emitted `ChangeSet` holds the server's response data for all affected entities.\n\nThe app can wait for the `saveEntities()` observable to terminate (either successfully or with an error), before proceeding (e.g., routing to another page).\n\n#### How it works\n\nInternally, the method creates a `SAVE_ENTITIES` action whose payload data includes the `ChangeSet`.\nThe action also has the URL to which the requested save should be sent and a `correlationId` (see below).\n\nThe method dispatches this action to the NgRx store where it is processed by the `EntityCacheReducer`.\nIf the action is \"optimistic\", the reducer updates the cache with changes immediately.\n\nThen the `EntityCacheEffects` picks up the `SAVE_ENTITIES` action and sends a \"save changes\" request to\nthe server's API endpoint (the URL).\n\nIf the request succeeds, the server returns data for all of the changed (and deleted) entities.\nThe `EntityCacheEffects` packages that data into a `SAVE_ENTITIES_SUCCESS` action and dispatches it to the store.\n\nThe `EntityCacheReducer` for the `SAVE_ENTITIES_SUCCESS` action\nupdates the cache with the (possibly altered) entity data from the server.\n\nMeanwhile, the `Observable<ChangeSet>` from the `saveEntities()` dispatcher method is\nwatching the stream of actions dispatched to the store.\nWhen a `SAVE_ENTITIES_SUCCESS` (or `SAVE_ENTITIES_ERROR`) action emerges and\nit has the same `correlationId` as the original `SAVE_ENTITIES` action,\nthe observable emits the `ChangeSet` (or error).\n\nThe subscriber to that observable now knows that this particular _save entities_ request is \"done\".\n\n<ngrx-docs-alert type=\"help\">\n\nThis complicated dance is standard NgRx. Fortunately, all you have to know is that you can call `saveEntities()` with the `ChangeSet` and URL, then wait for the returned observable to emit.\n\n</ngrx-docs-alert>\n\n#### _ChangeSet_\n\nThe `ChangeSet` interface is a simple structure with only one critical property,\n`changes`, which holds the entity data to save.\n\n<ngrx-code-example header=\"ChangeSet\">\n\n```ts\nexport interface ChangeSet<T = any> {\n  /** An array of ChangeSetItems to be processed in the array order */\n  changes: ChangeSetItem[];\n\n  /**\n   * An arbitrary, serializable object that should travel with the ChangeSet.\n   * Meaningful to the ChangeSet producer and consumer. Ignored by NgRx Data.\n   */\n  extras?: T;\n\n  /** An arbitrary string, identifying the ChangeSet and perhaps its purpose */\n  tag?: string;\n}\n```\n\n</ngrx-code-example>\n\nAt the heart of it is `changes`, an array of `ChangeSetItems` that describes a change operation to be performed with one or more entities of a particular type.\n\nFor example,\n\n- a `ChangeSetAdd` could add 3 new `Hero` entities to the server's `Hero` collection.\n- a `ChangeSetUpdate` could update 2 existing `Villain` entities.\n- a `ChangeSetDelete` could delete a `SideKick` entity by its primary key.\n- a `ChangeSetUpsert` could add two new `SuperPower` entities and update a third `SuperPower` entity.\n\nThere are four `ChangeSetOperations`\n\n<ngrx-code-example header=\"ChangeSetOperation\">\n\n```ts\nexport enum ChangeSetOperation {\n  Add = 'Add',\n  Delete = 'Delete',\n  Update = 'Update',\n  Upsert = 'Upsert',\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\n`Upsert` is a request to treat the entities in the `ChangeSetItem` as _either_ new entities or updates to _existing_ entities.\n\n</ngrx-docs-alert>\n\nEach kind of `ChangeSetItem` follows a pattern similar to `ChangeSetAdd`.\n\n<ngrx-code-example header=\"ChangeSetAdd\">\n\n```ts\nexport interface ChangeSetAdd<T = any> {\n  op: ChangeSetOperation.Add;\n  entityName: string;\n  entities: T[];\n}\n```\n\n</ngrx-code-example>\n\nThe `ChangeSetItem` flavors all have `op`, `entityName` and `entities` properties.\nThey differ substantively only in the nature of the `entities` array which corresponds to the change operation:\n\n- Add: entities\n- Delete: primary keys of the entities to delete\n- Update: NgRx Entity `Update<T>`s\n- Upsert: entities\n\n#### Pessimistic / Optimistic save\n\nThe `EntityCacheDispatcher.saveEntities` dispatches the `SAVE_ENTITIES` action (with its `ChangeSet`) to the store where it is processed by the `EntityCacheReducer`.\n\nIf the action is \"pessimistic\", the reducer sets the collection `loading` flags but doesn't update the entities in cache.\nThe reducer for the `SAVE_ENTITIES_SUCCESS` action, whose payload holds the successfully saved entities, will update the cached entities.\n\nIf the action is \"optimistic\", the reducer applies the changes to the cache immediately, before you send them to the server.\n\nYou can specify \"optimistic\" or \"pessimistic\" in the `options` parameter.\nIf you don't specify this option, NgRx Data uses the default value in\n`EntityDispatcherDefaultOptions.optimisticSaveEntities`.\nIt is `false` (pessimistic) by default.\n\n#### Specify your own defaults\n\nYou can provide alternative defaults.\n\n```typescript\n {\n  provide: EntityDispatcherDefaultOptions,\n  useValue: myDispatcherDefaultOptions\n}\n```\n\n#### Server\n\nThe server API (the usual recipient of a `ChangeSet`) must be able to process the request.\nNgRx Data doesn't know if the API can or cannot process a `ChangeSet` (and that includes whether the server can or cannot handle upserts).\n\nAs always, make sure only to send something that the server API can handle.\n\n#### EntityCacheEffects\n\nYou can handle the async HTTP _save changes_ request yourself, making your own calls to the server in your own way.\n\nYour solution can use the `EntityCacheDispacher` to dispatch `SAVE_ENTITIES`, `SAVE_ENTITIES_SUCCESS` and `SAVE_ENTITIES_ERROR` actions for updating the cache and managing the `ChangeState` of the entities in the `ChangeSet`.\n\nPerhaps better, you can let the `EntityCacheEffects` handle this for you in a manner similar to the v6 `EntityEffects` for single-entity saves.\n\nThe `EntityCacheEffects.saveEntities$` effect listens for `SAVE_ENTITIES` and makes a request to the designated URL via the (new) `EntityCacheDataService`.\nIt takes the response and dispatches either a `SAVE_ENTITIES_SUCCESS` or `SAVE_ENTITIES_ERROR`, as appropriate.\n\n<ngrx-docs-alert type=\"help\">\n\nIf you prefer to handle server interaction yourself,\nyou can disable the `EntityCacheEffects` by providing a null implementation, in your `NgModule`, e.g.,\n\n```typescript\n{\n  provide: EntityCacheEffects: useValue: {\n  }\n}\n```\n\n</ngrx-docs-alert>\n\n#### EntityCacheDataService\n\nThe `EntityCacheDataService` constructs and POSTS the actual request to the given API URL.\n\nWe anticipate that most server API implementors will not support the NgRx Entity `Update` structure within the `ChangeSet`.\nSo the `EntityCacheDataService.saveEntities()` method\nextracts the `changes` from the `Updates<T>[]` and sends these to the server; it then reconstructs the `Updates<T>[]` entities in from the server response so that the NgRx Data consumer of the response sees those `Update` structures.\n\nAs always, you can provide an alternative implementation:\n\n```typescript\n{\n  provide: EntityCacheDataService: useClass: MyCacheDataService;\n}\n```\n\n#### Updating the store with server response data\n\nIf the save was pessimistic, the EntityCache is unchanged until the server responds.\nYou need the results from the server to update the cache.\n\n<ngrx-docs-alert type=\"help\">\n\nThe changes are already in cache with an optimistic save.\nBut the server might have made additional changes to the data,\nin which case you'd want to (re)apply the server response data to cache.\n\n</ngrx-docs-alert>\n\nThe server API is supposed to return all changed entity data in the\nform of a `ChangeSet`.\n\nOften the server processes the saved entities without changing them.\nThere's no real need for the server to return the data.\nThe original request `ChangeSet` has all the information necessary to update the cache.\nResponding with a `\"204-No Content\"` instead would save time, bandwidth, and processing.\n\nThe server can respond `\"204-No Content\"` and send back nothing.\nThe `EntityCacheEffects` recognizes this condition and\nreturns a success action _derived_ from the original request `ChangeSet`.\n\nIf the save was pessimistic, it returns a `SaveEntitiesSuccess` action with the original `ChangeSet` in the payload.\n\nIf the save was optimistic, the changes are already in the cache and there's no point in updating the cache.\nInstead, the effect returns a merge observable that clears the loading flags\nfor each entity type in the original `CacheSet`.\n\n#### New _EntityOPs_ for multiple entity save\n\nWhen the server responds with a `ChangeSet`, or the effect re-uses the original request `ChangeSet`, the effect returns a `SAVE_ENTITIES_SUCCESS` action with the `ChangeSet` in the payload.\n\nThis `ChangeSet` has the same structure as the one in the `SAVE_ENTITIES` action, which was the source of the HTTP request.\n\nThe `EntityCacheReducer` converts the `ChangeSet.changes` into\na sequence of `EntityActions` to the entity collection reducers.\n\nThe `store` never sees these reducer calls (and you won't see them in the redux tools).\nThey are applied synchronously, in succession to an instance of the `EntityCache` object.\n\nAfter all `ChangeSet.changes` have been reduced, the `EntityCacheReducer` returns the updated `EntityCache` and the NgRx `Store` gets the new, fully-updated cache in one shot.\n\nThat should mean that the cache is in a stable state, with all relationships updated, before any code outside the store hears of the changes.\n\nAt that point, all affected entity `selectors$` will emit.\n\n#### New _EntityOPs_ for multiple entity save\n\nAs always, the entity collection reducers know what to do based on the `EntityAction.entityOp`.\n\nBefore v6.1, the _save_ `EntityOps` only worked for single entities.\nThis version adds multi-entity save actions to `EntityOp`:\n`SAVE_ADD_MANY...`,`SAVE_DELETE_MANY...`, `SAVE_UPDATE_MANY...`,`SAVE_UPSERT_MANY...`.\n\n<ngrx-docs-alert type=\"help\">\n\nThese ops do not have corresponding `EntityCommands` because a multi-entity save is dispatched (via `SAVE_ENTITIES..` actions) to the `EntityCache` reducer,\nnot to a collection reducer (at least not in this version).\n\n</ngrx-docs-alert>\n\n#### Transactions\n\nIt is up to the server to process the `ChangeSet` as a transaction.\nThat's easy if the server-side store is a relational database.\n\nIf your store doesn't support transactions, you'll have to decide if the multiple-entity save facility is right for you.\n\nOn the NgRx Data client, it is \"transactional\" in the sense that a successful result returned by the server will be applied to the cache all at once.\nIf the server returns an error result, the cache is not touched.\n\n**_Important_**: if you saved \"optimisitically\", NgRx Data updates the cache _before_ sending the request to the server.\n\nNgRx Data _does not roll back_ the `EntityCache` automatically when an _optimistic save_ fails.\n\nFortunately, the NgRx Data collection reducers updated the `ChangeState` of the affected entities _before merging_ the changes into the cache (see the NgRx Data `ChangeTracker`).\n\nYou have good options if the save fails.\n\n- You _could_ rollback using the `ChangeTracker`.\n- You could try again.\n- You could fail the app.\n\nLet your failure analysis and application business rules guide your decision.\n\n#### Cancellation\n\nYou can try to cancel a save by dispatching the `SAVE_ENTITIES_CANCEL` action with the\n**correlation id** of the _save action_ that you want to cancel.\n\nAn optional `EntityNames` array argument tells the `EntityCache` reducer to turn off the `loading` flags\nfor the collections named in that array (these flags would have been turned on by `SAVE_ENTITIES`).\nYou can also supply a cancellation \"reason\" and the usual action tag.\n\nThe `EntityCacheEffects.saveEntitiesCancel$` watches for this action and is piped into\nthe `EntityCacheEffects.saveEntities$`, where it can try to cancel the save operation\nor at least prevent the server response from updating the cache.\n\n<ngrx-docs-alert type=\"help\">\n\nIt's not obvious that this is ever a great idea.\nYou cannot tell the server to cancel this way and cannot know if the server did or did not save.\nNor can you count on processing the cancel request before the client receives the server response\nand applies the changes on the server or to the cache.\n\nIf you cancel before the server results arrive, the `EntityCacheEffect` will not try to update\nthe cache with late arriving server results.\nThe effect will issue a `SAVE_ENTITIES_CANCELED` action instead.\nThe `EntityCache` reducer ignores this action but you can listen for it among the store actions\nand thus know that the cancellation took effect on the client.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/effects/index.md",
    "content": "# @ngrx/effects\n\nEffects are an RxJS powered side effect model for [Store](guide/store). Effects use streams to provide [new sources](https://martinfowler.com/eaaDev/EventSourcing.html) of actions to reduce state based on external interactions such as network requests, web socket messages and time-based events.\n\n## Introduction\n\nIn a service-based Angular application, components are responsible for interacting with external resources directly through services. Instead, effects provide a way to interact with those services and isolate them from the components. Effects are where you handle tasks such as fetching data, long-running tasks that produce multiple events, and other external interactions where your components don't need explicit knowledge of these interactions.\n\n## Key Concepts\n\n- Effects isolate side effects from components, allowing for more _pure_ components that select state and dispatch actions.\n- Effects are long-running services that listen to an observable of _every_ action dispatched from the [Store](guide/store).\n- Effects filter those actions based on the type of action they are interested in. This is done by using an operator.\n- Effects perform tasks, which are synchronous or asynchronous and return a new action.\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/effects/install) page.\n\n## Comparison with Component-Based Side Effects\n\nIn a service-based application, your components interact with data through many different services that expose data through properties and methods. These services may depend on other services that manage other sets of data. Your components consume these services to perform tasks, giving your components many responsibilities.\n\nImagine that your application manages movies. Here is a component that fetches and displays a list of movies.\n\n<ngrx-code-example header=\"movies-page.component.ts\">\n\n```ts\nimport { Component, inject, OnInit } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n@Component({\n  template: `\n    <li *ngFor=\"let movie of movies\">\n      {{ movie.name }}\n    </li>\n  `,\n  imports: [CommonModule],\n})\nexport class MoviesPageComponent implements OnInit {\n  private moviesService = inject(MoviesService);\n  protected movies: Movie[] = [];\n\n  ngOnInit() {\n    this.movieService\n      .getAll()\n      .subscribe((movies) => (this.movies = movies));\n  }\n}\n```\n\n</ngrx-code-example>\n\nYou also have the corresponding service that handles the fetching of movies.\n\n<ngrx-code-example header=\"movies.service.ts\">\n\n```ts\nimport { Injectable, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class MoviesService {\n  private http = inject(HttpClient);\n\n  getAll(): Observable<Movie[]> {\n    return this.http.get<Movie[]>('/movies');\n  }\n}\n```\n\n</ngrx-code-example>\n\nThe component has multiple responsibilities:\n\n- Managing the _state_ of the movies.\n- Using the service to perform a _side effect_, reaching out to an external API to fetch the movies.\n- Changing the _state_ of the movies within the component.\n\n`Effects` when used along with `Store`, decrease the responsibility of the component. In a larger application, this becomes more important because you have multiple sources of data, with multiple services required to fetch those pieces of data, and services potentially relying on other services.\n\nEffects handle external data and interactions, allowing your services to be less stateful and only perform tasks related to external interactions. Next, refactor the component to put the shared movie data in the `Store`. Effects handle the fetching of movie data.\n\n<ngrx-code-example header=\"movies-page.component.ts\">\n\n```ts\nimport { Component, inject, OnInit } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n@Component({\n  template: `\n    <div *ngFor=\"let movie of movies$ | async\">\n      {{ movie.name }}\n    </div>\n  `,\n  imports: [CommonModule],\n})\nexport class MoviesPageComponent implements OnInit {\n  private store = inject(Store<{ movies: Movie[] }>);\n  protected movies$ = this.store.select((state) => state.movies);\n\n  ngOnInit() {\n    this.store.dispatch({ type: '[Movies Page] Load Movies' });\n  }\n}\n```\n\n</ngrx-code-example>\n\nThe movies are still fetched through the `MoviesService`, but the component is no longer concerned with how the movies are fetched and loaded. It's only responsible for declaring its _intent_ to load movies and using selectors to access movie list data. Effects are where the asynchronous activity of fetching movies happens. Your component becomes easier to test and less responsible for the data it needs.\n\n## Writing Effects\n\nTo isolate side effects from your component, you can create NgRx effects to listen for events and perform tasks.\n\nEffects are injectable service classes with distinct parts:\n\n- An injectable `Actions` service that provides an observable stream of _each_ action dispatched _after_ the latest state has been reduced.\n- Metadata is attached to the observable streams using the `createEffect` function. The metadata is used to register the streams that are subscribed to the store. Any action returned from the effect stream is then dispatched back to the `Store`.\n- Actions are filtered using a pipeable [`ofType` operator](guide/effects/operators#oftype). The `ofType` operator takes one or more action types as arguments to filter on which actions to act upon.\n- Effects are subscribed to the `Store` observable.\n- Services are injected into effects to interact with external APIs and handle streams.\n\n<ngrx-docs-alert type=\"help\">\n\n**Note:** Since NgRx v15.2, classes are not required to create effects. Learn more about functional effects [here](#functional-effects).\n\n</ngrx-docs-alert>\n\nTo show how you handle loading movies from the example above, let's look at `MoviesEffects`.\n\n<ngrx-code-example header=\"movies.effects.ts\">\n\n```ts\nimport { Injectable, inject } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { EMPTY } from 'rxjs';\nimport { map, exhaustMap, catchError } from 'rxjs/operators';\nimport { MoviesService } from './movies.service';\n\n@Injectable()\nexport class MoviesEffects {\n  private actions$ = inject(Actions);\n  private moviesService = inject(MoviesService);\n\n  loadMovies$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType('[Movies Page] Load Movies'),\n      exhaustMap(() =>\n        this.moviesService.getAll().pipe(\n          map((movies) => ({\n            type: '[Movies API] Movies Loaded Success',\n            payload: movies,\n          })),\n          catchError(() => EMPTY)\n        )\n      )\n    );\n  });\n}\n```\n\n</ngrx-code-example>\n\nThe `loadMovies$` effect is listening for all dispatched actions through the `Actions` stream, but is only interested in the `[Movies Page] Load Movies` event using the `ofType` operator. The stream of actions is then flattened and mapped into a new observable using the `exhaustMap` operator. The `MoviesService#getAll()` method returns an observable that maps the movies to a new action on success, and currently returns an empty observable if an error occurs. The action is dispatched to the `Store` where it can be handled by reducers when a state change is needed. It's also important to [handle errors](#handling-errors) when dealing with observable streams so that the effects continue running.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** Event streams are not limited to dispatched actions, but can be _any_ observable that produces new actions, such as observables from the Angular Router, observables created from browser events, and other observable streams.\n\n</ngrx-docs-alert>\n\n## Handling Errors\n\nEffects are built on top of observable streams provided by RxJS. Effects are listeners of observable streams that continue until an error or completion occurs. In order for effects to continue running in the event of an error in the observable, or completion of the observable stream, they must be nested within a \"flattening\" operator, such as `mergeMap`, `concatMap`, `exhaustMap`, and `switchMap`. The example below shows the `loadMovies$` effect handling errors when fetching movies.\n\n<ngrx-code-example header=\"movies.effects.ts\">\n\n```ts\nimport { Injectable, inject } from '@angular/core';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { of } from 'rxjs';\nimport { map, exhaustMap, catchError } from 'rxjs/operators';\nimport { MoviesService } from './movies.service';\n\n@Injectable()\nexport class MoviesEffects {\n  private actions$ = inject(Actions);\n  private moviesService = inject(MoviesService);\n\n  loadMovies$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType('[Movies Page] Load Movies'),\n      exhaustMap(() =>\n        this.moviesService.getAll().pipe(\n          map((movies) => ({\n            type: '[Movies API] Movies Loaded Success',\n            payload: movies,\n          })),\n          catchError(() =>\n            of({ type: '[Movies API] Movies Loaded Error' })\n          )\n        )\n      )\n    );\n  });\n}\n```\n\n</ngrx-code-example>\n\nThe `loadMovies$` effect returns a new observable in case an error occurs while fetching movies. The inner observable handles any errors or completions and returns a new observable so that the outer stream does not die. You still use the `catchError` operator to handle error events, but return an observable of a new action that is dispatched to the `Store`.\n\n## Functional Effects\n\nFunctional effects are also created by using the `createEffect` function. They provide the ability to create effects outside the effect classes.\n\nTo create a functional effect, add the `functional: true` flag to the effect config. Then, to inject services into the effect, use the [`inject` function](https://angular.dev/api/core/inject).\n\n<ngrx-code-example header=\"actors.effects.ts\">\n\n```ts\nimport { inject } from '@angular/core';\nimport { catchError, exhaustMap, map, of, tap } from 'rxjs';\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\n\nimport { ActorsService } from './actors.service';\nimport { ActorsPageActions } from './actors-page.actions';\nimport { ActorsApiActions } from './actors-api.actions';\n\nexport const loadActors = createEffect(\n  (\n    actions$ = inject(Actions),\n    actorsService = inject(ActorsService)\n  ) => {\n    return actions$.pipe(\n      ofType(ActorsPageActions.opened),\n      exhaustMap(() =>\n        actorsService.getAll().pipe(\n          map((actors) =>\n            ActorsApiActions.actorsLoadedSuccess({ actors })\n          ),\n          catchError((error: { message: string }) =>\n            of(\n              ActorsApiActions.actorsLoadedFailure({\n                errorMsg: error.message,\n              })\n            )\n          )\n        )\n      )\n    );\n  },\n  { functional: true }\n);\n\nexport const displayErrorAlert = createEffect(\n  () => {\n    return inject(Actions).pipe(\n      ofType(ActorsApiActions.actorsLoadedFailure),\n      tap(({ errorMsg }) => alert(errorMsg))\n    );\n  },\n  { functional: true, dispatch: false }\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\nIt's recommended to inject all dependencies as effect function arguments for easier testing. However, it's also possible to inject dependencies in the effect function body. In that case, the [`inject` function](https://angular.dev/api/core/inject) must be called within the synchronous context.\n\n</ngrx-docs-alert>\n\n## Registering Effects\n\nEffect classes and functional effects are registered using the `provideEffects` method.\n\nAt the root level, effects are registered in the `providers` array of the application configuration.\n\n<ngrx-docs-alert type=\"inform\">\n\nEffects start running **immediately** after instantiation to ensure they are listening for all relevant actions as soon as possible.\nServices used in root-level effects are **not** recommended to be used with services that are used with the `APP_INITIALIZER` token.\n\n</ngrx-docs-alert>\n\n<ngrx-code-example header=\"main.ts\">\n\n```ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideStore } from '@ngrx/store';\nimport { provideEffects } from '@ngrx/effects';\n\nimport { AppComponent } from './app.component';\nimport { MoviesEffects } from './effects/movies.effects';\nimport * as actorsEffects from './effects/actors.effects';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideStore(),\n    provideEffects(MoviesEffects, actorsEffects),\n  ],\n});\n```\n\n</ngrx-code-example>\n\nFeature-level effects are registered in the `providers` array of the route config.\nThe same `provideEffects()` method is used to register effects for a feature.\n\n<ngrx-docs-alert type=\"inform\">\n\nRegistering an effects class multiple times (for example in different lazy loaded features) does not cause the effects to run multiple times.\n\n</ngrx-docs-alert>\n\n<ngrx-code-example header=\"movie-routes.ts\">\n\n```ts\nimport { Route } from '@angular/router';\nimport { provideEffects } from '@ngrx/effects';\n\nimport { MoviesEffects } from './effects/movies.effects';\nimport * as actorsEffects from './effects/actors.effects';\n\nexport const routes: Route[] = [\n  {\n    path: 'movies',\n    providers: [provideEffects(MoviesEffects, actorsEffects)],\n  },\n];\n```\n\n</ngrx-code-example>\n\n### Alternative Way of Registering Effects\n\nYou can provide root-/feature-level effects with the provider `USER_PROVIDED_EFFECTS`.\n\n<ngrx-code-example>\n\n```ts\nproviders: [\n  MoviesEffects,\n  {\n    provide: USER_PROVIDED_EFFECTS,\n    multi: true,\n    useValue: [MoviesEffects],\n  },\n];\n```\n\n</ngrx-code-example>\n\n## Incorporating State\n\nIf additional metadata is needed to perform an effect besides the initiating action's `type`, we should rely on passed metadata from an action creator's `props` method.\n\nLet's look at an example of an action initiating a login request using an effect with additional passed metadata:\n\n<ngrx-code-example header=\"login-page.actions.ts\">\n\n```ts\nimport { createAction, props } from '@ngrx/store';\nimport { Credentials } from '../models/user';\n\nexport const login = createAction(\n  '[Login Page] Login',\n  props<{ credentials: Credentials }>()\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"auth.effects.ts\">\n\n```ts\nimport { Injectable, inject } from '@angular/core';\nimport { Actions, ofType, createEffect } from '@ngrx/effects';\nimport { of } from 'rxjs';\nimport { catchError, exhaustMap, map } from 'rxjs/operators';\nimport { LoginPageActions, AuthApiActions } from '../actions';\nimport { Credentials } from '../models/user';\nimport { AuthService } from '../services/auth.service';\n\n@Injectable()\nexport class AuthEffects {\n  private actions$ = inject(Actions);\n  private authService = inject(AuthService);\n\n  login$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(LoginPageActions.login),\n      exhaustMap((action) =>\n        this.authService.login(action.credentials).pipe(\n          map((user) => AuthApiActions.loginSuccess({ user })),\n          catchError((error) =>\n            of(AuthApiActions.loginFailure({ error }))\n          )\n        )\n      )\n    );\n  });\n}\n```\n\n</ngrx-code-example>\n\nThe `login` action has additional `credentials` metadata which is passed to a service to log the specific user into the application.\n\nHowever, there may be cases when the required metadata is only accessible from state. When state is needed, the RxJS `withLatestFrom` or the @ngrx/effects `concatLatestFrom` operators can be used to provide it.\n\nThe example below shows the `addBookToCollectionSuccess$` effect displaying a different alert depending on the number of books in the collection state.\n\n<ngrx-code-example header=\"collection.effects.ts\">\n\n```ts\nimport { Injectable, inject } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport {\n  Actions,\n  ofType,\n  createEffect,\n  concatLatestFrom,\n} from '@ngrx/effects';\nimport { tap } from 'rxjs/operators';\nimport { CollectionApiActions } from '../actions';\nimport * as fromBooks from '../reducers';\n\n@Injectable()\nexport class CollectionEffects {\n  private actions$ = inject(Actions);\n  private store = inject(Store<fromBooks.State>);\n\n  addBookToCollectionSuccess$ = createEffect(\n    () => {\n      return this.actions$.pipe(\n        ofType(CollectionApiActions.addBookSuccess),\n        concatLatestFrom((_action) =>\n          this.store.select(fromBooks.getCollectionBookIds)\n        ),\n        tap(([_action, bookCollection]) => {\n          if (bookCollection.length === 1) {\n            window.alert('Congrats on adding your first book!');\n          } else {\n            window.alert(\n              'You have added book number ' + bookCollection.length\n            );\n          }\n        })\n      );\n    },\n    { dispatch: false }\n  );\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\nFor performance reasons, use a flattening operator like `concatLatestFrom` to prevent the selector from firing until the correct action is dispatched.\n\n</ngrx-docs-alert>\n\nTo learn about testing effects that incorporate state, see the [Effects that use State](guide/effects/testing#effect-that-uses-state) section in the testing guide.\n\n## Using Other Observable Sources for Effects\n\nBecause effects are merely consumers of observables, they can be used without actions and the `ofType` operator. This is useful for effects that don't need to listen to some specific actions, but rather to some other observable source.\n\nFor example, imagine we want to track click events and send that data to our monitoring server. This can be done by creating an effect that listens to the `document` `click` event and emits the event data to our server.\n\n<ngrx-code-example header=\"user-activity.effects.ts\">\n\n```ts\nimport { Injectable, inject } from '@angular/core';\nimport { Observable, fromEvent } from 'rxjs';\nimport { concatMap } from 'rxjs/operators';\nimport { createEffect } from '@ngrx/effects';\n\nimport { UserActivityService } from '../services/user-activity.service';\n\n@Injectable()\nexport class UserActivityEffects {\n  private userActivityService = inject(UserActivityService);\n\n  trackUserActivity$ = createEffect(\n    () => {\n      return fromEvent(document, 'click').pipe(\n        concatMap((event) =>\n          this.userActivityService.trackUserActivity(event)\n        )\n      );\n    },\n    { dispatch: false }\n  );\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nAn example of the `@ngrx/effects` in module-based applications is available at the [following link](https://v17.ngrx.io/guide/effects).\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/effects/install.md",
    "content": "# Installation\n\n## Installing with `ng add`\n\nYou can install the Effects to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/effects@latest\n```\n\n### Optional `ng add` flags\n\n| flag          | description                                                                                                                                                                         | value type | default value |\n| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- |\n| `--path`      | Path to the module that you wish to add the import for the `EffectsModule` to.                                                                                                      | `string`   |\n| `--flat`      | Indicate if a directory is to be created to hold your effects file.                                                                                                                 | `boolean`  | `true`        |\n| `--skipTests` | When true, does not create test files.                                                                                                                                              | `boolean`  | `false`       |\n| `--project`   | Name of the project defined in your `angular.json` to help locating the module to add the `EffectsModule` to.                                                                       | `string`   |\n| `--module`    | Name of file containing the module that you wish to add the import for the `EffectsModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts` | `string`   | `app`         |\n| `--minimal`   | When true, only provide minimal setup for the root effects setup. Only registers `EffectsModule.forRoot()` in the provided `module` with an empty array.                            | `boolean`  | `true`        |\n| `--group`     | Group effects file within `effects` folder.                                                                                                                                         | `boolean`  | `false`       |\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/effects`.\n2. Run `npm install` to install those dependencies.\n3. Update your `src/app/app.module.ts` > `imports` array with `EffectsModule.forRoot([AppEffects])`. If you provided flags then the command will attempt to locate and update module found by the flags.\n4. If the project is using a `standalone bootstrap`, it adds `provideEffects()` into the application config.\n\n## Manual Installation\n\nYou can also install `@ngrx/effects` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/effects\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/effects/lifecycle.md",
    "content": "# Lifecycle\n\n### ROOT_EFFECTS_INIT\n\nAfter all the root effects have been added, the root effect dispatches a `ROOT_EFFECTS_INIT` action.\nYou can see this action as a lifecycle hook, which you can use in order to execute some code after all your root effects have been added.\n\n<ngrx-code-example header=\"init.effects.ts\">\n\n```ts\ninit$ = createEffect(() => {\n  return this.actions$.pipe(\n    ofType(ROOT_EFFECTS_INIT),\n    map(action => ...)\n  );\n});\n```\n\n</ngrx-code-example>\n\n## Effect Metadata\n\n### Non-dispatching Effects\n\nSometimes you don't want effects to dispatch an action, for example when you only want to log or navigate based on an incoming action. But when an effect does not dispatch another action, the browser will crash because the effect is both 'subscribing' to and 'dispatching' the exact same action, causing an infinite loop. To prevent this, add `{ dispatch: false }` to the `createEffect` function as the second argument.\n\nUsage:\n\n<ngrx-code-example header=\"log.effects.ts\">\n\n```ts\nimport { Injectable, inject } from '@angular/core';\nimport { Actions, createEffect } from '@ngrx/effects';\nimport { tap } from 'rxjs/operators';\n\n@Injectable()\nexport class LogEffects {\n  private actions$ = inject(Actions);\n\n  logActions$ = createEffect(\n    () => {\n      return this.actions$.pipe(tap((action) => console.log(action)));\n    },\n    { dispatch: false }\n  );\n}\n```\n\n</ngrx-code-example>\n\n### Resubscribe on Error\n\nStarting with version 8, when an error happens in the effect's main stream it is\nreported using Angular's `ErrorHandler`, and the source effect is\n**automatically** resubscribed to (instead of completing), so it continues to\nlisten to all dispatched Actions. By default, effects are resubscribed up to 10\nerrors.\n\nGenerally, errors should be handled by users. However, for the cases where errors were missed,\nthis new behavior adds an additional safety net.\n\nIn some cases where particular RxJS operators are used, the new behavior might\nproduce unexpected results. For example, if the `startWith` operator is within the\neffect's pipe then it will be triggered again.\n\nTo disable resubscriptions add `{useEffectsErrorHandler: false}` to the `createEffect`\nmetadata (second argument).\n\n<ngrx-code-example header=\"disable-resubscribe.effects.ts\">\n\n```ts\nimport { Injectable, inject } from '@angular/core';\nimport { Actions, ofType, createEffect } from '@ngrx/effects';\nimport { of } from 'rxjs';\nimport { catchError, exhaustMap, map } from 'rxjs/operators';\nimport { LoginPageActions, AuthApiActions } from '../actions';\nimport { AuthService } from '../services/auth.service';\n\n@Injectable()\nexport class AuthEffects {\n  private actions$ = inject(Actions);\n  private authService = inject(AuthService);\n\n  logins$ = createEffect(\n    () => {\n      return this.actions$.pipe(\n        ofType(LoginPageActions.login),\n        exhaustMap((action) =>\n          this.authService.login(action.credentials).pipe(\n            map((user) => AuthApiActions.loginSuccess({ user })),\n            catchError((error) =>\n              of(AuthApiActions.loginFailure({ error }))\n            )\n          )\n        )\n        // Errors are handled and it is safe to disable resubscription\n      );\n    },\n    { useEffectsErrorHandler: false }\n  );\n}\n```\n\n</ngrx-code-example>\n\n### Customizing the Effects Error Handler\n\nThe behavior of the default resubscription handler can be customized\nby providing a custom handler using the `EFFECTS_ERROR_HANDLER` injection token.\n\nThis allows you to provide a custom behavior, such as only retrying on\ncertain \"retryable\" errors, or change the maximum number of retries (it's set to\n10 by default).\n\n<ngrx-code-example header=\"customise-error-handler.effects.ts\">\n\n```ts\nimport { ErrorHandler, NgModule } from '@angular/core';\nimport { Observable, throwError } from 'rxjs';\nimport { retryWhen, mergeMap } from 'rxjs/operators';\nimport { Action } from '@ngrx/store';\nimport { EffectsModule, EFFECTS_ERROR_HANDLER } from '@ngrx/effects';\nimport { MoviesEffects } from './effects/movies.effects';\nimport {\n  CustomErrorHandler,\n  isRetryable,\n} from '../custom-error-handler';\n\nexport function effectResubscriptionHandler<T extends Action>(\n  observable$: Observable<T>,\n  errorHandler?: CustomErrorHandler\n): Observable<T> {\n  return observable$.pipe(\n    retryWhen((errors) =>\n      errors.pipe(\n        mergeMap((e) => {\n          if (isRetryable(e)) {\n            return errorHandler.handleRetryableError(e);\n          }\n\n          errorHandler.handleError(e);\n          return throwError(() => e);\n        })\n      )\n    )\n  );\n}\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    {\n      provide: EFFECTS_ERROR_HANDLER,\n      useValue: effectResubscriptionHandler,\n    },\n    {\n      provide: ErrorHandler,\n      useClass: CustomErrorHandler,\n    },\n  ],\n});\n```\n\n</ngrx-code-example>\n\n## Controlling Effects\n\n### OnInitEffects\n\nImplement this interface to dispatch a custom action after the effect has been added.\nYou can listen to this action in the rest of the application to execute something after the effect is registered.\n\nUsage:\n\n<ngrx-code-example header=\"user.effects.ts\">\n\n```ts\nclass UserEffects implements OnInitEffects {\n  ngrxOnInitEffects(): Action {\n    return { type: '[UserEffects]: Init' };\n  }\n}\n```\n\n</ngrx-code-example>\n\n### OnRunEffects\n\nBy default, effects are merged and subscribed to the store. Implement the `OnRunEffects` interface to control the lifecycle of the resolved effects.\n\nUsage:\n\n<ngrx-code-example header=\"user.effects.ts\">\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { exhaustMap, takeUntil, tap } from 'rxjs/operators';\nimport {\n  Actions,\n  OnRunEffects,\n  EffectNotification,\n  ofType,\n  createEffect,\n} from '@ngrx/effects';\n\n@Injectable()\nexport class UserEffects implements OnRunEffects {\n  private actions$ = inject(Actions);\n\n  updateUser$ = createEffect(\n    () => {\n      return this.actions$.pipe(\n        ofType('UPDATE_USER'),\n        tap((action) => {\n          console.log(action);\n        })\n      );\n    },\n    { dispatch: false }\n  );\n\n  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>) {\n    return this.actions$.pipe(\n      ofType('LOGGED_IN'),\n      exhaustMap(() =>\n        resolvedEffects$.pipe(\n          takeUntil(this.actions$.pipe(ofType('LOGGED_OUT')))\n        )\n      )\n    );\n  }\n}\n```\n\n</ngrx-code-example>\n\n### Identify Effects Uniquely\n\nBy default, each Effects class is registered once regardless of how many times the Effect class is loaded.\nBy implementing this interface, you define a unique identifier to register an Effects class instance multiple times.\n\nUsage:\n\n<ngrx-code-example header=\"user.effects.ts\">\n\n```ts\nclass EffectWithIdentifier implements OnIdentifyEffects {\n  constructor(private effectIdentifier: string) {}\n\n  ngrxOnIdentifyEffects() {\n    return this.effectIdentifier;\n  }\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/effects/operators.md",
    "content": "# Effects operators\n\nAs part of the `Effects` library, NgRx provides some useful operators that are frequently\nused.\n\n## `ofType`\n\nThe `ofType` operator filters the stream of actions based on either string\nvalues (that represent `type`s of actions) or Action Creators.\n\nThe generic for the `Actions<TypeUnion>` must be provided in order for type\ninference to work properly with string values. Action Creators that are based on\n`createAction` function do not have the same limitation.\n\nThe `ofType` operator takes up to 5 arguments with proper type inference. It can\ntake even more, however the type would be inferred as an `Action` interface.\n\n<ngrx-code-example header=\"auth.effects.ts\">\n\n```ts\nimport { Injectable, inject } from '@angular/core';\nimport { Actions, ofType, createEffect } from '@ngrx/effects';\nimport { of } from 'rxjs';\nimport { catchError, exhaustMap, map } from 'rxjs/operators';\nimport { LoginPageActions, AuthApiActions } from '../actions';\nimport { Credentials } from '../models/user';\nimport { AuthService } from '../services/auth.service';\n\n@Injectable()\nexport class AuthEffects {\n  private actions$ = inject(Actions);\n  private authService = inject(AuthService);\n\n  login$ = createEffect(() => {\n    return this.actions$.pipe(\n      // Filters by Action Creator 'login'\n      ofType(LoginPageActions.login),\n      exhaustMap((action) =>\n        this.authService.login(action.credentials).pipe(\n          map((user) => AuthApiActions.loginSuccess({ user })),\n          catchError((error) =>\n            of(AuthApiActions.loginFailure({ error }))\n          )\n        )\n      )\n    );\n  });\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/effects/testing.md",
    "content": "# Testing\n\n## Test helpers\n\n### `provideMockActions`\n\nAn Effect subscribes to the `Actions` Observable to perform side effects.\n`provideMockActions` provides a mock provider of the `Actions` Observable to subscribe to, for each test individually.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\nimport { provideMockActions } from '@ngrx/effects/testing';\n\nlet actions$ = new Observable<Action>();\n\nTestBed.configureTestingModule({\n  providers: [provideMockActions(() => actions$)],\n});\n```\n\n</ngrx-code-example>\n\nLater in the test cases, we assign the `actions$` variable to a stream of actions.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\n// by creating an Observable\nactions$ = of({ type: 'Action One' });\n\n// or by using a marble diagram\nactions$ = hot('--a-', { a: { type: 'Action One' } });\n```\n\n</ngrx-code-example>\n\n### Effects with parameters\n\nFor time dependant effects, for example `debounceTime`, we must be able override the default RxJS scheduler with the `TestScheduler` during our test.\nThat's why we create the effect as a function with parameters. By doing this we can assign default parameter values for the effect, and override these values later in the test cases.\n\nThis practice also allows us to hide the implementation details of the effect.\nIn the `debounceTime` test case, we can set the debounce time to a controlled value.\n\n<ngrx-code-example header=\"my.effects.ts\">\n\n```ts\nsearch$ = createEffect(() => ({\n  // assign default values\n  debounce = 300,\n  scheduler = asyncScheduler\n} = {}) =>\n  this.actions$.pipe(\n    ofType(BookActions.search),\n    debounceTime(debounce, scheduler),\n    ...\n  )\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\n// override the default values\neffects.search$({\n  debounce: 30,\n  scheduler: getTestScheduler(),\n});\n```\n\n</ngrx-code-example>\n\n## Testing practices\n\n### Marble diagrams\n\nTesting Effects via marble diagrams is particularly useful when the Effect is time sensitive or when the Effect has a lot of behavior.\n\n<ngrx-docs-alert type=\"help\">\n\nFor a detailed look on the marble syntax, see [Writing marble tests](https://rxjs.dev/guide/testing/marble-testing).\n\nThe `hot`, `cold`, and `toBeObservable` methods are imported from [`jasmine-marbles`](https://www.npmjs.com/package/jasmine-marbles).\n\n</ngrx-docs-alert>\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\n// create an actions stream to represent a user that is typing\nactions$ = hot('-a-b-', {\n  a: { type: '[Customers Page] Search Customers', name: 'J' },\n  b: { type: '[Customers Page] Search Customers', name: 'Jes' },\n})\n\n// mock the service to prevent an HTTP request to return an array of customers\ncustomersServiceSpy.searchCustomers.and.returnValue(\n  cold('--a|', { a: [...] })\n);\n\n// expect the first action to debounce and not to dispatch\n// expect the second action to result in a SUCCESS action\nconst expected = hot('-------a', {\n  a: {\n    type: '[Customers API] Search Customers Success',\n    customers: [...],\n  },\n});\n\nexpect(\n  effects.searchCustomers$({\n    debounce: 20,\n    scheduler: getTestScheduler(),\n  })\n).toBeObservable(expected);\n```\n\n</ngrx-code-example>\n\n### With `TestScheduler`\n\nInstead of using `jasmine-marbles`, we can also run tests with the [RxJS `TestScheduler`](https://rxjs.dev/guide/testing/marble-testing).\n\nTo use the `TestScheduler` we first have to instantiate it,\nthis can be done in the test case or within a `beforeEach` block.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\nimport { TestScheduler } from 'rxjs/testing';\n\nlet testScheduler: TestScheduler;\n\nbeforeEach(() => {\n  testScheduler = new TestScheduler((actual, expected) => {\n    expect(actual).toEqual(expected);\n  });\n});\n```\n\n</ngrx-code-example>\n\nThe `TestScheduler` provides a `run` method which expects a callback, it's here where we write the test for an effect.\nThe callback method provides helper methods to mock Observable streams, and also assertion helper methods to verify the output of a stream.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\n// more info about the API can be found at https://rxjs.dev/guide/testing/marble-testing#api\ntestScheduler.run(({ cold, hot, expectObservable }) => {\n  // use the `hot` and `cold` helper methods to create the action and service streams\n  actions$ = hot('-a', { a : { type: '[Customers Page] Get Customers' }});\n  customersServiceSpy.getAllCustomers.and.returnValue(cold('--a|', { a: [...] }));\n\n  // use the `expectObservable` helper method to assert if the output matches the expected output\n  expectObservable(effects.getAll$).toBe('---c', {\n    c: {\n      type: '[Customers API] Get Customers Success',\n      customers: [...],\n    }\n  });\n});\n```\n\n</ngrx-code-example>\n\nBy using the `TestScheduler` we can also test effects dependent on a scheduler.\nInstead of creating an effect as a method to override properties in test cases, as shown in [`Effects with parameters`](#effects-with-parameters), we can rewrite the test case by using the `TestScheduler`.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\ntestScheduler.run(({ cold, hot, expectObservable }) => {\n  // create an actions stream to represent a user that is typing\n  actions$ = hot('-a-b-', {\n    a: { type: '[Customers Page] Search Customers', name: 'J' },\n    b: { type: '[Customers Page] Search Customers', name: 'Jes' },\n  })\n\n  // mock the service to prevent an HTTP request to return an array of customers\n  customersServiceSpy.searchCustomers.and.returnValue(\n    cold('--a|', { a: [...] })\n  );\n\n  // the `300ms` is the set debounce time\n  // the `5ms` represents the time for the actions stream and the service to return a value\n  expectObservable(effects.searchCustomers).toBe('300ms 5ms c', {\n    c: {\n      type: '[Customers API] Search Customers Success',\n      customers: [...],\n    },\n  });\n});\n```\n\n</ngrx-code-example>\n\n### With Observables\n\nTo test simple Effects, it might be easier to create an Observable instead of using a marble diagram.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\n// create an actions stream and immediately dispatch a GET action\nactions$ = of({ type: '[Customers Page] Get Customers' });\n\n// mock the service to prevent an HTTP request\ncustomersServiceSpy.getAllCustomers.and.returnValue(of([...]));\n\n// subscribe to the Effect stream and verify it dispatches a SUCCESS action\neffects.getAll$.subscribe(action => {\n  expect(action).toEqual({\n    type: '[Customers API] Get Customers Success',\n    customers: [...],\n  });\n  done();\n});\n```\n\n</ngrx-code-example>\n\n### With `ReplaySubject`\n\nAs an alternative, it's also possible to use `ReplaySubject`.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\n// create a ReplaySubject\nactions$ = new ReplaySubject(1);\n\n// mock the service to prevent an HTTP request\ncustomersServiceSpy.getAllCustomers.and.returnValue(of([...]));\n\n// dispatch the GET action\n(actions$ as ReplaySubject).next({ type: '[Customers Page] Get Customers' })\n\n// subscribe to the Effect stream and verify it dispatches a SUCCESS action\neffects.getAll$.subscribe(action => {\n  expect(action).toEqual({\n    type: '[Customers API] Get Customers Success',\n    customers: [...],\n  });\n  done();\n});\n```\n\n</ngrx-code-example>\n\n## Examples\n\n### A non-dispatching Effect\n\nUntil now, we only saw Effects that dispatch an Action and we verified the dispatched action.\nWith an Effect that does not dispatch an action, we can't verify the Effects stream.\nWhat we can do, is verify the side-effect has been called.\n\nAn example of this is to verify we navigate to the correct page.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\nit('should navigate to the customers detail page', () => {\n  actions$ = of({\n    type: '[Customers Page] Customer Selected',\n    name: 'Bob',\n  });\n\n  // create a spy to verify the navigation will be called\n  spyOn(router, 'navigateByUrl');\n\n  // subscribe to execute the Effect\n  effects.selectCustomer$.subscribe();\n\n  // verify the navigation has been called\n  expect(router.navigateByUrl).toHaveBeenCalledWith('customers/bob');\n});\n```\n\n</ngrx-code-example>\n\n### Effect that uses state\n\nLeverage [`MockStore`](/guide/store/testing#using-a-mock-store) and [`MockSelectors`](/guide/store/testing#using-mock-selectors) to test Effects that are selecting slices of the state.\n\nAn example of this is to not fetch an entity (customer in this case) when it's already in the store state.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\nlet actions$: Observable<Action>;\n\nTestBed.configureTestingModule({\n  providers: [\n    CustomersEffects,\n    provideMockActions(() => actions$),\n    // mock the Store and the selectors that are used within the Effect\n    provideMockStore({\n      selectors: [\n        {\n          selector: selectCustomers,\n          value: {\n            Bob: { name: 'Bob' },\n          },\n        },\n      ],\n    }),\n  ],\n});\n\neffects = TestBed.inject<CustomersEffects>(CustomersEffects);\n\nit('should not fetch if the user is already in the store', () => {\n  actions$ = hot('-a--', {\n    a: { type: '[Customers Page] Search Customers', name: 'Bob' },\n  });\n\n  // there is no output, because Bob is already in the Store state\n  const expected = hot('----');\n\n  expect(effects.getByName$).toBeObservable(expected);\n});\n```\n\n</ngrx-code-example>\n\n### Setup without `TestBed`\n\nInstead of using the Angular `TestBed`, we can instantiate the Effect class.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\nit('should get customers', () => {\n  // instead of using `provideMockActions`,\n  // define the actions stream by creating a new `Actions` instance\n  const actions = new Actions(\n    hot('-a--', {\n      a: { type: '[Customers Page] Get Customers' },\n    })\n  );\n\n  // create the effect\n  const effects = new CustomersEffects(actions, customersServiceSpy);\n\n  const expected = hot('-a--', {\n    a: {\n      type: '[Customers API] Get Customers Success',\n      customers: [...],\n    }\n  });\n\n  // expect remains the same\n  expect(effects.getAll$).toBeObservable(expected);\n})\n```\n\n</ngrx-code-example>\n\nFor an Effect with store interaction, use `createMockStore` to create a new instance of `MockStore`.\n\n<ngrx-code-example header=\"my.effects.spec.ts\">\n\n```ts\nit('should get customers', () => {\n  // create the store, and provide selectors.\n  const store = createMockStore({\n    selectors: [\n      { selector: selectCustomers, value: { Bob: { name: 'Bob' } } },\n    ],\n  });\n\n  // instead of using `provideMockActions`,\n  // define the actions stream by creating a new `Actions` instance\n  const actions = new Actions(\n    hot('-a--', {\n      a: {\n        type: '[Search Customers Page] Get Customer',\n        name: 'Bob',\n      },\n    })\n  );\n\n  // create the effect\n  const effects = new CustomersEffects(\n    store as Store,\n    actions,\n    customersServiceSpy\n  );\n\n  // there is no output, because Bob is already in the Store state\n  const expected = hot('----');\n\n  expect(effects.getByName$).toBeObservable(expected);\n});\n```\n\n</ngrx-code-example>\n\n### Functional Effects\n\nFunctional effects can be tested like any other function. If we inject all dependencies as effect function arguments, `TestBed` is not required to mock dependencies. Instead, we can pass fake instances as input arguments to the functional effect.\n\n<ngrx-code-example header=\"actors.effects.spec.ts\">\n\n```ts\nimport { of } from 'rxjs';\n\nimport { loadActors } from './actors.effects';\nimport { ActorsService } from './actors.service';\nimport { actorsMock } from './actors.mock';\nimport { ActorsPageActions } from './actors-page.actions';\nimport { ActorsApiActions } from './actors-api.actions';\n\nit('loads actors successfully', (done) => {\n  const actorsServiceMock = {\n    getAll: () => of(actorsMock),\n  } as ActorsService;\n  const actionsMock$ = of(ActorsPageActions.opened());\n\n  loadActors(actionsMock$, actorsServiceMock).subscribe((action) => {\n    expect(action).toEqual(\n      ActorsApiActions.actorsLoadedSuccess({ actors: actorsMock })\n    );\n    done();\n  });\n});\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nYou can check the `loadActors` effect implementation [here](guide/effects#functional-effects).\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/entity/adapter.md",
    "content": "# Entity Adapter\n\n## createEntityAdapter<T>\n\nA method for returning a generic entity adapter for a single entity state collection. The\nreturned adapter provides many adapter methods for performing operations\nagainst the collection type. The method takes an object with 2 properties for configuration.\n\n- `selectId`: A method for selecting the primary id for the collection. Optional when the entity has a primary key of `id`\n- `sortComparer`: A compare function used to [sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) the collection. The comparer function is only needed if the collection needs to be sorted before being displayed. Set to `false` to leave the collection unsorted, which is more performant during CRUD operations.\n\nUsage:\n\n<ngrx-code-example header=\"user.reducer.ts\">\n\n```ts\nimport {\n  EntityState,\n  EntityAdapter,\n  createEntityAdapter,\n} from '@ngrx/entity';\n\nexport interface User {\n  id: string;\n  name: string;\n}\n\nexport interface State extends EntityState<User> {\n  // additional entities state properties\n  selectedUserId: string | null;\n}\n\nexport function selectUserId(a: User): string {\n  //In this case this would be optional since primary key is id\n  return a.id;\n}\n\nexport function sortByName(a: User, b: User): number {\n  return a.name.localeCompare(b.name);\n}\n\nexport const adapter: EntityAdapter<User> = createEntityAdapter<User>(\n  {\n    selectId: selectUserId,\n    sortComparer: sortByName,\n  }\n);\n```\n\n</ngrx-code-example>\n\n## Adapter Methods\n\nThese methods are provided by the adapter object returned\nwhen using createEntityAdapter. The methods are used inside your reducer function to manage\nthe entity collection based on your provided actions.\n\n### getInitialState\n\nReturns the `initialState` for entity state based on the provided type. Additional state is also provided through the provided configuration object. The initialState is provided to your reducer function.\n\nUsage:\n\n<ngrx-code-example header=\"user.reducer.ts\">\n\n```ts\nimport { Action, createReducer } from '@ngrx/store';\nimport {\n  EntityState,\n  EntityAdapter,\n  createEntityAdapter,\n} from '@ngrx/entity';\n\nexport interface User {\n  id: string;\n  name: string;\n}\n\nexport interface State extends EntityState<User> {\n  // additional entities state properties\n  selectedUserId: string | null;\n}\n\nexport const initialState: State = adapter.getInitialState({\n  // additional entity state properties\n  selectedUserId: null,\n});\n\nexport const userReducer = createReducer(initialState);\n```\n\n</ngrx-code-example>\n\n## Adapter Collection Methods\n\nThe entity adapter also provides methods for operations against an entity. These methods can change\none to many records at a time. Each method returns the newly modified state if changes were made and the same\nstate if no changes were made.\n\n- `addOne`: Add one entity to the collection.\n- `addMany`: Add multiple entities to the collection.\n- `setAll`: Replace current collection with provided collection.\n- `setOne`: Add or Replace one entity in the collection.\n- `setMany`: Add or Replace multiple entities in the collection.\n- `removeOne`: Remove one entity from the collection.\n- `removeMany`: Remove multiple entities from the collection, by id or by predicate.\n- `removeAll`: Clear entity collection.\n- `updateOne`: Update one entity in the collection. Supports partial updates.\n- `updateMany`: Update multiple entities in the collection. Supports partial updates.\n- `upsertOne`: Add or Update one entity in the collection.\n- `upsertMany`: Add or Update multiple entities in the collection.\n- `mapOne`: Update one entity in the collection by defining a map function.\n- `map`: Update multiple entities in the collection by defining a map function, similar to [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).\n\nUsage:\n\n<ngrx-code-example header=\"user.model.ts\">\n\n```ts\nexport interface User {\n  id: string;\n  name: string;\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"user.actions.ts\">\n\n```ts\nimport { createAction, props } from '@ngrx/store';\nimport {\n  Update,\n  EntityMap,\n  EntityMapOne,\n  Predicate,\n} from '@ngrx/entity';\n\nimport { User } from '../models/user.model';\n\nexport const loadUsers = createAction(\n  '[User/API] Load Users',\n  props<{ users: User[] }>()\n);\nexport const setUsers = createAction(\n  '[User/API] Set Users',\n  props<{ users: User[] }>()\n);\nexport const addUser = createAction(\n  '[User/API] Add User',\n  props<{ user: User }>()\n);\nexport const setUser = createAction(\n  '[User/API] Set User',\n  props<{ user: User }>()\n);\nexport const upsertUser = createAction(\n  '[User/API] Upsert User',\n  props<{ user: User }>()\n);\nexport const addUsers = createAction(\n  '[User/API] Add Users',\n  props<{ users: User[] }>()\n);\nexport const upsertUsers = createAction(\n  '[User/API] Upsert Users',\n  props<{ users: User[] }>()\n);\nexport const updateUser = createAction(\n  '[User/API] Update User',\n  props<{ update: Update<User> }>()\n);\nexport const updateUsers = createAction(\n  '[User/API] Update Users',\n  props<{ updates: Update<User>[] }>()\n);\nexport const mapUser = createAction(\n  '[User/API] Map User',\n  props<{ entityMap: EntityMapOne<User> }>()\n);\nexport const mapUsers = createAction(\n  '[User/API] Map Users',\n  props<{ entityMap: EntityMap<User> }>()\n);\nexport const deleteUser = createAction(\n  '[User/API] Delete User',\n  props<{ id: string }>()\n);\nexport const deleteUsers = createAction(\n  '[User/API] Delete Users',\n  props<{ ids: string[] }>()\n);\nexport const deleteUsersByPredicate = createAction(\n  '[User/API] Delete Users By Predicate',\n  props<{ predicate: Predicate<User> }>()\n);\nexport const clearUsers = createAction('[User/API] Clear Users');\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"user.reducer.ts\">\n\n```ts\nimport { Action, createReducer, on } from '@ngrx/store';\nimport {\n  EntityState,\n  EntityAdapter,\n  createEntityAdapter,\n} from '@ngrx/entity';\nimport { User } from '../models/user.model';\nimport * as UserActions from '../actions/user.actions';\n\nexport interface State extends EntityState<User> {\n  // additional entities state properties\n  selectedUserId: string | null;\n}\n\nexport const adapter: EntityAdapter<User> =\n  createEntityAdapter<User>();\n\nexport const initialState: State = adapter.getInitialState({\n  // additional entity state properties\n  selectedUserId: null,\n});\n\nexport const userReducer = createReducer(\n  initialState,\n  on(UserActions.addUser, (state, { user }) => {\n    return adapter.addOne(user, state);\n  }),\n  on(UserActions.setUser, (state, { user }) => {\n    return adapter.setOne(user, state);\n  }),\n  on(UserActions.upsertUser, (state, { user }) => {\n    return adapter.upsertOne(user, state);\n  }),\n  on(UserActions.addUsers, (state, { users }) => {\n    return adapter.addMany(users, state);\n  }),\n  on(UserActions.upsertUsers, (state, { users }) => {\n    return adapter.upsertMany(users, state);\n  }),\n  on(UserActions.updateUser, (state, { update }) => {\n    return adapter.updateOne(update, state);\n  }),\n  on(UserActions.updateUsers, (state, { updates }) => {\n    return adapter.updateMany(updates, state);\n  }),\n  on(UserActions.mapUser, (state, { entityMap }) => {\n    return adapter.mapOne(entityMap, state);\n  }),\n  on(UserActions.mapUsers, (state, { entityMap }) => {\n    return adapter.map(entityMap, state);\n  }),\n  on(UserActions.deleteUser, (state, { id }) => {\n    return adapter.removeOne(id, state);\n  }),\n  on(UserActions.deleteUsers, (state, { ids }) => {\n    return adapter.removeMany(ids, state);\n  }),\n  on(UserActions.deleteUsersByPredicate, (state, { predicate }) => {\n    return adapter.removeMany(predicate, state);\n  }),\n  on(UserActions.loadUsers, (state, { users }) => {\n    return adapter.setAll(users, state);\n  }),\n  on(UserActions.setUsers, (state, { users }) => {\n    return adapter.setMany(users, state);\n  }),\n  on(UserActions.clearUsers, (state) => {\n    return adapter.removeAll({ ...state, selectedUserId: null });\n  })\n);\n\nexport const getSelectedUserId = (state: State) =>\n  state.selectedUserId;\n\n// get the selectors\nconst { selectIds, selectEntities, selectAll, selectTotal } =\n  adapter.getSelectors();\n\n// select the array of user ids\nexport const selectUserIds = selectIds;\n\n// select the dictionary of user entities\nexport const selectUserEntities = selectEntities;\n\n// select the array of users\nexport const selectAllUsers = selectAll;\n\n// select the total user count\nexport const selectUserTotal = selectTotal;\n```\n\n</ngrx-code-example>\n\n### Entity Updates\n\nThere are a few caveats to be aware of when updating entities using the entity adapter.\n\nThe first is that `updateOne` and `updateMany` make use of the `Update<T>` interface shown below. This supports partial updates.\n\n```typescript\ninterface UpdateStr<T> {\n  id: string;\n  changes: Partial<T>;\n}\n\ninterface UpdateNum<T> {\n  id: number;\n  changes: Partial<T>;\n}\n\ntype Update<T> = UpdateStr<T> | UpdateNum<T>;\n```\n\nSecondly, `upsertOne` and `upsertMany` will perform an insert or update. These methods do not support partial updates.\n\n### Entity Selectors\n\nThe `getSelectors` method returned by the created entity adapter provides functions for selecting information from the entity.\n\nThe `getSelectors` method takes a selector function as its only argument to select the piece of state for a defined entity.\n\nUsage:\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nimport {\n  createSelector,\n  createFeatureSelector,\n  ActionReducerMap,\n} from '@ngrx/store';\nimport * as fromUser from './user.reducer';\n\nexport interface State {\n  users: fromUser.State;\n}\n\nexport const reducers: ActionReducerMap<State> = {\n  users: fromUser.reducer,\n};\n\nexport const selectUserState =\n  createFeatureSelector<fromUser.State>('users');\n\nexport const selectUserIds = createSelector(\n  selectUserState,\n  fromUser.selectUserIds // shorthand for usersState => fromUser.selectUserIds(usersState)\n);\nexport const selectUserEntities = createSelector(\n  selectUserState,\n  fromUser.selectUserEntities\n);\nexport const selectAllUsers = createSelector(\n  selectUserState,\n  fromUser.selectAllUsers\n);\nexport const selectUserTotal = createSelector(\n  selectUserState,\n  fromUser.selectUserTotal\n);\nexport const selectCurrentUserId = createSelector(\n  selectUserState,\n  fromUser.getSelectedUserId\n);\n\nexport const selectCurrentUser = createSelector(\n  selectUserEntities,\n  selectCurrentUserId,\n  (userEntities, userId) => userId && userEntities[userId]\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/entity/index.md",
    "content": "# @ngrx/entity\n\nEntity State adapter for managing record collections.\n\nEntity provides an API to manipulate and query entity collections.\n\n- Reduces boilerplate for creating reducers that manage a collection of models.\n- Provides performant CRUD operations for managing entity collections.\n- Extensible type-safe adapters for selecting entity information.\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/entity/install) page.\n\n## Entity and class instances\n\nEntity promotes the use of plain JavaScript objects when managing collections. _ES6 class instances will be transformed into plain JavaScript objects when entities are managed in a collection_. This provides you with some assurances when managing these entities:\n\n1. Guarantee that the data structures contained in state don't themselves contain logic, reducing the chance that they'll mutate themselves.\n2. State will always be serializable allowing you to store and rehydrate from browser storage mechanisms like local storage.\n3. State can be inspected via the Redux Devtools.\n\nThis is one of the [core principles](docs) of NgRx. The [Redux docs](https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state) also offers some more insight into this constraint.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/entity/install.md",
    "content": "# Installation\n\n## Installing with `ng add`\n\nYou can install the Entity package to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/entity@latest\n```\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/entity`.\n2. Run `npm install` to install those dependencies.\n\n## Manual Installation\n\nYou can also install `@ngrx/entity` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/entity\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/entity/interfaces.md",
    "content": "# Entity Interfaces\n\n## EntityState<T>\n\nThe Entity State is a predefined generic interface for a given entity collection with the following interface:\n\n<ngrx-code-example header=\"EntityState Interface\">\n\n```ts\ninterface EntityState<V> {\n  ids: string[] | number[];\n  entities: { [id: string | id: number]: V };\n}\n```\n\n</ngrx-code-example>\n\n- `ids`: An array of all the primary ids in the collection\n- `entities`: A dictionary of entities in the collection indexed by the primary id\n\nExtend this interface to provide any additional properties for the entity state.\n\nUsage:\n\n<ngrx-code-example header=\"user.reducer.ts\">\n\n```ts\nexport interface User {\n  id: string;\n  name: string;\n}\n\nexport interface State extends EntityState<User> {\n  // additional entity state properties\n  selectedUserId: string | null;\n}\n```\n\n</ngrx-code-example>\n\n## EntityAdapter<T>\n\nProvides a generic type interface for the provided entity adapter. The entity adapter provides many collection methods for managing the entity state.\n\nUsage:\n\n<ngrx-code-example header=\"user.reducer.ts\">\n\n```ts\nexport const adapter: EntityAdapter<User> =\n  createEntityAdapter<User>();\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/entity/recipes/additional-state-properties.md",
    "content": "# Additional Entity State Properties Update\n\nIt's possible to add extra properties to a `State` extending from `EntityState`. These properties must be updated manually. Just like in a non-entity state, we can update the added properties in the reducer. This can be done with or without using the `@ngrx/entity` helper functions.\n\nThe steps below show you how to extend the [Entity Adapter](guide/entity/adapter) example.\n\nUsage:\n\nDeclare the `selectedUserId` as an additional property in the interface.\n\n<ngrx-code-example header=\"user.reducer.ts\">\n\n```ts\nimport {\n  EntityState,\n  EntityAdapter,\n  createEntityAdapter,\n} from '@ngrx/entity';\n\nexport interface User {\n  id: string;\n  name: string;\n}\n\nexport interface State extends EntityState<User> {\n  // additional state property\n  selectedUserId: string | null;\n}\n\nexport const adapter: EntityAdapter<User> =\n  createEntityAdapter<User>();\n```\n\n</ngrx-code-example>\n\nThen create an action to update the `selectedUserId`\n\n<ngrx-code-example header=\"user.actions.ts\">\n\n```ts\nimport { createAction, props } from '@ngrx/store';\nimport { Update } from '@ngrx/entity';\n\nimport { User } from '../models/user.model';\n\nexport const selectUser = createAction(\n  '[Users Page] Select User',\n  props<{ userId: string }>()\n);\nexport const loadUsers = createAction(\n  '[User/API] Load Users',\n  props<{ users: User[] }>()\n);\n```\n\n</ngrx-code-example>\n\nThe entity adapter is only used to update the `EntityState` properties. The additional state properties should be updated same as normal state properties, as the example below.\n\n<ngrx-code-example header=\"user.reducer.ts\">\n\n```ts\nimport {\n  EntityState,\n  EntityAdapter,\n  createEntityAdapter,\n} from '@ngrx/entity';\nimport { Action, createReducer, on } from '@ngrx/store';\nimport { User } from '../models/user.model';\nimport * as UserActions from '../actions/user.actions';\n\nexport interface State extends EntityState<User> {\n  // additional state property\n  selectedUserId: string | null;\n}\n\nexport const adapter: EntityAdapter<User> =\n  createEntityAdapter<User>();\n\nexport const initialState: State = adapter.getInitialState({\n  // additional entity state properties\n  selectedUserId: null,\n});\n\nexport const reducer = createReducer(\n  initialState,\n  on(UserActions.selectUser, (state, { userId }) => {\n    return { ...state, selectedUserId: userId };\n  }),\n  on(UserActions.loadUsers, (state, { users }) => {\n    return adapter.addMany(users, { ...state, selectedUserId: null });\n  })\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/entity/recipes/entity-adapter-with-feature-creator.md",
    "content": "# Using Entity Adapter with Feature Creator\n\nThis recipe demonstrates how to combine [entity adapter](/guide/entity/adapter#entity-adapter) (selectors) within [extraSelectors](/guide/store/feature-creators#providing-extra-selectors).\n\nAs an example, let's look at some code from a User state management feature.\n\nStart by defining the state for the `User` feature.\n\n<ngrx-code-example header=\"users.state.ts\">\n\n```ts\nimport { EntityState, createEntityAdapter } from '@ngrx/entity';\nimport { User } from './user.model';\n\nexport interface State extends EntityState<User> {\n  selectedUserId: string | null;\n}\n\nconst adapter = createEntityAdapter<User>();\n\nexport const initialState: State = adapter.getInitialState({\n  selectedUserId: null,\n});\n```\n\n</ngrx-code-example>\n\nThen, we define the `User` actions: `addUser` and `selectUser`.\n\n<ngrx-code-example header=\"user-list-page.actions.ts\">\n\n```ts\nimport { createActionGroup, props } from '@ngrx/store';\nimport { User } from './user.model';\n\nexport const UserListPageActions = createActionGroup({\n  source: 'User List Page',\n  events: {\n    addUser: props<{ user: User }>(),\n    selectUser: props<{ userId: string }>(),\n  },\n});\n```\n\n</ngrx-code-example>\n\nThen use the `createReducer` function to create a reducer for the `User` feature.\n\n<ngrx-code-example header=\"users.state.ts\">\n\n```ts\nimport { createReducer, on } from '@ngrx/store';\nimport { UserListPageActions } from './user-list-page.actions';\n\nconst reducer = createReducer(\n  initialState,\n  on(UserListPageActions.addUser, (state, { user }) =>\n    adapter.addOne(user, state)\n  ),\n  on(UserListPageActions.selectUser, (state, { userId }) => ({\n    ...state,\n    selectedUserId: userId,\n  }))\n);\n```\n\n</ngrx-code-example>\n\nThen create the `User` feature using the `createFeature` function.\nThis generates all the basic selectors automatically, and we can specify extra selectors using the `extraSelectors` option.\n\n<ngrx-code-example header=\"users.state.ts\">\n\n```ts\nimport { createFeature, createSelector } from '@ngrx/store';\n\nexport const usersFeature = createFeature({\n  name: 'users',\n  reducer,\n  extraSelectors: ({\n    selectUsersState,\n    selectEntities,\n    selectSelectedUserId,\n  }) => ({\n    ...adapter.getSelectors(selectUsersState),\n    selectIsUserSelected: createSelector(\n      selectSelectedUserId,\n      (selectedId) => selectedId !== null\n    ),\n    selectSelectedUser: createSelector(\n      selectSelectedUserId,\n      selectEntities,\n      (selectedId, entities) =>\n        selectedId ? entities[selectedId] : null\n    ),\n  }),\n});\n```\n\n</ngrx-code-example>\n\nTo use the selector within a component, `inject` the `Store` and select the data from the state using the selectors generated by the `createFeature` function.\n\n<ngrx-code-example header=\"user-list.component.ts\">\n\n```ts\nimport { usersFeature } from './users.state';\n\n@Component({\n  /* ... */\n})\nexport class UserListComponent {\n  private readonly store = inject(Store);\n\n  readonly users$ = this.store.select(usersFeature.selectAll);\n  readonly isUserSelected$ = this.store.select(\n    usersFeature.selectIsUserSelected\n  );\n  readonly selectedUser$ = this.store.select(\n    usersFeature.selectSelectedUser\n  );\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/index.md",
    "content": "# Overview\n\nYou can use [ESLint](https://eslint.org/) to follow best practices and avoid common pitfalls in your application.\n\nThe NgRx ESLint Plugin is no different and promotes the key concepts to create maintainable projects.\nIt consists of [rules](#rules) that are grouped into predefined [configurations](#configurations) for each NgRx package to help you get started quickly.\n\nBy default, all rules have set the severity level to `error`.\nSome rules also include a recommendation or an automatic fix using `ng lint --fix`.\n\n## Configuration and Usage\n\n### ESLint v9 (Flat Config)\n\nTo use the NgRx ESLint Plugin with ESLint v9, include the desired configurations within your ESLint configuration file (e.g. `eslint.config.js`).\nOptionally override some rules via the `rules` property.\n\nImport the NgRx Plugin via `@ngrx/eslint-plugin/v9` and use one or more predefined [configurations](#configurations) by adding them to the `extends` array.\n\n<ngrx-code-example>\n\n```ts\nconst tseslint = require('typescript-eslint');\nconst ngrx = require('@ngrx/eslint-plugin/v9');\n\nmodule.exports = tseslint.config({\n  files: ['**/*.ts'],\n  extends: [\n    // 👇 Use all rules at once\n    ...ngrx.configs.all,\n    // 👇 Or only import the rules for a specific package\n    ...ngrx.configs.store,\n    ...ngrx.configs.effects,\n    ...ngrx.configs.componentStore,\n    ...ngrx.configs.operators,\n    ...ngrx.configs.signals,\n    // 👇 Include rules that require type information\n    ...ngrx.configs.allTypeChecked,\n    ...ngrx.configs.effectsChecked,\n    ...ngrx.configs.signalsChecked,\n  ],\n  rules: {\n    // 👇 Configure specific rules\n    '@ngrx/with-state-no-arrays-at-root-level': 'warn',\n  },\n});\n```\n\n</ngrx-code-example>\n\nTo enable rules that require type information, the ESLint `parserOptions` configuration needs be configured. For more info see the [TypeScript ESLint documentation](https://typescript-eslint.io/getting-started/typed-linting/).\n\n```ts\nconst tseslint = require('typescript-eslint');\nconst ngrx = require('@ngrx/eslint-plugin/v9');\n\nmodule.exports = tseslint.config({\n  files: ['**/*.ts'],\n  extends: [\n    // Rules that require type information\n    ...ngrx.configs.allTypeChecked,\n    ...ngrx.configs.effectsChecked,\n    ...ngrx.configs.signalsChecked,\n  ],\n  {\n    languageOptions: {\n      parserOptions: {\n        projectService: true,\n      },\n    }\n  }\n});\n```\n\n### ESLint v8 (Legacy Config)\n\nTo use the NgRx ESLint Plugin with ESLint v8, add it to your ESLint file (e.g. `.eslintrc.json`).\nAdd the `@ngrx` plugin to the `plugins` section and add the rules you want to use within your project to the `rules` section.\n\n```json\n{\n  \"plugins\": [\"@ngrx\"],\n  \"rules\": {\n    \"@ngrx/good-action-hygiene\": \"error\"\n  }\n}\n```\n\nFor rules that require type information, the ESLint configuration needs to provide the `parserOptions.project` property, otherwise the rule throws an error.\n\n```json\n{\n  \"plugins\": [\"@ngrx\"],\n  \"parserOptions\": {\n    \"project\": \"tsconfig.json\"\n  },\n  \"rules\": {\n    \"@ngrx/avoid-cyclic-effects\": \"error\"\n  }\n}\n```\n\nInstead of adding rules individually, you can use one of the [preconfigured configurations](#configurations) by adding it to the `extends` section.\nThis automatically includes all rules of the configuration.\nTo override a specific rule, add it to the `rules` section and adjust the severity level or the configuration.\n\n```json\n{\n  \"extends\": [\"plugin:@ngrx/all\"],\n  \"rules\": {\n    \"@ngrx/good-action-hygiene\": \"warn\"\n  }\n}\n```\n\nInstead of including all NgRx rules, you can also use a specific configuration for a package.\nThis is useful if you only use a specific package, as it only includes the rules relevant to that package.\n\n```json\n{\n  \"extends\": [\"@ngrx/signals\"]\n}\n```\n\n## Rules\n\n<!-- DO NOT EDIT, this table is automatically generated-->\n<!-- RULES-CONFIG:START -->\n\n### component-store\n\n| Name                                                                                                                    | Description                                                                             | Category   | Fixable | Has suggestions | Configurable | Requires type information |\n| ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ---------- | ------- | --------------- | ------------ | ------------------------- |\n| [@ngrx/avoid-combining-component-store-selectors](/guide/eslint-plugin/rules/avoid-combining-component-store-selectors) | Prefer combining selectors at the selector level.                                       | suggestion | No      | No              | No           | No                        |\n| [@ngrx/avoid-mapping-component-store-selectors](/guide/eslint-plugin/rules/avoid-mapping-component-store-selectors)     | Avoid mapping logic outside the selector level.                                         | problem    | No      | No              | No           | No                        |\n| [@ngrx/require-super-ondestroy](/guide/eslint-plugin/rules/require-super-ondestroy)                                     | Overriden ngOnDestroy method in component stores require a call to super.ngOnDestroy(). | problem    | No      | No              | No           | No                        |\n| [@ngrx/updater-explicit-return-type](/guide/eslint-plugin/rules/updater-explicit-return-type)                           | `Updater` should have an explicit return type.                                          | problem    | No      | No              | No           | No                        |\n\n### effects\n\n| Name                                                                                                                    | Description                                                                                     | Category   | Fixable | Has suggestions | Configurable | Requires type information |\n| ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------- | ------- | --------------- | ------------ | ------------------------- |\n| [@ngrx/avoid-cyclic-effects](/guide/eslint-plugin/rules/avoid-cyclic-effects)                                           | Avoid `Effect` that re-emit filtered actions.                                                   | problem    | No      | No              | No           | Yes                       |\n| [@ngrx/no-dispatch-in-effects](/guide/eslint-plugin/rules/no-dispatch-in-effects)                                       | `Effect` should not call `store.dispatch`.                                                      | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/no-effects-in-providers](/guide/eslint-plugin/rules/no-effects-in-providers)                                     | `Effect` should not be listed as a provider if it is added to the `EffectsModule`.              | problem    | Yes     | No              | No           | No                        |\n| [@ngrx/no-multiple-actions-in-effects](/guide/eslint-plugin/rules/no-multiple-actions-in-effects)                       | `Effect` should not return multiple actions.                                                    | problem    | No      | No              | No           | Yes                       |\n| [@ngrx/prefer-action-creator-in-of-type](/guide/eslint-plugin/rules/prefer-action-creator-in-of-type)                   | Using `action creator` in `ofType` is preferred over `string`.                                  | suggestion | No      | No              | No           | No                        |\n| [@ngrx/prefer-effect-callback-in-block-statement](/guide/eslint-plugin/rules/prefer-effect-callback-in-block-statement) | A block statement is easier to troubleshoot.                                                    | suggestion | Yes     | No              | No           | No                        |\n| [@ngrx/use-effects-lifecycle-interface](/guide/eslint-plugin/rules/use-effects-lifecycle-interface)                     | Ensures classes implement lifecycle interfaces corresponding to the declared lifecycle methods. | suggestion | Yes     | No              | No           | No                        |\n\n### operators\n\n| Name                                                                                    | Description                                                                                                                      | Category | Fixable | Has suggestions | Configurable | Requires type information |\n| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | --------------- | ------------ | ------------------------- |\n| [@ngrx/prefer-concat-latest-from](/guide/eslint-plugin/rules/prefer-concat-latest-from) | Use `concatLatestFrom` instead of `withLatestFrom` to prevent the selector from firing until the correct `Action` is dispatched. | problem  | Yes     | No              | Yes          | No                        |\n\n### signals\n\n| Name                                                                                                                          | Description                                                                       | Category   | Fixable | Has suggestions | Configurable | Requires type information |\n| ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ---------- | ------- | --------------- | ------------ | ------------------------- |\n| [@ngrx/enforce-type-call](/guide/eslint-plugin/rules/enforce-type-call)                                                       | The `type` function must be called.                                               | problem    | Yes     | No              | No           | No                        |\n| [@ngrx/prefer-protected-state](/guide/eslint-plugin/rules/prefer-protected-state)                                             | A Signal Store prefers protected state                                            | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/signal-state-no-arrays-at-root-level](/guide/eslint-plugin/rules/signal-state-no-arrays-at-root-level)                 | signalState should accept a record or dictionary as an input argument.            | problem    | No      | No              | No           | Yes                       |\n| [@ngrx/signal-store-feature-should-use-generic-type](/guide/eslint-plugin/rules/signal-store-feature-should-use-generic-type) | A custom Signal Store feature that accepts an input should define a generic type. | problem    | Yes     | No              | No           | No                        |\n| [@ngrx/with-state-no-arrays-at-root-level](/guide/eslint-plugin/rules/with-state-no-arrays-at-root-level)                     | withState should accept a record or dictionary as an input argument.              | problem    | No      | No              | No           | Yes                       |\n\n### store\n\n| Name                                                                                                                                    | Description                                                                      | Category   | Fixable | Has suggestions | Configurable | Requires type information |\n| --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------- | ------- | --------------- | ------------ | ------------------------- |\n| [@ngrx/avoid-combining-selectors](/guide/eslint-plugin/rules/avoid-combining-selectors)                                                 | Prefer combining selectors at the selector level.                                | suggestion | No      | No              | No           | No                        |\n| [@ngrx/avoid-dispatching-multiple-actions-sequentially](/guide/eslint-plugin/rules/avoid-dispatching-multiple-actions-sequentially)     | It is recommended to only dispatch one `Action` at a time.                       | suggestion | No      | No              | No           | No                        |\n| [@ngrx/avoid-duplicate-actions-in-reducer](/guide/eslint-plugin/rules/avoid-duplicate-actions-in-reducer)                               | A `Reducer` should handle an `Action` once.                                      | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/avoid-mapping-selectors](/guide/eslint-plugin/rules/avoid-mapping-selectors)                                                     | Avoid mapping logic outside the selector level.                                  | suggestion | No      | No              | No           | No                        |\n| [@ngrx/good-action-hygiene](/guide/eslint-plugin/rules/good-action-hygiene)                                                             | Ensures the use of good action hygiene.                                          | suggestion | No      | No              | No           | No                        |\n| [@ngrx/no-multiple-global-stores](/guide/eslint-plugin/rules/no-multiple-global-stores)                                                 | There should only be one global store injected.                                  | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/no-reducer-in-key-names](/guide/eslint-plugin/rules/no-reducer-in-key-names)                                                     | Avoid the word \"reducer\" in the key names.                                       | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/no-store-subscription](/guide/eslint-plugin/rules/no-store-subscription)                                                         | Using the `async` pipe is preferred over `store` subscription.                   | suggestion | No      | No              | No           | No                        |\n| [@ngrx/no-typed-global-store](/guide/eslint-plugin/rules/no-typed-global-store)                                                         | The global store should not be typed.                                            | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/on-function-explicit-return-type](/guide/eslint-plugin/rules/on-function-explicit-return-type)                                   | `On` function should have an explicit return type.                               | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/prefer-action-creator-in-dispatch](/guide/eslint-plugin/rules/prefer-action-creator-in-dispatch)                                 | Using `action creator` in `dispatch` is preferred over `object` or old `Action`. | suggestion | No      | No              | No           | No                        |\n| [@ngrx/prefer-action-creator](/guide/eslint-plugin/rules/prefer-action-creator)                                                         | Using `action creator` is preferred over `Action class`.                         | suggestion | No      | No              | No           | No                        |\n| [@ngrx/prefer-inline-action-props](/guide/eslint-plugin/rules/prefer-inline-action-props)                                               | Prefer using inline types instead of interfaces, types or classes.               | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/prefer-one-generic-in-create-for-feature-selector](/guide/eslint-plugin/rules/prefer-one-generic-in-create-for-feature-selector) | Prefer using a single generic to define the feature state.                       | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/prefer-selector-in-select](/guide/eslint-plugin/rules/prefer-selector-in-select)                                                 | Using a selector in the `select` is preferred over `string` or `props drilling`. | suggestion | No      | No              | No           | No                        |\n| [@ngrx/prefix-selectors-with-select](/guide/eslint-plugin/rules/prefix-selectors-with-select)                                           | The selector should start with \"select\", for example \"selectEntity\".             | suggestion | No      | Yes             | No           | No                        |\n| [@ngrx/select-style](/guide/eslint-plugin/rules/select-style)                                                                           | Selector can be used either with `select` as a pipeable operator or as a method. | suggestion | Yes     | No              | Yes          | No                        |\n| [@ngrx/use-consistent-global-store-name](/guide/eslint-plugin/rules/use-consistent-global-store-name)                                   | Use a consistent name for the global store.                                      | suggestion | No      | Yes             | Yes          | No                        |\n\n<!-- RULES-CONFIG:END -->\n\n## Configurations\n\n<!-- DO NOT EDIT, this table is automatically generated-->\n<!-- CONFIGURATIONS-CONFIG:START -->\n\n| Name                                                                                                                           |\n| ------------------------------------------------------------------------------------------------------------------------------ |\n| [all-type-checked](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/all-type-checked.json)         |\n| [all](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/all.json)                                   |\n| [component-store](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/component-store.json)           |\n| [effects-type-checked](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/effects-type-checked.json) |\n| [effects](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/effects.json)                           |\n| [operators](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/operators.json)                       |\n| [signals-type-checked](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/signals-type-checked.json) |\n| [signals](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/signals.json)                           |\n| [store](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/store.json)                               |\n\n<!-- CONFIGURATIONS-CONFIG:END -->\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/install.md",
    "content": "# Installation\n\n## Installing with `ng add`\n\nYou can install the ESLint plugin to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/eslint-plugin\n```\n\nThe command will prompt you to select which config you would like to have preconfigured; you can read the detailed list of available configs [here](guide/eslint-plugin/#configurations).\n\n## Manual Installation\n\nYou can also install `@ngrx/eslint-plugin` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/eslint-plugin\" npm-flags=\"--save-dev\" yarn-flags=\"-D\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/avoid-combining-component-store-selectors.md",
    "content": "# avoid-combining-component-store-selectors\n\nPrefer combining selectors at the selector level.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nExamples of **incorrect** code for this rule:\n\n### Enrich state with other state in component\n\n<ngrx-code-example>\n\n```ts\nexport class Component extends ComponentStore<MoviesState> {\n  all$ = combineLatest(\n    this.select((state) => state.movies),\n    this.select((state) => state.books)\n  );\n\n  constructor() {\n    super({ movies: [], books: [] });\n  }\n}\n```\n\n</ngrx-code-example>\n\n### Filter state in component\n\n```ts\nexport class Component extends ComponentStore<MoviesState> {\n  movie$ = combineLatest(\n    this.select((state) => state.movies),\n    this.select((state) => state.selectedId)\n  ).pipe(map(([movies, selectedId]) => movies[selectedId]));\n\n  constructor() {\n    super({ movies: [] });\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n### Enrich state with other state in selector\n\n```ts\nexport class Component extends ComponentStore<StoreState> {\n  movies$ = this.select((state) => state.movies);\n  books$ = this.select((state) => state.books);\n  all$ = this.select(this.movies$, this.books$, ([movies, books]) => {\n    return {\n      movies,\n      books,\n    };\n  });\n\n  constructor() {\n    super({ movies: [], books: [] });\n  }\n}\n```\n\n### Filter state in selector\n\n<ngrx-code-example>\n\n```ts\nexport class Component extends ComponentStore<MoviesState> {\n  movies$ = this.select((state) => state.movies);\n  selectedId$ = this.select((state) => state.selectedId);\n  movie$ = this.select(\n    this.movies$,\n    this.selectedId$,\n    ([movies, selectedId]) => movies[selectedId]\n  );\n\n  constructor() {\n    super({ movies: [] });\n  }\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/avoid-combining-selectors.md",
    "content": "# avoid-combining-selectors\n\nPrefer combining selectors at the selector level.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nA selector is a pure function that is used to derive state.\nBecause a selector is a pure function (and a synchronous function), it's easier to test.\n\nThat's why it's recommended to build a view model by composing multiple selectors into one selector, instead of consuming multiple selector observable streams to create a view model in the component.\n\nExamples of **incorrect** code for this rule:\n\n### Enrich state with other state in component\n\n<ngrx-code-example>\n\n```ts\nexport class Component {\n  vm$ = combineLatest(\n    this.store.select(selectCustomers),\n    this.store.select(selectOrders)\n  ).pipe(\n    map(([customers, orders]) => {\n      return customers.map((c) => {\n        return {\n          customerId: c.id,\n          name: c.name,\n          orders: orders.filter((o) => o.customerId === c.id),\n        };\n      });\n    })\n  );\n}\n```\n\n</ngrx-code-example>\n\n### Filter state in component\n\n```ts\nexport class Component {\n  customer$ = this.store\n    .select(selectCustomers)\n    .pipe(withLatestFrom(this.store.select(selectActiveCustomerId)))\n    .pipe(\n      map(([customers, customerId]) => {\n        return customers[customerId];\n      })\n    );\n}\n```\n\nExamples of **correct** code for this rule:\n\n### Enrich state with other state in selector\n\n```ts\nexport selectCustomersAndOrders = createSelector(\n  selectCustomers,\n  selectOrders,\n  (customers, orders) => {\n    return {\n      customerId: c.id,\n      name: c.name,\n      orders: orders.filter((o) => o.customerId === c.id),\n    }\n  }\n)\n\nexport class Component {\n  vm$ = this.store.select(selectCustomersAndOrders);\n}\n```\n\n### Filter state in selector\n\n<ngrx-code-example>\n\n```ts\nexport selectActiveCustomer = createSelector(\n  selectCustomers,\n  selectActiveCustomerId,\n  ([customers, customerId]) => {\n    return customers[customerId];\n  }\n)\n\nexport class Component {\n  customer$ = this.store.select(selectActiveCustomer);\n}\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- [Maximizing and Simplifying Component Views with NgRx Selectors - by Brandon Roberts](https://brandonroberts.dev/blog/posts/2020-12-14-maximizing-simplifying-component-views-ngrx-selectors/#building-view-models)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/avoid-cyclic-effects.md",
    "content": "# avoid-cyclic-effects\n\nAvoid `Effect` that re-emit filtered actions.\n\n- **Type**: problem\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: Yes\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThis rule prevents that the same action as the filtered action is dispatched in an effect, causing an infinite loop.\nEffects that are configured with `dispatch: false`, are discarded.\n\nFor the rare cases where you need to re-dispatch the same action, you can disable this rule.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nclass Effect {\n  details$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(fromCustomers.pageLoaded),\n      map(() => fromCustomers.pageLoaded())\n    )\n  );\n\n  constructor(private actions$: Actions) {}\n}\n\nclass Effect {\n  details$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(fromCustomers.pageLoaded),\n      tap(() => alert('Customers loaded'))\n    )\n  );\n\n  constructor(private actions$: Actions) {}\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nclass Effect {\n  details$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(fromCustomers.pageLoaded),\n      map(() => fromCustomers.pageLoadedSuccess())\n    )\n  );\n\n  constructor(private actions$: Actions) {}\n}\n\nclass Effect {\n  details$ = createEffect(\n    () =>\n      this.actions$.pipe(\n        ofType(fromCustomers.pageLoaded),\n        tap(() => alert('Customers loaded'))\n      ),\n    { dispatch: false }\n  );\n\n  constructor(private actions$: Actions) {}\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/avoid-dispatching-multiple-actions-sequentially.md",
    "content": "# avoid-dispatching-multiple-actions-sequentially\n\nIt is recommended to only dispatch one `Action` at a time.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nAn action should be an event that abstracts away the details of store internals.\nAn action can be a composition of several events, in which case the developer might be tempted to dispatch several actions in sequence. But a better approach is to dispatch one \"combining\" action, which exactly describes what that event entails.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Component implement OnInit {\n  constructor(\n    private readonly store: Store,\n  ) {}\n\n  ngOnInit() {\n    // ⚠ multiple actions dispatched\n    this.store.dispatch(loadEmployeeList());\n    this.store.dispatch(loadCompanyList());\n    this.store.dispatch(cleanData());\n  }\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\n// in component code:\nexport class Component implement OnInit {\n  constructor(\n    private readonly store: Store,\n  ) {}\n\n  ngOnInit() {\n    this.store.dispatch(componentLoaded());\n  }\n}\n\n// in effect:\nexport class Effects {\n\n  loadEmployeeList$ = createEffect(() => this.actions.pipe(\n    ofType(componentLoaded),\n    exhaustMap(() => this.dataService.loadEmployeeList().pipe(\n      map(response => loadEmployeeListSuccess(response)),\n      catchError(error => loadEmployeeListError(error)),\n    )),\n  ));\n\n  loadCompanyList$ = createEffect(() => this.actions.pipe(\n    ofType(componentLoaded),\n    // handle loadCompanyList\n  ));\n\n  cleanData$ = createEffect(() => this.actions.pipe(\n    ofType(componentLoaded),\n    // handle cleanData\n  ));\n\n  constructor(\n    private readonly actions$: Actions,\n  ) {}\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/avoid-duplicate-actions-in-reducer.md",
    "content": "# avoid-duplicate-actions-in-reducer\n\nA `Reducer` should handle an `Action` once.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nWhile it's technically allowed to handle an action more than once in a reducer, this often means something is wrong. Probably this is just a typo or a copy-paste mistake. When that's not the case, it's often desired to rethink and refactor the reducer.\n\nA valid reason why an action can be handled more than once in a single reducer, is when the reducer is consumed by a [higher-order reducer](https://github.com/ngrx/platform/issues/1956#issuecomment-526720340). If that's the case, this rule isn't triggered.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport const reducer = createReducer(\n  initialState,\n  on(customerLoaded, (state) => ({ ...state, status: 'loaded' })),\n  on(customerLoaded, (state) => ({ ...state, status: 'loaded' }))\n);\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport const reducer = createReducer(\n  initialState,\n  on(customerLoaded, (state) => ({ ...state, status: 'loaded' }))\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/avoid-mapping-component-store-selectors.md",
    "content": "# avoid-mapping-component-store-selectors\n\nAvoid mapping logic outside the selector level.\n\n- **Type**: problem\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class UserStore extends ComponentStore<UserState> {\n  loggedInUser$ = this.select((state) => state.loggedInUser);\n  //                                           ⚠ Avoid mapping logic outside the selector level.\n  name$ = this.select((state) => state.loggedInUser).pipe(\n    map((user) => user.name)\n  );\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class UserStore extends ComponentStore<UserState> {\n  loggedInUser$ = this.select((state) => state.loggedInUser);\n\n  name$ = this.select(this.loggedInUser$, (user) => user.name);\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/avoid-mapping-selectors.md",
    "content": "# avoid-mapping-selectors\n\nAvoid mapping logic outside the selector level.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nA selector is a pure function that is used to derive state.\nBecause a selector is a pure function (and it's synchronous), it's easier to test.\n\nThat's why it's recommended to put (mapping) logic into a selector, instead of in the component by using the RxJS `map` operator.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Component {\n  name$ = this.store\n    .select(selectLoggedInUser)\n    .pipe(map((user) => ({ name: user.name })));\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\n// in selectors.ts:\nexport selectLoggedInUserName = createSelector(\n  selectLoggedInUser,\n  (user) => user.name\n)\n\n// in component:\nexport class Component {\n  name$ = this.store.select(selectLoggedInUserName)\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/enforce-type-call.md",
    "content": "# enforce-type-call\n\nThe `type` function must be called.\n\n- **Type**: problem\n- **Fixable**: Yes\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThis rule ensures that the `type` function from `@ngrx/signals` is properly called when used to define types. The function must be invoked with parentheses `()` after the generic type parameter.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nimport { type } from '@ngrx/signals';\nconst stateType = type<{ count: number }>;\n```\n\n</ngrx-code-example>\n\n```ts\nimport { type as typeFn } from '@ngrx/signals';\nconst stateType = typeFn<{ count: number }>;\n```\n\nExamples of **correct** code for this rule:\n\n```ts\nimport { type } from '@ngrx/signals';\nconst stateType = type<{ count: number }>();\n```\n\n<ngrx-code-example>\n\n```ts\nimport { type as typeFn } from '@ngrx/signals';\nconst stateType = typeFn<{ count: number; name: string }>();\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- [Signal Store Documentation](guide/signals/signal-store)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/good-action-hygiene.md",
    "content": "# good-action-hygiene\n\nEnsures the use of good action hygiene.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nAn action should be a unique event in the application.\nAn action should:\n\n- tell where it's dispatched\n- tell what event has occurred\n\nThe template we use for an action's type is `[Source] Event`.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport const customersRefresh = createAction('Refresh Customers');\nexport const customersLoadedSuccess = createAction(\n  'Customers Loaded Success'\n);\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport const customersRefresh = createAction(\n  '[Customers Page] Refresh clicked'\n);\nexport const customersLoadedSuccess = createAction(\n  '[Customers API] Customers Loaded Success'\n);\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- [Good Action Hygiene with NgRx Mike Ryan](https://www.youtube.com/watch?v=JmnsEvoy-gY)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/no-dispatch-in-effects.md",
    "content": "# no-dispatch-in-effects\n\n`Effect` should not call `store.dispatch`.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nAn effect should handle actions and map them to a singular action.\nEach effect should be clear and concise and must be understandable to what it does affect.\nDispatching an action from inside an effect instead of (or together with) mapping it to an action can result in painfully hard-to-find bugs and is generally considered a bad practice.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Effects {\n  loadData$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(loadData),\n      exhaustMap(() =>\n        this.dataService.getData().pipe(\n          tap((response) => {\n            // ⚠ dispatching another action from an effect\n            if (response.condition) {\n              this.store.dispatch(anotherAction());\n            }\n          }),\n          map((response) => loadDataSuccess(response)),\n          catchError((error) => of(loadDataError(error)))\n        )\n      )\n    )\n  );\n\n  constructor(\n    private readonly actions$: Actions,\n    private readonly store: Store\n  ) {}\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Effects {\n  loadData$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(loadData),\n      exhaustMap(() =>\n        this.dataService.getData().pipe(\n          map((response) => loadDataSuccess(response)),\n          catchError((error) => of(loadDataError(error)))\n        )\n      )\n    )\n  );\n\n  handleCondition$ = createEffect(() =>\n    this.actions$.pipe(\n      ofType(loadDataSuccess),\n      filter((response) => response.condition),\n      exhaustMap(() =>\n        this.dataService.getOtherData().pipe(\n          map((data) => anotherAction(data)),\n          catchError((error) => of(handleConditionError(error)))\n        )\n      )\n    )\n  );\n\n  constructor(private readonly actions$: Actions) {}\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/no-effects-in-providers.md",
    "content": "# no-effects-in-providers\n\n`Effect` should not be listed as a provider if it is added to the `EffectsModule`.\n\n- **Type**: problem\n- **Fixable**: Yes\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nAn effect class should only be added to the `EffectsModule` by using the `forRoot()` and `forFeature()` methods, **not** by adding the effect class to the Angular providers.\n\nExamples of **incorrect** code for this rule:\n\nWith `forRoot`:\n\n<ngrx-code-example>\n\n```ts\n@NgModule({\n  imports: [EffectsModule.forRoot([CustomersEffect])],\n  providers: [CustomersEffect],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n\nWith `forFeature`:\n\n```ts\n@NgModule({\n  imports: [EffectsModule.forFeature([CustomersEffect])],\n  providers: [CustomersEffect],\n})\nexport class CustomersModule {}\n```\n\nExamples of **correct** code for this rule:\n\nWith `forRoot`:\n\n```ts\n@NgModule({\n  imports: [EffectsModule.forRoot([CustomersEffect])],\n})\nexport class AppModule {}\n```\n\nWith `forFeature`:\n\n<ngrx-code-example>\n\n```ts\n@NgModule({\n  imports: [EffectsModule.forFeature([CustomersEffect])],\n})\nexport class CustomersModule {}\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- [EffectsModule API](api/effects/EffectsModule)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/no-multiple-actions-in-effects.md",
    "content": "# no-multiple-actions-in-effects\n\n`Effect` should not return multiple actions.\n\n- **Type**: problem\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: Yes\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nAn Effect should map one event (action) to a single other action.\nAn action can result in several other Effects being triggered, or multiple changes in the reducer, in which case the developer might be tempted to map an Effect to several actions. A more understandable approach is to dispatch one \"combining\" action that describes what happened (a unique event), rather than multiple actions.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Effects {\n  loadEmployeeList$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(componentLoaded),\n      exhaustMap(() =>\n        this.dataService.loadEmployeeList().pipe(\n          switchMap((response) => [\n            loadEmployeeListSuccess(response),\n            loadCompanyList(),\n            cleanData(),\n          ]),\n          catchError((error) => loadEmployeeListError(error))\n        )\n      )\n    );\n  });\n\n  loadCompanyList$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(loadCompanyList)\n      // handle loadCompanyList\n    );\n  });\n\n  cleanData$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(cleanData)\n      // handle cleanData\n    );\n  });\n\n  constructor(private readonly actions$: Actions) {}\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\n// in effect:\nexport class Effects {\n  loadEmployeeList$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(componentLoaded),\n      exhaustMap(() =>\n        this.dataService.loadEmployeeList().pipe(\n          map((response) => loadEmployeeListSuccess(response)),\n          catchError((error) => loadEmployeeListError(error))\n        )\n      )\n    );\n  });\n\n  // use the one dispatched action\n\n  loadCompanyList$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(loadEmployeeListSuccess)\n      // handle loadCompanyList\n    );\n  });\n\n  //use the one dispatched action\n\n  cleanData$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(loadEmployeeListSuccess)\n      // handle cleanData\n    );\n  });\n\n  constructor(private readonly actions$: Actions) {}\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/no-multiple-global-stores.md",
    "content": "# no-multiple-global-stores\n\nThere should only be one global store injected.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThere is only one global store, thus there should also only be one global store injected in a class (component, service, ...). Violating this rule is often paired with violating the [`no-typed-global-store`](guide/eslint-plugin/rules/no-typed-global-store) rule.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Component {\n  constructor(\n    private readonly customersStore: Store<Customers>,\n    private readonly catalogStore: Store<Catalog>\n  ) {}\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Component {\n  constructor(private readonly store: Store) {}\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/no-reducer-in-key-names.md",
    "content": "# no-reducer-in-key-names\n\nAvoid the word \"reducer\" in the key names.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nStoreModule.forRoot({\n  customersReducer: customersReducer,\n});\n\nStoreModule.forFeature({\n  customersReducer,\n});\n\nexport const reducers: ActionReducerMap<AppState> = {\n  customersReducer: fromCustomers.reducer,\n};\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nStoreModule.forRoot({\n  customers: customersReducer,\n});\n\nStoreModule.forFeature({\n  customers: customersReducer,\n});\n\nexport const reducers: ActionReducerMap<AppState> = {\n  customers: fromCustomers.reducer,\n};\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- [Redux Style Guide: Name State Slices Based On the Stored Data](https://redux.js.org/style-guide/style-guide#name-state-slices-based-on-the-stored-data)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/no-store-subscription.md",
    "content": "# no-store-subscription\n\nUsing the `async` pipe is preferred over `store` subscription.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThis rule prevents manual subscriptions to the store, which can lead to memory leaks if not properly unsubscribed. Using the `async` pipe is the recommended approach because Angular automatically handles the subscription and unsubscription, keeping your component code cleaner and preventing memory leaks.\n\n### Why Avoid Store Subscriptions?\n\n⚠️ **Problems with manual store subscriptions:**\n\n- Creates a manual subscription that must be explicitly unsubscribed (e.g., using `takeUntil`, `async`, or `destroyRef`), otherwise it will persist beyond the component's lifecycle causing memory leaks\nWill not trigger change detection in zoneless mode. Users have to ensure it otherwise.\n\n✅ **Benefits of using the async pipe:**\n\n- No manual subscription management - Angular's `async` pipe handles subscription and unsubscription automatically\n- Keeps the component more declarative\n\nExamples of **incorrect** code for this rule:\n\n1. Subscribing to the store to get data from a selector\n\n<ngrx-code-example>\n\n```ts\nngOnInit() {\n  this.store.select(selectedProduct).subscribe(product => {\n    this.product = product;\n  })\n}\n```\n\n</ngrx-code-example>\n\n2. Subscribing to the store for side effects\n\n<ngrx-code-example>\n\n```ts\nngOnInit() {\n  this.subscription = this.store\n    .select(selectAuthenticatedUserState)\n    .subscribe(state => {\n      this.busy = state.busy;\n      if (state.authenticated) {\n        this.router.navigateByUrl('/');\n      }\n    });\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n1. Using the `async` pipe to get data from a selector\n\n<ngrx-code-example>\n\n```ts\n// in code\nselectedProduct$ = this.store.select(selectedProduct);\n\n// in the template\n<product-details [product]=\"selectedProduct$ | async\"></product-details>\n```\n\n</ngrx-code-example>\n\n2. Consider using an effect for side effects\n\n<ngrx-code-example>\n\n```ts\nredirectIfAuthenticated$ = createEffect(\n  () => {\n    return this.store.select(selectAuthenticatedUserState).pipe(\n      filter(state => state.authenticated),\n      tap(() => {\n        this.router.navigateByUrl('/');\n      })\n    );\n  },\n  { dispatch: false }\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/no-typed-global-store.md",
    "content": "# no-typed-global-store\n\nThe global store should not be typed.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nTyping the global `Store` is redundant because selectors are type-safe, so adding the generic state interface while injecting the store is unnecessary.\nProviding the wrong type can also result in unexpected type-related problems. See [discussion](https://github.com/ngrx/platform/issues/2780) for more info.\n\nTo prevent a misconception that there are multiple stores (and even that multiple stores are injected into the same component, see [`no-multiple-global-stores`](guide/eslint-plugin/rules/no-multiple-global-stores)), we only want to inject 1 global store into components, effects, and services.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Component {\n  data$ = this.store.select(data);\n\n  constructor(private readonly store: Store<{ data: Data }>) {}\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Component {\n  data$ = this.store.select(data);\n\n  constructor(private readonly store: Store) {}\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/on-function-explicit-return-type.md",
    "content": "# on-function-explicit-return-type\n\n`On` function should have an explicit return type.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nWhen we use the `on` function to create reducers, we usually copy the state into a new object, and then add the properties that are being modified after that certain action. This may result in unexpected typing problems, we can add new properties into the state that did not exist previously. TypeScript doesn't see this as a problem and might change the state's interface. The solution is to provide an explicit return type to the `on` function callback.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport interface AppState {\n  username: string;\n}\n\nconst reducer = createReducer<AppState>(\n  { username: '' },\n  on(setUsername, (state, action) => ({\n    ...state,\n    username: action.payload,\n    newProperty: 1, // we added a property that does not exist on `AppState`, and TS won't catch this problem\n  }))\n);\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport interface AppState {\n  username: string;\n}\n\nconst reducer = createReducer<AppState>(\n  { username: '' },\n  on(\n    setUsername,\n    (state, action): AppState => ({\n      ...state,\n      username: action.payload,\n      // adding new properties that do not exist on `AppState` is impossible, as the function return type is explicitly stated\n    })\n  )\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefer-action-creator-in-dispatch.md",
    "content": "# prefer-action-creator-in-dispatch\n\nUsing `action creator` in `dispatch` is preferred over `object` or old `Action`.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nstore$.dispatch(new CustomAction());\n\nthis.store$.dispatch(new AuthActions.Login({ type }));\n\nthis.store$.dispatch({ type: 'custom' });\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nstore$.dispatch(action);\n\nthis.store$.dispatch(BookActions.load());\n\nthis.store$.dispatch(AuthActions.Login({ payload }));\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefer-action-creator-in-of-type.md",
    "content": "# prefer-action-creator-in-of-type\n\nUsing `action creator` in `ofType` is preferred over `string`.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\neffectNOK = createEffect(() => this.actions$.pipe(ofType('PING')));\n\neffectNOK1 = createEffect(() =>\n  this.actions$.pipe(ofType(BookActions.load, 'PONG'))\n);\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\neffectOK = createEffect(() =>\n  this.actions$.pipe(ofType(userActions.ping.type))\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefer-action-creator.md",
    "content": "# prefer-action-creator\n\nUsing `action creator` is preferred over `Action class`.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nclass Test implements Action {\n  type = '[Customer Page] Load Customer';\n}\n\nclass Test implements ngrx.Action {\n  readonly type = ActionTypes.success;\n\n  constructor(readonly payload: Payload) {}\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport const loadUser = createAction('[User Page] Load User');\n\nclass Test {\n  type = '[Customer Page] Load Customer';\n}\n\nclass Test implements Action {\n  member = '[Customer Page] Load Customer';\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefer-concat-latest-from.md",
    "content": "# prefer-concat-latest-from\n\nUse `concatLatestFrom` instead of `withLatestFrom` to prevent the selector from firing until the correct `Action` is dispatched.\n\n- **Type**: problem\n- **Fixable**: Yes\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: Yes\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nUsing `concatLatestFrom` (a lazy version of `withLatestFrom`) ensures that the selector is only invoked when the effect receives the action.\nIn contrast to `withLatestFrom` that immediately subscribes whether the action is dispatched yet or not. If that state used by the selector is not initialized yet, you could get an error that you're not expecting.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nclass Effect {\n  detail$ = createEffect(() => {\n    return this.actions.pipe(\n      ofType(ProductDetailPage.loaded),\n      // ⚠\n      withLatestFrom(this.store.select(selectProducts)),\n      mergeMap(([action, products]) => {\n        ...\n      })\n    )\n  })\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nclass Effect {\n  detail$ = createEffect(() => {\n    return this.actions.pipe(\n      ofType(ProductDetailPage.loaded),\n      concatLatestFrom(() => this.store.select(selectProducts)),\n      mergeMap(([action, products]) => {\n        ...\n      })\n    )\n  })\n}\n```\n\n</ngrx-code-example>\n\n## Rule Config\n\nTo configure this rule you can use the `strict` option.\nThe default is `false`.\n\nTo always report the uses of `withLatestFrom` use:\n\n```json\n\"rules\": {\n  \"@ngrx/prefer-concat-latest-from\": [\"warn\", { \"strict\": true }]\n}\n```\n\nTo report only needed uses of `withLatestFrom` use:\n\n```json\n\"rules\": {\n  \"@ngrx/prefer-concat-latest-from\": [\"warn\", { \"strict\": false }]\n}\n```\n\n## Further reading\n\n- [`concatLatestFrom` API](api/operators/concatLatestFrom)\n- [Incorporating State](guide/effects#incorporating-state)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefer-effect-callback-in-block-statement.md",
    "content": "# prefer-effect-callback-in-block-statement\n\nA block statement is easier to troubleshoot.\n\n- **Type**: suggestion\n- **Fixable**: Yes\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThis rule prefers that the callback of an effect is a block statement.\nThis makes it easier to troubleshoot type errors, for when example an RxJS operator isn't imported.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nclass Effect {\n  effectNOK = createEffect(() =>\n    this.actions.pipe(\n      ofType(detailsLoaded),\n      concatMap(() => ...),\n    )\n  )\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nclass Effect {\n  effectOK = createEffect(() => {\n    return this.actions.pipe(\n      ofType(detailsLoaded),\n      concatMap(() => ...),\n    )\n  })\n}\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- https://github.com/ngrx/platform/issues/2192\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefer-inline-action-props.md",
    "content": "# prefer-inline-action-props\n\nPrefer using inline types instead of interfaces, types or classes.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nLots of actions in an NgRx codebase have props, and we need to define the props type of an action when the action is defined. It might seem better to use named interfaces or types while defining those types, but in reality, it will obscure their meaning to the developer using them. Actions props are essentially like function arguments, and the function caller needs to know exactly what type of data to provide (which results in a better IDE experience).\n\nNote: some property names are not allowed to be used, such as `type`\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport interface User {\n  id: number;\n  fullName: string;\n}\nexport const addUser = createAction(\n  '[Users] Add User',\n  props<User>()\n);\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport const addUser = createAction(\n  '[Users] Add User',\n  props<{ id: number; fullName: string }>()\n);\n// or\nexport const addUser = createAction(\n  '[Users] Add User',\n  props<{ user: User }>()\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefer-one-generic-in-create-for-feature-selector.md",
    "content": "# prefer-one-generic-in-create-for-feature-selector\n\nPrefer using a single generic to define the feature state.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\n`createFeatureSelector` is typically used with `forFeature`, which should not be aware about the shape of the Global Store. Most of the time, feature states are lazy-loaded. As such, they only need to know (and care) about their own shape.\n\nThis doesn't affect the [composability of these selectors](https://timdeschryver.dev/blog/sharing-data-between-modules-is-peanuts) across features.\nYou can still use multiple selectors from different feature states together.\n\n> Tip: If you're accessing a lazy loaded feature that isn't loaded yet, the state returned by `createFeatureSelector` is `undefined`.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nconst customersFeatureState = createFeatureSelector<\n  GlobalStore,\n  CustomersFeatureState\n>('customers');\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nconst customersFeatureState =\n  createFeatureSelector<CustomersFeatureState>('customers');\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefer-protected-state.md",
    "content": "# prefer-protected-state\n\nA Signal Store prefers protected state\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThis rule ensures that state changes are only managed by the Signal Store to prevent unintended modifications and provide clear ownership of where changes occur.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nconst Store = signalStore({ protectedState: false }, withState({}));\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n```ts\nconst Store = signalStore(withState({}));\n```\n\n<ngrx-code-example>\n\n```ts\nconst Store = signalStore({ protectedState: true }, withState({}));\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefer-selector-in-select.md",
    "content": "# prefer-selector-in-select\n\nUsing a selector in the `select` is preferred over `string` or `props drilling`.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nIt's recommended to use selectors to get data out of the state tree.\nA selector is memoized, thus this has the benefit that it's faster because the result is cached and will only be recalculated when it's needed.\n\nBecause a selector is just a pure function, it's easier to test.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\n// ⚠ Usage of strings to select state slices\nthis.store.select('customers');\nthis.store.pipe(select('customers'));\n\n// ⚠ Usage of props drilling to select state slices\nthis.store.select((state) => state.customers);\nthis.store.pipe(select((state) => state.customers));\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nimport * as fromCustomers from '@customers/selectors';\n\nthis.store.select(fromCustomers.selectAllCustomers);\nthis.store.pipe(select(fromCustomers.selectAllCustomers));\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- [Selector docs](guide/store/selectors)\n- [Testing selectors docs](guide/store/testing#testing-selectors)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/prefix-selectors-with-select.md",
    "content": "# prefix-selectors-with-select\n\nThe selector should start with \"select\", for example \"selectEntity\".\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\nIt's recommended prefixing selector function names with the word \"select\" combined with a description of the value being selected.\n\n## Rule Details\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\n// ⚠ Usage of a selector without any prefix\nexport const feature = createSelector(\n  (state: AppState) => state.feature\n);\n\n// ⚠ Usage of a selector without any description\nexport const select = (id: string) =>\n  createSelector((state: AppState) => state.feature);\n\n// ⚠ Usage of a selector with a `get` prefix\nexport const getFeature: MemoizedSelector<any, any> = (\n  state: AppState\n) => state.feature;\n\n// ⚠ Usage of a selector with improper casing\nconst selectfeature = createFeatureSelector<AppState, FeatureState>(\n  featureKey\n);\n\n// ⚠ Usage of a `createSelectorFactory` without `select` prefix\nconst createSelector = createSelectorFactory((projectionFun) =>\n  defaultMemoize(\n    projectionFun,\n    orderDoesNotMatterComparer,\n    orderDoesNotMatterComparer\n  )\n);\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport const selectFeature = createSelector(\n  (state: AppState) => state.feature\n);\n\nexport const selectFeature: MemoizedSelector<any, any> = (\n  state: AppState\n) => state.feature;\n\nconst selectFeature = createFeatureSelector<FeatureState>(featureKey);\n\nexport const selectThing = (id: string) =>\n  createSelector(selectThings, (things) => things[id]);\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- [Redux Style Guide](https://redux.js.org/style-guide/style-guide#name-selector-functions-as-selectthing)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/require-super-ondestroy.md",
    "content": "# require-super-ondestroy\n\nOverriden ngOnDestroy method in component stores require a call to super.ngOnDestroy().\n\n- **Type**: problem\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThis rule enforces that any class which inherits the `ComponentStore` class and overrides the `ngOnDestroy` lifecycle hook must include a call to `super.ngOnDestroy()`. This ensures proper cleanup of resources managed by the `ComponentStore` class.\n\nExample of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\n@Injectable()\nexport class BooksStore\n  extends ComponentStore<BooksState>\n  implements OnDestroy\n{\n  // ... other BooksStore class members\n\n  override ngOnDestroy(): void {\n    this.cleanUp(); // custom cleanup logic\n  }\n}\n```\n\n</ngrx-code-example>\n\nExample of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\n@Injectable()\nexport class BooksStore\n  extends ComponentStore<BooksState>\n  implements OnDestroy\n{\n  // ... other BooksStore class members\n\n  override ngOnDestroy(): void {\n    this.cleanUp();\n    super.ngOnDestroy();\n  }\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/select-style.md",
    "content": "# select-style\n\nSelector can be used either with `select` as a pipeable operator or as a method.\n\n- **Type**: suggestion\n- **Fixable**: Yes\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: Yes\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThere are two ways of selecting data from the store, either by using the `this.store.select(selectorFn)` method, or by using the `this.store.pipe(select(selectorFn))` operator. Either way is considered correct, although the first way is preferred as it requires less code and it doesn't require the need to import the `selector` operator.\n\nBecause it's important to keep things consistent, this rule disallows using both across the same codebase.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Component {\n  someData$ = this.store.select(someData);\n  otherData$ = this.store.pipe(select(otherData));\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class Component {\n  someData$ = this.store.select(someData);\n  otherData$ = this.store.select(otherData);\n}\n```\n\n</ngrx-code-example>\n\n## Rule Config\n\nTo configure this rule you can change the preferred `mode` of the selectors, the allowed values are `method` and `operator`.\nThe default is `method`.\n\nTo prefer the **method** syntax (`this.store.select(selector)`) use:\n\n```json\n\"rules\": {\n  \"@ngrx/select-style\": [\"warn\", \"method\"]\n}\n```\n\nTo prefer the **operator** syntax (`this.store.pipe(select(selector))`) use:\n\n```json\n\"rules\": {\n  \"@ngrx/select-style\": [\"warn\", \"operator\"]\n}\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/signal-state-no-arrays-at-root-level.md",
    "content": "# signal-state-no-arrays-at-root-level\n\nsignalState should accept a record or dictionary as an input argument.\n\n- **Type**: problem\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: Yes\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThis rule ensure that a Signal State shouldn't accept an array type at the root level.\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nconst store = withState({ foo: 'bar' });\n\nconst store = withState({ arrayAsProperty: ['foo', 'bar'] });\n\nconst initialState = {};\nconst store = signalStore(withState(initialState));\n```\n\n</ngrx-code-example>\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nconst store = withState([1, 2, 3]);\n\nconst store = withState([{ foo: 'bar' }]);\n\nconst store = withState<string[]>([]);\n\nconst initialState = [];\nconst store = withState(initialState);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/signal-store-feature-should-use-generic-type.md",
    "content": "# signal-store-feature-should-use-generic-type\n\nA custom Signal Store feature that accepts an input should define a generic type.\n\n- **Type**: problem\n- **Fixable**: Yes\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThis rule ensure that a Signal Store feature uses a generic type to define the feature state.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nconst withY = () =>\n  signalStoreFeature({ state: type<{ y: number }>() }, withState({}));\n```\n\n</ngrx-code-example>\n\n```ts\nconst withY = () => {\n  return signalStoreFeature(\n    type<{ state: { y: number } }>(),\n    withState({})\n  );\n};\n```\n\n```ts\nfunction withY() {\n  return signalStoreFeature(\n    type<{ state: { y: number } }>(),\n    withState({})\n  );\n}\n```\n\nExamples of **correct** code for this rule:\n\n```ts\nconst withY = <Y>() =>\n  signalStoreFeature({ state: type<{ y: Y }>() }, withState({}));\n```\n\n```ts\nconst withY = <_>() => {\n  return signalStoreFeature(\n    type<{ state: { y: number } }>(),\n    withState({})\n  );\n};\n```\n\n<ngrx-code-example>\n\n```ts\nfunction withY<_>() {\n  return signalStoreFeature(\n    { state: type<{ y: Y }>() },\n    withState({})\n  );\n}\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- [Known TypeScript Issues with Custom Store Features](guide/signals/signal-store/custom-store-features#known-typescript-issues)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/updater-explicit-return-type.md",
    "content": "# updater-explicit-return-type\n\n`Updater` should have an explicit return type.\n\n- **Type**: problem\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nTo enforce that the `updater` method from `@ngrx/component-store` returns the expected state interface, we must explicitly add the return type.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\ninterface MoviesState {\n  movies: Movie[];\n}\n\nclass MoviesStore extends ComponentStore<MoviesState> {\n  readonly addMovie = this.updater((state, movie: Movie) => ({\n    movies: [...state.movies, movie],\n    // ⚠ this doesn't throw, but is caught by the linter\n    extra: 'property',\n  }));\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\ninterface MoviesState {\n  movies: Movie[];\n}\n\nclass MoviesStore extends ComponentStore<MoviesState> {\n  readonly addMovie = this.updater(\n    (state, movie: Movie): MoviesState => ({\n      movies: [...state.movies, movie],\n      // ⚠ this does throw\n      extra: 'property',\n    })\n  );\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/use-consistent-global-store-name.md",
    "content": "# use-consistent-global-store-name\n\nUse a consistent name for the global store.\n\n- **Type**: suggestion\n- **Fixable**: No\n- **Suggestion**: Yes\n- **Requires type checking**: No\n- **Configurable**: Yes\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThe name of the global store should be used consistent.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class ClassOne {\n  constructor(private store: Store) {}\n}\n\nexport class ClassTwo {\n  constructor(private customersStore: Store) {}\n}\n```\n\n</ngrx-code-example>\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nexport class ClassOne {\n  constructor(private store: Store) {}\n}\n\nexport class ClassTwo {\n  constructor(private store: Store) {}\n}\n```\n\n</ngrx-code-example>\n\n## Rule Config\n\nTo configure this rule you can change the preferred `name` of the global store.\nThe default is `store`.\n\nTo change the name of the global store use:\n\n```json\n\"rules\": {\n  \"@ngrx/use-consistent-global-store-name\": [\"warn\", \"store$\"]\n}\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/use-effects-lifecycle-interface.md",
    "content": "# use-effects-lifecycle-interface\n\nEnsures classes implement lifecycle interfaces corresponding to the declared lifecycle methods.\n\n- **Type**: suggestion\n- **Fixable**: Yes\n- **Suggestion**: No\n- **Requires type checking**: No\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nIf a class is using an effect lifecycle hook, it should implement the corresponding interface.\nThis prevents signature typos, and it's safer if the signature changes in the future.\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nclass Effect {\n  ngrxOnInitEffects(): Action {\n    return { type: '[Effect] Init' };\n  }\n}\n```\n\n</ngrx-code-example>\n\n```ts\nclass Effect {\n  constructor(private actions$: Actions) {}\n\n  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>) {\n    return this.actions$.pipe(\n      ofType('LOGGED_IN'),\n      exhaustMap(() =>\n        resolvedEffects$.pipe(\n          takeUntil(this.actions$.pipe(ofType('LOGGED_OUT')))\n        )\n      )\n    );\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```ts\nimport { OnInitEffects } from '@ngrx/effects';\n\nclass Effect implements OnInitEffects {\n  ngrxOnInitEffects(): Action {\n    return { type: '[Effect] Init' };\n  }\n}\n```\n\n<ngrx-code-example>\n\n```ts\nimport { OnRunEffects } from '@ngrx/effects';\n\nclass Effect implements OnRunEffects {\n  constructor(private actions$: Actions) {}\n  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>) {\n    return this.actions$.pipe(\n      ofType('LOGGED_IN'),\n      exhaustMap(() =>\n        resolvedEffects$.pipe(\n          takeUntil(this.actions$.pipe(ofType('LOGGED_OUT')))\n        )\n      )\n    );\n  }\n}\n```\n\n</ngrx-code-example>\n\n## Further reading\n\n- [Effect lifecycle docs](guide/effects/lifecycle#controlling-effects)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/eslint-plugin/rules/with-state-no-arrays-at-root-level.md",
    "content": "# with-state-no-arrays-at-root-level\n\nwithState should accept a record or dictionary as an input argument.\n\n- **Type**: problem\n- **Fixable**: No\n- **Suggestion**: No\n- **Requires type checking**: Yes\n- **Configurable**: No\n\n<!-- Everything above this generated, do not edit -->\n<!-- MANUAL-DOC:START -->\n\n## Rule Details\n\nThis rule ensures that `withState` does not accept a non-record type at the root level. Arrays, sets, maps, and other non-plain-object types are forbidden as the initial state argument. A factory function is also accepted, in which case its return type is checked instead.\n\nExamples of **correct** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nconst store = signalStore(withState({ count: 0 }));\n\nconst store = signalStore(withState({ items: [] }));\n\nconst initialState = { count: 0 };\nconst store = signalStore(withState(initialState));\n\n// Factory function - return type is checked\nconst store = signalStore(withState(() => ({ count: 0 })));\n\n// Factory function with dependency injection\nconst INITIAL_STATE = new InjectionToken('InitialState', {\n  factory: () => ({ count: 0 }),\n});\nconst store = signalStore(withState(() => inject(INITIAL_STATE)));\n```\n\n</ngrx-code-example>\n\nExamples of **incorrect** code for this rule:\n\n<ngrx-code-example>\n\n```ts\nconst store = signalStore(withState([1, 2, 3]));\n\nconst store = signalStore(withState(new Set()));\n\nconst store = signalStore(withState(new Map()));\n\nconst initialState: number[] = [];\nconst store = signalStore(withState(initialState));\n\n// Factory function returning a forbidden type\nconst store = signalStore(withState(() => [1, 2, 3]));\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v10.md",
    "content": "# V10 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@10\n```\n\n## Dependencies\n\nVersion 10 has the minimum version requirements:\n\n- Angular version 10.x\n- Angular CLI version 10.x\n- TypeScript version 3.9.x\n- RxJS version 6.5.x\n\n## Breaking changes\n\n### @ngrx/effects\n\n#### Stricter Effects typing\n\nReturning an `EMPTY` observable without `{ dispatch: false }` now produces a type error.\n\nBEFORE:\n\n```ts\nsomeEffect$ = createEffect(() => EMPTY);\n```\n\nAFTER:\n\n```ts\nsomeEffect$ = createEffect(() => EMPTY, { dispatch: false });\n```\n\n### @ngrx/schematics\n\n#### `skipTest` option is removed\n\nThe `skipTest` option is renamed to `skipTests`.\n\nBEFORE:\n\n```bash\nng generate container UsersPage --skipTest\n```\n\nAFTER:\n\n```bash\nng generate container UsersPage --skipTests\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v11.md",
    "content": "# V11 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@11\n```\n\n## Dependencies\n\nVersion 11 has the minimum version requirements:\n\n- Angular version 11.x\n- Angular CLI version 11.x\n- TypeScript version 4.0.x\n- RxJS version 6.5.x\n\n## Breaking changes\n\n### @ngrx/store\n\n#### Stricter `props` for `createAction`\n\n`createAction` doesn't allow `{}` as a `props` type anymore, because `{}` is compatible with primitive types.\n\nBEFORE:\n\n```ts\nconst customerPageLoaded = createAction(\n  '[Customer Page] Loaded',\n  props<{}>()\n);\n\ncustomerPageLoaded({}); // ✔️\ncustomerPageLoaded({ foo: 'bar' }); // ✔️\ncustomerPageLoaded(0); // 👈 no error\ncustomerPageLoaded(false); // 👈 no error\n```\n\nAFTER:\n\n```ts\nconst customerPageLoaded = createAction(\n  '[Customer Page] Loaded',\n  props<Record<string, unknown>>()\n);\n```\n\n#### Renames\n\n- the interface `Props` is renamed to `ActionCreatorProps`\n- the interface `On<T>` is renamed to `ReducerTypes<T>`\n\n### @ngrx/entity\n\n#### Removed `addAll` in favor of `setAll`\n\nTo overwrite the entities, we previously used the `addAll` method but the method name was confusing.\n\nBEFORE:\n\n```ts\nadapter.addAll(action.entities, state);\n```\n\nAFTER:\n\nThe new method name `setAll` describes the intention better.\n\n```ts\nadapter.setAll(action.entities, state);\n```\n\n### @ngrx/router-store\n\n#### Optimized `selectQueryParams`, `selectQueryParam` and `selectFragment` selectors\n\nThey select query parameters/fragment from the root router state node instead of the last router state node.\n\nBEFORE:\n\n```ts\nconst queryParams$ = this.store.select(selectQueryParams);\nconst fragment$ = this.store.select(selectFragment);\n\n/*\nrouter state:\n{\n  root: {\n    queryParams: {\n      search: 'foo',\n    },\n    fragment: 'bar',\n    firstChild: {\n      queryParams: {\n        search: 'foo', 👈 query parameters are selected from here\n      },\n      fragment: 'bar', 👈 fragment is selected from here\n      firstChild: undefined,\n    },\n  },\n  url: '/books?search=foo#bar',\n}\n*/\n```\n\nAFTER:\n\n```ts\nconst queryParams$ = this.store.select(selectQueryParams);\nconst fragment$ = this.store.select(selectFragment);\n\n/*\nrouter state:\n{\n  root: {\n    queryParams: {\n      search: 'foo', 👈 query parameters are selected from here\n    },\n    fragment: 'bar', 👈 fragment is selected from here\n    firstChild: {\n      queryParams: {\n        search: 'foo',\n      },\n      fragment: 'bar',\n      firstChild: undefined,\n    },\n  },\n  url: '/books?search=foo#bar',\n}\n*/\n```\n\n### @ngrx/store-devtools\n\n#### Error handling\n\nThe error handler now receives the full `Error` object instead of the error stack when an error occurs while computing the state.\n\n## Deprecations\n\n### @ngrx/effects\n\n#### The Effect decorator\n\nThe Effect decorator, `@Effect`, is deprecated in favor for the `createEffect` method.\n\nSee the [docs](/guide/effects#writing-effects) for more info.\n\nBEFORE:\n\n```ts\n@Effect()\nlogin$ = this.actions$.pipe(...);\n```\n\nAFTER:\n\n```ts\nlogin$ = createEffect(() => {\n  return this.actions$.pipe(...);\n});\n```\n\nTo automatically migrate `@Effect` usages to the `createEffect` method, run the following NgRx migration (this migration is only available in v11 and v12):\n\n```sh\nng generate @ngrx/schematics:create-effect-migration\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v12.md",
    "content": "# V12 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@12\n```\n\n## Dependencies\n\nVersion 12 has the minimum version requirements:\n\n- Angular version 12.x\n- Angular CLI version 12.x\n- TypeScript version 4.2.x\n- RxJS version 6.5.x\n\n## Deprecations\n\n### @ngrx/store\n\n#### Selectors With Props\n\nSelectors with props are deprecated in favor of \"normal\" factory selectors.\nFactory selectors have the following benefits:\n\n- easier to write and to teach\n- selectors are typed\n- child selectors are correctly memoized\n\nBEFORE:\n\n```ts\nconst selectCustomer = createSelector(\n  selectCustomers,\n  (customers, props: { customerId: number }) => {\n    return customers[props.customerId];\n  }\n);\n\n// Or if the selector is already defined as a factory selector\n\nconst selectCustomer = () =>\n  createSelector(\n    selectCustomers,\n    (customers, props: { customerId: number }) => {\n      return customers[props.customerId];\n    }\n  );\n```\n\nAFTER:\n\n```ts\nconst selectCustomer = (customerId: number) =>\n  createSelector(selectCustomers, (customers) => {\n    return customers[customerId];\n  });\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v13.md",
    "content": "# V13 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@13\n```\n\n## Dependencies\n\nVersion 13 has the minimum version requirements:\n\n- Angular version 13.x\n- Angular CLI version 13.x\n- TypeScript version 4.4.x\n- RxJS version 6.5.x, 6.6.x, or 7.4.x\n\n## Breaking changes\n\n### @ngrx/store\n\n#### Variadic tuple types for createSelector\n\nThe `createSelector` can now be combined with an unlimited amount of child selectors, which was previously limited to 8 child selectors.\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided to update the generic types of createSelector.\n\n</ngrx-docs-alert>\n\nBEFORE:\n\n```ts\nconst selector = createSelector<\n  State,\n  Customer,\n  Order[],\n  CustomerWithOrder\n>;\n```\n\nAFTER:\n\n```ts\n//                  needs to be a tuple 👇\nconst selector = createSelector<\n  State,\n  [Customer, Order[]],\n  CustomerWithOrder\n>;\n```\n\n#### Action creator props\n\nThe `props` of an action created with `createAction` can't be a primitive type (`string`, `number`, `boolean`).\nAdditionally, the error messages are updated to be more explicit about what is wrong.\n\nBEFORE:\n\n```ts\nconst action = createAction('[Source] Event', props<string>());\n```\n\nAFTER:\n\n```ts\nconst action = createAction(\n  '[Source] Event',\n  props<{ name: string }>()\n);\n```\n\n#### StoreModule.forFeature with FeatureSlice\n\nThe `StoreModule.forFeature()` method doesn't accept a configuration object anymore.\n\nBEFORE:\n\n```ts\nStoreModule.forFeature(featureSlice, {\n  initialState: 100,\n  metaReducers: [metaReducer],\n});\n\nStoreModule.forFeature(\n  { name: 'feature', reducer: featureReducer },\n  { initialState: 100, metaReducers: [metaReducer] }\n);\n```\n\nAFTER:\n\n```ts\nStoreModule.forFeature(featureSlice);\n\nStoreModule.forFeature({ name: 'feature', reducer: featureReducer });\n```\n\n#### StoreModule initialState config\n\nThe `initialState` provided via the configuration object is now typed and needs to match the interface of the state interface.\n\n#### Testing: Reset mock store\n\nMock stores are not reset automagically after each test.\n\nTo restore the previous behavior you add add your own reset logic in an `afterEach` hook.\nNote that this only applicable when the Angular `TestBed` isn't teared down by Angular, for more info see the [ModuleTeardownOptions options](https://angular.dev/api/core/testing/ModuleTeardownOptions).\n\nWhen using Jasmine, reset the mock store after each test by adding the following to the `test.ts`:\n\n```ts\nimport { getTestBed } from '@angular/core/testing';\nimport { MockStore } from '@ngrx/store/testing';\n\nafterEach(() => {\n  getTestBed().inject(MockStore, null)?.resetSelectors();\n});\n```\n\nWhen using Jest, reset the mock store after each test by using a `afterEach` hook:\n\n```ts\nimport { MockStore } from '@ngrx/store/testing';\n\nafterEach(() => {\n  TestBed.inject(MockStore)?.resetSelectors();\n});\n```\n\n### @ngrx/schematics\n\n#### create-effect-migration\n\nThe `create-effect-migration` migration has been removed.\nIt's now added to the automated migration (run with `ng-update`) of the `@ngrx/effects` module.\nThis replaces all references to `@Effect` (which is deprecated) with the `createEffect` method.\n\n### @ngrx/data\n\nThe `queryManySuccess` query sets the `loaded` flag to `true`.\n\n### @ngrx/component\n\n#### PushPipe\n\n`PushPipe` no longer has a class-level generic type parameter.\nThis change is needed to make `PushPipe` work with strict templates.\nIt affects the use of `PushPipe` outside of component templates.\n\n## Deprecations\n\n### @ngrx/store\n\n#### createFeatureSelector\n\nThe `createFeatureSelector<State, FeatureState>` method with two generics, has been deprecated.\nInstead, only provide the `FeatureState` generic.\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided to update the generic types of `createFeatureSelector`.\n\n</ngrx-docs-alert>\n\nBEFORE:\n\n```ts\nconst selectFeature = createFeatureSelector<State, Feature>(\n  'feature'\n);\n```\n\nAFTER:\n\n```ts\nconst selectFeature = createFeatureSelector<Feature>('feature');\n```\n\n#### Selectors With Props\n\nSelectors with props are deprecated in favor of \"normal\" factory selectors.\nFactory selectors have the following benefits:\n\n- easier to write and to teach\n- selectors are typed\n- child selectors are correctly memoized\n\nBEFORE:\n\n```ts\nconst selectCustomer = createSelector(\n  selectCustomers,\n  (customers, props: { customerId: number }) => {\n    return customers[props.customerId];\n  }\n);\n\n// Or if the selector is already defined as a factory selector\n\nconst selectCustomer = () =>\n  createSelector(\n    selectCustomers,\n    (customers, props: { customerId: number }) => {\n      return customers[props.customerId];\n    }\n  );\n```\n\nAFTER:\n\n```ts\nconst selectCustomer = (customerId: number) =>\n  createSelector(selectCustomers, (customers) => {\n    return customers[customerId];\n  });\n```\n\n### @ngrx/effects\n\n#### @Effect decorator\n\nThe `@Effect` decorator is deprecated in favor of `createEffect`.\nSee the [docs](/guide/effects#writing-effects) for more info.\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided to update to remove the `@Effect` decorator, and to wrap the effect within a `createEffect` method.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v14.md",
    "content": "# V14 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@14\n```\n\n## Dependencies\n\nVersion 14 has the minimum version requirements:\n\n- Angular version 14.x\n- Angular CLI version 14.x\n- TypeScript version 4.6.x\n- RxJS version ^6.5.3 || ^7.5.0\n\n## Breaking changes\n\n### @ngrx/router-store\n\n#### Default serialized and serializer names\n\n- Renamed `DefaultRouterStateSerializer` to `FullRouterStateSerializer` (used with `RouterState.Full`)\n- `MinimalRouterStateSerializer` is the default serializer (used with `RouterState.Minimal`)\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided to rename `DefaultRouterStateSerializer` to `FullRouterStateSerializer`.\n\n</ngrx-docs-alert>\n\n### @ngrx/component\n\n#### Use global render strategy in zone-less mode\n\nThe native local rendering strategy is replaced by global in zone-less mode for better performance.\n\nBEFORE:\n\nThe change detection is triggered via `changeDetectorRef.detectChanges` in zone-less mode.\n\nAFTER:\n\nThe change detection is triggered via `ɵmarkDirty` in zone-less mode.\n\n#### Add error as value to `LetDirective`'s context\n\nThe `$error` property from `LetDirective`'s view context is a thrown error or `undefined` instead of `true`/`false`.\n\nBEFORE:\n\n```html\n<p *ngrxLet=\"obs$; $error as e\">{{ e }}</p>\n```\n\n- `e` will be `true` when `obs$` emits error event.\n- `e` will be `false` when `obs$` emits next/complete event.\n\nAFTER:\n\n```html\n<p *ngrxLet=\"obs$; $error as e\">{{ e }}</p>\n```\n\n- `e` will be thrown error when `obs$` emits error event.\n- `e` will be `undefined` when `obs$` emits next/complete event.\n\n#### Add ability to pass non-observable values\n\n1. The context of `LetDirective` is strongly typed when `null` or\n   `undefined` is passed as input.\n\nBEFORE:\n\n```html\n<p *ngrxLet=\"null as n\">{{ n }}</p>\n<p *ngrxLet=\"undefined as u\">{{ u }}</p>\n```\n\n- The type of `n` is `any`.\n- The type of `u` is `any`.\n\nAFTER:\n\n```html\n<p *ngrxLet=\"null as n\">{{ n }}</p>\n<p *ngrxLet=\"undefined as u\">{{ u }}</p>\n```\n\n- The type of `n` is `null`.\n- The type of `u` is `undefined`.\n\n---\n\n2. Arrays, iterables, generator functions, and readable streams are\n   not treated as observable-like inputs anymore. To keep the same behavior\n   as in v13, convert the array/iterable/generator function/readable stream\n   to observable using the `from` function from the `rxjs` package\n   before passing it to the `LetDirective`/`PushPipe`.\n\nBEFORE:\n\n```ts\n@Component({\n  template: `\n    <p *ngrxLet=\"numbers as n\">{{ n }}</p>\n    <p>{{ numbers | ngrxPush }}</p>\n  `,\n})\nexport class NumbersComponent {\n  numbers = [1, 2, 3];\n}\n```\n\nAFTER:\n\n```ts\n@Component({\n  template: `\n    <p *ngrxLet=\"numbers$ as n\">{{ n }}</p>\n    <p>{{ numbers$ | ngrxPush }}</p>\n  `,\n})\nexport class NumbersComponent {\n  numbers$ = from([1, 2, 3]);\n}\n```\n\n### @ngrx/schematics\n\n#### Remove `defaultCollection`\n\nRemoved the `defaultCollection` option in favor of `schematicCollections`.\nWhen the schematics are installed, `@ngrx/schematics` is added to the `schematicCollections` in the `angular.json` file.\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided to add `@ngrx/schematics` to the `schematicCollections`.\n\n</ngrx-docs-alert>\n\n## Deprecations\n\n### @ngrx/component\n\n#### ReactiveComponentModule\n\n`ReactiveComponentModule` is deprecated in favor of `LetModule` and `PushModule`.\n\nBEFORE:\n\n```ts\nimport { ReactiveComponentModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    ReactiveComponentModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\nAFTER:\n\nIf the components declared in the `MyFeatureModule` use only the `*ngrxLet` directive:\n\n```ts\nimport { LetModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    LetModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\nIf the components declared in the `MyFeatureModule` use only the `ngrxPush` pipe:\n\n```ts\nimport { PushModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    PushModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\nIf the components declared in the `MyFeatureModule` use both the `*ngrxLet` directive and the `ngrxPush` pipe:\n\n```ts\nimport { LetModule, PushModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    LetModule,\n    PushModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v15.md",
    "content": "# V15 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@15\n```\n\n## Dependencies\n\nVersion 15 has the minimum version requirements:\n\n- Angular version 15.x\n- Angular CLI version 15.x\n- TypeScript version 4.8.x\n- RxJS version ^6.5.3 || ^7.5.0\n\n## Breaking changes\n\n### @ngrx/store\n\n#### Strict Selector Projector\n\nThe projector function on the selector is type-safe by default.\nThe projector function also has become strict for selectors with props.\n\nBEFORE:\n\nThe projector is not type-safe, allowing for potential mismatch types in the projector function.\n\n```ts\nconst mySelector = createSelector(\n  () => 'one',\n  () => 2,\n  (one, two) => 3\n);\n\nmySelector.projector(); // <- type is projector(...args: any[]): number\n```\n\nAFTER:\n\nThe projector is strict by default, but can be bypassed with an `any` type assertion to specify a less specific type.\n\n```ts\nconst mySelector = createSelector(\n  () => 'one',\n  () => 2,\n  (one, two) => 3\n);\n\nmySelector.projector(); // <- Results in type error. Type is projector(s1: string, s2: number): number\n```\n\nTo retain previous behavior\n\n```ts\nconst mySelector = createSelector(\n  () => 'one',\n  () => 2,\n  (one, two) => 3\n)(mySelector.projector as any)();\n```\n\n### @ngrx/effects\n\n#### Removal of @Effect\n\nThe `@Effect` decorator is removed in favor of [`createEffect`](api/effects/createEffect).\nThis change also means that the ESLint rules @ngrx/no-effect-decorator-and-creator and @ngrx/no-effect-decorator are removed.\n\n> A migration was added (in the v13 release) to upgrade existing codebases to the `createEffect` function.\n\nBEFORE:\n\nAn effect is defined with the `@Effect` decorator.\n\n```ts\n@Effect()\ndata$ = this.actions$.pipe();\n```\n\nAFTER:\n\nYou need to define an effect with `createEffect`.\n\n```ts\ndata$ = createEffect(() => this.actions$.pipe());\n```\n\n### @ngrx/router-store\n\n#### Title\n\nBREAKING CHANGE:\n\nThe property `title: string | undefined` is added to the `MinimalActivatedRouteSnapshot` interface when a title added to the route config.\n\nBEFORE:\n\nThe `MinimalActivatedRouteSnapshot` interface doesn't contain the `title` property.\n\nAFTER:\n\nThe `MinimalActivatedRouteSnapshot` interface contains the required `title` property.\n\n### @ngrx/component\n\n#### Removal of ReactiveComponentModule\n\nThe `ReactiveComponentModule` is removed in favor of `LetModule` and `PushModule`.\n\nBEFORE:\n\n```ts\nimport { ReactiveComponentModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    ReactiveComponentModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\nAFTER:\n\n```ts\nimport { LetModule, PushModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    LetModule,\n    PushModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\n#### Renamed LetViewContext Properties\n\nThe `$` prefix is removed from `LetViewContext` property names.\n\nBEFORE:\n\n`LetViewContext` property names contain the `$` prefix:\n\n```html\n<ng-container *ngrxLet=\"obs$; $error as e; $complete as c\">\n  ...\n</ng-container>\n```\n\nAFTER:\n\n`LetViewContext` property names don't contain the `$` prefix:\n\n```html\n<ng-container *ngrxLet=\"obs$; error as e; complete as c\">\n  ...\n</ng-container>\n```\n\n#### LetDirective Behavior on Suspense Event\n\nThe `LetDirective` view will be cleared when the replaced observable is in a suspense state.\nAlso, the `suspense` property is removed from the `LetViewContext` because it would always be `false` when the `LetDirective` view is rendered.\nInstead of `suspense` property, use [suspense template](guide/component/let#using-suspense-template) to handle the suspense state.\n\nBEFORE:\n\nThe `LetDirective` view will not be cleared when the replaced observable is in a suspense state and the suspense template is not passed:\n\n```ts\n@Component({\n  template: `\n    <!-- When button is clicked, the 'LetDirective' view won't be cleared. -->\n    <!-- Instead, the value of 'o' will be 'undefined' until the replaced -->\n    <!-- observable emits the first value (after 1 second). -->\n    <p *ngrxLet=\"obs$ as o\">{{ o }}</p>\n    <button (click)=\"replaceObs()\">Replace Observable</button>\n  `,\n})\nexport class TestComponent {\n  obs$ = of(1);\n\n  replaceObs(): void {\n    this.obs$ = of(2).pipe(delay(1000));\n  }\n}\n```\n\nAFTER:\n\nThe `LetDirective` view will be cleared when the replaced observable is in a suspense state and the suspense template is not passed:\n\n```ts\n@Component({\n  template: `\n    <!-- When button is clicked, the 'LetDirective' view will be cleared. -->\n    <!-- The view will be created again when the replaced observable -->\n    <!-- emits the first value (after 1 second). -->\n    <p *ngrxLet=\"obs$ as o\">{{ o }}</p>\n    <button (click)=\"replaceObs()\">Replace Observable</button>\n  `,\n})\nexport class TestComponent {\n  obs$ = of(1);\n\n  replaceObs(): void {\n    this.obs$ = of(2).pipe(delay(1000));\n  }\n}\n```\n\n## Deprecations\n\n### @ngrx/store\n\n#### Deprecated `createFeature` Signature with Root State (Introduced in v15.2)\n\nThe `createFeature` signature with root state is deprecated in favor of a signature without root state.\n\nBEFORE:\n\n```ts\ninterface AppState {\n  users: State;\n}\n\nexport const usersFeature = createFeature<AppState>({\n  name: 'users',\n  reducer: createReducer(initialState /* case reducers */),\n});\n```\n\nAFTER:\n\n```ts\nexport const usersFeature = createFeature({\n  name: 'users',\n  reducer: createReducer(initialState /* case reducers */),\n});\n```\n\n#### Deprecated `getMockStore` in favor of `createMockStore` (Introduced in v15.4)\n\nBEFORE:\n\n```ts\nimport { getMockStore } from '@ngrx/store/testing';\nconst mockStore = getMockStore();\n```\n\nAFTER:\n\n```ts\nimport { createMockStore } from '@ngrx/store/testing';\nconst mockStore = createMockStore();\n```\n\n### @ngrx/router-store\n\n#### Renamed `getSelectors` Function (Introduced in v15.2)\n\nThe `getSelectors` function is deprecated in favor of `getRouterSelectors`.\n\nBEFORE:\n\n```ts\nimport { getSelectors } from '@ngrx/router-store';\n\nconst routerSelectors = getSelectors();\n```\n\nAFTER:\n\n```ts\nimport { getRouterSelectors } from '@ngrx/router-store';\n\nconst routerSelectors = getRouterSelectors();\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v16.md",
    "content": "# V16 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@16\n```\n\n## Dependencies\n\nVersion 16 has the minimum version requirements:\n\n- Angular version 16.x\n- Angular CLI version 16.x\n- TypeScript version 5.x\n- RxJS version ^6.5.x || ^7.5.0\n\n## Breaking changes\n\n### @ngrx/store\n\n#### Preserve the event name case with createActionGroup\n\nThe event name case is preserved when converting to the action name by using the createActionGroup function.\n\nBEFORE:\n\nAll letters of the event name will be lowercase, except for the initial letters of words starting from the second word, which will be uppercase.\n\n```ts\nconst authApiActions = createActionGroup({\n  source: 'Auth API',\n  events: {\n    'LogIn Success': emptyProps(),\n    'login failure': emptyProps(),\n    'Logout Success': emptyProps(),\n    logoutFailure: emptyProps(),\n  },\n});\n\n// generated actions:\nconst { loginSuccess, loginFailure, logoutSuccess, logoutfailure } =\n  authApiActions;\n```\n\nAFTER:\n\nThe initial letter of the first word of the event name will be lowercase, and the initial letters of the other words will be uppercase. The case of other letters in the event name will remain the same.\n\n```ts\nconst authApiActions = createActionGroup({\n  source: 'Auth API',\n  events: {\n    'LogIn Success': emptyProps(),\n    'login failure': emptyProps(),\n    'Logout Success': emptyProps(),\n    logoutFailure: emptyProps(),\n  },\n});\n\n// generated actions:\nconst { logInSuccess, loginFailure, logoutSuccess, logoutFailure } =\n  authApiActions;\n```\n\n#### Strongly typed `createFeature` selectors\n\nProjectors of selectors generated by createFeature are strongly typed.\n\nBEFORE:\n\nProjector function arguments of selectors generated by createFeature are not strongly typed:\n\n```ts\nconst counterFeature = createFeature({\n  name: 'counter',\n  reducer: createReducer({ count: 0 }),\n});\n\ncounterFeature.selectCount.projector;\n// ^ type: (...args: any[]) => number\n```\n\nAFTER:\n\nProjector function arguments of selectors generated by createFeature are strongly typed:\n\n```ts\nconst counterFeature = createFeature({\n  name: 'counter',\n  reducer: createReducer({ count: 0 }),\n});\n\ncounterFeature.selectCount.projector;\n// ^ type: (featureState: { count: number; }) => number\n```\n\n#### Remove `createFeature` signature with root state\n\nThe `createFeature` signature with root state is removed in favor of a signature without root state.\nAn automatic migration is added to remove this signature.\n\nBEFORE:\n\n```ts\ninterface AppState {\n  users: State;\n}\n\nexport const usersFeature = createFeature<AppState>({\n  name: 'users',\n  reducer: createReducer(initialState /* case reducers */),\n});\n```\n\nAFTER:\n\n```ts\nexport const usersFeature = createFeature({\n  name: 'users',\n  reducer: createReducer(initialState /* case reducers */),\n});\n```\n\n#### Replace `getMockStore` with `createMockStore`\n\nThe `getMockStore` function is replaced in favor of `createMockStore`.\nAn automatic migration is added to rename the function.\n\nBEFORE:\n\n```ts\nimport { getMockStore } from '@ngrx/store/testing';\nconst mockStore = getMockStore();\n```\n\nAFTER:\n\n```ts\nimport { createMockStore } from '@ngrx/store/testing';\nconst mockStore = createMockStore();\n```\n\n### @ngrx/router-store\n\n#### Replace `getSelectors` with `getRouterSelectors`\n\nThe `getSelectors` function is replaced in favor of `getRouterSelectors`.\nAn automatic migration is added to rename the function.\n\nBEFORE:\n\n```ts\nimport { getSelectors } from '@ngrx/router-store';\n\nconst routerSelectors = getSelectors();\n```\n\nAFTER:\n\n```ts\nimport { getRouterSelectors } from '@ngrx/router-store';\n\nconst routerSelectors = getRouterSelectors();\n```\n\n### @ngrx/schematics\n\n#### Replace `any` types with `unknown` type\n\nNgRx schematics do not use `any` types to define actions, these are replaced with the `unknown` type.\n\n## Deprecations\n\n### @ngrx/component\n\n#### LetModule\n\n`LetModule` is deprecated in favor of the standalone `LetDirective`.\n\nBEFORE:\n\n```ts\nimport { LetModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    LetModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\n```ts\nimport { Component } from '@angular/core';\nimport { LetModule } from '@ngrx/component';\n\n@Component({\n  // ... other metadata\n  standalone: true,\n  imports: [\n    // ... other imports\n    LetModule,\n  ],\n})\nexport class MyStandaloneComponent {}\n```\n\nAFTER:\n\n```ts\nimport { LetDirective } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    LetDirective,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\n```ts\nimport { Component } from '@angular/core';\nimport { LetDirective } from '@ngrx/component';\n\n@Component({\n  // ... other metadata\n  standalone: true,\n  imports: [\n    // ... other imports\n    LetDirective,\n  ],\n})\nexport class MyStandaloneComponent {}\n```\n\n#### PushModule\n\n`PushModule` is deprecated in favor of the standalone `PushPipe`.\n\nBEFORE:\n\n```ts\nimport { PushModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    PushModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\n```ts\nimport { Component } from '@angular/core';\nimport { PushModule } from '@ngrx/component';\n\n@Component({\n  // ... other metadata\n  standalone: true,\n  imports: [\n    // ... other imports\n    PushModule,\n  ],\n})\nexport class MyStandaloneComponent {}\n```\n\nAFTER:\n\n```ts\nimport { PushPipe } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    PushPipe,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\n```ts\nimport { Component } from '@angular/core';\nimport { PushPipe } from '@ngrx/component';\n\n@Component({\n  // ... other metadata\n  standalone: true,\n  imports: [\n    // ... other imports\n    PushPipe,\n  ],\n})\nexport class MyStandaloneComponent {}\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v17.md",
    "content": "# V17 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@17\n```\n\n## Dependencies\n\nVersion 17 has the minimum version requirements:\n\n- Angular version 17.x\n- Angular CLI version 17.x\n- TypeScript version 5.x\n- RxJS version ^6.5.x || ^7.5.0\n\n## Breaking changes\n\n### @ngrx/entity\n\n#### `adapter.getSelectors` return a `MemoizedSelector` type\n\nSelectors returned by the `adapter.getSelectors` signature that accepts a parent selector are strongly typed.\n\nBEFORE:\n\n```ts\nconst {\n  selectIds, // type: (state: object) => string[] | number[]\n  selectEntities, // type: (state: object) => Dictionary<Book>\n  selectAll, // type: (state: object) => Book[]\n  selectTotal, // type: (state: object) => number\n} = adapter.getSelectors(selectBooksState);\n```\n\nAFTER:\n\n```ts\nconst {\n  selectIds, // type: MemoizedSelector<object, string[] | number[]>\n  selectEntities, // type: MemoizedSelector<object, Dictionary<Book>>\n  selectAll, // type: MemoizedSelector<object, Book[]>\n  selectTotal, // type: MemoizedSelector<object, number>\n} = adapter.getSelectors(selectBooksState);\n```\n\n## @ngrx/store-devtools\n\n### Rename of `connectOutsideZone` to `connectInZone`\n\nThe DevTools extension connection is established outside of the Angular zone.\nTo revert to the previous behavior, set the `connectInZone` config property to `true`.\nAn automatic migration is added to configure the DevTools to the original behavior.\n\nBEFORE:\n\nThe DevTools extension connection is established in the Angular zone.\nTo change this behavior the config `connectOutsideZone` can be set to `true`.\n\n```ts\nprovideStoreDevtools({\n    connectOutsideZone: true\n}),\n```\n\nAFTER:\n\nThe DevTools extension connection is established outside of the Angular zone.\nTo change this behavior the config `connectInZone` can be set to `true`.\n\n```ts\nprovideStoreDevtools({\n    connectInZone: true\n}),\n```\n\n## @ngrx/component\n\n### Removal of `LetModule`\n\nThe `LetModule` is removed in favor of the standalone `LetDirective`.\n\nBEFORE:\n\n```ts\nimport { LetModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    LetModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\nAFTER:\n\n```ts\nimport { Component } from '@angular/core';\nimport { LetDirective } from '@ngrx/component';\n\n@Component({\n  // ... other metadata\n  standalone: true,\n  imports: [\n    // ... other imports\n    LetDirective,\n  ],\n})\nexport class MyStandaloneComponent {}\n```\n\n### Removal of `PushModule`\n\nThe `PushModule` is deprecated in favor of the standalone `PushPipe`.\n\nBEFORE:\n\n```ts\nimport { PushModule } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    PushModule,\n  ],\n})\nexport class MyFeatureModule {}\n```\n\nAFTER:\n\n```ts\nimport { PushPipe } from '@ngrx/component';\n\n@NgModule({\n  imports: [\n    // ... other imports\n    PushPipe,\n  ],\n})\nexport class MyFeatureModule {}\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v18.md",
    "content": "# V18 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@18\n```\n\n## Dependencies\n\nVersion 18 has the minimum version requirements:\n\n- Angular version 18.x\n- Angular CLI version 18.x\n- TypeScript version 5.4.x\n- RxJS version ^6.5.x || ^7.5.0\n\n## Breaking changes\n\n### @ngrx/store\n\n#### Merge `Action` and `TypedAction` interfaces\n\nThe Action and TypedAction interfaces are merged into one interface.\nThis change has an ng-update schematic that automatically updates your code.\n\nBEFORE:\n\nThere was a separation between the Action and TypedAction interfaces.\n\nAFTER:\n\nThe Action interface accepts a generic type parameter that represents the payload type (defaults to string).\nThe TypedAction interface is removed.\n\n### @ngrx/effects\n\n#### Remove `concatLatestFrom` operator\n\nThe `concatLatestFrom` operator has been removed from `@ngrx/effects` in favor of the `@ngrx/operators` package.\nThis change has an ng-update schematic that automatically updates your code.\n\nBEFORE:\n\n```ts\nimport { concatLatestFrom } from '@ngrx/effects';\n```\n\nAFTER:\n\n```ts\nimport { concatLatestFrom } from '@ngrx/operators';\n```\n\n### @ngrx/component-store\n\n#### Remove `tapResponse` operator\n\nThe `tapResponse` operator has been removed from `@ngrx/component-store` in favor of the `@ngrx/operators` package.\nThis change has an ng-update schematic that automatically updates your code.\n\nBEFORE:\n\n```ts\nimport { tapResponse } from '@ngrx/component-store';\n```\n\nAFTER:\n\n```ts\nimport { tapResponse } from '@ngrx/operators';\n```\n\n### @ngrx/router-store\n\n#### Include `string[]` as return type for `selectQueryParam`\n\nThe Angular router can return an array of query parameters. The `selectQueryParam` selector now includes `string[]` as a possible return type.\n\nBEFORE:\n\nThe return type of `selectQueryParam` is `MemoizedSelector<V, string | undefined>`.I\n\nAFTER:\n\nThe return type of `selectQueryParam` now includes `string[]`, making the return type `MemoizedSelector<V, string | string[] | undefined>`.\n\n### @ngrx/eslint-plugin\n\n#### Simplify configs\n\nThe rules have been regrouped into preconfigured configigurations to make it easier to find and configure them.\nThis change has been done to improve the developer experience while using ESLint v9.\nThe new configuration can be found in the documentation at https://ngrx.io/guide/eslint-plugin.\n\nBEFORE:\n\nRules were grouped for a package, strict-mode, with(out) type-checking.\n\nAFTER:\n\nRules are grouped per package. The differences were to small to further divide the rules in configurations.\nIf needed, you can always disable a rule.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v19.md",
    "content": "# V19 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@19\n```\n\n## Dependencies\n\nVersion 19 has the minimum version requirements:\n\n- Angular version 19.x\n- Angular CLI version 19.x\n- TypeScript version 5.6.x\n- RxJS version ^6.5.x || ^7.5.0\n\n## Breaking changes\n\n### Signals\n\n#### `computed` is replaced with `props`\n\nTo support more cases, the `props` property is added to `signalStoreFeature`, which replaces the existing `computed` property.\nThis change has an ng-update schematic that automatically updates your code.\n\n- The `computed` property in the `SignalStoreFeature` type is renamed to `props`.\n- The `computed` property in the `signalStoreFeature` method is renamed to `props`.\n- The `EntityComputed` and `NamedEntityComputed` types in the `entities` plugin are renamed to `EntityProps` and `NamedEntityProps`.\n\nBEFORE:\n\n```ts\nimport { computed, Signal } from '@angular/core';\nimport {\n  signalStoreFeature,\n  SignalStoreFeature,\n  type,\n  withComputed,\n} from '@ngrx/signals';\nimport { EntityComputed } from '@ngrx/signals/entities';\n\nexport function withTotalEntities<Entity>(): SignalStoreFeature<\n  { state: {}; computed: EntityComputed<Entity>; methods: {} },\n  { state: {}; computed: { total: Signal<number> }; methods: {} }\n> {\n  return signalStoreFeature(\n    { computed: type<EntityComputed<Entity>>() },\n    withComputed(({ entities }) => ({\n      total: computed(() => entities().length),\n    }))\n  );\n}\n```\n\nAFTER:\n\n```ts\nimport { computed, Signal } from '@angular/core';\nimport {\n  signalStoreFeature,\n  SignalStoreFeature,\n  type,\n  withComputed,\n} from '@ngrx/signals';\nimport { EntityProps } from '@ngrx/signals/entities';\n\nexport function withTotalEntities<Entity>(): SignalStoreFeature<\n  { state: {}; props: EntityProps<Entity>; methods: {} },\n  { state: {}; props: { total: Signal<number> }; methods: {} }\n> {\n  return signalStoreFeature(\n    { props: type<EntityProps<Entity>>() },\n    withComputed(({ entities }) => ({\n      total: computed(() => entities().length),\n    }))\n  );\n}\n```\n\n#### Rename `rxMethod.unsubscribe()` to `rxMethod.destroy()`\n\nThe `unsubscribe` method from `rxMethod` is renamed to `destroy`.\n\nBEFORE:\n\n```ts\nconst logNumber = rxMethod<number>(tap(console.log));\n\nconst num1Ref = logNumber(interval(1_000));\nconst num2Ref = logNumber(interval(2_000));\n\n// destroy `num1Ref` after 2 seconds\nsetTimeout(() => num1Ref.unsubscribe(), 2_000);\n\n// destroy all reactive method refs after 5 seconds\nsetTimeout(() => logNumber.unsubscribe(), 5_000);\n```\n\nAFTER:\n\n```ts\nconst logNumber = rxMethod<number>(tap(console.log));\n\nconst num1Ref = logNumber(interval(1_000));\nconst num2Ref = logNumber(interval(2_000));\n\n// destroy `num1Ref` after 2 seconds\nsetTimeout(() => num1Ref.destroy(), 2_000);\n\n// destroy all reactive method refs after 5 seconds\nsetTimeout(() => logNumber.destroy(), 5_000);\n```\n\n### Schematics\n\n#### Standalone is the default\n\n(Container) components created by the Container Schematic are now standalone by default.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v20.md",
    "content": "# V20 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\n# NgRx Store related packages\nng update @ngrx/store@20.0.0\n\n# NgRx Signals package\nng update @ngrx/signals@20.0.0\n```\n\n## Dependencies\n\nVersion 20 has the minimum version requirements:\n\n- Angular version 20.x\n- Angular CLI version 20.x\n- TypeScript version 5.8.x\n- RxJS version ^6.5.x || ^7.5.0\n\n## Breaking changes\n\n### Signals\n\nThe internal `STATE_SOURCE` is no longer represented as a single `WritableSignal` holding the entire state object. Instead, each top-level state property becomes its own `WritableSignal` or remains as-is if a `WritableSignal` is provided as a state property.\n\nBEFORE:\n\n1. The initial state object reference is preserved:\n\n```ts\nconst initialState = { ngrx: 'rocks' };\n\n// signalState:\nconst state = signalState(initialState);\nstate() === initialState; // true\n\n// withState:\nconst Store = signalStore(withState(initialState));\nconst store = new Store();\ngetState(store) === initialState; // true\n```\n\n2. Root state properties can be added dynamically:\n\n```ts\n// signalState:\nconst state = signalState<Record<string, string>>({});\nconsole.log(state()); // {}\n\npatchState(state, { ngrx: 'rocks' });\nconsole.log(state()); // { ngrx: 'rocks' }\n\n// withState:\nconst Store = signalStore(\n  { protectedState: false },\n  withState<Record<string, string>>({})\n);\nconst store = new Store();\nconsole.log(getState(store)); // {}\n\npatchState(store, { ngrx: 'rocks' });\nconsole.log(getState(store)); // { ngrx: 'rocks' }\n```\n\nAFTER:\n\n1. The initial state object reference is not preserved:\n\n```ts\nconst initialState = { ngrx: 'rocks' };\n\n// signalState:\nconst state = signalState(initialState);\nstate() === initialState; // false\n\n// withState:\nconst Store = signalStore(withState(initialState));\nconst store = new Store();\ngetState(store) === initialState; // false\n```\n\n2. Root state properties can not be added dynamically:\n\n```ts\n// signalState:\nconst state = signalState<Record<string, string>>({});\nconsole.log(state()); // {}\n\npatchState(state, { ngrx: 'rocks' });\nconsole.log(state()); // {}\n\n// withState:\nconst Store = signalStore(\n  { protectedState: false },\n  withState<Record<string, string>>({})\n);\nconst store = new Store();\nconsole.log(getState(store)); // {}\n\npatchState(store, { ngrx: 'rocks' });\nconsole.log(getState(store)); // {}\n```\n\n### Entity\n\n#### `getInitialState` is type-safe\n\n`getInitialState` is now type-safe, meaning that the initial state must match the entity state type. This change ensures that the initial state is correctly typed and prevents additional properties from being added to the state.\n\nBEFORE:\n\n```ts\nimport {\n  EntityState,\n  createEntityAdapter,\n  EntityAdapter,\n} from '@ngrx/entity';\n\ninterface Book {\n  id: string;\n  title: string;\n}\ninterface BookState extends EntityState<Book> {\n  selectedBookId: string | null;\n}\n\nexport const adapter: EntityAdapter<Book> =\n  createEntityAdapter<Book>();\nexport const initialState: BookState = adapter.getInitialState({\n  selectedBookId: '1',\n  otherProperty: 'value',\n});\n```\n\nAFTER:\n\n```ts\nimport {\n  EntityState,\n  createEntityAdapter,\n  EntityAdapter,\n} from '@ngrx/entity';\n\ninterface Book {\n  id: string;\n  title: string;\n}\ninterface BookState extends EntityState<Book> {\n  selectedBookId: string | null;\n}\n\nexport const adapter: EntityAdapter<Book> =\n  createEntityAdapter<Book>();\nexport const initialState: BookState = adapter.getInitialState({\n  selectedBookId: '1',\n  // 👇 this throws an error\n  // otherProperty: 'value',\n});\n```\n\n### Effects\n\n#### `act` operator is removed\n\nThe `act` operator is removed in favor of core RxJS flattening operators and `mapResponse` from the `@ngrx/operators` package.\n\nBEFORE:\n\n```ts\n@Injectable()\nexport class AuthEffects {\n  readonly login$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(LoginPageActions.loginFormSubmitted),\n      act({\n        project: ({ credentials }) =>\n          this.authService\n            .login(credentials)\n            .pipe(\n              map((user) => AuthApiActions.loginSuccess({ user }))\n            ),\n        error: (error) => AuthApiActions.loginFailure({ error }),\n        operator: exhaustMap,\n      })\n    );\n  });\n}\n```\n\nAFTER:\n\n```ts\n@Injectable()\nexport class AuthEffects {\n  readonly login$ = createEffect(() => {\n    return this.actions$.pipe(\n      ofType(LoginPageActions.loginFormSubmitted),\n      exhaustMap(({ credentials }) =>\n        this.authService.login(credentials).pipe(\n          mapResponse({\n            next: (user) => AuthApiActions.loginSuccess({ user }),\n            error: (error) => AuthApiActions.loginFailure({ error }),\n          })\n        )\n      )\n    );\n  });\n}\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v21.md",
    "content": "# V21 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@21\n```\n\n## Dependencies\n\nVersion 21 has the minimum version requirements:\n\n- Angular version 21\n- Angular CLI version 21\n- TypeScript version 5.9\n- RxJS version ^6.5.x || ^7.5.0\n\n## Breaking changes\n\n### @ngrx/signals\n\n#### `withEffects` is renamed to `withEventHandlers`\n\nThe `withEffects` feature from the `@ngrx/signals/events` plugin is renamed to `withEventHandlers`.\n\nBEFORE:\n\n```ts\nimport { withEffects } from '@ngrx/signals/events';\n\nexport const CounterStore = signalStore(\n  withState({ count: 0 }),\n  withEffects((store, events = inject(Events)) => ({\n    logCount$: events\n      .on(increment)\n      .pipe(tap(() => console.log(store.count()))),\n  }))\n);\n```\n\nAFTER:\n\n```ts\nimport { withEventHandlers } from '@ngrx/signals/events';\n\nexport const CounterStore = signalStore(\n  withState({ count: 0 }),\n  withEventHandlers((store, events = inject(Events)) => ({\n    logCount$: events\n      .on(increment)\n      .pipe(tap(() => console.log(store.count()))),\n  }))\n);\n```\n\n#### `rxMethod` and `signalMethod` accept computation functions as arguments\n\n`rxMethod` and `signalMethod` provide the ability to pass computation functions as input arguments in addition to signals.\nBecause of that, defining callbacks as direct inputs needs be handled differently.\n\nBEFORE:\n\n```ts\n// rxMethod:\nconst myRxMethod = rxMethod<() => void>(\n  tap((callback) => callback())\n);\n\nmyRxMethod(() => console.log('ngrx'));\n\n// signalMethod:\nconst mySignalMethod = signalMethod<() => void>((callback) =>\n  callback()\n);\nmySignalMethod(() => console.log('signals'));\n```\n\nAFTER:\n\n```ts\n// rxMethod:\nconst myRxMethod = rxMethod<{ callback: () => void }>(\n  tap(({ callback }) => callback())\n);\n\nmyRxMethod({ callback: () => console.log('ngrx') });\n\n// signalMethod:\nconst mySignalMethod = signalMethod<{ callback: () => void }>(\n  ({ callback }) => callback()\n);\nmySignalMethod({ callback: () => console.log('signals') });\n```\n\n### @ngrx/eslint-plugin\n\nThe lint rules that require type information have been moved to seperate predefined configurations.\n\n| Configuration    | Configuration including type checked rules |\n| ---------------- | ------------------------------------------ |\n| `all`            | `allTypeChecked`                           |\n| `componentStore` | N/A                                        |\n| `effects`        | `effectsTypeChecked`                       |\n| `operators`      | N/A                                        |\n| `signals`        | `signalsTypeChecked`                       |\n| `store`          | N/A                                        |\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v4.md",
    "content": "# V4 Update Guide\n\n## Dependencies\n\nYou need to have the latest versions of TypeScript and RxJS to use NgRx version 4 libraries.\n\nTypeScript 2.4.x\nRxJS 5.4.x\n\n## @ngrx/core\n\n`@ngrx/core` is no longer needed and conflicts with @ngrx/store. Remove the dependency from your project.\n\nBEFORE:\n\n```ts\nimport { compose } from '@ngrx/core/compose';\n```\n\nAFTER:\n\n```ts\nimport { compose } from '@ngrx/store';\n```\n\n## @ngrx/store\n\n### Action interface\n\nThe `payload` property has been removed from the `Action` interface. It was a source of type-safety\nissues, especially when used with `@ngrx/effects`. If your interface/class has a payload, you need to provide\nthe type.\n\nBEFORE:\n\n```ts\nimport { Action } from '@ngrx/store';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { Effect, Actions } from '@ngrx/effects';\n\n@Injectable()\nexport class MyEffects {\n  @Effect()\n  someEffect$: Observable<Action> = this.actions$\n    .ofType(UserActions.LOGIN)\n    .pipe(\n      map((action) => action.payload),\n      map(() => new AnotherAction())\n    );\n\n  constructor(private actions$: Actions) {}\n}\n```\n\nAFTER:\n\n```ts\nimport { Action } from '@ngrx/store';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { Effect, Actions } from '@ngrx/effects';\nimport { Login } from '../actions/auth';\n\n@Injectable()\nexport class MyEffects {\n  @Effect()\n  someEffect$: Observable<Action> = this.actions$\n    .ofType<Login>(UserActions.LOGIN)\n    .pipe(\n      map((action) => action.payload),\n      map(() => new AnotherAction())\n    );\n\n  constructor(private actions$: Actions) {}\n}\n```\n\nIf you prefer to keep the `payload` interface property, you can provide your own parameterized version.\n\n```ts\nexport interface ActionWithPayload<T> extends Action {\n  payload: T;\n}\n```\n\nAnd if you need an unsafe version to help with transition.\n\n```ts\nexport interface UnsafeAction extends Action {\n  payload?: any;\n}\n```\n\n### Registering Reducers\n\nPreviously to be AOT compatible, it was required to pass a function to the `provideStore` method to compose the reducers into one root reducer. The `initialState` was also provided to the method as an object in the second argument.\n\nBEFORE:\n\n`reducers/index.ts`\n\n```ts\nconst reducers = {\n  auth: fromAuth.reducer,\n  layout: fromLayout.reducer,\n};\n\nconst rootReducer = combineReducers(reducers);\n\nexport function reducer(state: any, action: any) {\n  return rootReducer(state, action);\n}\n```\n\n`app.module.ts`\n\n```ts\nimport { StoreModule } from '@ngrx/store';\nimport { reducer } from './reducers';\n\n@NgModule({\n  imports: [\n    StoreModule.provideStore(reducer, {\n      auth: {\n        loggedIn: true,\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\nThis has been simplified to only require a map of reducers that will be composed together by the library. A second argument is a configuration object where you provide the `initialState`.\n\nAFTER:\n\n`reducers/index.ts`\n\n```ts\nimport { ActionReducerMap } from '@ngrx/store';\n\nexport interface State {\n  auth: fromAuth.State;\n  layout: fromLayout.State;\n}\n\nexport const reducers: ActionReducerMap<State> = {\n  auth: fromAuth.reducer,\n  layout: fromLayout.reducer,\n};\n```\n\n`app.module.ts`\n\n```ts\nimport { StoreModule } from '@ngrx/store';\nimport { reducers } from './reducers';\n\n@NgModule({\n  imports: [\n    StoreModule.forRoot(reducers, {\n      initialState: {\n        auth: {\n          loggedIn: true,\n        },\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n## @ngrx/effects\n\n### Registering Effects\n\nBEFORE:\n\n`app.module.ts`\n\n```ts\n@NgModule({\n  imports: [EffectsModule.run(SourceA), EffectsModule.run(SourceB)],\n})\nexport class AppModule {}\n```\n\nAFTER:\n\nThe `EffectsModule.forRoot` method is _required_ in your root `AppModule`. Provide an empty array\nif you don't need to register any root-level effects.\n\n`app.module.ts`\n\n```ts\n@NgModule({\n  imports: [EffectsModule.forRoot([SourceA, SourceB, SourceC])],\n})\nexport class AppModule {}\n```\n\nImport `EffectsModule.forFeature` in any NgModule, whether be the `AppModule` or a feature module.\n\n`feature.module.ts`\n\n```ts\n@NgModule({\n  imports: [\n    EffectsModule.forFeature([\n      FeatureSourceA,\n      FeatureSourceB,\n      FeatureSourceC,\n    ]),\n  ],\n})\nexport class FeatureModule {}\n```\n\n### Init Action\n\nThe `@ngrx/store/init` action now fires prior to effects starting. Use defer() for the same behaviour.\n\nBEFORE:\n\n`app.effects.ts`\n\n```ts\nimport { Dispatcher, Action } from '@ngrx/store';\nimport { Actions, Effect } from '@ngrx/effects';\n\nimport * as auth from '../actions/auth.actions';\n\n@Injectable()\nexport class AppEffects {\n  @Effect()\n  init$: Observable<Action> = this.actions$\n    .ofType(Dispatcher.INIT)\n    .switchMap((action) => {\n      return of(new auth.LoginAction());\n    });\n\n  constructor(private actions$: Actions) {}\n}\n```\n\nAFTER:\n\n`app.effects.ts`\n\n```ts\nimport { Action } from '@ngrx/store';\nimport { Actions, Effect } from '@ngrx/effects';\nimport { defer } from 'rxjs';\n\nimport * as auth from '../actions/auth.actions';\n\n@Injectable()\nexport class AppEffects {\n  @Effect()\n  init$: Observable<Action> = defer(() => {\n    return of(new auth.LoginAction());\n  });\n\n  constructor(private actions$: Actions) {}\n}\n```\n\n### Testing Effects\n\nBEFORE:\n\n```ts\nimport {\n  EffectsTestingModule,\n  EffectsRunner,\n} from '@ngrx/effects/testing';\nimport { MyEffects } from './my-effects';\n\ndescribe('My Effects', () => {\n  let effects: MyEffects;\n  let runner: EffectsRunner;\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [EffectsTestingModule],\n      providers: [\n        MyEffects,\n        // other providers\n      ],\n    });\n\n    effects = TestBed.inject(MyEffects);\n    runner = TestBed.inject(EffectsRunner);\n  });\n\n  it('should work', () => {\n    runner.queue(SomeAction);\n\n    effects.someSource$.subscribe((result) => {\n      expect(result).toBe(AnotherAction);\n    });\n  });\n});\n```\n\nAFTER:\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport { hot, cold } from 'jasmine-marbles';\nimport { MyEffects } from './my-effects';\nimport { ReplaySubject } from 'rxjs/ReplaySubject';\n\ndescribe('My Effects', () => {\n  let effects: MyEffects;\n  let actions: Observable<any>;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [\n        MyEffects,\n        provideMockActions(() => actions),\n        // other providers\n      ],\n    });\n\n    effects = TestBed.inject(MyEffects);\n  });\n\n  it('should work', () => {\n    actions = hot('--a-', { a: SomeAction, ... });\n\n    const expected = cold('--b', { b: AnotherAction });\n\n    expect(effects.someSource$).toBeObservable(expected);\n  });\n\n  it('should work also', () => {\n    actions = new ReplaySubject(1);\n\n    actions.next(SomeAction);\n\n    effects.someSource$.subscribe(result => {\n      expect(result).toBe(AnotherAction);\n    });\n  });\n});\n```\n\n## @ngrx/router-store\n\n### Registering the module\n\nBEFORE:\n\n`reducers/index.ts`\n\n```ts\nimport * as fromRouter from '@ngrx/router-store';\n\nexport interface State {\n  router: fromRouter.RouterState;\n}\n\nconst reducers = {\n  router: fromRouter.routerReducer,\n};\n\nconst rootReducer = combineReducers(reducers);\n\nexport function reducer(state: any, action: any) {\n  return rootReducer(state, action);\n}\n```\n\n`app.module.ts`\n\n```ts\nimport { RouterModule } from '@angular/router';\nimport { RouterStoreModule } from '@ngrx/router-store';\nimport { reducer } from './reducers';\n\n@NgModule({\n  imports: [\n    StoreModule.provideStore(reducer),\n    RouterModule.forRoot([\n      // some routes\n    ])\n    RouterStoreModule.connectRouter()\n  ]\n})\nexport class AppModule {}\n```\n\nAFTER:\n\n`reducers/index.ts`\n\n```ts\nimport * as fromRouter from '@ngrx/router-store';\n\nexport interface State {\n  routerReducer: fromRouter.RouterReducerState;\n}\n\nexport const reducers = {\n  routerReducer: fromRouter.routerReducer,\n};\n```\n\n`app.module.ts`\n\n```ts\nimport { StoreRouterConnectingModule } from '@ngrx/router-store';\nimport { reducers } from './reducers';\n\n@NgModule({\n  imports: [\n    StoreModule.forRoot(reducers),\n    RouterModule.forRoot([\n      // some routes\n    ]),\n    StoreRouterConnectingModule,\n  ],\n})\nexport class AppModule {}\n```\n\n### Navigation actions\n\nNavigation actions are not provided as part of the V4 package. You provide your own\ncustom navigation actions that use the `Router` within effects to navigate.\n\nBEFORE:\n\n```ts\nimport { go, back, forward } from '@ngrx/router-store';\n\nstore.dispatch(\n  go(['/path', { routeParam: 1 }], { page: 1 }, { replaceUrl: false })\n);\n\nstore.dispatch(back());\n\nstore.dispatch(forward());\n```\n\nAFTER:\n\n```ts\nimport { Action } from '@ngrx/store';\nimport { NavigationExtras } from '@angular/router';\n\nexport const GO = '[Router] Go';\nexport const BACK = '[Router] Back';\nexport const FORWARD = '[Router] Forward';\n\nexport class Go implements Action {\n  readonly type = GO;\n\n  constructor(\n    public payload: {\n      path: any[];\n      query?: object;\n      extras?: NavigationExtras;\n    }\n  ) {}\n}\n\nexport class Back implements Action {\n  readonly type = BACK;\n}\n\nexport class Forward implements Action {\n  readonly type = FORWARD;\n}\n\nexport type Actions = Go | Back | Forward;\n```\n\n```ts\nimport * as RouterActions from './actions/router';\n\nstore.dispatch(new RouterActions.Go({\n  path: ['/path', { routeParam: 1 }],\n  query: { page: 1 },\n  extras: { replaceUrl: false }\n});\n\nstore.dispatch(new RouterActions.Back());\n\nstore.dispatch(new RouterActions.Forward());\n```\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Location } from '@angular/common';\nimport { Effect, Actions } from '@ngrx/effects';\nimport { map, tap } from 'rxjs/operators';\nimport * as RouterActions from './actions/router';\n\n@Injectable()\nexport class RouterEffects {\n  @Effect({ dispatch: false })\n  navigate$ = this.actions$.ofType(RouterActions.GO).pipe(\n    map((action: RouterActions.Go) => action.payload),\n    tap(({ path, query: queryParams, extras }) =>\n      this.router.navigate(path, { queryParams, ...extras })\n    )\n  );\n\n  @Effect({ dispatch: false })\n  navigateBack$ = this.actions$\n    .ofType(RouterActions.BACK)\n    .do(() => this.location.back());\n\n  @Effect({ dispatch: false })\n  navigateForward$ = this.actions$\n    .ofType(RouterActions.FORWARD)\n    .do(() => this.location.forward());\n\n  constructor(\n    private actions$: Actions,\n    private router: Router,\n    private location: Location\n  ) {}\n}\n```\n\n## @ngrx/store-devtools\n\n### Instrumentation method\n\n**NOTE:** store-devtools currently causes severe performance problems when\nused with router-store. We are working to\n[fix this](https://github.com/ngrx/platform/issues/97), but for now, avoid\nusing them together.\n\nBEFORE:\n\n`app.module.ts`\n\n```ts\nimport { StoreDevtoolsModule } from '@ngrx/store-devtools';\n\n@NgModule({\n  imports: [\n    StoreDevtoolsModule.instrumentStore({ maxAge: 50 }),\n    // OR\n    StoreDevtoolsModule.instrumentOnlyWithExtension({\n      maxAge: 50,\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\nAFTER:\n\n`app.module.ts`\n\n```ts\nimport { StoreDevtoolsModule } from '@ngrx/store-devtools';\nimport { environment } from '../environments/environment'; // Angular CLI environment\n\n@NgModule({\n  imports: [\n    !environment.production\n      ? StoreDevtoolsModule.instrument({ maxAge: 50 })\n      : [],\n  ],\n})\nexport class AppModule {}\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v7.md",
    "content": "# V7 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@7\n```\n\n## Dependencies\n\nV7 has the minimum version requirements:\n\n- Angular version 7\n- TypeScript version 3.1.x\n- RxJS version 6.x\n\n## @ngrx/store\n\n### Feature loaded action\n\nWhen multiple feature reducers are registered, a single action is dispatched instead of an action for each added feature reducer.\n\nBEFORE:\n\nWhen adding/removing one feature:\n\n```ts\n{type: '@ngrx/store/update-reducers', feature: 'feat1'}\n```\n\nWhen adding/removing multiple features:\n\n```ts\n{type: '@ngrx/store/update-reducers', feature: 'feat1'}\n{type: '@ngrx/store/update-reducers', feature: 'feat2'}\n```\n\nAFTER:\n\nWhen adding/removing one feature:\n\n```ts\n{type: '@ngrx/store/update-reducers', features: ['feat1']}\n```\n\nWhen adding/removing multiple features:\n\n```ts\n{type: '@ngrx/store/update-reducers', features: ['feat1', 'feat2']}\n```\n\n## @ngrx/effects\n\n### `ofType` removal\n\nIn NgRx 6.1 the `ofType` function was marked as deprecated in favor of the `ofType` operator, in NgRx v7 this function was dropped.\n\nBEFORE:\n\n```ts\nimport { Effect, Actions } from '@ngrx/effects';\n\n@Injectable()\nexport class MyEffects {\n  @Effect()\n  someEffect$: Observable<Action> = this.actions$\n    .ofType(UserActions.LOGIN)\n    .pipe(map(() => new AnotherAction()));\n\n  constructor(private actions$: Actions) {}\n}\n```\n\nAFTER:\n\n```ts\nimport { Effect, Actions, ofType } from '@ngrx/effects'; // import ofType operator\n\n@Injectable()\nexport class MyEffects {\n  @Effect()\n  someEffect$: Observable<Action> = this.actions$.pipe(\n    ofType(UserActions.LOGIN), // use the pipeable ofType operator\n    map(() => new AnotherAction())\n  );\n\n  constructor(private actions$: Actions) {}\n}\n```\n\n## @ngrx/router-store\n\n### Default state key\n\nThe default NgRx router state key is changed from `routerReducer` to `router`.\n\nBEFORE:\n\n```ts\nStoreRouterConnectingModule.forRoot({\n  stateKey: 'router',\n}),\n```\n\nAFTER:\n\n```ts\nStoreRouterConnectingModule.forRoot(),\n```\n\n### ActivatedRouteSnapshot.RouteConfig\n\nThe default router serializer now returns a `null` value for `routeConfig` when `routeConfig` doesn't exist on the `ActivatedRouteSnapshot` instead of an empty object.\n\nBEFORE:\n\n```json\n{\n  \"routeConfig\": {}\n}\n```\n\nAFTER:\n\n```json\n{\n  \"routeConfig\": null\n}\n```\n\n## @ngrx/store-devtools\n\n### Recompute state action\n\nThe devtools is using the new `@ngrx/store-devtools/recompute` action to recompute its state instead of the `@ngrx/store/update-reducers` action.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v8.md",
    "content": "# V8 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@8\n```\n\n## Dependencies\n\nV8 has the minimum version requirements:\n\n- Angular version 8.x\n- Angular CLI version 8.0.2\n- TypeScript version 3.4.x\n- RxJS version 6.4.0\n\n## Breaking changes\n\n### @ngrx/store\n\n#### `META_REDUCERS` token\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided to rename the `META_REDUCERS` token to `USER_PROVIDED_META_REDUCERS`\n\n</ngrx-docs-alert>\n\nThe `META_REDUCERS` token has been renamed to `USER_PROVIDED_META_REDUCERS`.\n\nThe `META_REDUCERS` token has become a multi token and can be used by\nlibrary authors.\n\n#### Selectors with only a projector function aren't valid anymore\n\nThis change will make the usage of selectors more consistent.\n\nBEFORE:\n\n```ts\nconst getTodosById = createSelector(\n  (state: TodoAppSchema, id: number) =>\n    state.todos.find((p) => p.id === id)\n);\n```\n\nAFTER:\n\n```ts\nconst getTodosById = createSelector(\n  (state: TodoAppSchema) => state.todos,\n  (todos: Todo[], id: number) => todos.find((p) => p.id === id)\n);\n```\n\n#### MemoizedSelector enforces the return type to strictly match the second generic type.\n\nFor example, prior to 8.0.0 the following would be fine, since the return type `boolean` is widened to `boolean | null`.\n\nBEFORE:\n\n```ts\nexport const getLoginPagePending: MemoizedSelector<\n  State,\n  boolean | null\n> = createSelector(\n  selectLoginPageState,\n  (loginState) => loginState.pending // boolean\n);\n```\n\nNow this will produce an error:\n\n```txt\n error TS2322: Type 'MemoizedSelector<State, boolean>' is not assignable to type 'MemoizedSelector<State, boolean | null>'.\n  Types of property 'setResult' are incompatible.\n    Type '(result?: boolean | undefined) => void' is not assignable to type '(result?: boolean | null | undefined) => void'.\n      Types of parameters 'result' and 'result' are incompatible.\n        Type 'boolean | null | undefined' is not assignable to type 'boolean | undefined'.\n          Type 'null' is not assignable to type 'boolean | undefined'.\n```\n\nFix would be to specify the type correctly.\n\nAFTER:\n\n```ts\nexport const getLoginPagePending: MemoizedSelector<State, boolean> =\n  createSelector(\n    selectLoginPageState,\n    (loginState) => loginState.pending // boolean\n  );\n```\n\nAnother interesting case is when object literals are returned, e.g.\n\nBEFORE:\n\n```ts\ninterface Reaction {\n  happy: boolean;\n  tweet: string;\n}\nexport const getNews: MemoizedSelector<State, Reaction> =\n  createSelector(newsState, (news) => {\n    if (news.isFake) {\n      return {\n        happy: false,\n        tweet: 'blah blah blah',\n      };\n    }\n    return {\n      happy: true,\n      tweet: 'anyway',\n    };\n  });\n```\n\nNow the error message would happen (and it is a bit cryptic):\n\n```txt\nType 'MemoizedSelector<State, { happy: false; tweet: string; } | { happy: true; tweet: string; }>' is not assignable to type 'MemoizedSelector<State, Reaction>'.\n  Type 'Reaction' is not assignable to type '{ happy: false; tweet: string; } | { happy: true; tweet: string; }'.\n    Type 'Reaction' is not assignable to type '{ happy: true; tweet: string; }'.\n      Types of property 'happy' are incompatible.\n        Type 'boolean' is not assignable to type 'true'.ts(2322)\n```\n\nFix would be to add the return type to the `projector` function\n\nAFTER:\n\n```ts\nexport const getNews: MemoizedSelector<State, Reaction> =\n  createSelector(newsState, (news): Reaction => {\n    if (news.isFake) {\n      return {\n        happy: false,\n        tweet: 'blah blah blah',\n      };\n    }\n    return {\n      happy: true,\n      tweet: 'anyway',\n    };\n  });\n```\n\n#### Return type of `createSelectorFactory` and `createSelector`\n\nThe return type of the `createSelectorFactory` and `createSelector` functions are now a `MemoizedSelector` instead of a `Selector`.\n\n#### Deprecation of ngrx-store-freeze\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided to remove the usage `ngrx-store-freeze`, remove it from the `package.json`, and to enable the built-in runtime checks `strictStateImmutability` and `strictActionImmutability`.\n\n</ngrx-docs-alert>\n\nWith the new built-in runtime checks, the usage of the `ngrx-store-freeze` package has become obsolete.\n\n### @ngrx/effects\n\n#### Resubscribe on Errors\n\nIf an error occurs (or is flattened) in the main effect's pipe then it will be\nreported and the effect is resubscribed automatically. In cases when this new behavior is\nundesirable, it can be disabled using `{resubscribeOnError: false}` within the effect metadata\n(for each effect individually). [Learn more](/guide/effects/lifecycle#resubscribe-on-error).\n\nBEFORE:\n\n```ts\nlogin$ = createEffect(() =>\n  this.actions$.pipe(\n    ofType(LoginPageActions.login),\n    exhaustMap((action) =>\n      this.authService.login(action.credentials).pipe(\n        map((user) => AuthApiActions.loginSuccess({ user })),\n        catchError((error) =>\n          of(AuthApiActions.loginFailure({ error }))\n        )\n      )\n    )\n  )\n);\n```\n\nAFTER:\n\n```ts\nlogins$ = createEffect(\n  () =>\n    this.actions$.pipe(\n      ofType(LoginPageActions.login),\n      exhaustMap((action) =>\n        this.authService.login(action.credentials).pipe(\n          map((user) => AuthApiActions.loginSuccess({ user })),\n          catchError((error) =>\n            of(AuthApiActions.loginFailure({ error }))\n          )\n        )\n      )\n    ),\n  { resubscribeOnError: false }\n);\n```\n\n### @ngrx/router-store\n\n#### Required usage of `forRoot`\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided and will append `forRoot` to `StoreRouterConnectingModule`\n\n</ngrx-docs-alert>\n\nUsage of `forRoot` is now required for `StoreRouterConnectingModule`\n\nBEFORE:\n\n```ts\n@NgModule({\n  imports: [StoreRouterConnectingModule],\n})\nexport class AppModule {}\n```\n\nAFTER:\n\n```ts\n@NgModule({\n  imports: [StoreRouterConnectingModule.forRoot()],\n})\nexport class AppModule {}\n```\n\n### @ngrx/entity\n\n#### add undefined to Dictionary's index signature\n\n`Dictionary` and `DictionaryNum` could be producing `undefined` but previous typings were not explicit about it.\n\n### @ngrx/store-devtools\n\n#### `actionsWhitelist` is renamed to `actionsSafelist`\n\nBEFORE:\n\n```ts\nStoreDevtoolsModule.instrument({\n  actionsWhitelist: ['...'],\n});\n```\n\nAFTER:\n\n```ts\nStoreDevtoolsModule.instrument({\n  actionsSafelist: ['...'],\n});\n```\n\n#### `actionsBlacklist` is renamed to `actionsBlocklist`\n\nBEFORE:\n\n```ts\nStoreDevtoolsModule.instrument({\n  actionsBlacklist: ['...'],\n});\n```\n\nAFTER:\n\n```ts\nStoreDevtoolsModule.instrument({\n  actionsBlocklist: ['...'],\n});\n```\n\n## @ngrx/data\n\n### Renames\n\nTo stay consistent with the other `@ngrx/*` packages, the following has been renamed:\n\n- `NgrxDataModule` is renamed to `EntityDataModule`\n- `NgrxDataModuleWithoutEffects` is renamed to `EntityDataModuleWithoutEffects`\n- `NgrxDataModuleConfig` is renamed to `EntityDataModuleConfig`\n\n<ngrx-docs-alert type=\"help\">\n\nThe installation of `@ngrx/data` package via `ng add @ngrx/data` will remove `ngrx-data` from the `package.json` and will also perform these renames.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/migration/v9.md",
    "content": "# V9 Update Guide\n\n## Angular CLI update\n\nNgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes.\n\nTo update your packages to the latest released version, run the command below.\n\n```sh\nng update @ngrx/store@9\n```\n\n## Dependencies\n\nVersion 9 has the minimum version requirements:\n\n- Angular version 9.x\n- Angular CLI version 9.x\n- TypeScript version 3.7.x\n- RxJS version 6.5.x\n\n## Breaking changes\n\n### @ngrx/store\n\n#### Immutability checks are turned on by default\n\nIn the previous version of NgRx, [runtime checks](/guide/store/configuration/runtime-checks) were opt-in.\nIn this version, the immutability runtime check is turned on by default.\n\nTo turn them off use:\n\n```ts\n@NgModule({\n  imports: [\n    StoreModule.forRoot(reducers, {\n      runtimeChecks: {\n        strictStateImmutability: false,\n        strictActionImmutability: false,\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n#### Change default value of minimal to true (Store Schematics)\n\nThis change only registers `StoreModule.forRoot({})` in the provided `module` with an empty array.\nBefore, it also registered an empty reducer.\n\n#### Change default value of creators to true (Store Schematics)\n\nAction and reducers creators are now the default, instead of defining Actions as classes, and reducers as switch-based method.\n\n### @ngrx/effects\n\n#### `resubscribeOnError` is renamed to `useEffectsErrorHandler`\n\nThe option `resubscribeOnError` has been renamed to `useEffectsErrorHandler`.\nThis change is made to make it possible to provide a [custom effect error handler](/guide/effects/lifecycle#customizing-the-effects-error-handler).\n\nBefore:\n\n```ts\n// decorator\n@Effect({ resubscribeOnError: false })\neffect$ = ...\n\n// with createEffect\neffect$ = createEffect(() => ..., {resubscribeOnError: false })\n```\n\nAfter:\n\n```ts\n// decorator\n@Effect({ useEffectsErrorHandler: false })\neffect$ = ...\n\n// with createEffect\neffect$ = createEffect(() => ..., { useEffectsErrorHandler: false })\n```\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided to rename the occurrences.\n\n</ngrx-docs-alert>\n\n#### Limit retries to 10 by default\n\nBy default, effects are resubscribed up to 10 errors, previously this was unlimited.\n\nTo change the number, implement a [custom effect error handler](/guide/effects/lifecycle#customizing-the-effects-error-handler), or [change the number](/guide/effects/lifecycle#customizing-the-effects-error-handler) TK add example to docs and provide link here.\n\n#### Dispatch init actions once\n\nPreviously, when an Effect implemented the `OnInitEffects` lifecycle hook, the defined action would be dispatched after each time a lazy loaded module was loaded with the same Effect class.\nNow, the action will only be dispatched after the first module is loaded.\n\n#### Change default value of minimal to true (Effects Schematics)\n\nThis change only registers `EffectsModule.forRoot()` in the provided `module` with an empty array.\nBefore, it also registered an empty effect class.\n\n#### Change default value of creators to true (Effects Schematics)\n\nEffects creators are now the default, instead of defining Effects with the `@Effect()` decorator.\n\n### @ngrx/router-store\n\n#### Minimal router state is the default\n\nMinimal router state becomes the default, this also means that the [`MinimalRouterStateSerializer`](/guide/router-store/configuration) will be used by default. The minimal only contains state that is serializable, see [`MinimalActivatedRouteSnapshot`](/api/router-store/MinimalActivatedRouteSnapshot) for more info.\n\nThe event on the payload of the dispatched actions will only contain the router event id and the url, instead of the Angular RouterEvent.\n\n<ngrx-docs-alert type=\"help\">\n\nA migration is provided and adds the `DefaultRouterStateSerializer` if a serializer isn't set.\n\n</ngrx-docs-alert>\n\n## Deprecations\n\n### @ngrx/entity\n\n#### `addAll`\n\nThe `addAll` entity adapter method has been deprecated, in favor of `setAll`.\n`setAll`'s behavior is identical to `addAll`, but the name covers its intention better.\n\nBEFORE:\n\n```ts\nadapter.addAll([person1, person2, person3], state);\n```\n\nAFTER:\n\n```ts\nadapter.setAll([person1, person2, person3], state);\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/nightlies.md",
    "content": "# Nightly builds\n\nNightly builds are provided to allow developers to use the latest features that haven't been released yet. The nightly builds of each package are published on each successful build of the main branch. Use the installation instructions below for each respective package.\n\n### Store\n\n```sh\nnpm install github:ngrx/store-builds\n```\n\n```sh\nyarn add github:ngrx/store-builds\n```\n\n### Store Devtools\n\n```sh\nnpm install github:ngrx/store-devtools-builds\n```\n\n```sh\nyarn add github:ngrx/store-devtools-builds\n```\n\n### Effects\n\n```sh\nnpm install github:ngrx/effects-builds\n```\n\n```sh\nyarn add github:ngrx/effects-builds\n```\n\n### Router Store\n\n```sh\nnpm install github:ngrx/router-store-builds\n```\n\n```sh\nyarn add github:ngrx/router-store-builds\n```\n\n### Entity\n\n```sh\nnpm install github:ngrx/entity-builds\n```\n\n```sh\nyarn add github:ngrx/entity-builds\n```\n\n### Data\n\n```sh\nnpm install github:ngrx/data-builds\n```\n\n```sh\nyarn add github:ngrx/data-builds\n```\n\n### Component\n\n```sh\nnpm install github:ngrx/component-builds\n```\n\n```sh\nyarn add github:ngrx/component-builds\n```\n\n### ComponentStore\n\n```sh\nnpm install github:ngrx/component-store-builds\n```\n\n```sh\nyarn add github:ngrx/component-store-builds\n```\n\n### Schematics\n\n```sh\nnpm install github:ngrx/schematics-builds --save-dev\n```\n\n```sh\nyarn add github:ngrx/schematics-builds --dev\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/operators/index.md",
    "content": "# @ngrx/operators\n\nNgRx Operators is a utility library with frequently used RxJS operators for managing state and side effects.\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/operators/install) page.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/operators/install.md",
    "content": "# Installation\n\n## Installing with `ng add`\n\nYou can install the `@ngrx/operators` to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/operators@latest\n```\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/operators`.\n2. Run the package manager to install the added dependency.\n\n## Manual Installation\n\nYou can also install `@ngrx/operators` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/operators\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/operators/operators.md",
    "content": "# Operators\n\nThe operators library provides some useful operators that are frequently\nused when managing state and side effects.\n\n## `concatLatestFrom`\n\nThe `concatLatestFrom` operator functions similarly to `withLatestFrom` with one important difference - it lazily evaluates the provided Observable factory.\n\nThis allows you to utilize the source value when selecting additional sources to concat.\n\nAdditionally, because the factory is not executed until it is needed, it also mitigates the performance impact of creating some kinds of Observables.\n\nFor example, when selecting data from the store with `store.select`, `concatLatestFrom` will prevent the\nselector from being evaluated until the source emits a value.\n\nThe `concatLatestFrom` operator takes an Observable factory function that returns an array of Observables, or a single Observable.\n\n<ngrx-docs-alert type=\"help\">\n\nThe `concatLatestFrom` operator has been moved from `@ngrx/effects` to `@ngrx/operators`. If you're looking for the older documentation (prior to v18), see the [v17 documentation](https://v17.ngrx.io/guide/effects/operators#concatlatestfrom).\n\n</ngrx-docs-alert>\n\n<ngrx-code-example header=\"router-effects.ts\">\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\n\nimport { map, tap } from 'rxjs';\n\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { Store } from '@ngrx/store';\nimport { routerNavigatedAction } from '@ngrx/router-store';\nimport { concatLatestFrom } from '@ngrx/operators';\n\nimport { selectRouteData } from './router-selectors';\n\n@Injectable()\nexport class RouterEffects {\n  readonly #actions$ = inject(Actions);\n  readonly #store = inject(Store);\n  readonly #titleService = inject(Title);\n\n  updateTitle$ = createEffect(\n    () =>\n      this.#actions$.pipe(\n        ofType(routerNavigatedAction),\n        concatLatestFrom(() => this.#store.select(selectRouteData)),\n        map(([, data]) => `Book Collection - ${data['title']}`),\n        tap((title) => this.#titleService.setTitle(title))\n      ),\n    { dispatch: false }\n  );\n}\n```\n\n</ngrx-code-example>\n\n## tapResponse\n\nAn easy way to handle the response with an Observable in a safe way, without additional boilerplate is to use the `tapResponse` operator. It enforces that the error case is handled and that the effect would still be running should an error occur. It is essentially a simple wrapper around two operators:\n\n- `tap` that handles success and error cases.\n- `catchError(() => EMPTY)` that ensures that the effect continues to run after the error.\n\n<ngrx-docs-alert type=\"help\">\n\nThe `tapResponse` operator has been moved from `@ngrx/component-store` to `@ngrx/operators`. If you're looking for the older documentation (prior to v18), see the [v17 documentation](https://v17.ngrx.io/guide/component-store/effect#tapresponse).\n\n</ngrx-docs-alert>\n\n<ngrx-code-example header=\"movies-store.ts\">\n\n```ts\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\nimport { tapResponse } from '@ngrx/operators';\n// ... other imports\n\n@Injectable()\nexport class MoviesStore {\n  // ... other store members\n\n  readonly loadMovie = rxMethod<string>(\n    pipe(\n      // 👇 Handle race condition with the proper choice of the flattening operator.\n      switchMap(() =>\n        this.moviesService.getMovie(id).pipe(\n          //👇 Act on the result within inner pipe.\n          tapResponse({\n            next: (movie) => this.addMovie(movie),\n            error: (error: HttpErrorResponse) => this.logError(error),\n          })\n        )\n      )\n    )\n  );\n}\n```\n\n</ngrx-code-example>\n\nIn addition to the `next` and `error` callbacks, `tapResponse` provides the ability to pass `complete` and/or `finalize` callbacks:\n\n<ngrx-code-example header=\"movies-store.ts\">\n\n```ts\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\nimport { tapResponse } from '@ngrx/operators';\n// ... other imports\n\n@Injectable()\nexport class MoviesStore {\n  // ... other store members\n\n  readonly loadMoviesByQuery = rxMethod<string>(\n    pipe(\n      tap(() => this.isLoading.set(true),\n      switchMap((query) =>\n        this.moviesService.getMoviesByQuery(query).pipe(\n          tapResponse({\n            next: (movies) => this.movies.set(movies),\n            error: (error: HttpErrorResponse) => this.logError(error),\n            finalize: () => this.isLoading.set(false),\n          })\n        )\n      )\n    )\n  );\n}\n```\n\n</ngrx-code-example>\n\n## mapResponse\n\nThe `mapResponse` operator is particularly useful in scenarios where you need to transform data and handle potential errors with minimal boilerplate.\n\nIn the example below, we use `mapResponse` within an NgRx effect to handle loading movies from an API. It demonstrates how to map successful API responses to an action indicating success, and how to handle errors by dispatching an error action.\n\n<ngrx-code-example header=\"movies-effects.ts\">\n\n```ts\nimport { createEffect } from '@ngrx/effects';\nimport { mapResponse } from '@ngrx/operators';\n// ...other imports\n\nexport const loadMovies = createEffect(\n  (\n    actions$ = inject(Actions),\n    moviesService = inject(MoviesService)\n  ) => {\n    return actions$.pipe(\n      ofType(MoviesPageActions.opened),\n      exhaustMap(() =>\n        moviesService.getAll().pipe(\n          mapResponse({\n            next: (movies) =>\n              MoviesApiActions.moviesLoadedSuccess({ movies }),\n            error: (error: { message: string }) =>\n              MoviesApiActions.moviesLoadedFailure({\n                errorMsg: error.message,\n              }),\n          })\n        )\n      )\n    );\n  },\n  { functional: true }\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/router-store/actions.md",
    "content": "# Router Actions\n\nRouter Store provides five navigation actions which are dispatched in a specific order. The `routerReducer` provided by Router Store updates its state with the latest router state given by the actions. By default we recommend to use the creator functions we provide.\n\n## Actions\n\n### routerRequestAction\n\nAt the start of each navigation, the router will dispatch a `ROUTER_REQUEST` action.\n\n### routerNavigationAction\n\nDuring navigation, before any guards or resolvers run, the router will dispatch a `ROUTER_NAVIGATION` action.\n\nIf you want the `ROUTER_NAVIGATION` to be dispatched after guards or resolvers run, change the Navigation Action Timing.\n\n### routerNavigatedAction\n\nAfter a successful navigation, the router will dispatch a `ROUTER_NAVIGATED` action.\n\n### routerCancelAction\n\nWhen the navigation is cancelled, for example due to a guard saying that the user cannot access the requested page, the router will dispatch a `ROUTER_CANCEL` action.\n\nThe action contains the store state before the navigation. You can use it to restore the consistency of the store.\n\n### routerErrorAction\n\nWhen there is an error during navigation, the router will dispatch a `ROUTER_ERROR` action.\n\nThe action contains the store state before the navigation. You can use it to restore the consistency of the store.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** You can also still use the action type, which was the previously defined way before action creators were introduced in NgRx. If you are looking for examples of the action types, visit the documentation for [versions 7.x and prior](https://v7.ngrx.io/guide/router-store/actions).\n\n</ngrx-docs-alert>\n\n## Order of actions\n\nSuccess case:\n\n- `ROUTER_REQUEST`\n- `ROUTER_NAVIGATION`\n- `ROUTER_NAVIGATED`\n\nError / Cancel case (with early Navigation Action Timing):\n\n- `ROUTER_REQUEST`\n- `ROUTER_NAVIGATION`\n- `ROUTER_CANCEL` / `ROUTER_ERROR`\n\nError / Cancel case (with late Navigation Action Timing):\n\n- `ROUTER_REQUEST`\n- `ROUTER_CANCEL` / `ROUTER_ERROR`\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/router-store/configuration.md",
    "content": "# Configuration Options\n\n<ngrx-code-example header=\"RouterStore Config\">\n\n```ts\ninterface StoreRouterConfig {\n  stateKey?: string | Selector<any, RouterReducerState<T>>;\n  serializer?: new (...args: any[]) => RouterStateSerializer;\n  navigationActionTiming?: NavigationActionTiming;\n  routerState?: RouterState;\n}\n```\n\n</ngrx-code-example>\n\n- `stateKey`: The name of reducer key, defaults to `router`. It's also possible to provide a selector function.\n- `serializer`: How a router snapshot is serialized. Defaults to `MinimalRouterStateSerializer`. See [Custom Router State Serializer](#custom-router-state-serializer) for more information.\n- `navigationActionTiming`: When the `ROUTER_NAVIGATION` is dispatched. Defaults to `NavigationActionTiming.PreActivation`. See [Navigation Action Timing](#navigation-action-timing) for more information.\n- `routerState`: Set this property to decide which serializer should be used, if none is provided, and the metadata on the dispatched action.\n\n## Custom Router State Serializer\n\nDuring each navigation cycle, a `RouterNavigationAction` is dispatched with a snapshot of the state in its payload, the `RouterStateSnapshot`. The `RouterStateSnapshot` is a large complex structure, containing many pieces of information about the current state and what's rendered by the router. This can cause performance\nissues when used with the Store Devtools. In most cases, you may only need a piece of information from the `RouterStateSnapshot`. In order to pare down the `RouterStateSnapshot` provided during navigation, you provide a custom serializer for the snapshot to only return what you need to be added to the payload and store.\n\nYour custom serializer should implement the abstract class `RouterStateSerializer` and return a snapshot which should have an interface extending `BaseRouterStoreState`.\n\nYou then provide the serializer through the config.\n\n**In a custom serializer ts file**\n\n<ngrx-code-example header=\"custom-route-serializer.ts\">\n\n```ts\nimport { Params, RouterStateSnapshot } from '@angular/router';\nimport { RouterStateSerializer } from '@ngrx/router-store';\n\nexport interface RouterStateUrl {\n  url: string;\n  params: Params;\n  queryParams: Params;\n}\n\nexport class CustomSerializer\n  implements RouterStateSerializer<RouterStateUrl>\n{\n  serialize(routerState: RouterStateSnapshot): RouterStateUrl {\n    let route = routerState.root;\n\n    while (route.firstChild) {\n      route = route.firstChild;\n    }\n\n    const {\n      url,\n      root: { queryParams },\n    } = routerState;\n    const { params } = route;\n\n    // Only return an object including the URL, params and query params\n    // instead of the entire snapshot\n    return { url, params, queryParams };\n  }\n}\n```\n\n</ngrx-code-example>\n\n**In your root reducer**\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nexport interface State {\n  router: RouterReducerState<any>;\n}\n\nexport const reducers: ActionReducerMap<State> = {\n  router: routerReducer,\n};\n```\n\n</ngrx-code-example>\n\n**In your application config**\n\n<ngrx-code-example header=\"app.config.ts\">\n\n```ts\nimport { ApplicationConfig } from '@angular/core';\nimport { provideRouter } from '@angular/router';\nimport { provideStore } from '@ngrx/store';\nimport {\n  provideRouterStore,\n  routerReducer,\n} from '@ngrx/router-store';\n\nimport { AppComponent } from './app.component';\nimport { CustomSerializer } from './custom-serializer';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideRouter([\n      // routes\n    ]),\n    provideStore({\n      router: routerReducer,\n    }),\n    provideRouterStore({\n      serializer: CustomSerializer,\n    }),\n  ],\n};\n```\n\n</ngrx-code-example>\n\n## Navigation action timing\n\n`ROUTER_NAVIGATION` is by default dispatched before any guards or resolvers run. This may not always be ideal, for example if you rely on the action to be dispatched after guards and resolvers successfully ran and the new route will be activated. You can change the dispatch timing by providing the corresponding config:\n\n<ngrx-code-example header=\"app.config.ts\">\n\n```ts\nprovideRouterStore({\n  navigationActionTiming: NavigationActionTiming.PostActivation,\n});\n```\n\n</ngrx-code-example>\n\n## Router State Snapshot\n\nThis property decides which router serializer should be used. If there is a custom serializer provided, it will use the provided serializer. `routerState` also sets the metadata on dispatched `@ngrx/router-store` action.\n\n### RouterState.Minimal\n\n`RouterState.Minimal` uses the `MinimalRouterStateSerializer` serializer to serialize the Angular Router's `RouterState` and `RouterEvent`.\n\nThe difference between `FullRouterStateSerializer` and the `MinimalRouterStateSerializer` is that this serializer is fully serializable. To make the state and the actions serializable, the properties `paramMap`, `queryParamMap` and `component` are ignored.\n\n<ngrx-code-example header=\"app.config.ts\">\n\n```ts\nprovideRouterStore({\n  routerState: RouterState.Minimal,\n});\n```\n\n</ngrx-code-example>\n\n### RouterState.Full\n\nWhen this property is set to `RouterState.Full`, `@ngrx/router-store` uses the `FullRouterStateSerializer` serializer to serialize the Angular router event.\n\nThe metadata on the action contains the Angular router event, e.g. `NavigationStart` and `RoutesRecognized`.\n\n<ngrx-code-example header=\"app.config.ts\">\n\n```ts\nprovideRouterStore({\n  routerState: RouterState.Full,\n});\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\nThe `FullRouterStateSerializer` cannot be used when [serializability runtime checks](guide/store/configuration/runtime-checks) are enabled.\nWith serializability runtime checks enabled, the `MinimalRouterStateSerializer` serializer **must** be used.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/router-store/index.md",
    "content": "# @ngrx/router-store\n\nBindings to connect the Angular Router with [Store](guide/store). During each router navigation cycle, multiple [actions](guide/router-store/actions) are dispatched that allow you to listen for changes in the router's state. You can then select data from the state of the router to provide additional information to your application.\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/router-store/install) page.\n\n## Setup\n\n<ngrx-code-example header=\"app.config.ts\">\n\n```ts\nimport { ApplicationConfig } from '@angular/core';\nimport { provideRouter } from '@angular/router';\nimport { provideStore } from '@ngrx/store';\nimport {\n  provideRouterStore,\n  routerReducer,\n} from '@ngrx/router-store';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideRouter([\n      // routes\n    ]),\n    provideStore({\n      router: routerReducer,\n    }),\n    provideRouterStore(),\n  ],\n};\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nAn example of the `@ngrx/router-store` setup in module-based applications is available at the [following link](https://v17.ngrx.io/guide/router-store#setup).\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/router-store/install.md",
    "content": "# Installation\n\n## Installing with `ng add`\n\nYou can install the Router Store to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/router-store@latest\n```\n\n### Optional `ng add` flags\n\n| flag        | description                                                                                                                                                                                        | value type | default value |\n| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- |\n| `--path`    | Path to the module that you wish to add the import for the `StoreRouterConnectingModule` to.                                                                                                       | `string`   |\n| `--project` | Name of the project defined in your `angular.json` to help locating the module to add the `StoreRouterConnectingModule` to.                                                                        | `string`   |\n| `--module`  | Name of file containing the module that you wish to add the import for the `StoreRouterConnectingModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts`. | `string`   | `app`         |\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/router-store`.\n2. Run `npm install` to install those dependencies.\n3. By default it adds `provideRouterStore()` to the `ApplicationConfig` in the `app.config.ts` file. If you provided flags then the command will attempt to locate and update the corresponding config found by the flags.\n\n## Manual Installation\n\nYou can also install `@ngrx/router-store` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/router-store\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/router-store/selectors.md",
    "content": "# Router selectors\n\nThe `getRouterSelectors` method supplied within `@ngrx/router-store` provides functions for selecting common information from the router state.\n\nThe default behavior of `getRouterSelectors` selects the router state for the `router` state key.\nIf the default router state config is overwritten with a different router state key, the `getRouterSelectors` method takes a selector function to select the piece of state where the router state is being stored.\nThe example below shows how to provide a selector for the top level `router` key in your state object.\n\n**Note:** The `getRouterSelectors` method works with the `routerReducer` provided by `@ngrx/router-store`. If you use a [custom serializer](guide/router-store/configuration#custom-router-state-serializer), you'll need to provide your own selectors.\n\nUsage:\n\n<ngrx-docs-alert type=\"help\">\n\nYou can see the full example at StackBlitz: <ngrx-docs-stackblitz name=\"router-store-selectors\"></ngrx-docs-stackblitz>\n\n</ngrx-docs-alert>\n\n## Creating a Selector for A Single Entity With Id As Route Param\n\n<ngrx-code-example header=\"router.selectors.ts\" path=\"router-store-selectors/src/app/router.selectors.ts\" region=\"routerSelectors\">\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"car.reducer.ts\" path=\"router-store-selectors/src/app/car/car.reducer.ts\" region=\"carReducer\">\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"car.selectors.ts\" path=\"router-store-selectors/src/app/car/car.selectors.ts\" region=\"carSelectors\">\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"car.component.ts\" path=\"router-store-selectors/src/app/car/car.component.ts\" region=\"carComponent\">\n\n</ngrx-code-example>\n\n## Extracting all params in the current route\n\nThe `selectRouteParam{s}` selector extracts params from the **leaf child** route segment only.\n\nIt means that when using nested routes with the Angular router (use of the `children` property), only params from the leaf of the matching URL Tree will be extracted.\n\nIf the routes are defined as:\n\n```typescript\n[\n  {\n    path: 'my/:urlPath',\n    component: /* ... */,\n    children: [\n      {\n        path: 'is/:matched',\n        component: /* ... */,\n      },\n    ],\n  },\n]\n```\n\nUsing `selectRouteParam{s}` will get the `matched` param but not the `urlPath` param, because it is not located in a leaf of the URL Tree.\n\nIf all params in the URL Tree need to be extracted (both `urlPath` and `matched`), the following custom selector can be used. It accumulates params of all the segments in the matched route:\n\n<ngrx-code-example>\n\n```ts\nimport { Params } from '@angular/router';\nimport { createSelector } from '@ngrx/store';\n\nexport const selectRouteNestedParams = createSelector(\n  selectRouter,\n  (router) => {\n    let currentRoute = router?.state?.root;\n    let params: Params = {};\n    while (currentRoute?.firstChild) {\n      currentRoute = currentRoute.firstChild;\n      params = {\n        ...params,\n        ...currentRoute.params,\n      };\n    }\n    return params;\n  }\n);\n\nexport const selectRouteNestedParam = (param: string) =>\n  createSelector(\n    selectRouteNestedParams,\n    (params) => params && params[param]\n  );\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\nBeware of using this accumulation technique when two params with the same name exist in the route (e.g. `my/:route/:id/with/another/:id`). Only the rightmost value is accessible because leftmost values are overwritten by the rightmost one in the traversal.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/action.md",
    "content": "# Action\n\n---\n\n## Overview\n\nGenerates an action file that includes a sample action,\ndefined using the `createActionGroup` function.\n\n## Command\n\n```sh\nng generate action ActionName [options]\n```\n\n##### OR\n\n```sh\nng generate a ActionName [options]\n```\n\n### Options\n\nProvide the project name where the action files will be created.\n\n- `--project`\n  - Alias: `-p`\n  - Type: `string`\n\nSpecify the path to create the action file.\n\n- `--path`\n  - Type: `string`\n  - Format: `path`\n  - Visible: `false`\n  - Default: `working directory`\n\nNest the actions file within a folder based on the action `name`.\n\n- `--flat`\n  - Type: `boolean`\n  - Default: `true`\n\nGroup the action file within an `actions` folder.\n\n- `--group`\n  - Alias: `-g`\n  - Type: `boolean`\n  - Default: `false`\n\nSpecifies if api success and failure actions should be generated.\n\n- `--api`\n  - Alias: `-a`\n  - Type: `boolean`\n  - Default: `false`\n\nSpecify the prefix for the actions.\n\n- `--prefix`\n  - Type: `string`\n  - Default: `load`\n\n## Examples\n\nGenerate a `User` actions file with an associated spec file.\n\n```sh\nng generate action User\n```\n\nGenerate a `User` actions file within a nested folder\n\n```sh\nng generate action User --flat false\n```\n\nGenerate a `User` actions file within a nested `actions` folder\n\n```sh\nng generate action User --group\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/container.md",
    "content": "# Container\n\n---\n\n## Overview\n\nGenerates a component with `Store` injected into its constructor. You can optionally provide the path to your reducers and your state interface.\n\n## Command\n\n```sh\nng generate container ComponentName [options]\n```\n\n##### OR\n\n```sh\nng generate co ComponentName [options]\n```\n\n### General Options\n\n`Angular CLI` [component options](https://github.com/angular/angular-cli/wiki/generate-component#options).\n\n## Container Options\n\nProvide the path to your file with an exported state interface\n\n- `--state`\n  - Type: `string`\n\nProvide the name of the interface exported for your state interface\n\n- `--state-interface`\n  - Type: `string`\n  - Default: `State`\n\nSpecifies whether to create a unit test or an integration test\n\n- `--test-depth`\n  - Type: `string`\n  - Values: `unit|integration`\n  - Default: `integration`\n\n## Examples\n\nGenerate a `UsersPage` container component with your reducers imported and the `Store` typed a custom interface named `MyState`.\n\n```sh\nng generate container UsersPage --state reducers/index.ts --state-interface MyState\n```\n\nIf you want to generate a container with an scss file, add `@ngrx/schematics:container` to the `schematics` in your `angular.json`.\n\n```json\n\"schematics\": {\n  \"@ngrx/schematics:container\": {\n    \"style\": \"scss\"\n  }\n}\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/data.md",
    "content": "# Data\n\n---\n\n## Overview\n\nGenerates the data entity model and service.\n\n## Command\n\n```sh\nng generate data EntityName [options]\n```\n\n##### OR\n\n```sh\nng generate dt EntityName [options]\n```\n\n### Options\n\nProvide the project name where the entity files will be created.\n\n- `--project`\n  - Alias: `-p`\n  - Type: `string`\n\nNest the data entity files within a folder based on the `data`.\n\n- `--flat`\n  - Type: `boolean`\n  - Default: `true`\n\nGroup the data entity files within an `data` folder.\n\n- `--group`\n  - Alias: `-g`\n  - Type: `boolean`\n  - Default: `false`\n\nGenerate a spec file alongside the data entity files.\n\n- `--skip-tests`\n  - Type: `boolean`\n  - Default: `false`\n\n#### Examples\n\nGenerate a `User` data entity files with an associated spec file.\n\n```sh\nng generate data User\n```\n\nGenerate a `User` data entity files within a nested folder\n\n```sh\nng generate data User --flat false\n```\n\nGenerate a `User` data entity file within a nested `data` folder\n\n```sh\nng generate data User --group\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/effect.md",
    "content": "# Effect\n\n---\n\n## Overview\n\nGenerates an effect file for `@ngrx/effects`.\n\n## Command\n\n```sh\nng generate effect EffectName [options]\n```\n\n##### OR\n\n```sh\nng generate ef EffectName [options]\n```\n\n## Options\n\nProvide the project name where the effect files will be created.\n\n- `--project`\n  - Alias: `-p`\n  - Type: `string`\n\nNest the effects file within a folder based by the effect `name`.\n\n- `--flat`\n  - Type: `boolean`\n  - Default: `true`\n\nGroup the effect file within an `effects` folder.\n\n- `--group`\n  - Alias: `-g`\n  - Type: `boolean`\n  - Default: `false`\n\nProvide the path to a file containing an `Angular Module` and the effect will be added to its `imports` array. If the `--root` option is not included, the effect will be registered using `EffectsModule.forFeature`.\n\n- `--module`\n  - Alias: `-m`\n  - Type: `string`\n\nWhen used with the `--module` option, it registers an effect within the `Angular Module` using `EffectsModule.forRoot`.\n\n- `--root`\n  - Type: `boolean`\n  - Default: `false`\n\nOnly provide minimal setup for the root effects setup. Only registers `EffectsModule.forRoot()` in the provided module with an empty array.\n\n- `--minimal`\n  - Type: `boolean`\n  - Default: `false`\n\nSpecifies if effect has api success and failure actions wired up.\n\n- `--api`\n  - Alias: `-a`\n  - Type: `boolean`\n  - Default: `false`\n\nGenerate a spec file alongside the effect file.\n\n- `--skip-tests`\n  - Type: `boolean`\n  - Default: `false`\n\n## Examples\n\nGenerate a `UserEffects` file and register it within the root Angular module in the root-level effects.\n\n```sh\nng generate effect User --root -m app.module.ts\n```\n\nGenerate a `UserEffects` file within a `user` folder and register it with the `user.module.ts` file in the same folder.\n\n```sh\nng generate module User --flat false\nng generate effect user/User -m user.module.ts\n```\n\nGenerate a `UserEffects` file within a nested `effects` folder\n\n```sh\nng generate effect User --group\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/entity.md",
    "content": "# Entity\n\n---\n\n## Overview\n\nGenerates a set of entity files for managing a collection using `@ngrx/entity` including a set of predefined `actions`, a collection `model` and a `reducer` with state selectors.\n\n## Command\n\n```sh\nng generate entity EntityName [options]\n```\n\n##### OR\n\n```sh\nng generate en EntityName [options]\n```\n\n## Options\n\nProvide the project name where the entity files will be created.\n\n- `--project`\n  - Alias: `-p`\n  - Type: `string`\n\nNest the effects file within a folder based on the entity `name`.\n\n- `--flat`\n  - Type: `boolean`\n  - Default: `true`\n\nProvide the path to a file containing an `Angular Module` and the entity reducer will be added to its `imports` array using `StoreModule.forFeature`.\n\n- `--module`\n  - Alias: `-m`\n  - Type: `string`\n\nProvide the path to a `reducers` file containing a state interface and a object map of action reducers. The generated entity interface will be imported and added to the first defined interface within the file. The entity reducer will be imported and added to the first defined object with an `ActionReducerMap` type.\n\n- `--reducers`\n  - Alias: `-r`\n  - Type: `string`\n\nGenerate spec files associated with the entity files.\n\n- `--skip-tests`\n  - Type: `boolean`\n  - Default: `false`\n\n## Examples\n\nGenerate a set of `User` entity files and add it to a defined map of reducers generated from a [feature state](guide/schematics/store#examples).\n\n```sh\nng generate entity User --reducers reducers/index.ts\n```\n\nGenerate a set of `User` entity files within a nested folder.\n\n```sh\nng generate entity User --flat false\n```\n\nGenerate a set of `User` entity files and register it within the `Angular Module` in `app.module.ts` as a feature state.\n\n```sh\nng generate entity User -m app.module.ts\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/feature.md",
    "content": "# Feature\n\n---\n\n## Overview\n\nGenerates a feature set containing an `actions`, `effects`, `reducer`, and `selectors` file. You use this to build out a new feature area that provides a new piece of state.\n\n## Command\n\n```sh\nng generate feature FeatureName [options]\n```\n\n##### OR\n\n```sh\nng generate f FeatureName [options]\n```\n\n## Options\n\nProvide the project name where the feature files will be created.\n\n- `--project`\n  - Alias: `-p`\n  - Type: `string`\n\nNest the feature files within a folder based on the feature `name`.\n\n- `--flat`\n  - Type: `boolean`\n  - Default: `true`\n\nGroup the feature files within their respective folders.\n\n- `--group`\n  - Alias: `-g`\n  - Type: `boolean`\n  - Default: `false`\n\nProvide the path to a file containing an `Angular Module` and the feature reducer will be added to its `imports` array using `StoreModule.forFeature`.\n\n- `--module`\n  - Alias: `-m`\n  - Type: `string`\n\nProvide the path to a `reducers` file containing a state interface and a object map of action reducers. The generated feature interface will be imported added to the first defined interface within the file. The feature reducer will be imported and added to the first defined object with an `ActionReducerMap` type.\n\n- `--reducers`\n  - Alias: `-r`\n  - Type: `string`\n\nSpecifies if api success and failure `actions`, `reducer`, and `effects` should be generated as part of this feature.\n\n- `--api`\n  - Alias: `-a`\n  - Type: `boolean`\n  - Default: `false`\n\nGenerate spec files associated with the feature files.\n\n- `--skip-tests`\n  - Type: `boolean`\n  - Default: `false`\n\n## Examples\n\nGenerate a `User` feature set and register it within an `Angular Module`.\n\n```sh\nng generate feature User -m app.module.ts\n```\n\nGenerate a `User` feature set and add it to a defined set of reducers.\n\n```sh\nng generate feature User --group --reducers reducers/index.ts\n```\n\nGenerate a `User` feature set within a `user` folder and register it with the `user.module.ts` file in the same `user` folder.\n\n```sh\nng generate module User --flat false\nng generate feature user/User -m user.module.ts --group\n```\n\nGenerate a `User` feature set with `actions`, `effects`, `reducer`, and `selectors` file nested within their respective folders.\n\n```sh\nng generate feature User --group\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/index.md",
    "content": "# @ngrx/schematics\n\nScaffolding library for Angular applications using NgRx libraries.\n\nSchematics provides Angular CLI commands for generating files when building new NgRx feature areas and expanding existing ones. Built on top of [`Schematics`](https://angular.dev/tools/cli/schematics), this tool integrates with the [`Angular CLI`](https://angular.dev/tools/cli).\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/schematics/install) page.\n\n## Dependencies\n\nAfter installing `@ngrx/schematics`, install the NgRx dependencies.\n\n```sh\nnpm install @ngrx/{store,effects,entity,store-devtools} --save\n```\n\n```sh\nyarn add @ngrx/{store,effects,entity,store-devtools}\n```\n\n## Initial State Setup\n\nGenerate the initial state management and register it within the `app.module.ts`\n\n```sh\nng generate @ngrx/schematics:store State --root --module app.module.ts\n```\n\n<ngrx-docs-alert type=\"inform\">\n\nThe @ngrx/schematics command prefix is only needed when the default collection isn't set.\n\n</ngrx-docs-alert>\n\n## Initial Effects Setup\n\nGenerate the root effects and register it within the `app.module.ts`\n\n```sh\nng generate @ngrx/schematics:effect App --root --module app.module.ts\n```\n\n## Adding NgRx schematics to schematicCollections\n\nTo use `@ngrx/schematics` in your Angular CLI project, add it manually to your `angular.json` or with the following command:\n\n```sh\nng config cli.schematicCollections \"[\\\"@ngrx/schematics\\\"]\"\n```\n\nYou should end up with the following result in your `angular.json`:\n\n```json\n{\n  \"cli\": {\n    \"schematicCollections\": [\"@ngrx/schematics\"]\n  }\n}\n```\n\nOr, when the Angular schematic is also registered you should end up with following result:\n\n```json\n{\n  \"cli\": {\n    \"schematicCollections\": [\n      \"@schematics/angular\",\n      \"@ngrx/schematics\"\n    ]\n  }\n}\n```\n\nThe [collection schema](https://github.com/ngrx/platform/tree/main/modules/schematics/collection.json) also has aliases to the most common schematics used to generate files.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/install.md",
    "content": "# Installation\n\n## Installing with `ng add`\n\nYou can make `@ngrx/schematics` the default collection for your application with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/schematics@latest\n```\n\nThis command will automate the following steps:\n\n1. Add `@ngrx/schematics` to `angular.json` > `cli > schematicCollections`. If `schematicCollections` does not exist, it will be created.\n\n## Manual Installation\n\nYou can also install `@ngrx/schematics` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/schematics\" npm-flags=\"--save-dev\" yarn-flags=\"--dev\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/reducer.md",
    "content": "# Reducer\n\n---\n\n## Overview\n\nGenerates a reducer file that contains a state interface,\nan initial state object for the reducer, and a reducer function.\n\n## Command\n\n```sh\nng generate reducer ReducerName [options]\n```\n\n##### OR\n\n```sh\nng generate r ReducerName [options]\n```\n\n### Options\n\nProvide the project name where the reducer files will be created.\n\n- `--project`\n  - Alias: `-p`\n  - Type: `string`\n\nNest the reducer file within a folder based on the reducer `name`.\n\n- `--flat`\n  - Type: `boolean`\n  - Default: `true`\n\nGroup the reducer file within a `reducers` folder.\n\n- `--group`\n  - Alias: `-g`\n  - Type: `boolean`\n  - Default: `false`\n\nProvide the path to a file containing an `Angular Module` and the entity reducer will be added to its `imports` array using `StoreModule.forFeature`.\n\n- `--module`\n  - Alias: `-m`\n  - Type: `string`\n\nProvide the path to a `reducers` file containing a state interface and an object map of action reducers. The generated reducer interface will be imported and added to the first defined interface within the file. The reducer will be imported and added to the first defined object with an `ActionReducerMap` type.\n\n- `--reducers`\n  - Alias: `-r`\n  - Type: `string`\n\nSpecifies if api success and failure actions should be added to the reducer.\n\n- `--api`\n  - Alias: `-a`\n  - Type: `boolean`\n  - Default: `false`\n\nGenerate a spec file alongside the reducer file.\n\n- `--skip-tests`\n  - Type: `boolean`\n  - Default: `false`\n\n## Examples\n\nGenerate a `User` reducer file add it to a defined map of reducers generated from a [feature state](guide/schematics/store#examples).\n\n```sh\nng generate reducer User --reducers reducers/index.ts\n```\n\nGenerate a `User` reducer file within a nested folder based on the reducer name.\n\n```sh\nng generate reducer User --flat false\n```\n\nGenerate a `User` reducer and register it within the `Angular Module` in `app.module.ts`.\n\n```sh\nng generate reducer User --module app.module.ts\n```\n\nGenerate a `User` reducer file within a nested `reducers` folder.\n\n```sh\nng generate reducer User --group\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/selector.md",
    "content": "# Selector\n\n---\n\n## Overview\n\nGenerates a selector file for `@ngrx/store`.\n\n## Command\n\n```sh\nng generate selector selectorName [options]\n```\n\n##### OR\n\n```sh\nng generate se selectorName [options]\n```\n\n## Options\n\nProvide the project name where the selector files will be created.\n\n- `--project`\n  - Alias: `-p`\n  - Type: `string`\n\nNest the effects file within a folder based by the selector `name`.\n\n- `--flat`\n  - Type: `boolean`\n  - Default: `true`\n\nGroup the selector file within an `selectors` folder.\n\n- `--group`\n  - Alias: `-g`\n  - Type: `boolean`\n  - Default: `false`\n\nGenerate a spec file alongside the selector file.\n\n- `--skip-tests`\n  - Type: `boolean`\n  - Default: `false`\n\n## Examples\n\nGenerate a selector file.\n\n```sh\nng generate selector User\n```\n\nGenerate a selector file within a nested `selectors` folder\n\n```sh\nng generate selector User --group\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/schematics/store.md",
    "content": "# Store\n\n---\n\n## Overview\n\nGenerates the initial setup for state management and registering new feature states. It registers the `@ngrx/store-devtools` integration and generates a state management file containing the state interface, the object map of action reducers and any associated meta-reducers.\n\n## Command\n\n```sh\nng generate store State [options]\n```\n\n##### OR\n\n```sh\nng generate st State [options]\n```\n\n## Options\n\nProvide the project name where the state files will be created.\n\n- `--project`\n  - Alias: `-p`\n  - Type: `string`\n\nProvide the path to a file containing an `Angular Module` and the feature state will be added to its `imports` array using `StoreModule.forFeature` or `StoreModule.forRoot`.\n\n- `--module`\n  - Alias: `-m`\n  - Type: `string`\n\nWhen used with the `--module` option, it registers the state within the `Angular Module` using `StoreModule.forRoot`. The `--root` option should only be used to setup the global `@ngrx/store` providers.\n\n- `--root`\n  - Type: `boolean`\n  - Default: `false`\n\nOnly provide minimal setup for the root state management. Only registers `StoreModule.forRoot()` in the provided module with an empty object, and default runtime checks.\n\n- `--minimal`\n  - Type: `boolean`\n  - Default: `false`\n\nProvide the folder where the state files will be created.\n\n- `--state-path`\n  - Type: `string`\n  - Default: `reducers`\n\nProvide the name of the interface exported for your state. When defining with the `--root` option, the name of the store will be used to define the interface name.\n\n- `--state-interface`\n  - Type: `string`\n  - Default: `State`\n\n## Examples\n\nGenerate the initial state management files and register it within the `app.module.ts`.\n\n```sh\nng generate store State --root --module app.module.ts\n```\n\nGenerate an `Admin` feature state within the `admin` folder and register it with the `admin.module.ts` in the same folder.\n\n```sh\nng generate module admin --flat false\nng generate store admin/Admin -m admin.module.ts\n```\n\nGenerate the initial state management files within a `store` folder and register it within the `app.module.ts`.\n\n```sh\nng generate store State --root --state-path store --module app.module.ts\n```\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/deep-computed.md",
    "content": "# DeepComputed\n\nThe `deepComputed` function creates a `DeepSignal` when a computation result is an object literal.\nIt can be used as a regular computed signal, but it also contains computed signals for each nested property.\n\n<ngrx-code-example>\n\n```ts\nimport { signal } from '@angular/core';\nimport { deepComputed } from '@ngrx/signals';\n\nconst limit = signal(25);\nconst offset = signal(0);\nconst totalItems = signal(100);\n\nconst pagination = deepComputed(() => ({\n  currentPage: Math.floor(offset() / limit()) + 1,\n  pageSize: limit(),\n  totalPages: Math.ceil(totalItems() / limit()),\n}));\n\nconsole.log(pagination()); // logs: { currentPage: 1, pageSize: 25, totalPages: 4 }\nconsole.log(pagination.currentPage()); // logs: 1\nconsole.log(pagination.pageSize()); // logs: 25\nconsole.log(pagination.totalPages()); // logs: 4\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nFor enhanced performance, deeply nested signals are generated lazily and initialized only upon first access.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/faq.md",
    "content": "# Frequently Asked Questions\n\n<details>\n  <summary><b>#1</b> How to connect my SignalStore(s) with Redux DevTools?</summary>\n\nThere's no official connection between `@ngrx/signals` and the Redux Devtools.\nWe expect the Angular Devtools will provide support for signals soon, which can be used to track the state.\nHowever, you could create a feature for this, or you can make use of the [`withDevtools` feature](https://github.com/angular-architects/ngrx-toolkit?tab=readme-ov-file#devtools-withdevtools) from the `@angular-architects/ngrx-toolkit` package.\n\n</details>\n\n<details>\n  <summary><b>#2</b> Can I use the Flux/Redux pattern with SignalStore?</summary>\n\nYes. Starting from NgRx version 19.2, the Events plugin introduces support for a Flux-style state management with SignalStore.\nIt enables defining and dispatching events, handling them through reducers and effects, and maintaining a unidirectional data flow similar to the traditional Redux pattern.\nFor more information, see the Events Plugin documentation.\n\n</details>\n\n<details>\n  <summary><b>#3</b> Can I define my SignalStore as a class?</summary>\n\nYes, it is possible to define a SignalStore using a class-based approach.\nHowever, the NgRx team recommends using the functional style for defining SignalStores.\n\nTo define a class-based SignalStore, create a new class and extend from `signalStore`.\n\n<ngrx-code-example>\n\n```ts\n@Injectable()\nexport class CounterStore extends signalStore(\n  { protectedState: false },\n  withState({ count: 0 })\n) {\n  readonly doubleCount = computed(() => this.count() * 2);\n\n  increment(): void {\n    patchState(this, { count: this.count() + 1 });\n  }\n}\n```\n\n</ngrx-code-example>\n\n</details>\n\n<details>\n  <summary><b>#4</b> How can I get the type of a SignalStore?</summary>\n\nTo get the type of a SignalStore, use the `InstanceType` utility type.\n\n```ts\nconst CounterStore = signalStore(withState({ count: 0 }));\n\ntype CounterStore = InstanceType<typeof CounterStore>;\n\nfunction logCount(store: CounterStore): void {\n  console.log(store.count());\n}\n```\n\n</details>\n\n<details>\n  <summary><b>#5</b> Can I inject a SignalStore via the constructor?</summary>\n\nYes. To inject a SignalStore via the constructor, define and export its type with the same name.\n\n```ts\n// counter-store.ts\nexport const CounterStore = signalStore(withState({ count: 0 }));\n\nexport type CounterStore = InstanceType<typeof CounterStore>;\n\n// counter.ts\nimport { CounterStore } from './counter.store';\n\n@Component({\n  /* ... */\n})\nexport class Counter {\n  constructor(readonly store: CounterStore) {}\n}\n```\n\n</details>\n\n<details>\n  <summary><b>#6</b> Can features like `withComputed` or `withMethods` reference other members inside the same feature?</summary>\n\nIt may be necessary for a computed in a `withComputed` feature to need to reference another computed value,\nor a method in a `withMethods` feature to refer to another method. To do so, you can break out the common piece\nwith a helper that can serve as a function or computed itself.\n\nAlthough it is possible to have multiple features that reference each other, we recommend having everything in one call.\nThat adheres more to JavaScript's functional style and keeps features co-located.\n\n<ngrx-code-example>\n\n```ts\nexport const BooksStore = signalStore(\n  withState(initialState),\n  withComputed(({ filter }) => {\n    // 👇 Define helper functions (or computed signals).\n    const sortDirection = computed(() =>\n      filter.order() === 'asc' ? 1 : -1\n    );\n\n    return {\n      sortDirection,\n      sortDirectionReversed: () => sortDirection() * -1,\n    };\n  })\n);\n```\n\n</ngrx-code-example>\n\n</details>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/index.md",
    "content": "# @ngrx/signals\n\nNgRx Signals is a standalone library that provides a reactive state management solution and a set of utilities for Angular Signals.\n\n## Key Principles\n\n- **Simple and Intuitive:** Designed with ease of use in mind, NgRx Signals provides a straightforward and intuitive API for developers to efficiently work with Angular Signals.\n- **Lightweight and Performant:** Keep your application size optimal with a lightweight library that adds minimal overhead to your projects and performs efficiently.\n- **Declarative:** NgRx Signals is built around the concept of declarative programming, ensuring clean and concise code.\n- **Modular, Extensible, and Scalable:** Modularity and extensibility are the guiding principles of this library. NgRx Signals enables the creation of independent building blocks that can be easily combined for flexible and scalable implementations.\n- **Opinionated, but Flexible:** Strike a balance between flexibility and opinionation, offering customization where needed while providing thoughtful conventions for a smooth development experience.\n- **Type-safe:** NgRx Signals is designed with a strong focus on type safety, ensuring the prevention of errors and misuse at compile time.\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/signals/install) page.\n\n## Main Features\n\n- [SignalStore](guide/signals/signal-store): A fully-featured state management solution that provides native support for Angular Signals and offers a robust way to manage application state.\n- [SignalState](guide/signals/signal-state): A lightweight utility for managing signal-based state in Angular components and services in a concise and minimalistic manner.\n- [RxJS Integration](guide/signals/rxjs-integration): A plugin for opt-in integration with RxJS, enabling easier handling of asynchronous side effects.\n- [Entities](guide/signals/signal-store/entity-management): A plugin for manipulating and querying entity collections in a simple and performant way.\n- [Events](guide/signals/signal-store/events): A plugin for event-based state management.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/install.md",
    "content": "# Installation\n\n## Installing with `ng add`\n\nYou can install the `@ngrx/signals` to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/signals@latest\n```\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/signals`.\n2. Run the package manager to install the added dependency.\n\n## Manual Installation\n\nYou can also install `@ngrx/signals` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/signals\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/rxjs-integration.md",
    "content": "# RxJS Integration\n\nRxJS is still a major part of NgRx and the Angular ecosystem, and the `@ngrx/signals` package provides **opt-in** integration with RxJS APIs through the `rxjs-interop` plugin.\n\n## RxMethod\n\nThe `rxMethod` is a standalone factory function designed for managing side effects by utilizing RxJS APIs.\nIt takes a chain of RxJS operators as input and returns a reactive method.\nThe reactive method can accept a static value, a signal, a computation function, or an observable as an input argument.\nInput can be typed by providing a generic argument to the `rxMethod` function.\n\n<ngrx-code-example>\n\n```ts\nimport { Component } from '@angular/core';\nimport { map, pipe, tap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  // 👇 This reactive method will have an input argument\n  // of type `number | (() => number) | Observable<number>`.\n  readonly logDoubledNumber = rxMethod<number>(\n    // 👇 RxJS operators are chained together using the `pipe` function.\n    pipe(\n      map((num) => num * 2),\n      tap(console.log)\n    )\n  );\n}\n```\n\n</ngrx-code-example>\n\nEach invocation of the reactive method pushes the input value through the reactive chain.\nWhen called with a static value, the reactive chain executes once.\n\n```ts\nimport { Component } from '@angular/core';\nimport { map, pipe, tap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  readonly logDoubledNumber = rxMethod<number>(\n    pipe(\n      map((num) => num * 2),\n      tap(console.log)\n    )\n  );\n\n  constructor() {\n    this.logDoubledNumber(1);\n    // console output: 2\n\n    this.logDoubledNumber(2);\n    // console output: 4\n  }\n}\n```\n\nWhen a reactive method is called with a signal the reactive chain is executed every time the signal value changes.\n\n```ts\nimport { Component, signal } from '@angular/core';\nimport { map, pipe, tap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  readonly logDoubledNumber = rxMethod<number>(\n    pipe(\n      map((num) => num * 2),\n      tap(console.log)\n    )\n  );\n\n  constructor() {\n    const num = signal(10);\n    this.logDoubledNumber(num);\n    // console output: 20\n\n    num.set(20);\n    // console output: 40\n  }\n}\n```\n\nIn addition to providing a signal, it is also possible to provide a computation function and combine multiple signals within it.\n\n```ts\nimport { Component, signal } from '@angular/core';\nimport { map, pipe, tap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  readonly logSum = rxMethod<{ a: number; b: number }>(\n    pipe(\n      map(({ a, b }) => a + b),\n      tap(console.log)\n    )\n  );\n\n  constructor() {\n    const num1 = signal(10);\n    const num2 = signal(20);\n\n    this.logSum(() => ({ a: num1(), b: num2() }));\n    // console output: 30\n\n    setTimeout(() => b.set(30), 3_000);\n    // console output after 3 seconds: 50\n  }\n}\n```\n\nWhen a reactive method is called with an observable, the reactive chain is executed every time the observable emits a new value.\n\n```ts\nimport { Component } from '@angular/core';\nimport { interval, map, of, pipe, tap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  readonly logDoubledNumber = rxMethod<number>(\n    pipe(\n      map((num) => num * 2),\n      tap(console.log)\n    )\n  );\n\n  constructor() {\n    const num1$ = of(100, 200, 300);\n    this.logDoubledNumber(num1$);\n    // console output: 200, 400, 600\n\n    const num2$ = interval(2_000);\n    this.logDoubledNumber(num2$);\n    // console output: 0, 2, 4, 6, 8, 10, ... (every 2 seconds)\n  }\n}\n```\n\nBy default, the `rxMethod` needs to be executed within an injection context.\nIt's tied to its lifecycle and is automatically cleaned up when the injector is destroyed.\n\n### Handling API Calls\n\nThe `rxMethod` is a great choice for handling API calls in a reactive manner.\nThe subsequent example demonstrates how to use `rxMethod` to fetch the book by id whenever the `selectedBookId` signal value changes.\n\n```ts\nimport { Component, inject, signal } from '@angular/core';\nimport { concatMap, filter, pipe } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\nimport { tapResponse } from '@ngrx/operators';\nimport { BooksService } from './books-service';\nimport { Book } from './book';\n\n@Component({\n  /* ... */\n})\nexport class BookList {\n  readonly #booksService = inject(BooksService);\n\n  readonly bookMap = signal<Record<string, Book>>({});\n  readonly selectedBookId = signal<string | null>(null);\n\n  readonly loadBookById = rxMethod<string | null>(\n    pipe(\n      filter((id) => !!id && !this.bookMap()[id]),\n      concatMap((id) => {\n        return this.#booksService.getById(id).pipe(\n          tapResponse({\n            next: (book) => this.addBook(book),\n            error: console.error,\n          })\n        );\n      })\n    )\n  );\n\n  constructor() {\n    // 👇 Load book by id whenever the `selectedBookId` value changes.\n    this.loadBookById(this.selectedBookId);\n  }\n\n  addBook(book: Book): void {\n    this.bookMap.update((bookMap) => ({\n      ...bookMap,\n      [book.id]: book,\n    }));\n  }\n}\n```\n\n<ngrx-docs-alert type=\"inform\">\n\nFor safe handling of API responses, it is recommended to use the `tapResponse` operator from the `@ngrx/operators` package.\nLearn more about it in the [tapResponse](guide/operators/operators#tapresponse) guide.\n\n</ngrx-docs-alert>\n\nThe `rxMethod` function can also be utilized to define reactive methods for SignalStore.\nFurther details can be found in the [Reactive Store Methods](guide/signals/signal-store#reactive-store-methods) guide.\n\n### Reactive Methods without Arguments\n\nTo create a reactive method without arguments, the `void` type should be specified as a generic argument to the `rxMethod` function.\n\n```ts\nimport { Component, inject, signal } from '@angular/core';\nimport { exhaustMap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\nimport { tapResponse } from '@ngrx/operators';\nimport { BooksService } from './books-service';\nimport { Book } from './book';\n\n@Component({\n  /* ... */\n})\nexport class BookList {\n  readonly #booksService = inject(BooksService);\n  readonly books = signal<Book[]>([]);\n\n  // 👇 Creating a reactive method without arguments.\n  readonly loadAllBooks = rxMethod<void>(\n    exhaustMap(() => {\n      return this.#booksService.getAll().pipe(\n        tapResponse({\n          next: (books) => this.books.set(books),\n          error: console.error,\n        })\n      );\n    })\n  );\n\n  constructor() {\n    this.loadAllBooks();\n  }\n}\n```\n\n### Reactive Methods and Injector Hierarchies\n\nThe cleanup behavior of reactive methods differs when they're created and called across different injector hierarchies.\n\nIf the reactive method is called within the descendant injection context, the call will be automatically cleaned up when the descendant injector is destroyed.\nHowever, when the call is made outside of the descendant injection context, it's necessary to explicitly provide the descendant injector reference to ensure proper cleanup. Otherwise, the cleanup occurs when the ascendant injector where the reactive method is initialized gets destroyed.\n\n```ts\nimport {\n  Component,\n  inject,\n  Injectable,\n  Injector,\n  OnInit,\n} from '@angular/core';\nimport { tap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\n@Injectable({ providedIn: 'root' })\nexport class NumbersService {\n  readonly log = rxMethod<number>(tap(console.log));\n}\n\n@Component({\n  /* ... */\n})\nexport class Numbers implements OnInit {\n  readonly #injector = inject(Injector);\n  readonly #numbersService = inject(NumbersService);\n\n  constructor() {\n    const num1$ = interval(1_000);\n    // 👇 Automatic cleanup when component is destroyed.\n    this.#numbersService.log(num1$);\n  }\n\n  ngOnInit(): void {\n    const num2$ = interval(2_000);\n    // 👇 Requires injector for cleanup when component is destroyed.\n    this.#numbersService.log(num2$, { injector: this.#injector });\n  }\n}\n```\n\n<ngrx-docs-alert type=\"warn\">\n\nCalling a reactive method with a signal, a computation function, or an observable outside of an injection context without providing an explicit injector is deprecated. In a future version, this will throw an error. Either call it within an injection context (e.g. in a constructor or field initializer) or pass an injector explicitly via the config parameter.\n\n</ngrx-docs-alert>\n\n### Manual Cleanup\n\nIf a reactive method needs to be cleaned up before the injector is destroyed, manual cleanup can be performed by calling the `destroy` method.\n\n```ts\nimport { Component } from '@angular/core';\nimport { interval, tap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  readonly logNumber = rxMethod<number>(tap(console.log));\n\n  constructor() {\n    const num1$ = interval(500);\n    const num2$ = interval(1_000);\n\n    this.logNumber(num1$);\n    this.logNumber(num2$);\n\n    setTimeout(() => {\n      // 👇 Destroy the reactive method after 3 seconds.\n      this.logNumber.destroy();\n    }, 3_000);\n  }\n}\n```\n\nWhen invoked, the reactive method returns the object with the `destroy` method.\nThis allows manual cleanup of a specific call, preserving the activity of other reactive method calls until the corresponding injector is destroyed.\n\n```ts\nimport { Component } from '@angular/core';\nimport { interval, tap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  readonly logNumber = rxMethod<number>(tap(console.log));\n\n  constructor() {\n    const num1$ = interval(500);\n    const num2$ = interval(1_000);\n\n    const num1Ref = this.logNumber(num1$);\n    const num2Ref = this.logNumber(num2$);\n\n    setTimeout(() => {\n      // 👇 Destroy the first reactive method call after 2 seconds.\n      num1Ref.destroy();\n    }, 2_000);\n  }\n}\n```\n\n### Initialization Outside of Injection Context\n\nInitialization of the reactive method outside an injection context is possible by providing an injector as the second argument to the `rxMethod` function.\n\n<ngrx-code-example>\n\n```ts\nimport { Component, inject, Injector, OnInit } from '@angular/core';\nimport { tap } from 'rxjs';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\n@Component({\n  /* ... */\n})\nexport class Numbers implements OnInit {\n  readonly #injector = inject(Injector);\n\n  ngOnInit(): void {\n    const logNumber = rxMethod<number>(tap(console.log), {\n      injector: this.#injector,\n    });\n\n    logNumber(10);\n  }\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-method.md",
    "content": "# SignalMethod\n\n`signalMethod` is a standalone factory function used for managing side effects with Angular signals. It accepts a callback and returns a processor function that can handle either a static value, a signal, or a computation function. The input type can be specified using a generic type argument:\n\n<ngrx-code-example>\n\n```ts\nimport { Component } from '@angular/core';\nimport { signalMethod } from '@ngrx/signals';\n\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  // 👇 This method will have an input argument\n  // of type `number | (() => number)`.\n  readonly logDoubledNumber = signalMethod<number>((num) => {\n    const double = num * 2;\n    console.log(double);\n  });\n}\n```\n\n</ngrx-code-example>\n\n`logDoubledNumber` can be called with a static value of type `number` or a `Signal<number>`.\n\n```ts\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  readonly logDoubledNumber = signalMethod<number>((num) => {\n    const double = num * 2;\n    console.log(double);\n  });\n\n  constructor() {\n    this.logDoubledNumber(1);\n    // console output: 2\n\n    const num = signal(2);\n    this.logDoubledNumber(num);\n    // console output: 4\n\n    setTimeout(() => num.set(3), 3_000);\n    // console output after 3 seconds: 6\n  }\n}\n```\n\nIn addition to providing a Signal, it is also possible to provide a computation function and combine multiple Signals within it.\n\n```ts\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  readonly logSum = signalMethod<{ a: number; b: number }>(\n    ({ a, b }) => console.log(a + b)\n  );\n\n  constructor() {\n    const num1 = signal(1);\n    const num2 = signal(2);\n    this.logSum(() => ({ a: num1(), b: num2() }));\n    // console output: 3\n\n    setTimeout(() => num1.set(3), 3_000);\n    // console output after 3 seconds: 5\n  }\n}\n```\n\n## Automatic Cleanup\n\n`signalMethod` uses an `effect` internally to track the Signal changes.\nBy default, the `effect` runs in the injection context of the caller. In the example above, that is the `Numbers` component. That means, that the `effect` is automatically cleaned up when the component is destroyed.\n\nIf the call happens outside an injection context, then the injector of the `signalMethod` is used. This would be the case, if `logDoubledNumber` runs in `ngOnInit`:\n\n```ts\n@Component({\n  /* ... */\n})\nexport class Numbers implements OnInit {\n  readonly logDoubledNumber = signalMethod<number>((num) => {\n    const double = num * 2;\n    console.log(double);\n  });\n\n  ngOnInit(): void {\n    const value = signal(2);\n    // 👇 Uses the injection context of the `Numbers` component.\n    this.logDoubledNumber(value);\n  }\n}\n```\n\nEven though `logDoubledNumber` is called outside an injection context, automatic cleanup occurs when the `Numbers` component is destroyed, since `logDoubledNumber` was created within the component's injection context.\n\nHowever, when creating a `signalMethod` in an ancestor injection context, the cleanup behavior is different:\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class NumbersService {\n  readonly logDoubledNumber = signalMethod<number>((num) => {\n    const double = num * 2;\n    console.log(double);\n  });\n}\n\n@Component({\n  /* ... */\n})\nexport class Numbers implements OnInit {\n  readonly numbersService = inject(NumbersService);\n\n  ngOnInit(): void {\n    const value = signal(2);\n    // 👇 Uses the injection context of the `NumbersService`, which is root.\n    this.numbersService.logDoubledNumber(value);\n  }\n}\n```\n\nHere, the `effect` used internally by `signalMethod` outlives the component, which would produce a memory leak.\n\n<ngrx-docs-alert type=\"warn\">\n\nCalling `signalMethod` with a signal or a computation function outside of an injection context without providing an explicit injector is deprecated. In a future version, this will throw an error. Either call it within an injection context (e.g. in a constructor or field initializer) or pass an injector explicitly via the config parameter.\n\n</ngrx-docs-alert>\n\n## Manual Cleanup\n\nWhen a `signalMethod` is created in an ancestor injection context, it's necessary to explicitly provide the caller injector to ensure proper cleanup:\n\n```ts\n@Component({\n  /* ... */\n})\nexport class Numbers implements OnInit {\n  readonly numbersService = inject(NumbersService);\n  readonly injector = inject(Injector);\n\n  ngOnInit(): void {\n    const value = signal(1);\n    // 👇 Providing the `Numbers` component injector\n    // to ensure cleanup on component destroy.\n    this.numbersService.logDoubledNumber(value, {\n      injector: this.injector,\n    });\n\n    // 👇 No need to provide an injector for static values.\n    this.numbersService.logDoubledNumber(2);\n  }\n}\n```\n\n## Initialization Outside of Injection Context\n\nThe `signalMethod` must be initialized within an injection context. To initialize it outside an injection context, it's necessary to provide an injector as the second argument:\n\n```ts\n@Component({\n  /* ... */\n})\nexport class Numbers implements OnInit {\n  readonly injector = inject(Injector);\n\n  ngOnInit(): void {\n    const logDoubledNumber = signalMethod<number>(\n      (num) => console.log(num * 2),\n      { injector: this.injector }\n    );\n  }\n}\n```\n\n## Advantages over Effect\n\nAt first sight, `signalMethod`, might be the same as `effect`:\n\n<ngrx-code-example>\n\n```ts\n@Component({\n  /* ... */\n})\nexport class Numbers {\n  readonly num = signal(2);\n  readonly logDoubledNumberEffect = effect(() => {\n    console.log(this.num() * 2);\n  });\n  readonly logDoubledNumber = signalMethod<number>((num) => {\n    console.log(num * 2);\n  });\n\n  constructor() {\n    this.logDoubledNumber(this.num);\n  }\n}\n```\n\n</ngrx-code-example>\n\nHowever, `signalMethod` offers three distinctive advantages over `effect`:\n\n- **Flexible Input**: The input argument can be a static value, not just a Signal or a computation function. Additionally, the processor function can be called multiple times with different inputs.\n- **No Injection Context Required**: Unlike an `effect`, which requires an injection context or an Injector, `signalMethod`'s \"processor function\" can be called without an injection context.\n- **Explicit Tracking**: Only the provided Signal or Signals used inside the computation function are tracked, while Signals within the \"processor function\" stay untracked.\n\n## `signalMethod` compared to `rxMethod`\n\n`signalMethod` is `rxMethod` without RxJS, and is therefore much smaller in terms of bundle size.\n\nBe aware that RxJS is superior to Signals in managing race conditions. Signals have a glitch-free effect, meaning that for multiple synchronous changes, only the last change is propagated. Additionally, they lack powerful operators like `switchMap` or `concatMap`.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-state.md",
    "content": "# SignalState\n\nSignalState is a lightweight utility designed for managing signal-based state in a concise and minimalistic manner.\nIt's suitable for managing modest-sized states and can be used directly in components, services, or standalone functions.\n\n## Creating a SignalState\n\nSignalState is instantiated using the `signalState` function, which accepts an initial state as an input argument.\n\n<ngrx-code-example>\n\n```ts\nimport { signalState } from '@ngrx/signals';\nimport { User } from './user';\n\ntype UserState = { user: User; isAdmin: boolean };\n\nconst userState = signalState<UserState>({\n  user: { firstName: 'Eric', lastName: 'Clapton' },\n  isAdmin: false,\n});\n```\n\n</ngrx-code-example>\n\nThe state's type must be a record/object literal. Add arrays or primitive values to properties.\n\n`signalState` returns an extended version of a signal that possesses all the capabilities of a read-only signal.\n\n```ts\nimport { computed, effect } from '@angular/core';\n\n// 👇 Creating computed signals.\nconst userStateStr = computed(() => JSON.stringify(userState()));\n\n// 👇 Performing side effects.\neffect(() => console.log('userState', userState()));\n```\n\nAdditionally, the `signalState` function generates signals for each state property.\n\n```ts\nconst user = userState.user; // type: DeepSignal<User>\nconst isAdmin = userState.isAdmin; // type: Signal<boolean>\n\nconsole.log(user()); // logs: { firstName: 'Eric', lastName: 'Clapton' }\nconsole.log(isAdmin()); // logs: false\n```\n\nWhen a state property holds an object as its value, the `signalState` function generates a `DeepSignal`.\nIt can be used as a regular read-only signal, but it also contains signals for each property of the object it refers to.\n\n```ts\nconst firstName = user.firstName; // type: Signal<string>\nconst lastName = user.lastName; // type: Signal<string>\n\nconsole.log(firstName()); // logs: 'Eric'\nconsole.log(lastName()); // logs: 'Clapton'\n```\n\n<ngrx-docs-alert type=\"help\">\n\nFor enhanced performance, deeply nested signals are generated lazily and initialized only upon first access.\n\n</ngrx-docs-alert>\n\n## Updating State\n\nThe `patchState` function provides a type-safe way to perform updates on pieces of state.\nIt takes a SignalState or SignalStore instance as the first argument, followed by a sequence of partial states or partial state updaters as additional arguments.\n\n```ts\nimport { patchState } from '@ngrx/signals';\n\n// 👇 Providing a partial state object.\npatchState(userState, { isAdmin: true });\n\n// 👇 Providing a partial state updater.\npatchState(userState, (state) => ({\n  user: { ...state.user, firstName: 'Jimi' },\n}));\n\n// 👇 Providing a sequence of partial state objects and/or updaters.\npatchState(userState, { isAdmin: false }, (state) => ({\n  user: { ...state.user, lastName: 'Hendrix' },\n}));\n```\n\n<ngrx-docs-alert type=\"error\">\n\nUpdaters passed to the `patchState` function must perform state updates in an immutable manner.\n\n</ngrx-docs-alert>\n\n### Custom State Updaters\n\nInstead of providing partial states or updaters directly to the `patchState` function, it's possible to create custom state updaters.\n\n```ts\nimport { PartialStateUpdater } from '@ngrx/signals';\n\nfunction setFirstName(\n  firstName: string\n): PartialStateUpdater<{ user: User }> {\n  return (state) => ({ user: { ...state.user, firstName } });\n}\n\nconst setAdmin = () => ({ isAdmin: true });\n```\n\nCustom state updaters are easy to test and can be reused across different parts of the application.\n\n```ts\n// Before:\npatchState(userState, (state) => ({\n  user: { ...state.user, firstName: 'Stevie' },\n  isAdmin: true,\n}));\n\n// After:\npatchState(userState, setFirstName('Stevie'), setAdmin());\n```\n\n## Usage\n\n### Example 1: SignalState in a Component\n\n<ngrx-code-example header=\"counter.ts\">\n\n```ts\nimport { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { signalState, patchState } from '@ngrx/signals';\n\n@Component({\n  selector: 'ngrx-counter',\n  template: `\n    <p>Count: {{ state.count() }}</p>\n\n    <button (click)=\"increment()\">Increment</button>\n    <button (click)=\"decrement()\">Decrement</button>\n    <button (click)=\"reset()\">Reset</button>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class Counter {\n  readonly state = signalState({ count: 0 });\n\n  increment(): void {\n    patchState(this.state, (state) => ({ count: state.count + 1 }));\n  }\n\n  decrement(): void {\n    patchState(this.state, (state) => ({ count: state.count - 1 }));\n  }\n\n  reset(): void {\n    patchState(this.state, { count: 0 });\n  }\n}\n```\n\n</ngrx-code-example>\n\n### Example 2: SignalState in a Service\n\n<ngrx-code-tabs>\n<ngrx-code-example header=\"book-list-store.ts\">\n\n```ts\nimport { inject, Injectable } from '@angular/core';\nimport { exhaustMap, pipe, tap } from 'rxjs';\nimport { signalState, patchState } from '@ngrx/signals';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\nimport { tapResponse } from '@ngrx/operators';\nimport { BooksService } from './books-service';\nimport { Book } from './book';\n\ntype BookListState = { books: Book[]; isLoading: boolean };\n\nconst initialState: BookListState = {\n  books: [],\n  isLoading: false,\n};\n\n@Injectable()\nexport class BookListStore {\n  readonly #booksService = inject(BooksService);\n  readonly #state = signalState(initialState);\n\n  readonly books = this.#state.books;\n  readonly isLoading = this.#state.isLoading;\n\n  readonly loadBooks = rxMethod<void>(\n    pipe(\n      tap(() => patchState(this.#state, { isLoading: true })),\n      exhaustMap(() => {\n        return this.#booksService.getAll().pipe(\n          tapResponse({\n            next: (books) => patchState(this.#state, { books }),\n            error: console.error,\n            finalize: () =>\n              patchState(this.#state, { isLoading: false }),\n          })\n        );\n      })\n    )\n  );\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"book-list.ts\">\n\n```ts\nimport {\n  ChangeDetectionStrategy,\n  Component,\n  inject,\n  OnInit,\n} from '@angular/core';\nimport { BookListStore } from './book-list-store';\n\n@Component({\n  selector: 'ngrx-book-list',\n  template: `\n    <h1>Books</h1>\n\n    @if (store.isLoading()) {\n      <p>Loading...</p>\n    } @else {\n      <ul>\n        @for (book of store.books(); track book.id) {\n          <li>{{ book.title }}</li>\n        }\n      </ul>\n    }\n  `,\n  providers: [BookListStore],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BookList {\n  readonly store = inject(BookListStore);\n\n  constructor() {\n    this.store.loadBooks();\n  }\n}\n```\n\n</ngrx-code-example>\n</ngrx-code-tabs>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/custom-store-features.md",
    "content": "# Custom Store Features\n\nCustom SignalStore features provide a robust mechanism for extending core functionality and encapsulating common patterns, facilitating reuse across multiple stores.\n\n## Creating a Custom Feature\n\nA custom feature is created using the `signalStoreFeature` function, which accepts a sequence of base or other custom features as input arguments and merges them into a single feature.\n\n### Example 1: Tracking Request Status\n\nThe following example demonstrates how to create a custom feature that includes the `requestStatus` state slice along with computed signals for checking the request status.\n\n<ngrx-code-example header=\"with-request-status.ts\">\n\n```ts\nimport { computed } from '@angular/core';\nimport {\n  signalStoreFeature,\n  withComputed,\n  withState,\n} from '@ngrx/signals';\n\nexport type RequestStatus =\n  | 'idle'\n  | 'pending'\n  | 'fulfilled'\n  | { error: string };\nexport type RequestStatusState = { requestStatus: RequestStatus };\n\nexport function withRequestStatus() {\n  return signalStoreFeature(\n    withState<RequestStatusState>({ requestStatus: 'idle' }),\n    withComputed(({ requestStatus }) => ({\n      isPending: computed(() => requestStatus() === 'pending'),\n      isFulfilled: computed(() => requestStatus() === 'fulfilled'),\n      error: computed(() => {\n        const status = requestStatus();\n        return typeof status === 'object' ? status.error : null;\n      }),\n    }))\n  );\n}\n```\n\n</ngrx-code-example>\n\nIn addition to the state slice and computed signals, this feature also specifies a set of state updaters for modifying the request status.\n\n<ngrx-code-example header=\"with-request-status.ts\">\n\n```ts\nexport function setPending(): RequestStatusState {\n  return { requestStatus: 'pending' };\n}\n\nexport function setFulfilled(): RequestStatusState {\n  return { requestStatus: 'fulfilled' };\n}\n\nexport function setError(error: string): RequestStatusState {\n  return { requestStatus: { error } };\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\nFor a custom feature, it is recommended to define state updaters as standalone functions rather than feature methods. This approach enables tree-shaking, simplifies testing, and facilitates their use alongside other updaters in a single `patchState` call.\n\n</ngrx-docs-alert>\n\nThe `withRequestStatus` feature and updaters can be used to add the `requestStatus` state slice, along with the `isPending`, `isFulfilled`, and `error` computed signals to the `BooksStore`, as follows:\n\n<ngrx-code-example header=\"books-store.ts\">\n\n```ts\nimport { inject } from '@angular/core';\nimport { patchState, signalStore, withMethods } from '@ngrx/signals';\nimport { setAllEntities, withEntities } from '@ngrx/signals/entities';\nimport {\n  setFulfilled,\n  setPending,\n  withRequestStatus,\n} from './with-request-status';\nimport { BooksService } from './books-service';\nimport { Book } from './book';\n\nexport const BooksStore = signalStore(\n  withEntities<Book>(),\n  withRequestStatus(),\n  withMethods((store, booksService = inject(BooksService)) => ({\n    async loadAll() {\n      patchState(store, setPending());\n\n      const books = await booksService.getAll();\n      patchState(store, setAllEntities(books), setFulfilled());\n    },\n  }))\n);\n```\n\n</ngrx-code-example>\n\nThe `BooksStore` instance will contain the following properties and methods:\n\n- State signals from `withEntities` feature:\n  - `entityMap: Signal<EntityMap<Book>>`\n  - `ids: Signal<EntityId[]>`\n- Computed signals from `withEntities` feature:\n  - `entities: Signal<Book[]>`\n- State signals from `withRequestStatus` feature:\n  - `requestStatus: Signal<RequestStatus>`\n- Computed signals from `withRequestStatus` feature:\n  - `isPending: Signal<boolean>`\n  - `isFulfilled: Signal<boolean>`\n  - `error: Signal<string | null>`\n- Methods:\n  - `loadAll(): Promise<void>`\n\n<ngrx-docs-alert type=\"help\">\n\nIn this example, the `withEntities` feature from the `entities` plugin is utilized.\nFor more details, refer to the [Entity Management guide](guide/signals/signal-store/entity-management).\n\n</ngrx-docs-alert>\n\n### Example 2: Logging State Changes\n\nThe following example shows how to create a custom feature that logs SignalStore state changes to the console.\n\n<ngrx-code-example header=\"with-logger.ts\">\n\n```ts\nimport { effect } from '@angular/core';\nimport {\n  getState,\n  signalStoreFeature,\n  withHooks,\n} from '@ngrx/signals';\n\nexport function withLogger(name: string) {\n  return signalStoreFeature(\n    withHooks({\n      onInit(store) {\n        effect(() => {\n          const state = getState(store);\n          console.log(`${name} state changed`, state);\n        });\n      },\n    })\n  );\n}\n```\n\n</ngrx-code-example>\n\nThe `withLogger` feature can be used in the `BooksStore` as follows:\n\n<ngrx-code-example header=\"books-store.ts\">\n\n```ts\nimport { signalStore } from '@ngrx/signals';\nimport { withEntities } from '@ngrx/signals/entities';\nimport { withRequestStatus } from './with-request-status';\nimport { withLogger } from './with-logger';\nimport { Book } from './book';\n\nexport const BooksStore = signalStore(\n  withEntities<Book>(),\n  withRequestStatus(),\n  withLogger('books')\n);\n```\n\n</ngrx-code-example>\n\nState changes will be logged to the console whenever the `BooksStore` state is updated.\n\n## Creating a Custom Feature with Input\n\nThe `signalStoreFeature` function provides the ability to create a custom feature that requires specific state slices, properties, and/or methods to be defined in the store where it is used.\nThis enables the utilization of input properties within the custom feature, even if they are not explicitly defined within the feature itself.\n\nThe expected input type should be defined as the first argument of the `signalStoreFeature` function, using the `type` helper function from the `@ngrx/signals` package.\n\n<ngrx-docs-alert type=\"inform\">\n\nIt's recommended to define loosely-coupled/independent features whenever possible.\n\n</ngrx-docs-alert>\n\n### Example 3: Managing Selected Entity\n\nThe following example demonstrates how to create the `withSelectedEntity` feature.\n\n<ngrx-code-example header=\"with-selected-entity.ts\">\n\n```ts\nimport { computed } from '@angular/core';\nimport {\n  signalStoreFeature,\n  type,\n  withComputed,\n  withState,\n} from '@ngrx/signals';\nimport { EntityId, EntityState } from '@ngrx/signals/entities';\n\nexport type SelectedEntityState = {\n  selectedEntityId: EntityId | null;\n};\n\nexport function withSelectedEntity<Entity>() {\n  return signalStoreFeature(\n    { state: type<EntityState<Entity>>() },\n    withState<SelectedEntityState>({ selectedEntityId: null }),\n    withComputed(({ entityMap, selectedEntityId }) => ({\n      selectedEntity: computed(() => {\n        const selectedId = selectedEntityId();\n        return selectedId ? entityMap()[selectedId] : null;\n      }),\n    }))\n  );\n}\n```\n\n</ngrx-code-example>\n\nThe `withSelectedEntity` feature adds the `selectedEntityId` state slice and the `selectedEntity` computed signal to the store where it is used.\nHowever, it expects state properties from the `EntityState` type to be defined in that store.\nThese properties can be added to the store by using the `withEntities` feature from the `entities` plugin.\n\n<ngrx-code-example header=\"books-store.ts\">\n\n```ts\nimport { signalStore } from '@ngrx/signals';\nimport { withEntities } from '@ngrx/signals/entities';\nimport { withSelectedEntity } from './with-selected-entity';\nimport { Book } from './book';\n\nexport const BooksStore = signalStore(\n  withEntities<Book>(),\n  withSelectedEntity()\n);\n```\n\n</ngrx-code-example>\n\nThe `BooksStore` instance will contain the following properties:\n\n- State signals from `withEntities` feature:\n  - `entityMap: Signal<EntityMap<Book>>`\n  - `ids: Signal<EntityId[]>`\n- Computed signals from `withEntities` feature:\n  - `entities: Signal<Book[]>`\n- State signals from `withSelectedEntity` feature:\n  - `selectedEntityId: Signal<EntityId | null>`\n- Computed signals from `withSelectedEntity` feature:\n  - `selectedEntity: Signal<Book | null>`\n\nThe `@ngrx/signals` package offers high-level type safety.\nTherefore, if `BooksStore` does not contain state properties from the `EntityState` type, the compilation error will occur.\n\n<ngrx-code-example header=\"books-store.ts\">\n\n```ts\nimport { signalStore } from '@ngrx/signals';\nimport { withSelectedEntity } from './with-selected-entity';\nimport { Book } from './book';\n\nexport const BooksStore = signalStore(\n  withState({ books: [] as Book[], isLoading: false }),\n  // Error: `EntityState` properties (`entityMap` and `ids`) are missing in the `BooksStore`.\n  withSelectedEntity()\n);\n```\n\n</ngrx-code-example>\n\n### Example 4: Defining Properties and Methods as Input\n\nIn addition to state, it's also possible to define expected properties and methods in the following way:\n\n<ngrx-code-example header=\"with-baz.ts\">\n\n```ts\nimport { Signal } from '@angular/core';\nimport { signalStoreFeature, type, withMethods } from '@ngrx/signals';\n\nexport function withBaz<Foo extends string | number>() {\n  return signalStoreFeature(\n    {\n      props: type<{ foo: Signal<Foo> }>(),\n      methods: type<{ bar(foo: number): void }>(),\n    },\n    withMethods((store) => ({\n      baz(): void {\n        const foo = store.foo();\n        store.bar(typeof foo === 'number' ? foo : Number(foo));\n      },\n    }))\n  );\n}\n```\n\n</ngrx-code-example>\n\nThe `withBaz` feature can only be used in a store where the property `foo` and the method `bar` are defined.\n\n## Using `withFeature`\n\nAn alternative approach to custom features with input is using the `withFeature` utility, which offers more flexibility.\n\nThe `withFeature` function accepts a callback that receives a store instance and returns a SignalStore feature.\nThis enables using features that rely on external inputs without depending on the store’s internal structure.\n\n```ts\nimport { computed, Signal } from '@angular/core';\nimport {\n  patchState,\n  signalStore,\n  signalStoreFeature,\n  withComputed,\n  withFeature,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\nimport { withEntities } from '@ngrx/signals/entities';\nimport { Book } from './book';\n\nexport function withBooksFilter(books: Signal<Book[]>) {\n  return signalStoreFeature(\n    withState({ query: '' }),\n    withComputed(({ query }) => ({\n      filteredBooks: computed(() =>\n        books().filter((b) => b.name.includes(query()))\n      ),\n    })),\n    withMethods((store) => ({\n      setQuery(query: string): void {\n        patchState(store, { query });\n      },\n    }))\n  );\n}\n\nexport const BooksStore = signalStore(\n  withEntities<Book>(),\n  // 👇 Using `withFeature` to pass input to the `withBooksFilter` feature.\n  withFeature(({ entities }) => withBooksFilter(entities))\n);\n```\n\n## Known TypeScript Issues\n\nCombining multiple custom features with static input may cause unexpected compilation errors:\n\n```ts\nfunction withZ() {\n  return signalStoreFeature(\n    { state: type<{ x: number }>() },\n    withState({ z: 10 })\n  );\n}\n\nfunction withW() {\n  return signalStoreFeature(\n    { state: type<{ y: number }>() },\n    withState({ w: 100 })\n  );\n}\n\nconst Store = signalStore(\n  withState({ x: 10, y: 100 }),\n  withZ(),\n  withW()\n); // ❌ compilation error\n```\n\nThis issue arises specifically with custom features that accept input but do not define any generic parameters.\nTo prevent this issue, it is recommended to specify an unused generic for such custom features:\n\n<ngrx-code-example>\n\n```ts\n//            👇\nfunction withZ<_>() {\n  return signalStoreFeature(\n    { state: type<{ x: number }>() },\n    withState({ z: 10 })\n  );\n}\n\n//            👇\nfunction withW<_>() {\n  return signalStoreFeature(\n    { state: type<{ y: number }>() },\n    withState({ w: 100 })\n  );\n}\n\nconst Store = signalStore(\n  withState({ x: 10, y: 100 }),\n  withZ(),\n  withW()\n); // ✅ works as expected\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/custom-store-properties.md",
    "content": "# Custom Store Properties\n\nThe `withProps` feature can be used to add static properties, observables, dependencies, or other custom properties to a SignalStore.\nIt accepts a factory function that returns an object containing additional properties for the store.\nThe factory function receives an object containing state signals, previously defined properties, and methods as its input argument.\n\n## Exposing Observables\n\n`withProps` can be useful for exposing observables from a SignalStore, which can serve as integration points with RxJS-based APIs:\n\n<ngrx-code-example header=\"books-store.ts\">\n\n```ts\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport { signalStore, withProps, withState } from '@ngrx/signals';\nimport { Book } from './book';\n\ntype BooksState = {\n  books: Book[];\n  isLoading: boolean;\n};\n\nexport const BooksStore = signalStore(\n  withState<BooksState>({ books: [], isLoading: false }),\n  withProps(({ isLoading }) => ({\n    isLoading$: toObservable(isLoading),\n  }))\n);\n```\n\n</ngrx-code-example>\n\n## Grouping Dependencies\n\nDependencies required across multiple store features can be grouped using `withProps`:\n\n<ngrx-code-example header=\"books-store.ts\">\n\n```ts\nimport { inject } from '@angular/core';\nimport { signalStore, withProps, withState } from '@ngrx/signals';\nimport { Logger } from './logger';\nimport { BooksService } from './books-service';\nimport { Book } from './book';\n\ntype BooksState = {\n  books: Book[];\n  isLoading: boolean;\n};\n\nexport const BooksStore = signalStore(\n  withState<BooksState>({ books: [], isLoading: false }),\n  withProps(() => ({\n    booksService: inject(BooksService),\n    logger: inject(Logger),\n  })),\n  withMethods(({ booksService, logger, ...store }) => ({\n    async loadBooks(): Promise<void> {\n      logger.debug('Loading books...');\n      patchState(store, { isLoading: true });\n\n      const books = await booksService.getAll();\n      logger.debug('Books loaded successfully', books);\n\n      patchState(store, { books, isLoading: false });\n    },\n  })),\n  withHooks({\n    onInit({ logger }) {\n      logger.debug('BooksStore initialized');\n    },\n  })\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/entity-management.md",
    "content": "# Entity Management\n\nThe `@ngrx/signals/entities` plugin offers a simple and efficient way to manage entity collections with NgRx SignalStore.\nThis plugin provides the `withEntities` feature and a set of entity updaters.\n\n## `withEntities` Feature\n\nThe `withEntities` feature integrates entity state into the store.\nBy default, `withEntities` requires an entity to have an `id` property, which serves as a unique identifier and must be of type `EntityId` (either a `string` or a `number`).\n\n<ngrx-code-example header=\"todos-store.ts\">\n\n```ts\nimport { computed } from '@angular/core';\nimport { signalStore } from '@ngrx/signals';\nimport { withEntities } from '@ngrx/signals/entities';\n\ntype Todo = {\n  id: number;\n  text: string;\n  completed: boolean;\n};\n\nexport const TodosStore = signalStore(withEntities<Todo>());\n```\n\n</ngrx-code-example>\n\nThe `withEntities` feature adds the following signals to the `TodosStore`:\n\n- `ids: Signal&lt;EntityId[]&gt;`: An array of all entity IDs.\n- `entityMap: Signal&lt;EntityMap&lt;Todo&gt;&gt;`: A map of entities where each key is an ID.\n- `entities: Signal&lt;Todo[]&gt;`: An array of all entities.\n\nThe `ids` and `entityMap` are state slices, while `entities` is a computed signal.\n\n## Entity Updaters\n\nThe `entities` plugin provides a set of standalone entity updaters.\nThese functions can be used with `patchState` to facilitate entity collection updates.\n\n<ngrx-code-example header=\"todos-store.ts\">\n\n```ts\nimport { patchState, signalStore, withMethods } from '@ngrx/signals';\nimport {\n  addEntity,\n  removeEntities,\n  updateAllEntities,\n  withEntities,\n} from '@ngrx/signals/entities';\n\ntype Todo = {\n  /* ... */\n};\n\nexport const TodosStore = signalStore(\n  withEntities<Todo>(),\n  withMethods((store) => ({\n    addTodo(todo: Todo): void {\n      patchState(store, addEntity(todo));\n    },\n    removeEmptyTodos(): void {\n      patchState(\n        store,\n        removeEntities(({ text }) => !text)\n      );\n    },\n    completeAllTodos(): void {\n      patchState(store, updateAllEntities({ completed: true }));\n    },\n  }))\n);\n```\n\n</ngrx-code-example>\n\n### `addEntity`\n\nAdds an entity to the collection.\nIf the entity collection has an entity with the same ID, it is not overridden and no error is thrown.\n\n```ts\npatchState(store, addEntity(todo));\n```\n\n### `addEntities`\n\nAdds multiple entities to the collection.\nIf the entity collection has entities with the same IDs, they are not overridden and no error is thrown.\n\n```ts\npatchState(store, addEntities([todo1, todo2]));\n```\n\n### `prependEntity`\n\nAdds an entity to the beginning of the collection.\nIf the entity collection has an entity with the same ID, it is not added and no error is thrown.\n\n```ts\npatchState(store, prependEntity(todo));\n```\n\n### `prependEntities`\n\nAdds multiple entities to the beginning of the collection, maintaining their relative order.\nIf the entity collection has entities with the same IDs, they are not added and no error is thrown.\n\n```ts\npatchState(store, prependEntities([todo1, todo2]));\n```\n\n### `updateEntity`\n\nUpdates an entity in the collection by ID. Supports partial updates. No error is thrown if an entity doesn't exist.\n\n```ts\npatchState(\n  store,\n  updateEntity({ id: 1, changes: { completed: true } })\n);\n\npatchState(\n  store,\n  updateEntity({\n    id: 1,\n    changes: (todo) => ({ completed: !todo.completed }),\n  })\n);\n```\n\n### `updateEntities`\n\nUpdates multiple entities in the collection by IDs or predicate. Supports partial updates. No error is thrown if entities don't exist.\n\n```ts\n// update entities by IDs\npatchState(\n  store,\n  updateEntities({ ids: [1, 2], changes: { completed: true } })\n);\n\npatchState(\n  store,\n  updateEntities({\n    ids: [1, 2],\n    changes: (todo) => ({ completed: !todo.completed }),\n  })\n);\n\n// update entities by predicate\npatchState(\n  store,\n  updateEntities({\n    predicate: ({ text }) => text.endsWith('✅'),\n    changes: { text: '' },\n  })\n);\n\npatchState(\n  store,\n  updateEntities({\n    predicate: ({ text }) => text.endsWith('❓'),\n    changes: (todo) => ({ text: todo.text.slice(0, -1) }),\n  })\n);\n```\n\n### `updateAllEntities`\n\nUpdates all entities in the collection. Supports partial updates. No error is thrown if entities don't exist.\n\n```ts\npatchState(store, updateAllEntities({ text: '' }));\n\npatchState(\n  store,\n  updateAllEntities((todo) => ({ text: `${todo.text} ${todo.id}` }))\n);\n```\n\n### `setEntity`\n\nAdds or replaces an entity in the collection.\n\n```ts\npatchState(store, setEntity(todo));\n```\n\n### `setEntities`\n\nAdds or replaces multiple entities in the collection.\n\n```ts\npatchState(store, setEntities([todo1, todo2]));\n```\n\n### `setAllEntities`\n\nReplaces the current entity collection with the provided collection.\n\n```ts\npatchState(store, setAllEntities([todo1, todo2, todo3]));\n```\n\n### `upsertEntity`\n\nAdds or updates an entity in the collection.\nWhen updating, it does not replace the existing entity but merges it with the provided one.\nOnly the properties provided in the updated entity are merged with the existing entity.\nProperties not present in the updated entity remain unchanged.\n\n```ts\npatchState(store, upsertEntity(todo));\n```\n\n### `upsertEntities`\n\nAdds or updates multiple entities in the collection.\nWhen updating, it does not replace existing entities but merges them with the provided ones.\nOnly the properties provided in updated entities are merged with existing entities.\nProperties not present in updated entities remain unchanged.\n\n```ts\npatchState(store, upsertEntities([todo1, todo2]));\n```\n\n### `removeEntity`\n\nRemoves an entity from the collection by ID. No error is thrown if an entity doesn't exist.\n\n```ts\npatchState(store, removeEntity(1));\n```\n\n### `removeEntities`\n\nRemoves multiple entities from the collection by IDs or predicate. No error is thrown if entities don't exist.\n\n```ts\n// remove entities by IDs\npatchState(store, removeEntities([1, 2]));\n\n// remove entities by predicate\npatchState(\n  store,\n  removeEntities((todo) => todo.completed)\n);\n```\n\n### `removeAllEntities`\n\nRemoves all entities from the collection. No error is thrown if entities don't exist.\n\n```ts\npatchState(store, removeAllEntities());\n```\n\n## Custom Entity Identifier\n\nIf an entity doesn't have an identifier named `id`, a custom ID selector should be used.\nThe selector's return type should be either `string` or `number`.\n\nCustom ID selectors should be provided when adding, setting, or updating entities.\nTherefore, all variations of the `add*`, `set*`, and `update*` functions include an optional second argument, which is a config object that allows specifying the `selectId` function.\n\n<ngrx-code-example header=\"todos-store.ts\">\n\n```ts\nimport { patchState, signalStore, withMethods } from '@ngrx/signals';\nimport {\n  addEntities,\n  removeEntity,\n  SelectEntityId,\n  setEntity,\n  updateAllEntities,\n  withEntities,\n} from '@ngrx/signals/entities';\n\ntype Todo = {\n  key: number;\n  text: string;\n  completed: boolean;\n};\n\nconst selectId: SelectEntityId<Todo> = (todo) => todo.key;\n\nexport const TodosStore = signalStore(\n  withEntities<Todo>(),\n  withMethods((store) => ({\n    addTodos(todos: Todo[]): void {\n      patchState(store, addEntities(todos, { selectId }));\n    },\n    setTodo(todo: Todo): void {\n      patchState(store, setEntity(todo, { selectId }));\n    },\n    completeAllTodos(): void {\n      patchState(\n        store,\n        updateAllEntities({ completed: true }, { selectId })\n      );\n    },\n    removeTodo(key: number): void {\n      patchState(store, removeEntity(key));\n    },\n  }))\n);\n```\n\n</ngrx-code-example>\n\nThe `remove*` updaters automatically select the correct identifier, so it is not necessary to provide a custom ID selector.\n\n## Named Entity Collections\n\nThe `withEntities` feature allows specifying a custom prefix for entity properties by providing a collection name as an input argument.\n\n<ngrx-code-example header=\"todos-store.ts\">\n\n```ts\nimport { signalStore, type } from '@ngrx/signals';\nimport { withEntities } from '@ngrx/signals/entities';\n\ntype Todo = {\n  id: number;\n  text: string;\n  completed: boolean;\n};\n\nexport const TodosStore = signalStore(\n  // 💡 Entity type is specified using the `type` function.\n  withEntities({ entity: type<Todo>(), collection: 'todo' })\n);\n```\n\n</ngrx-code-example>\n\nThe names of the `TodosStore` properties are changed from `ids`, `entityMap`, and `entities` to `todoIds`, `todoEntityMap`, and `todoEntities`.\n\nAll updaters that operate on named entity collections require a collection name.\n\n<ngrx-code-example header=\"todos-store.ts\">\n\n```ts\nimport {\n  patchState,\n  signalStore,\n  type,\n  withMethods,\n} from '@ngrx/signals';\nimport {\n  addEntity,\n  removeEntity,\n  withEntities,\n} from '@ngrx/signals/entities';\n\ntype Todo = {\n  /* ... */\n};\n\nexport const TodosStore = signalStore(\n  withEntities({ entity: type<Todo>(), collection: 'todo' }),\n  withMethods((store) => ({\n    addTodo(todo: Todo): void {\n      patchState(store, addEntity(todo, { collection: 'todo' }));\n    },\n    removeTodo(id: number): void {\n      patchState(store, removeEntity(id, { collection: 'todo' }));\n    },\n  }))\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nNamed entity collections allow managing multiple collections in a single store by using the `withEntities` feature multiple times.\n\n```ts\nexport const LibraryStore = signalStore(\n  withEntities({ entity: type<Book>(), collection: 'book' }),\n  withEntities({ entity: type<Author>(), collection: 'author' }),\n  withEntities({ entity: type<Category>(), collection: 'category' }),\n  withMethods((store) => ({\n    addBook(book: Book): void {\n      patchState(store, addEntity(book, { collection: 'book' }));\n    },\n    addAuthor(author: Author): void {\n      patchState(store, addEntity(author, { collection: 'author' }));\n    },\n    addCategory(category: Category): void {\n      patchState(\n        store,\n        addEntity(category, { collection: 'category' })\n      );\n    },\n  }))\n);\n```\n\nAlthough it is possible to manage multiple collections in one store, in most cases, it is recommended to have dedicated stores for each entity type.\n\n</ngrx-docs-alert>\n\n## `entityConfig`\n\nThe `entityConfig` function reduces repetitive code when defining a custom entity configuration and ensures strong typing.\nIt accepts a config object where the entity type is required, and the collection name and custom ID selector are optional.\n\n<ngrx-code-example header=\"todos-store.ts\">\n\n```ts\nimport {\n  patchState,\n  signalStore,\n  type,\n  withMethods,\n} from '@ngrx/signals';\nimport {\n  addEntity,\n  entityConfig,\n  removeEntity,\n  withEntities,\n} from '@ngrx/signals/entities';\n\ntype Todo = {\n  key: number;\n  text: string;\n  completed: boolean;\n};\n\nconst todoConfig = entityConfig({\n  entity: type<Todo>(),\n  collection: 'todo',\n  selectId: (todo) => todo.key,\n});\n\nexport const TodosStore = signalStore(\n  withEntities(todoConfig),\n  withMethods((store) => ({\n    addTodo(todo: Todo): void {\n      patchState(store, addEntity(todo, todoConfig));\n    },\n    removeTodo(todo: Todo): void {\n      patchState(store, removeEntity(todo, todoConfig));\n    },\n  }))\n);\n```\n\n</ngrx-code-example>\n\n## Private Entity Collections\n\nPrivate entity collections are defined by using the `_` prefix for the collection name.\n\n<ngrx-code-example>\n\n```ts\nconst todoConfig = entityConfig({\n  entity: type<Todo>(),\n  // 👇 private collection\n  collection: '_todo',\n});\n\nconst TodosStore = signalStore(\n  withEntities(todoConfig),\n  withComputed(({ _todoEntities }) => ({\n    // 👇 exposing entity array publicly\n    todos: _todoEntities,\n  }))\n);\n\n@Component({\n  /* ... */\n  template: `\n    <h1>Todos</h1>\n    <ngrx-todo-list [todos]=\"store.todos()\" />\n  `,\n  providers: [TodosStore],\n})\nclass Todos {\n  readonly store = inject(TodosStore);\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nLearn more about private store members in the [Private Store Members](/guide/signals/signal-store/private-store-members) guide.\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/events.md",
    "content": "# Events\n\nThe Events plugin extends SignalStore with an event-based state management layer.\nIt takes inspiration from the original Flux architecture and incorporates the best practices and patterns from NgRx Store, NgRx Effects, and RxJS.\n\n<figure>\n  <img src=\"images/guide/signals/app-architecture-with-events-plugin.png\" alt=\"Application Architecture with Events Plugin\" width=\"100%\" height=\"100%\" />\n</figure>\n\nThe application architecture with the Events plugin is composed of the following building blocks:\n\n1. **Event:** Describes an occurrence within the system. Events are dispatched to trigger state changes and/or side effects.\n2. **Dispatcher:** An event bus that forwards events to their corresponding handlers in the stores.\n3. **Store:** Contains event handlers that manage state transitions and handle side effects, maintaining a clean and predictable application flow.\n4. **View:** Reflects state changes and dispatches new events, enabling continuous interaction between the user interface and the underlying system.\n\nBy dispatching events and reacting to them, the _what_ (the event that occurred) is decoupled from the _how_ (the state changes or side effects that result), leading to predictable data flow and more maintainable code.\n\n<ngrx-docs-alert type=\"help\">\n\nWhile the default SignalStore approach is sufficient for most use cases, the Events plugin excels in more advanced scenarios that involve inter-store coordination or benefit from a decoupled architecture.\n\n</ngrx-docs-alert>\n\n## Defining Event Creators\n\nEvent creators are defined using utilities provided by the Events plugin.\nThe `event` function is used for declaring individual event creators, while the `eventGroup` function enables grouping multiple event creators under a common source.\n\n### Using `event` Function\n\nThe simplest way to define an event creator is with the `event` function,\nwhich takes an event type and an optional payload schema.\nCalling the event creator produces an event object with a `type` property and, if a payload is defined, a `payload` property.\n\n<ngrx-code-example header=\"book-search-events.ts\">\n\n```ts\nimport { type } from '@ngrx/signals';\nimport { event } from '@ngrx/signals/events';\n\nexport const opened = event('[Book Search Page] Opened');\nexport const queryChanged = event(\n  '[Book Search Page] Query Changed',\n  // 👇 The payload type is defined using the `type` function.\n  type<string>()\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"books-api-events.ts\">\n\n```ts\nimport { type } from '@ngrx/signals';\nimport { event } from '@ngrx/signals/events';\nimport { Book } from './book';\n\nexport const loadedSuccess = event(\n  '[Books API] Loaded Success',\n  type<Book[]>()\n);\nexport const loadedFailure = event(\n  '[Books API] Loaded Failure',\n  type<string>()\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\nIt's recommended to use the \"[Source] EventName\" pattern when defining the event type.\n\n</ngrx-docs-alert>\n\nEach of these exported constants is an event creator function.\nWhen called, it returns a plain event object.\nFor example, calling `opened()` returns an object `{ type: '[Book Search Page] Opened' }`, and calling `loadedSuccess([book1, book2])` returns an object `{ type: '[Books API] Loaded Success', payload: [book1, book2] }`.\nThe `type` property serves as a unique identifier for the event, and the optional `payload` carries additional data.\n\n### Using `eventGroup` Function\n\nDefining many events with the same source can become repetitive.\nThe `eventGroup` API is used to create a set of events with the common source.\nThis function takes an object with two properties:\n\n- `source`: Identifies the origin of the event group (e.g., 'Book Search Page', 'Books API').\n- `events`: A dictionary of named event creators, where each key defines the event name and each value defines the payload type.\n\nThe type of all event creators in the group are prefixed with the provided `source`.\n\n<ngrx-code-example header=\"book-search-events.ts\">\n\n```ts\nimport { type } from '@ngrx/signals';\nimport { eventGroup } from '@ngrx/signals/events';\n\nexport const bookSearchEvents = eventGroup({\n  source: 'Book Search Page',\n  events: {\n    // 👇 Defining an event creator without a payload.\n    opened: type<void>(),\n    queryChanged: type<string>(),\n  },\n});\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"books-api-events.ts\">\n\n```ts\nimport { type } from '@ngrx/signals';\nimport { eventGroup } from '@ngrx/signals/events';\nimport { Book } from './book';\n\nexport const booksApiEvents = eventGroup({\n  source: 'Books API',\n  events: {\n    loadedSuccess: type<Book[]>(),\n    loadedFailure: type<string>(),\n  },\n});\n```\n\n</ngrx-code-example>\n\nEvent types are automatically formatted as \"[Source] EventName\".\nFor example, calling `bookSearchEvents.opened()` yields `{ type: '[Book Search Page] opened' }`, and `booksApiEvents.loadedSuccess([book1, book2])` yields `{ type: '[Books API] loadedSuccess', payload: [book1, book2] }`.\n\n## Defining State Transitions\n\nTo handle state transitions in response to events, the Events plugin provides the `withReducer` feature.\nCase reducers are defined using the `on` function, which maps one or more events to a case reducer handler.\nA handler is a function that receives the dispatched event as the first and the current state as the second argument.\nThe return value of a case reducer handler can be a partial state object, a partial state updater, or an array of partial state objects and/or updaters.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\nimport { signalStore, withState } from '@ngrx/signals';\nimport { on, withReducer } from '@ngrx/signals/events';\nimport { bookSearchEvents } from './book-search-events';\nimport { booksApiEvents } from './books-api-events';\nimport { Book } from './book';\n\ntype State = { query: string; books: Book[]; isLoading: boolean };\n\nexport const BookSearchStore = signalStore(\n  withState<State>({ query: '', books: [], isLoading: false }),\n  withReducer(\n    on(bookSearchEvents.opened, () => ({ isLoading: true })),\n    on(bookSearchEvents.queryChanged, ({ payload: query }) => ({\n      query,\n      isLoading: true,\n    })),\n    on(booksApiEvents.loadedSuccess, ({ payload: books }) => ({\n      books,\n      isLoading: false,\n    })),\n    on(booksApiEvents.loadedFailure, () => ({ isLoading: false }))\n  )\n);\n```\n\n</ngrx-code-example>\n\nWhen an event is dispatched, the corresponding case reducer logic runs and the SignalStore's state is updated.\n\n<ngrx-docs-alert type=\"help\">\n\nIn addition to partial state objects, it's also possible to return a partial state updater or an array of partial state objects and/or updaters as the result of a case reducer handler.\n\n```ts\nconst incrementBy = event(\n  '[Counter Page] Increment By',\n  type<number>()\n);\nconst increment = event('[Counter Page] Increment');\nconst incrementBoth = event('[Counter Page] Increment Both');\n\nexport const CounterStore = signalStore(\n  withState({ count1: 0, count2: 0 }),\n  withReducer(\n    // 👇 Returning a partial state object.\n    on(incrementBy, (event, state) => ({\n      count1: state.count1 + event.payload,\n    })),\n    // 👇 Returning a partial state updater.\n    on(increment, () => incrementFirst()),\n    // 👇 Returning an array of partial state updaters.\n    on(incrementBoth, () => [incrementFirst(), incrementSecond()])\n  )\n);\n\nfunction incrementFirst(): PartialStateUpdater<{ count1: number }> {\n  return (state) => ({ count1: state.count1 + 1 });\n}\n\nfunction incrementSecond(): PartialStateUpdater<{ count2: number }> {\n  return (state) => ({ count2: state.count2 + 1 });\n}\n```\n\n</ngrx-docs-alert>\n\n## Defining Event Handlers\n\nEvent handlers, such as those that perform asynchronous side effects, can be defined using the `withEventHandlers` feature.\nThis feature accepts a function that receives the store instance as an argument and returns either a dictionary or an array of event handlers.\nEach event handler is defined as an observable that reacts to specific events using the `Events` service.\nThis service provides the `on` method that returns an observable of dispatched events filtered by the specified event types.\nIf an event handler returns a new event, that event is automatically dispatched.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\n// ... other imports\nimport { switchMap, tap } from 'rxjs';\nimport { Events, withEventHandlers } from '@ngrx/signals/events';\nimport { mapResponse } from '@ngrx/operators';\nimport { BooksService } from './books-service';\n\nexport const BookSearchStore = signalStore(\n  // ... other features\n  withEventHandlers(\n    (\n      store,\n      events = inject(Events),\n      booksService = inject(BooksService)\n    ) => ({\n      loadBooksByQuery$: events\n        .on(bookSearchEvents.opened, bookSearchEvents.queryChanged)\n        .pipe(\n          switchMap(() =>\n            booksService.getByQuery(store.query()).pipe(\n              mapResponse({\n                next: (books) => booksApiEvents.loadedSuccess(books),\n                error: (error: { message: string }) =>\n                  booksApiEvents.loadedFailure(error.message),\n              })\n            )\n          )\n        ),\n      logError$: events\n        .on(booksApiEvents.loadedFailure)\n        .pipe(tap(({ payload }) => console.error(payload))),\n    })\n  )\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nIn addition to the `Events` service, event handlers can be defined by listening to any other observable source.\nIt's also possible to return an array of handlers from the `withEventHandlers` feature.\n\n```ts\n// ... other imports\nimport { exhaustMap, tap, timer } from 'rxjs';\nimport { withEventHandlers } from '@ngrx/signals/events';\nimport { mapResponse } from '@ngrx/operators';\nimport { BooksService } from './books-service';\n\nexport const BookSearchStore = signalStore(\n  // ... other features\n  withEventHandlers((store, booksService = inject(BooksService)) => [\n    timer(0, 30_000).pipe(\n      exhaustMap(() =>\n        booksService.getAll().pipe(\n          mapResponse({\n            next: (books) => booksApiEvents.loadedSuccess(books),\n            error: (error: { message: string }) =>\n              booksApiEvents.loadedFailure(error.message),\n          })\n        )\n      )\n    ),\n    events\n      .on(booksApiEvents.loadedFailure)\n      .pipe(tap(({ payload }) => console.error(payload))),\n  ])\n);\n```\n\n</ngrx-docs-alert>\n\n<ngrx-docs-alert type=\"inform\">\n\nThe `withEventHandlers` feature can also serve as a way to implement custom state transitions in cases where `withReducer` does not fully address the requirements.\nFor this purpose, the `ReducerEvents` service is recommended, as it receives dispatched events before the `Events` service.\nThis ensures that state transitions are applied before other event handlers react.\n\n```ts\n// ... other imports\nimport {\n  ReducerEvents,\n  withEventHandlers,\n} from '@ngrx/signals/events';\n\nconst counterPageEvents = eventGroup({\n  source: 'Counter Page',\n  events: {\n    increment: type<void>(),\n    set: type<number>(),\n  },\n});\n\nexport const CounterStore = signalStore(\n  withState({ count: 0 }),\n  withEventHandlers((store, events = inject(ReducerEvents)) => [\n    events\n      .on(counterPageEvents.increment)\n      .pipe(\n        tap(() => patchState(store, { count: store.count() + 1 }))\n      ),\n    events\n      .on(counterPageEvents.set)\n      .pipe(\n        tap(({ payload }) => patchState(store, { count: payload }))\n      ),\n  ])\n);\n```\n\n</ngrx-docs-alert>\n\n## Reading State\n\nThe Events plugin doesn’t change how the state is exposed or consumed.\nIt only changes how the state is updated (via reducers rather than direct method calls).\nTherefore, components can access state and computed signals by using the store instance.\n\n<ngrx-code-example header=\"book-search.ts\">\n\n```ts\nimport {\n  ChangeDetectionStrategy,\n  Component,\n  inject,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { BookSearchStore } from './book-search-store';\n\n@Component({\n  selector: 'ngrx-book-search',\n  imports: [FormsModule],\n  template: `\n    <h1>Search Books</h1>\n\n    <input type=\"text\" [ngModel]=\"store.query()\" />\n\n    @if (store.isLoading()) {\n      <p>Loading...</p>\n    }\n\n    <ul>\n      @for (book of store.books(); track book.id) {\n        <li>{{ book.title }}</li>\n      }\n    </ul>\n  `,\n  providers: [BookSearchStore],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BookSearch {\n  readonly store = inject(BookSearchStore);\n}\n```\n\n</ngrx-code-example>\n\n## Dispatching Events\n\nOnce events and their corresponding handlers have been defined, the remaining step is to dispatch events in response to user interactions or other triggers.\nDispatching an event allows any matching reducers or event handlers to process it accordingly.\n\n### Using `Dispatcher` Service\n\nTo initiate state changes or side effects, events can be dispatched using the `Dispatcher` service.\nIt provides the `dispatch` method that takes an event as input.\n\n<ngrx-code-example header=\"book-search.ts\">\n\n```ts\n// ... other imports\nimport { Dispatcher } from '@ngrx/signals/events';\nimport { bookSearchEvents } from './book-search-events';\n\n@Component({\n  // ... component config\n  template: `\n    <h1>Search Books</h1>\n\n    <input\n      type=\"text\"\n      [ngModel]=\"store.query()\"\n      (ngModelChange)=\"changeQuery($event)\"\n    />\n\n    <!-- ... rest of the template -->\n  `,\n})\nexport class BookSearch {\n  readonly dispatcher = inject(Dispatcher);\n  readonly store = inject(BookSearchStore);\n\n  constructor() {\n    this.dispatcher.dispatch(bookSearchEvents.opened());\n  }\n\n  changeQuery(query: string): void {\n    this.dispatcher.dispatch(bookSearchEvents.queryChanged(query));\n  }\n}\n```\n\n</ngrx-code-example>\n\n### Using `injectDispatch` Function\n\nManually injecting the `Dispatcher` service and invoking the `dispatch` method for each event can lead to repetitive code.\nTo streamline this process, the Events plugin provides the `injectDispatch` utility.\nWhen invoked with a dictionary of event creators, this function returns an object that reflects the structure of the event definitions.\nEach member of the returned object is a method that, when called, automatically creates and dispatches the corresponding event.\n\n<ngrx-code-example header=\"book-search.ts\">\n\n```ts\n// ... other imports\nimport { injectDispatch } from '@ngrx/signals/events';\n\n@Component({\n  // ... component config\n  template: `\n    <h1>Search Books</h1>\n\n    <input\n      type=\"text\"\n      [ngModel]=\"store.query()\"\n      (ngModelChange)=\"dispatch.queryChanged($event)\"\n    />\n\n    <!-- ... rest of the template -->\n  `,\n})\nexport class BookSearch {\n  readonly dispatch = injectDispatch(bookSearchEvents);\n  readonly store = inject(BookSearchStore);\n\n  constructor() {\n    this.dispatch.opened();\n  }\n}\n```\n\n</ngrx-code-example>\n\n## Scoped Events\n\nBy default, the `Dispatcher` and `Events` services operate in a global scope where all dispatched events are handled application-wide.\nIn some cases, event handling should be isolated to a particular feature or component subtree.\nTypical examples include local state management scenarios where events should stay within a specific feature, or micro-frontend architectures where each remote module needs its own isolated event scope.\nTo support this, the Events plugin provides a built-in mechanism for scoped events.\n\n### Creating Local Scope\n\nA new event scope can be created at a feature or component level by using the `provideDispatcher()` function.\nAny events dispatched inside this boundary will belong to the local scope unless explicitly forwarded.\n\nWhen dispatching an event, the scope can be explicitly selected using the dispatch configuration:\n\n- `self` (default): An event dispatched and handled only within the local scope.\n- `parent`: An event is forwarded to the parent dispatcher.\n- `global`: An event is forwarded to the global dispatcher.\n\n<ngrx-code-example header=\"book-search.ts\">\n\n```ts\n// ... other imports\nimport { provideDispatcher } from '@ngrx/signals/events';\n\n@Component({\n  // ... component config\n  providers: [\n    // 👇 Provide local `Dispatcher` and `Events` services\n    // at the `BookSearch` injector level.\n    provideDispatcher(),\n    BookSearchStore,\n  ],\n})\nexport class BookSearch {\n  readonly store = inject(BookSearchStore);\n  readonly dispatch = injectDispatch(bookSearchEvents);\n\n  constructor() {\n    // 👇 Dispatch event to the local scope.\n    this.dispatch.opened();\n  }\n\n  changeQuery(query: string): void {\n    // 👇 Dispatch event to the parent scope.\n    this.dispatch({ scope: 'parent' }).queryChanged(query);\n  }\n\n  triggerRefresh(): void {\n    // 👇 Dispatch event to the global scope.\n    this.dispatch({ scope: 'global' }).refreshTriggered();\n  }\n}\n```\n\n</ngrx-code-example>\n\nEvent flow within scopes follows a hierarchical visibility rule, which means that `Events` service receives events dispatched in their own scope and events dispatched in any parent scope, including the global scope.\nOn the other hand, events dispatched locally are not visible to ancestor scopes.\n\n<ngrx-docs-alert type=\"help\">\n\nWhen using `Dispatcher`, the scope can be provided as a second argument of the `dispatch` method.\n\n```ts\n@Component({\n  // ... component config\n  providers: [provideDispatcher(), CounterStore],\n})\nexport class Counter {\n  readonly dispatcher = inject(Dispatcher);\n\n  increment(): void {\n    this.dispatcher.dispatch(counterPageEvents.increment(), {\n      scope: 'parent',\n    });\n  }\n\n  incrementBy(count: number): void {\n    this.dispatcher.dispatch(counterPageEvents.incrementBy(count), {\n      scope: 'global',\n    });\n  }\n}\n```\n\n</ngrx-docs-alert>\n\n### Scoped Events in Event Handlers\n\nScoped events can also be dispatched from event handlers. The Events plugin provides:\n\n- `toScope`: Forwards a returned event to the specified scope.\n- `mapToScope`: RxJS operator that applies scope forwarding to all returned events within a handler.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\n// ... other imports\nimport { mapToScope, toScope } from '@ngrx/signals/events';\n\nexport const BookSearchStore = signalStore(\n  // ... other features\n  withEventHandlers(\n    (\n      store,\n      events = inject(Events),\n      booksService = inject(BooksService)\n    ) => ({\n      loadBooksByQuery$: events\n        .on(bookSearchEvents.queryChanged)\n        .pipe(\n          switchMap(({ payload: query }) =>\n            booksService.getByQuery(query).pipe(\n              mapResponse({\n                // 👇 Dispatch `loadedSuccess` to the current scope.\n                next: (books) => booksApiEvents.loadedSuccess(books),\n                // 👇 Dispatch `loadedFailure` to the global scope.\n                error: (error: { message: string }) => [\n                  booksApiEvents.loadedFailure(error.message),\n                  toScope('global'),\n                ],\n              })\n            )\n          )\n        ),\n      loadBookById$: events.on(bookSearchEvents.bookSelected).pipe(\n        exhaustMap(({ payload: bookId }) =>\n          booksService.getById(bookId).pipe(\n            mapResponse({\n              next: (book) => booksApiEvents.loadedByIdSuccess(book),\n              error: (error: { message: string }) =>\n                booksApiEvents.loadedByIdFailure(error.message),\n            }),\n            // 👇 Dispatch all returned events to the parent scope.\n            mapToScope('parent')\n          )\n        )\n      ),\n    })\n  )\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/index.md",
    "content": "# SignalStore\n\nNgRx SignalStore is a fully-featured state management solution that offers a robust way to manage application state.\nWith its native support for Signals, it provides the ability to define stores in a clear and declarative manner.\nThe simplicity and flexibility of SignalStore, coupled with its opinionated and extensible design, establish it as a versatile solution for effective state management in Angular.\n\n## Creating a Store\n\nA SignalStore is created using the `signalStore` function. This function accepts a sequence of store features.\nThrough the combination of store features, the SignalStore gains state, properties, and methods, allowing for a flexible and extensible store implementation.\nBased on the utilized features, the `signalStore` function returns an injectable service that can be provided and injected where needed.\n\nThe `withState` feature is used to add state slices to the SignalStore.\nThis feature accepts initial state as an input argument. As with `signalState`, the state's type must be a record/object literal.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\nimport { signalStore, withState } from '@ngrx/signals';\nimport { Book } from './book';\n\ntype BookSearchState = {\n  books: Book[];\n  isLoading: boolean;\n  filter: { query: string; order: 'asc' | 'desc' };\n};\n\nconst initialState: BookSearchState = {\n  books: [],\n  isLoading: false,\n  filter: { query: '', order: 'asc' },\n};\n\nexport const BookSearchStore = signalStore(withState(initialState));\n```\n\n</ngrx-code-example>\n\nFor each state slice, a corresponding signal is automatically created.\nThe same applies to nested state properties, with all deeply nested signals being generated lazily on demand.\n\nThe `BookSearchStore` instance will contain the following properties:\n\n- `books: Signal<Book[]>`\n- `isLoading: Signal<boolean>`\n- `filter: DeepSignal<{ query: string; order: 'asc' | 'desc' }>`\n- `filter.query: Signal<string>`\n- `filter.order: Signal<'asc' | 'desc'>`\n\n<ngrx-docs-alert type=\"help\">\n\nThe `withState` feature also has a signature that takes the initial state factory as an input argument.\nThe factory is executed within the injection context, allowing initial state to be obtained from a service or injection token.\n\n```ts\nconst BOOK_SEARCH_STATE = new InjectionToken<BookSearchState>(\n  'BookSearchState',\n  { factory: () => initialState }\n);\n\nconst BookSearchStore = signalStore(\n  withState(() => inject(BOOK_SEARCH_STATE))\n);\n```\n\n</ngrx-docs-alert>\n\n## Providing and Injecting the Store\n\nSignalStore can be provided locally and globally.\nBy default, a SignalStore is not registered with any injectors and must be included in a providers array at the component, route, or root level before injection.\n\n<ngrx-code-example header=\"book-search.ts\">\n\n```ts\nimport { Component, inject } from '@angular/core';\nimport { BookSearchStore } from './book-search-store';\n\n@Component({\n  /* ... */\n  // 👇 Providing `BookSearchStore` at the component level.\n  providers: [BookSearchStore],\n})\nexport class BookSearch {\n  readonly store = inject(BookSearchStore);\n}\n```\n\n</ngrx-code-example>\n\nWhen provided at the component level, the store is tied to the component lifecycle, making it useful for managing local/component state.\nAlternatively, a SignalStore can be globally registered by setting the `providedIn` property to `root` when defining the store.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\nimport { signalStore, withState } from '@ngrx/signals';\nimport { Book } from './book';\n\ntype BookSearchState = {\n  /* ... */\n};\n\nconst initialState: BookSearchState = {\n  /* ... */\n};\n\nexport const BookSearchStore = signalStore(\n  // 👇 Providing `BookSearchStore` at the root level.\n  { providedIn: 'root' },\n  withState(initialState)\n);\n```\n\n</ngrx-code-example>\n\nWhen provided globally, the store is registered with the root injector and becomes accessible anywhere in the application.\nThis is beneficial for managing global state, as it ensures a single shared instance of the store across the entire application.\n\n## Reading State\n\nSignals generated for state slices can be utilized to access state values, as demonstrated below.\n\n<ngrx-code-example header=\"book-search.ts\">\n\n```ts\nimport { ChangeDetectionStrategy, Component, inject } from '@angular/core';\nimport { JsonPipe } from '@angular/common';\nimport { BookSearchStore } from './book-search-store';\n\n@Component({\n  imports: [JsonPipe],\n  template: `\n    <p>Books: {{ store.books() | json }}</p>\n    <p>Loading: {{ store.isLoading() }}</p>\n\n    <!-- 👇 The `DeepSignal` value can be read in the same way as `Signal`. -->\n    <p>Pagination: {{ store.filter() | json }}</p>\n\n    <!-- 👇 Nested signals are created as `DeepSignal` properties. -->\n    <p>Query: {{ store.filter.query() }}</p>\n    <p>Order: {{ store.filter.order() }}</p>\n  `,\n  providers: [BookSearchStore],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BookSearch {\n  readonly store = inject(BookSearchStore);\n}\n```\n\n</ngrx-code-example>\n\n## Defining Store Properties\n\nComputed signals can be added to the store using the `withComputed` feature.\nThis feature accepts a factory function as an input argument, which is executed within the injection context.\nThe factory should return a dictionary containing either computed signals or functions that return values (which are automatically wrapped in computed signals), utilizing previously defined state signals, properties, and methods that are accessible through its input argument.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\nimport { computed } from '@angular/core';\nimport { signalStore, withComputed, withState } from '@ngrx/signals';\nimport { Book } from './book';\n\ntype BookSearchState = {\n  /* ... */\n};\n\nconst initialState: BookSearchState = {\n  /* ... */\n};\n\nexport const BookSearchStore = signalStore(\n  withState(initialState),\n  // 👇 Accessing previously defined state signals and properties.\n  withComputed(({ books, filter }) => ({\n    booksCount: computed(() => books().length),\n    // 👇 Adds computed automatically\n    sortedBooks: () => {\n      const direction = filter.order() === 'asc' ? 1 : -1;\n\n      return books().toSorted(\n        (a, b) => direction * a.title.localeCompare(b.title)\n      );\n    },\n  }))\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nThe `withProps` feature can be used to add static properties, observables, dependencies, and any other custom properties to a SignalStore.\nFor more details, see the [Custom Store Properties](/guide/signals/signal-store/custom-store-properties) guide.\n\n</ngrx-docs-alert>\n\n## Defining Store Methods\n\nMethods can be added to the store using the `withMethods` feature.\nThis feature takes a factory function as an input argument and returns a dictionary of methods.\nSimilar to `withComputed`, the `withMethods` factory is also executed within the injection context.\nThe store instance, including previously defined state signals, properties, and methods, is accessible through the factory input.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\nimport { computed } from '@angular/core';\nimport {\n  patchState,\n  signalStore,\n  withComputed,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\nimport { Book } from './book';\n\ntype BookSearchState = {\n  /* ... */\n};\n\nconst initialState: BookSearchState = {\n  /* ... */\n};\n\nexport const BookSearchStore = signalStore(\n  withState(initialState),\n  withComputed(/* ... */),\n  // 👇 Accessing a store instance with previously defined state signals,\n  // properties, and methods.\n  withMethods((store) => ({\n    updateQuery(query: string): void {\n      // 👇 Updating state using the `patchState` function.\n      patchState(store, (state) => ({\n        filter: { ...state.filter, query },\n      }));\n    },\n    updateOrder(order: 'asc' | 'desc'): void {\n      patchState(store, (state) => ({\n        filter: { ...state.filter, order },\n      }));\n    },\n  }))\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nThe state of the SignalStore is updated using the `patchState` function.\nFor more details on the `patchState` function, refer to the [Updating State](/guide/signals/signal-state#updating-state) guide.\n\n</ngrx-docs-alert>\n\n<ngrx-docs-alert type=\"inform\">\n\nBy default, SignalStore's state is protected from external modifications, ensuring a consistent and predictable data flow.\nThis is the recommended approach.\nHowever, external updates to the state can be enabled by setting the `protectedState` option to `false` when creating a SignalStore.\n\n```ts\nexport const BookSearchStore = signalStore(\n  { protectedState: false }, // 👈\n  withState(initialState)\n);\n\n@Component({\n  /* ... */\n})\nexport class BookSearch {\n  readonly store = inject(BookSearchStore);\n\n  addBook(book: Book): void {\n    // ⚠️ The state of the `BookSearchStore` is unprotected from external modifications.\n    patchState(this.store, ({ books }) => ({\n      books: [...books, book],\n    }));\n  }\n}\n```\n\n</ngrx-docs-alert>\n\nIn addition to methods for updating state, the `withMethods` feature can also be used to create methods for performing side effects.\nAsynchronous side effects can be executed using Promise-based APIs, as demonstrated below.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\nimport { computed, inject } from '@angular/core';\nimport { patchState, signalStore /* ... */ } from '@ngrx/signals';\nimport { BooksService } from './books-service';\nimport { Book } from './book';\n\ntype BookSearchState = {\n  /* ... */\n};\n\nconst initialState: BookSearchState = {\n  /* ... */\n};\n\nexport const BookSearchStore = signalStore(\n  withState(initialState),\n  withComputed(/* ... */),\n  // 👇 `BooksService` can be injected within the `withMethods` factory.\n  withMethods((store, booksService = inject(BooksService)) => ({\n    /* ... */\n    // 👇 Defining a method to load all books.\n    async loadAll(): Promise<void> {\n      patchState(store, { isLoading: true });\n\n      const books = await booksService.getAll();\n      patchState(store, { books, isLoading: false });\n    },\n  }))\n);\n```\n\n</ngrx-code-example>\n\n### Reactive Store Methods\n\nIn more complex scenarios, opting for RxJS to handle asynchronous side effects is advisable.\nTo create a reactive SignalStore method that harnesses RxJS APIs, use the `rxMethod` function from the `rxjs-interop` plugin.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\nimport { computed, inject } from '@angular/core';\nimport {\n  debounceTime,\n  distinctUntilChanged,\n  pipe,\n  switchMap,\n  tap,\n} from 'rxjs';\nimport { patchState, signalStore /* ... */ } from '@ngrx/signals';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\nimport { tapResponse } from '@ngrx/operators';\nimport { BooksService } from './books-service';\nimport { Book } from './book';\n\ntype BookSearchState = {\n  /* ... */\n};\n\nconst initialState: BookSearchState = {\n  /* ... */\n};\n\nexport const BookSearchStore = signalStore(\n  withState(initialState),\n  withComputed(/* ... */),\n  withMethods((store, booksService = inject(BooksService)) => ({\n    /* ... */\n    // 👇 Defining a method to load books by query.\n    loadByQuery: rxMethod<string>(\n      pipe(\n        debounceTime(300),\n        distinctUntilChanged(),\n        tap(() => patchState(store, { isLoading: true })),\n        switchMap((query) => {\n          return booksService.getByQuery(query).pipe(\n            tapResponse({\n              next: (books) =>\n                patchState(store, { books, isLoading: false }),\n              error: (err) => {\n                patchState(store, { isLoading: false });\n                console.error(err);\n              },\n            })\n          );\n        })\n      )\n    ),\n  }))\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nTo learn more about the `rxMethod` function, visit the [RxJS Integration](/guide/signals/rxjs-integration) page.\n\n</ngrx-docs-alert>\n\n## Putting It All Together\n\nThe final `BookSearchStore` implementation with state, computed signals, and methods from this guide is shown below.\n\n<ngrx-code-example header=\"book-search-store.ts\">\n\n```ts\nimport { computed, inject } from '@angular/core';\nimport {\n  debounceTime,\n  distinctUntilChanged,\n  pipe,\n  switchMap,\n  tap,\n} from 'rxjs';\nimport {\n  patchState,\n  signalStore,\n  withComputed,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\nimport { tapResponse } from '@ngrx/operators';\nimport { BooksService } from './books-service';\nimport { Book } from './book';\n\ntype BookSearchState = {\n  books: Book[];\n  isLoading: boolean;\n  filter: { query: string; order: 'asc' | 'desc' };\n};\n\nconst initialState: BookSearchState = {\n  books: [],\n  isLoading: false,\n  filter: { query: '', order: 'asc' },\n};\n\nexport const BookSearchStore = signalStore(\n  withState(initialState),\n  withComputed(({ books, filter }) => ({\n    booksCount: computed(() => books().length),\n    sortedBooks: computed(() => {\n      const direction = filter.order() === 'asc' ? 1 : -1;\n\n      return books().toSorted(\n        (a, b) => direction * a.title.localeCompare(b.title)\n      );\n    }),\n  })),\n  withMethods((store, booksService = inject(BooksService)) => ({\n    updateQuery(query: string): void {\n      patchState(store, (state) => ({\n        filter: { ...state.filter, query },\n      }));\n    },\n    updateOrder(order: 'asc' | 'desc'): void {\n      patchState(store, (state) => ({\n        filter: { ...state.filter, order },\n      }));\n    },\n    loadByQuery: rxMethod<string>(\n      pipe(\n        debounceTime(300),\n        distinctUntilChanged(),\n        tap(() => patchState(store, { isLoading: true })),\n        switchMap((query) => {\n          return booksService.getByQuery(query).pipe(\n            tapResponse({\n              next: (books) => patchState(store, { books }),\n              error: console.error,\n              finalize: () => patchState(store, { isLoading: false }),\n            })\n          );\n        })\n      )\n    ),\n  }))\n);\n```\n\n</ngrx-code-example>\n\nThe `BookSearchStore` instance will contain the following properties and methods:\n\n- State signals:\n  - `books: Signal<Book[]>`\n  - `isLoading: Signal<boolean>`\n  - `filter: DeepSignal<{ query: string; order: 'asc' | 'desc' }>`\n  - `filter.query: Signal<string>`\n  - `filter.order: Signal<'asc' | 'desc'>`\n- Computed signals:\n  - `booksCount: Signal<number>`\n  - `sortedBooks: Signal<Book[]>`\n- Methods:\n  - `updateQuery(query: string): void`\n  - `updateOrder(order: 'asc' | 'desc'): void`\n  - `loadByQuery: RxMethod<string>`\n\n<ngrx-docs-alert type=\"help\">\n\nThe `BookSearchStore` implementation can be enhanced further by utilizing the `entities` plugin and creating custom SignalStore features.\nFor more details, refer to the [Entity Management](guide/signals/signal-store/entity-management) and [Custom Store Features](guide/signals/signal-store/custom-store-features) guides.\n\n</ngrx-docs-alert>\n\nThe `BookSearch` component can use the `BookSearchStore` to manage the state, as demonstrated below.\n\n<ngrx-code-example header=\"book-search.ts\">\n\n```ts\nimport {\n  ChangeDetectionStrategy,\n  Component,\n  inject,\n} from '@angular/core';\nimport { BookFilter } from './book-filter';\nimport { BookList } from './book-list';\nimport { BooksStore } from './books.store';\n\n@Component({\n  imports: [BookFilter, BookList],\n  template: `\n    <h1>Books ({{ store.booksCount() }})</h1>\n\n    <ngrx-book-filter\n      [query]=\"store.filter.query()\"\n      [order]=\"store.filter.order()\"\n      (queryChange)=\"store.updateQuery($event)\"\n      (orderChange)=\"store.updateOrder($event)\"\n    />\n\n    <ngrx-book-list\n      [books]=\"store.sortedBooks()\"\n      [isLoading]=\"store.isLoading()\"\n    />\n  `,\n  providers: [BookSearchStore],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BookSearch {\n  readonly store = inject(BookSearchStore);\n\n  constructor() {\n    const query = this.store.filter.query;\n    // 👇 Re-fetch books whenever the value of query signal changes.\n    this.store.loadByQuery(query);\n  }\n}\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nIn addition to component lifecycle hooks, SignalStore also offers the ability to define them at the store level.\nLearn more about SignalStore lifecycle hooks [here](/guide/signals/signal-store/lifecycle-hooks).\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/lifecycle-hooks.md",
    "content": "# Lifecycle Hooks\n\nThe `@ngrx/signals` package provides the `withHooks` feature for incorporating lifecycle hooks into a SignalStore.\nThis feature enables performing additional logic when the store is initialized or destroyed.\n\nThe `withHooks` feature has two signatures.\nThe first signature expects an object with `onInit` and/or `onDestroy` methods.\nBoth methods receive the store instance as input arguments.\n\n<ngrx-code-example header=\"counter-store.ts\">\n\n```ts\nimport { computed } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { interval } from 'rxjs';\nimport {\n  patchState,\n  signalStore,\n  withState,\n  withHooks,\n  withMethods,\n} from '@ngrx/signals';\n\nexport const CounterStore = signalStore(\n  withState({ count: 0 }),\n  withMethods((store) => ({\n    increment(): void {\n      patchState(store, (state) => ({ count: state.count + 1 }));\n    },\n  })),\n  withHooks({\n    onInit(store) {\n      // 👇 Increment the `count` every 2 seconds.\n      interval(2_000)\n        // 👇 Automatically unsubscribe when the store is destroyed.\n        .pipe(takeUntilDestroyed())\n        .subscribe(() => store.increment());\n    },\n    onDestroy(store) {\n      console.log('count on destroy', store.count());\n    },\n  })\n);\n```\n\n</ngrx-code-example>\n\nThe `onInit` hook is executed within the injection context, enabling the injection of dependencies or the utilization of functions that must be invoked within the injection context, such as `takeUntilDestroyed`.\n\nIf there is a need to share code between lifecycle hooks or use injected dependencies within the `onDestroy` hook, the second signature can be utilized.\nSimilar to the `withMethods` and `withComputed` features, the second signature of the `withHooks` feature expects a factory function.\nThis function receives a store instance as an input argument, returns an object with `onInit` and/or `onDestroy` methods, and is executed within the injection context.\n\n<ngrx-code-example header=\"counter-store.ts\">\n\n```ts\nexport const CounterStore = signalStore(\n  /* ... */\n  withHooks((store) => {\n    const logger = inject(Logger);\n    let interval = 0;\n\n    return {\n      onInit() {\n        interval = setInterval(() => store.increment(), 2_000);\n      },\n      onDestroy() {\n        logger.info('count on destroy', store.count());\n        clearInterval(interval);\n      },\n    };\n  })\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/linked-state.md",
    "content": "# Linked State\n\nThe `withLinkedState` feature enables the creation of state slices that depend on other signals.\nThis feature accepts a factory function as an input argument, which is executed within the injection context.\nThe factory should return a dictionary containing linked state slices, defined as either computation functions or `WritableSignal` instances.\nThese linked state slices become an integral part of the SignalStore's state and are treated the same as regular state slices - `DeepSignal`s are created for each of them, and they can be updated using `patchState`.\n\n## Implicit Linking\n\nWhen a computation function is provided, the SignalStore wraps it in a `linkedSignal()`.\nAs a result, the linked state slice is updated automatically whenever any of its dependent signals change.\n\n<ngrx-code-tabs>\n\n<ngrx-code-example header=\"options-store.ts\">\n\n```ts\nimport {\n  patchState,\n  signalStore,\n  withLinkedState,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\n\nexport const OptionsStore = signalStore(\n  withState({ options: [1, 2, 3] }),\n  withLinkedState(({ options }) => ({\n    // 👇 Defining a linked state slice.\n    selectedOption: () => options()[0] ?? undefined,\n  })),\n  withMethods((store) => ({\n    setOptions(options: number[]): void {\n      patchState(store, { options });\n    },\n    setSelectedOption(selectedOption: number): void {\n      // 👇 Updating a linked state slice.\n      patchState(store, { selectedOption });\n    },\n  }))\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"option-list.ts\">\n\n```ts\n@Component({\n  // ... other metadata\n  providers: [OptionsStore],\n})\nexport class OptionList {\n  readonly store = inject(OptionsStore);\n\n  constructor() {\n    console.log(this.store.selectedOption()); // logs: 1\n\n    this.store.setSelectedOption(2);\n    console.log(this.store.selectedOption()); // logs: 2\n\n    this.store.setOptions([4, 5, 6]);\n    console.log(this.store.selectedOption()); // logs: 4\n  }\n}\n```\n\n</ngrx-code-example>\n\n</ngrx-code-tabs>\n\n## Explicit Linking\n\nThe `withLinkedState` feature also supports providing `WritableSignal` instances as linked state slices.\nThis can include signals created using `linkedSignal()` with `source` and `computation` options, as well as any other `WritableSignal` instances.\nIn both cases, the SignalStore and the original signal remain fully synchronized - updating one immediately reflects in the other.\n\n<ngrx-code-example header=\"options-store.ts\">\n\n```ts\nimport { linkedSignal } from '@angular/core';\nimport {\n  signalStore,\n  withLinkedState,\n  withState,\n} from '@ngrx/signals';\n\nexport const OptionsStore = signalStore(\n  withState({ options: [] as Option[] }),\n  withLinkedState(({ options }) => ({\n    selectedOption: linkedSignal<Option[], Option>({\n      source: options,\n      computation: (newOptions, previous) => {\n        const option = newOptions.find(\n          (o) => o.id === previous?.value.id\n        );\n        return option ?? newOptions[0];\n      },\n    }),\n  }))\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/private-store-members.md",
    "content": "# Private Store Members\n\nSignalStore allows defining private members that cannot be accessed from outside the store by using the `_` prefix.\nThis includes root-level state slices, properties, and methods.\n\n<ngrx-code-tabs>\n<ngrx-code-example header=\"counter-store.ts\">\n\n```ts\nimport { computed } from '@angular/core';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport {\n    patchState,\n    signalStore,\n    withComputed,\n    withMethods,\n    withProps,\n    withState,\n} from '@ngrx/signals';\n\nexport const CounterStore = signalStore(\n    withState({\n        count1: 0,\n        // 👇 private state slice\n        _count2: 0,\n    }),\n    withComputed(({ count1, _count2 }) => ({\n        // 👇 private computed signal\n        _doubleCount1: computed(() => count1() _ 2),\n        doubleCount2: computed(() => _count2() _ 2),\n    })),\n    withProps(({ count2, _doubleCount1 }) => ({\n        // 👇 private property\n        _count2$: toObservable(count2),\n        doubleCount1$: toObservable(_doubleCount1),\n    })),\n    withMethods((store) => ({\n        increment1(): void {\n            patchState(store, { count1: store.count1() + 1 });\n        },\n        // 👇 private method\n        _increment2(): void {\n            patchState(store, { _count2: store._count2() + 1 });\n        },\n    })),\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"counter.ts\">\n\n```ts\nimport { Component, inject, OnInit } from '@angular/core';\nimport { CounterStore } from './counter-store';\n\n@Component({\n  /* ... */\n  providers: [CounterStore],\n})\nexport class Counter implements OnInit {\n  readonly store = inject(CounterStore);\n\n  ngOnInit(): void {\n    console.log(this.store.count1()); // ✅\n    console.log(this.store._count2()); // ❌\n\n    console.log(this.store._doubleCount1()); // ❌\n    console.log(this.store.doubleCount2()); // ✅\n\n    this.store._count2$.subscribe(console.log); // ❌\n    this.store.doubleCount1$.subscribe(console.log); // ✅\n\n    this.store.increment1(); // ✅\n    this.store._increment2(); // ❌\n  }\n}\n```\n\n</ngrx-code-example>\n</ngrx-code-tabs>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/state-tracking.md",
    "content": "# State Tracking\n\nState tracking enables the implementation of custom SignalStore features such as logging, state undo/redo, and storage synchronization.\n\n## Using `getState` and `effect`\n\nThe `getState` function is used to get the current state value of the SignalStore.\nWhen used within a reactive context, state changes are automatically tracked.\n\n<ngrx-code-example header=\"counter-store.ts\">\n\n```ts\nimport { effect } from '@angular/core';\nimport {\n  getState,\n  patchState,\n  signalStore,\n  withHooks,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\n\nexport const CounterStore = signalStore(\n  withState({ count: 0 }),\n  withMethods((store) => ({\n    increment(): void {\n      patchState(store, { count: store.count() + 1 });\n    },\n  })),\n  withHooks({\n    onInit(store) {\n      effect(() => {\n        // 👇 The effect is re-executed on state change.\n        const state = getState(store);\n        console.log('counter state', state);\n      });\n\n      setInterval(() => store.increment(), 1_000);\n    },\n  })\n);\n```\n\n</ngrx-code-example>\n\nDue to the `effect` glitch-free behavior, if the state is changed multiple times in the same tick, the effect function will be executed only once with the final state value.\nWhile the asynchronous effect execution is beneficial for performance reasons, functionalities such as state undo/redo require tracking all SignalStore's state changes without coalescing state updates in the same tick.\n\n## Using `watchState`\n\nThe `watchState` function allows for synchronous tracking of SignalStore's state changes.\nIt accepts a SignalStore instance as the first argument and a watcher function as the second argument.\n\nBy default, the `watchState` function needs to be executed within an injection context.\nIt is tied to its lifecycle and is automatically cleaned up when the injector is destroyed.\n\n<ngrx-code-example header=\"counter-store.ts\">\n\n```ts\nimport { effect } from '@angular/core';\nimport {\n  getState,\n  patchState,\n  signalStore,\n  watchState,\n  withHooks,\n  withState,\n} from '@ngrx/signals';\n\nexport const CounterStore = signalStore(\n  withState({ count: 0 }),\n  withMethods((store) => ({\n    increment(): void {\n      patchState(store, { count: store.count() + 1 });\n    },\n  })),\n  withHooks({\n    onInit(store) {\n      watchState(store, (state) => {\n        console.log('[watchState] counter state', state);\n      }); // logs: { count: 0 }, { count: 1 }, { count: 2 }\n\n      effect(() => {\n        console.log('[effect] counter state', getState(store));\n      }); // logs: { count: 2 }\n\n      store.increment();\n      store.increment();\n    },\n  })\n);\n```\n\n</ngrx-code-example>\n\nIn the example above, the `watchState` function will execute the provided watcher 3 times: once with the initial counter state value and two times after each increment.\nConversely, the `effect` function will be executed only once with the final counter state value.\n\n### Manual Cleanup\n\nIf a state watcher needs to be cleaned up before the injector is destroyed, manual cleanup can be performed by calling the `destroy` method.\n\n<ngrx-code-example header=\"counter-store.ts\">\n\n```ts\nimport {\n  patchState,\n  signalStore,\n  watchState,\n  withHooks,\n  witMethods,\n  withState,\n} from '@ngrx/signals';\n\nexport const CounterStore = signalStore(\n  withState({ count: 0 }),\n  withMethods((store) => ({\n    increment(): void {\n      patchState(store, { count: store.count() + 1 });\n    },\n  })),\n  withHooks({\n    onInit(store) {\n      const { destroy } = watchState(store, console.log);\n\n      setInterval(() => store.increment(), 1_000);\n\n      // 👇 Stop watching after 5 seconds.\n      setTimeout(() => destroy(), 5_000);\n    },\n  })\n);\n```\n\n</ngrx-code-example>\n\n### Usage Outside of Injection Context\n\nThe `watchState` function can be used outside an injection context by providing an injector as the second argument.\n\n<ngrx-code-example header=\"counter.ts\">\n\n```ts\nimport { Component, inject, Injector, OnInit } from '@angular/core';\nimport { watchState } from '@ngrx/signals';\nimport { CounterStore } from './counter-store';\n\n@Component({\n  /* ... */\n  providers: [CounterStore],\n})\nexport class Counter implements OnInit {\n  readonly #injector = inject(Injector);\n  readonly store = inject(CounterStore);\n\n  ngOnInit(): void {\n    watchState(this.store, console.log, {\n      injector: this.#injector,\n    });\n\n    setInterval(() => this.store.increment(), 2_000);\n  }\n}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/signals/signal-store/testing.md",
    "content": "# Testing\n\nA SignalStore is an Angular service and is tested like any other service. This guide assumes Vitest (in browser mode); the same ideas apply to other test runners.\n\nTesting can be approached in three ways:\n\n- **Testing the store in isolation** by mocking its dependencies and exercising its API. When doing so, tests should not spy on the store's methods - if a method grows complex, that logic can be extracted into a service; the method calls the service, and the test mocks or fakes the service.\n- **Including the store in a wider test**, such as a full feature where both components and store are tested together.\n- **Providing a fake or mock of the store** when testing a component or service that uses it.\n\n## Guiding principles\n\n- **Public API only.** Asserting on internal state or calling internal methods ties tests to implementation and makes them brittle.\n- **TestBed** is used when testing the store. It supplies dependency injection and the injection context that many features like `rxMethod()`, `signalMethod()`, and `inject()` require; instantiating the store with `new` won't work for those.\n\n## Testing SignalStores with Different Scopes\n\nA SignalStore can be provided locally or globally. In both cases, `TestBed` can inject it.\n\nFor the sake of brevity, the test examples contain both the implementation and the test.\n\n### Globally provided store\n\nIf the store is created with `{ providedIn: 'root' }`, `TestBed.inject(CounterStore)` is enough to instantiate the store.\n\n<ngrx-code-example header=\"counter-store.spec.ts\">\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport { signalStore, withState } from '@ngrx/signals';\n\nconst CounterStore = signalStore(\n  { providedIn: 'root' },\n  withState({ count: 0 })\n);\n\n// Test\ndescribe('CounterStore (global)', () => {\n  it('is defined with an initial count', () => {\n    const store = TestBed.inject(CounterStore);\n\n    expect(store).toBeDefined();\n    expect(store.count()).toBe(0);\n  });\n});\n```\n\n</ngrx-code-example>\n\n### Locally provided store\n\nIf the store is not provided in root, the testing module provides it.\n\n<ngrx-code-example header=\"counter-store.spec.ts\">\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport { signalStore, withState } from '@ngrx/signals';\n\nconst CounterStore = signalStore(withState({ count: 0 }));\n\n// Test\ndescribe('CounterStore (local)', () => {\n  it('is defined with an initial count', () => {\n    TestBed.configureTestingModule({\n      // 👇 provide the store in the testing module\n      providers: [CounterStore],\n    });\n\n    const store = TestBed.inject(CounterStore);\n\n    expect(store).toBeDefined();\n    expect(store.count()).toBe(0);\n  });\n});\n```\n\n</ngrx-code-example>\n\n## Testing SignalStore Members\n\nA SignalStore is tested like any other Angular service by asserting on initial state, on derived values, and on the effect of calling its methods. The following example uses the same `CounterStore` as in the previous section, extended with a computed value and a method. `CounterStore` doesn't have any dependencies or async operations.\n\n<ngrx-code-example header=\"counter-store.spec.ts\">\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport {\n  patchState,\n  signalStore,\n  withComputed,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\n\nconst CounterStore = signalStore(\n  { providedIn: 'root' },\n  withState({ count: 0 }),\n  withComputed(({ count }) => ({\n    doubleCount: () => count() * 2,\n  })),\n  withMethods((store) => ({\n    increment() {\n      patchState(store, ({ count }) => ({ count: count + 1 }));\n    },\n  }))\n);\n\n// Test\ndescribe('CounterStore', () => {\n  it('has an initial state and derived doubleCount', () => {\n    const store = TestBed.inject(CounterStore);\n\n    expect(store.count()).toBe(0);\n    expect(store.doubleCount()).toBe(0);\n  });\n\n  it('updates doubleCount when count changes on increment', () => {\n    const store = TestBed.inject(CounterStore);\n\n    store.increment();\n    expect(store.count()).toBe(1);\n    expect(store.doubleCount()).toBe(2);\n\n    store.increment();\n    expect(store.count()).toBe(2);\n    expect(store.doubleCount()).toBe(4);\n  });\n});\n```\n\n</ngrx-code-example>\n\n## The `unprotected` Helper\n\n`patchState` cannot update a SignalStore instance whose state is protected (the default). Setting state directly is sometimes needed when the necessary public setters are not available.\n\nWrapping the store instance with `unprotected` returns a writable view that can be updated with `patchState`.\n\nTo assert that the computed `doubleCount` updates when `count` changes, the state is patched via `unprotected` and the computed is read from the store.\n\n<ngrx-code-example header=\"counter-store.spec.ts\">\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport {\n  patchState,\n  signalStore,\n  withComputed,\n  withState,\n} from '@ngrx/signals';\nimport { unprotected } from '@ngrx/signals/testing';\n\nconst CounterStore = signalStore(\n  { providedIn: 'root' },\n  withState({ count: 0 }),\n  withComputed(({ count }) => ({\n    doubleCount: () => count() * 2,\n  }))\n);\n\n// Test\ndescribe('CounterStore', () => {\n  it('recomputes doubleCount when count is patched via unprotected', () => {\n    const store = TestBed.inject(CounterStore);\n\n    //         👇 makes the store writable\n    patchState(unprotected(store), { count: 5 });\n\n    expect(store.count()).toBe(5);\n    expect(store.doubleCount()).toBe(10);\n  });\n});\n```\n\n</ngrx-code-example>\n\n## Mocking SignalStore Dependencies\n\nWhen a store injects a service (for example inside `withMethods`), a test could mock that service.\n\nThe most straightforward approach is to register the dependency with `useValue` and provide an object that implements the methods the store uses. In the following example, the `CounterStore` depends on a `StepService` to determine the increment step; the test provides a mock `StepService` returning a fixed step so that assertions are predictable.\n\n<ngrx-code-example header=\"counter-store.spec.ts\">\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport {\n  patchState,\n  signalStore,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\nimport { inject, Injectable } from '@angular/core';\n\n@Injectable({ providedIn: 'root' })\nclass StepService {\n  getStep() {\n    return 1;\n  }\n}\n\nconst CounterStore = signalStore(\n  { providedIn: 'root' },\n  withState({ count: 0 }),\n  withMethods((store, stepService = inject(StepService)) => ({\n    increment() {\n      patchState(store, ({ count }) => ({\n        count: count + stepService.getStep(),\n      }));\n    },\n  }))\n);\n\n// Test\ndescribe('CounterStore with StepService', () => {\n  it('increments by the step returned by the injected service', () => {\n    const mockStepService = { getStep: () => 3 };\n\n    TestBed.configureTestingModule({\n      providers: [\n        //                      👇 provide the mock service\n        { provide: StepService, useValue: mockStepService },\n      ],\n    });\n\n    const store = TestBed.inject(CounterStore);\n\n    store.increment();\n\n    expect(store.count()).toBe(3);\n  });\n});\n```\n\n</ngrx-code-example>\n\n## Testing `signalMethod` Instance\n\nWhen testing a store that exposes a method created with [`signalMethod`](/guide/signals/signal-method), the `TestBed` supplies the injection context. When the method is called with a Signal, the test must wait for the effect (e.g. `TestBed.tick()` or `expect.poll`) before asserting. Additionally, the call must be made within an injection context.\n\n<ngrx-code-example header=\"counter-store.spec.ts\">\n\n```ts\nimport { signal } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  patchState,\n  signalStore,\n  signalMethod,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\n\nconst CounterStore = signalStore(\n  { providedIn: 'root' },\n  withState({ count: 0 }),\n  withMethods((store) => ({\n    increment: signalMethod<number>((step) => {\n      patchState(store, ({ count }) => ({ count: count + step }));\n    }),\n  }))\n);\n\n// Test\ndescribe('CounterStore with signalMethod', () => {\n  it('increments by a static step synchronously', () => {\n    const store = TestBed.inject(CounterStore);\n\n    store.increment(1);\n    expect(store.count()).toBe(1);\n\n    store.increment(2);\n    expect(store.count()).toBe(3);\n  });\n\n  it('increments by a signal step after tick', async () => {\n    const store = TestBed.inject(CounterStore);\n    const step = signal(2);\n\n    TestBed.runInInjectionContext(() => store.increment(step));\n    expect(store.count()).toBe(0);\n\n    await expect.poll(() => store.count()).toBe(2);\n\n    step.set(3);\n    await expect.poll(() => store.count()).toBe(5);\n\n    // Alternatively, TestBed.tick() would flush the effect\n    step.set(1);\n    TestBed.tick();\n    expect(store.count()).toBe(6);\n  });\n});\n```\n\n</ngrx-code-example>\n\n## Testing `rxMethod` Instance\n\nTesting a store method created with [`rxMethod`](/guide/signals/rxjs-integration) follows the same ideas as testing [`signalMethod`](/guide/signals/signal-store/testing#testing-signalmethod-instance). The only difference is that `rxMethod` also supports an `Observable` as an input argument.\n\n<ngrx-code-example header=\"counter-store.spec.ts\">\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport { of, scheduled, tap, asyncScheduler } from 'rxjs';\nimport {\n  patchState,\n  signalStore,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\nimport { rxMethod } from '@ngrx/signals/rxjs-interop';\n\nconst CounterStore = signalStore(\n  { providedIn: 'root' },\n  withState({ count: 0 }),\n  withMethods((store) => ({\n    increment: rxMethod<number>(\n      tap((n) =>\n        patchState(store, ({ count }) => ({ count: count + n }))\n      )\n    ),\n  }))\n);\n\n// Test\ndescribe('CounterStore with rxMethod', () => {\n  it('adds emitted values to count when called with a synchronous Observable', () => {\n    const store = TestBed.inject(CounterStore);\n\n    store.increment(of(1, 2, 3));\n    expect(store.count()).toBe(6);\n  });\n\n  it('adds emitted values to count when called with an asynchronous Observable', async () => {\n    const store = TestBed.inject(CounterStore);\n\n    store.increment(scheduled([1, 2, 3], asyncScheduler));\n    expect(store.count()).toBe(0);\n\n    await expect.poll(() => store.count()).toBe(6);\n  });\n});\n```\n\n</ngrx-code-example>\n\n## Mocking the SignalStore\n\nWhen testing a component that uses a SignalStore, the store can be replaced with a plain object that exposes the same API: signals for state and computed values, and functions for methods. The component injects the store and the test provides a mock via dependency injection.\n\nThe following example uses a minimal counter component that displays the count and has a button to increment it. Two testing styles are shown: one that verifies state change by implementing the mock's `increment`, and one that verifies the interaction by providing `increment` as a `vi.fn()` and asserting it was called.\n\n<ngrx-code-example header=\"counter.component.spec.ts\">\n\n```ts\nimport { Component, inject, signal } from '@angular/core';\nimport { TestBed } from '@angular/core/testing';\nimport {\n  patchState,\n  signalStore,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\nimport { page } from 'vitest/browser';\n\nconst CounterStore = signalStore(\n  { providedIn: 'root' },\n  withState({ count: 0 }),\n  withMethods((store) => ({\n    increment() {\n      patchState(store, ({ count }) => ({ count: count + 1 }));\n    },\n  }))\n);\n\n@Component({\n  selector: 'app-counter',\n  template: `\n    <p aria-label=\"count\">{{ store.count() }}</p>\n    <button type=\"button\" (click)=\"store.increment()\">\n      Increment\n    </button>\n  `,\n})\nclass CounterComponent {\n  protected readonly store = inject(CounterStore);\n}\n\n// Test\ndescribe('CounterComponent', () => {\n  it('updates displayed count when the mock implements increment', async () => {\n    const count = signal(0);\n    const mockStore = {\n      count,\n      increment() {\n        count.set(count() + 1);\n      },\n    };\n\n    TestBed.configureTestingModule({\n      providers: [{ provide: CounterStore, useValue: mockStore }],\n    }).createComponent(CounterComponent);\n\n    await expect\n      .element(page.getByLabelText('count'))\n      .toHaveTextContent('0');\n    await page.getByRole('button', { name: 'Increment' }).click();\n    await expect\n      .element(page.getByLabelText('count'))\n      .toHaveTextContent('1');\n  });\n\n  it('calls increment when the button is clicked and mock uses vi.fn()', async () => {\n    const count = signal(0);\n    const increment = vi.fn(() => count.set(count() + 1));\n    const mockStore = { count, increment };\n\n    TestBed.configureTestingModule({\n      providers: [{ provide: CounterStore, useValue: mockStore }],\n    }).createComponent(CounterComponent);\n\n    await page.getByRole('button', { name: 'Increment' }).click();\n    expect(increment).toHaveBeenCalledTimes(1);\n  });\n});\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\n**Note:** Asserting on state (as in the first test) is preferred over asserting that a method was called (as in the second test). See [Guiding principles](/guide/signals/signal-store/testing#guiding-principles).\n\n</ngrx-docs-alert>\n\n## Testing Custom SignalStore Features\n\nCustom features built with [`signalStoreFeature`](/guide/signals/signal-store/custom-store-features) can be tested by creating a minimal store that uses the feature and asserting on that store.\n\n<ngrx-code-example header=\"with-counter.spec.ts\">\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport {\n  patchState,\n  signalStore,\n  signalStoreFeature,\n  withComputed,\n  withMethods,\n  withState,\n} from '@ngrx/signals';\n\nfunction withCounter() {\n  return signalStoreFeature(\n    withState({ count: 0 }),\n    withComputed(({ count }) => ({\n      doubleCount: () => count() * 2,\n    })),\n    withMethods((store) => ({\n      increment() {\n        patchState(store, ({ count }) => ({ count: count + 1 }));\n      },\n    }))\n  );\n}\n\n// Test\ndescribe('withCounter', () => {\n  it('has initial count and doubleCount, and increment updates both', () => {\n    // 👇 \"testing store\" wraps the feature\n    const CounterStore = signalStore(\n      { providedIn: 'root' },\n      withCounter()\n    );\n\n    const store = TestBed.inject(CounterStore);\n\n    expect(store.count()).toBe(0);\n    expect(store.doubleCount()).toBe(0);\n\n    store.increment();\n    expect(store.count()).toBe(1);\n    expect(store.doubleCount()).toBe(2);\n  });\n});\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/action-groups.md",
    "content": "# Action Groups\n\n<div class=\"video-container\">\n  <div class=\"video-responsive-wrapper\">\n    <iframe\n      src=\"https://www.youtube.com/embed/rk83ZMqEDV4\"\n      allow=\"accelerometer; encrypted-media; gyroscope; picture-in-picture\"\n      allowfullscreen\n      frameborder=\"0\"\n    ></iframe>\n  </div>\n</div>\n\nThe `createActionGroup` function creates a group of action creators with the same source.\nIt accepts an action group source and an event dictionary as input arguments, where an event is a key-value pair of an event name and event props.\n\n<ngrx-code-example header=\"products-page.actions.ts\">\n\n```ts\nimport { createActionGroup, emptyProps, props } from '@ngrx/store';\n\nexport const ProductsPageActions = createActionGroup({\n  source: 'Products Page',\n  events: {\n    // defining an event without payload using the `emptyProps` function\n    Opened: emptyProps(),\n\n    // defining an event with payload using the `props` function\n    'Pagination Changed': props<{ page: number; offset: number }>(),\n\n    // defining an event with payload using the props factory\n    'Query Changed': (query: string) => ({ query }),\n  },\n});\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nThe `emptyProps` function is used to define an action creator without payload within an action group.\n\n</ngrx-docs-alert>\n\nIf we create a new action creator using the `createAction` function by copying the previous one but accidentally forget to change its type, the compilation will pass.\nFortunately, this is not the case with the `createActionGroup` function because we will get a compilation error if two actions from the same group have the same type.\n\nThe `createActionGroup` function returns a dictionary of action creators where the name of each action creator is created by camel-casing the event name, and the action type is created using the \"[Source] Event Name\" pattern.\nAlso, there is no longer a need for barrel files or named imports because the action group can be imported directly into another file.\n\n<ngrx-code-example header=\"products.component.ts\">\n\n```ts\nimport { Component, inject, OnInit } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\nimport { ProductsPageActions } from './products-page.actions';\n\n@Component({\n  /* ... */\n})\nexport class ProductsComponent implements OnInit {\n  private readonly store = inject(Store);\n\n  ngOnInit(): void {\n    // action type: [Products Page] Opened\n    this.store.dispatch(ProductsPageActions.opened());\n  }\n\n  onPaginationChange(page: number, offset: number): void {\n    // action type: [Products Page] Pagination Changed\n    this.store.dispatch(\n      ProductsPageActions.paginationChanged({ page, offset })\n    );\n  }\n\n  onQueryChange(query: string): void {\n    // action type: [Products Page] Query Changed\n    this.store.dispatch(ProductsPageActions.queryChanged(query));\n  }\n}\n```\n\n</ngrx-code-example>\n\n## Alternative way of defining event names\n\nIn the previous example, event names are defined in the title case format.\nIn that case, it can be challenging to search for unused action creators because their names are automatically generated by camel-casing the event names.\n\nThe `createActionGroup` function provides the ability to define event names in the camel case format as well, so action creators will have the same names as events.\nThis makes it easier to search for their usage within the codebase.\n\n<ngrx-code-example header=\"products-api.actions.ts\">\n\n```ts\nimport { createActionGroup, props } from '@ngrx/store';\n\nimport { Product } from './product.model';\n\nexport const ProductsApiActions = createActionGroup({\n  source: 'Products API',\n  events: {\n    productsLoadedSuccess: props<{ products: Product[] }>(),\n    productsLoadedFailure: props<{ errorMsg: string }>(),\n  },\n});\n\n// generated action creators:\nconst {\n  productsLoadedSuccess, // type: \"[Products API] productsLoadedSuccess\"\n  productsLoadedFailure, // type: \"[Products API] productsLoadedFailure\"\n} = ProductsApiActions;\n```\n\n</ngrx-code-example>\n\n## Limitations\n\nAn action group uses the event names to create properties within the group that represent the action creators.\nThe action creator names are generated and are the camelCased version of the event names.\nFor example, for the event name `Query Changed`, the action creator name will be `queryChanged`.\nTherefore, it is not possible to define action creators whose names differ from their event names using the `createActionGroup` function.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/actions.md",
    "content": "# Actions\n\nActions are one of the main building blocks in NgRx. Actions express _unique events_ that happen throughout your application. From user interaction with the page, external interaction through network requests, and direct interaction with device APIs, these and more events are described with actions.\n\n## Introduction\n\nActions are used in many areas of NgRx. Actions are the inputs and outputs of many systems in NgRx. Actions help you to understand how events are handled in your application. This guide provides general rules and examples for writing actions in your application.\n\n## The Action interface\n\nAn `Action` in NgRx is made up of a simple interface:\n\n<ngrx-code-example header=\"Action Interface\">\n\n```ts\ninterface Action {\n  type: string;\n}\n```\n\n</ngrx-code-example>\n\nThe interface has a single property, the `type`, represented as a string. The `type` property is for describing the action that will be dispatched in your application. The value of the type comes in the form of `[Source] Event` and is used to provide a context of what category of action it is, and where an action was dispatched from. You add properties to an action to provide additional context or metadata for an action.\n\nListed below are examples of actions written as plain old JavaScript objects (POJOs):\n\n```json\n{\n  \"type\": \"[Auth API] Login Success\"\n}\n```\n\nThis action describes an event triggered by a successful authentication after interacting with a backend API.\n\n```json\n{\n  type: '[Login Page] Login',\n  username: string;\n  password: string;\n}\n```\n\nThis action describes an event triggered by a user clicking a login button from the login page to attempt to authenticate a user. The username and password are defined as additional metadata provided from the login page.\n\n## Writing actions\n\nThere are a few rules to writing good actions within your application.\n\n- Upfront - write actions before developing features to understand and gain a shared knowledge of the feature being implemented.\n- Divide - categorize actions based on the event source.\n- Many - actions are inexpensive to write, so the more actions you write, the better you express flows in your application.\n- Event-Driven - capture _events_ **not** _commands_ as you are separating the description of an event and the handling of that event.\n- Descriptive - provide context that are targeted to a unique event with more detailed information you can use to aid in debugging with the developer tools.\n\nFollowing these guidelines helps you follow how these actions flow throughout your application.\n\nLet's look at an example action of initiating a login request.\n\n<ngrx-code-example header=\"login-page.actions.ts\">\n\n```ts\nimport { createAction, props } from '@ngrx/store';\n\nexport const login = createAction(\n  '[Login Page] Login',\n  props<{ username: string; password: string }>()\n);\n```\n\n</ngrx-code-example>\n\nThe `createAction` function returns a function, that when called returns an object in the shape of the `Action` interface. The `props` method is used to define any additional metadata needed for the handling of the action. Action creators provide a consistent, type-safe way to construct an action that is being dispatched.\n\nUse the action creator to return the `Action` when dispatching.\n\n<ngrx-code-example header=\"login-page.component.ts\">\n\n```ts\nonSubmit(username: string, password: string) {\n    store.dispatch(login({ username: username, password: password }));\n  }\n```\n\n</ngrx-code-example>\n\nThe `login` action creator receives an object of `username` and `password` and returns a plain JavaScript object with a `type` property of `[Login Page] Login`, with `username` and `password` as additional properties.\n\nThe returned action has very specific context about where the action came from and what event happened.\n\n- The category of the action is captured within the square brackets `[]`.\n- The category is used to group actions for a particular area, whether it be a component page, backend API, or browser API.\n- The `Login` text after the category is a description about what event occurred from this action. In this case, the user clicked a login button from the login page to attempt to authenticate with a username and password.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** You can also write actions using class-based action creators, which was the previously defined way before action creators were introduced in NgRx. If you are looking for examples of class-based action creators, visit the documentation for [versions 7.x and prior](https://v7.ngrx.io/guide/store/actions).\n\n</ngrx-docs-alert>\n\n## Dispatching actions on signal changes\n\nYou can also dispatch functions that return actions, with property values derived from signals:\n\n<ngrx-code-example header=\"book.component.ts\">\n\n```ts\nclass BookComponent {\n  bookId = input.required<number>();\n\n  constructor(store: Store) {\n    store.dispatch(() => loadBook({ id: this.bookId() })));\n  }\n}\n```\n\n</ngrx-code-example>\n\n`dispatch` executes initially and every time the `bookId` changes. If `dispatch` is called within an injection context, the signal is tracked until the context is destroyed. In the example above, that would be when `BookComponent` is destroyed.\n\nWhen `dispatch` is called outside a component's injection context, the signal is tracked globally throughout the application's lifecycle. To ensure proper cleanup in such a case, provide the component's injector to the `dispatch` method:\n\n<ngrx-code-example header=\"book.component.ts\">\n\n```ts\nclass BookComponent {\n  bookId = input.required<number>();\n  injector = inject(Injector);\n  store = inject(Store);\n\n  ngOnInit() {\n    // runs outside the injection context\n    this.store.dispatch(() => loadBook({ id: this.bookId() }), {\n      injector: this.injector,\n    });\n  }\n}\n```\n\n</ngrx-code-example>\n\nWhen passing a function to the `dispatch` method, it returns an `EffectRef`. For manual cleanup, call the `destroy` method on the `EffectRef`:\n\n<ngrx-code-example header=\"book.component.ts\">\n\n```ts\nclass BookComponent {\n  bookId = input.required<number>();\n  loadBookEffectRef: EffectRef | undefined;\n  store = inject(Store);\n\n  ngOnInit() {\n    // uses the injection context of Store, i.e. root injector\n    this.loadBookEffectRef = this.store.dispatch(() =>\n      loadBook({ id: this.bookId() })\n    );\n  }\n\n  ngOnDestroy() {\n    if (this.loadBookEffectRef) {\n      // destroys the effect\n      this.loadBookEffectRef.destroy();\n    }\n  }\n}\n```\n\n</ngrx-code-example>\n\n## Next Steps\n\nAction's only responsibilities are to express unique events and intents. Learn how they are handled in the guides below.\n\n- [Reducers](guide/store/reducers)\n- [Effects](guide/effects)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/architecture.md",
    "content": "# Architecture\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/configuration/runtime-checks.md",
    "content": "# Runtime checks\n\nRuntime checks are here to guide developers to follow the NgRx and Redux core concepts and best practices. They are here to shorten the feedback loop of easy-to-make mistakes when you're starting to use NgRx, or even a well-seasoned developer might make. During development, when a rule is violated, an error is thrown notifying you what and where something went wrong.\n\n`@ngrx/store` ships with six (6) built-in runtime checks:\n\n- Default On:\n  - [`strictStateImmutability`](#strictstateimmutability): verifies that the state isn't mutated.\n  - [`strictActionImmutability`](#strictactionimmutability): verifies that actions aren't mutated\n- Default Off:\n  - [`strictStateSerializability`](#strictstateserializability): verifies if the state is serializable\n  - [`strictActionSerializability`](#strictactionserializability): verifies if the actions are serializable\n  - [`strictActionWithinNgZone`](#strictactionwithinngzone): verifies if actions are dispatched within NgZone\n  - [`strictActionTypeUniqueness`](#strictactiontypeuniqueness): verifies if registered action types are unique\n\nAll checks will automatically be disabled in production builds.\n\n## Configuring runtime checks\n\nIt's possible to override the default configuration of runtime checks. To do so, use the `runtimeChecks` property on the root store's config object. For each runtime check you can toggle the check with a `boolean`, `true` to enable the check, `false` to disable the check.\n\n<ngrx-code-example>\n\n```ts\n@NgModule({\n  imports: [\n    StoreModule.forRoot(reducers, {\n      runtimeChecks: {\n        strictStateImmutability: true,\n        strictActionImmutability: true,\n        strictStateSerializability: true,\n        strictActionSerializability: true,\n        strictActionWithinNgZone: true,\n        strictActionTypeUniqueness: true,\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\nThe serializability runtime checks cannot be enabled if you use `@ngrx/router-store` with the `FullRouterStateSerializer`. The [full serializer](guide/router-store/configuration) has an unserializable router state and actions that are not serializable. To use the serializability runtime checks either use the `MinimalRouterStateSerializer` or implement a custom router state serializer.\n\n</ngrx-docs-alert>\n\n### strictStateImmutability\n\nThe number one rule of NgRx, immutability. This `strictStateImmutability` check verifies if a developer tries to modify the state object. This check is important to be able to work with the state in a predictable way, it should always be possible to recreate the state.\n\nExample violation of the rule:\n\n```ts\nexport const reducer = createReducer(\n  initialState,\n  on(addTodo, (state, { todo }) => {\n    // Violation 1: we assign a new value to `todoInput` directly\n    ((state.todoInput = ''),\n      // Violation 2: `push` modifies the array\n      state.todos.push(todo));\n  })\n);\n```\n\nTo fix the above violation, a new reference to the state has to be created:\n\n```ts\nexport const reducer = createReducer(\n  initialState,\n  on(addTodo, (state, { todo }) => ({\n    ...state,\n    todoInput: '',\n    todos: [...state.todos, todo],\n  }))\n);\n```\n\n### strictActionImmutability\n\nUses the same check as `strictStateImmutability`, but for actions. An action should not be modified.\n\nExample violation of the rule:\n\n```ts\nexport const reducer = createReducer(\n  initialState,\n  on(addTodo, (state, { todo }) => {\n    // Violation, it's not allowed to modify an action\n    todo.id = generateUniqueId();\n    return {\n      ...state,\n      todos: [...state.todos, todo],\n    };\n  })\n);\n```\n\nTo fix the above violation, the todo's id should be set in the action creator or should be set in an immutable way. That way we can simply append the todo to the current `todos`:\n\n```ts\nexport const addTodo = createAction(\n  '[Todo List] Add Todo',\n  (description: string) => ({ id: generateUniqueId(), description })\n);\nexport const reducer = createReducer(\n  initialState,\n  on(addTodo, (state, { todo }) => ({\n    ...state,\n    todos: [...state.todos, todo],\n  }))\n);\n```\n\n### strictStateSerializability\n\nThis check verifies if the state is serializable. A serializable state is important to be able to persist the current state to be able to rehydrate the state in the future.\n\nExample violation of the rule:\n\n```ts\nexport const reducer = createReducer(\n  initialState,\n  on(completeTodo, (state, { id }) => ({\n    ...state,\n    todos: {\n      ...state.todos,\n      [id]: {\n        ...state.todos[id],\n        // Violation, Date is not serializable\n        completedOn: new Date(),\n      },\n    },\n  }))\n);\n```\n\nAs a fix of the above violation the `Date` object must be made serializable:\n\n```ts\nexport const reducer = createReducer(\n  initialState,\n  on(completeTodo, (state, { id }) => ({\n    ...state,\n    todos: {\n      ...state.todos,\n      [id]: {\n        ...state.todos[id],\n        completedOn: new Date().toJSON(),\n      },\n    },\n  }))\n);\n```\n\n### strictActionSerializability\n\nThe `strictActionSerializability` check resembles `strictStateSerializability` but as the name says, it verifies if the action is serializable. An action must be serializable to be replayed, this can be helpful during development while using the Redux DevTools and in production to be able to debug errors.\n\nExample violation of the rule:\n\n```ts\nconst createTodo = createAction(\n  '[Todo List] Add new todo',\n  (todo) => ({\n    todo,\n    // Violation, a function is not serializable\n    logTodo: () => {\n      console.log(todo);\n    },\n  })\n);\n```\n\nThe fix for this violation is to not add functions on actions, as a replacement a function can be created:\n\n```ts\nconst createTodo = createAction(\n  '[Todo List] Add new todo',\n  props<{ todo: Todo }>()\n);\n\nfunction logTodo(todo: Todo) {\n  console.log(todo);\n}\n```\n\n<ngrx-docs-alert type=\"inform\">\n\nPlease note, you may not need to set `strictActionSerializability` to `true` unless you are storing/replaying actions using external resources, for example `localStorage`.\n\n</ngrx-docs-alert>\n\n### strictActionWithinNgZone\n\nThe `strictActionWithinNgZone` check verifies that Actions are dispatched by asynchronous tasks running within `NgZone`. Actions dispatched by tasks, running outside of `NgZone`, will not trigger ChangeDetection upon completion and may result in a stale view.\n\nExample violation of the rule:\n\n```ts\n// Callback running outside of NgZone\nfunction callbackOutsideNgZone() {\n  this.store.dispatch(clearTodos());\n}\n```\n\nTo fix ensure actions are running within `NgZone`. Identify the event trigger and then verify if the code can be updated to use a `NgZone` aware feature. If this is not possible use the `NgZone.run` method to explicitly run the asynchronous task within NgZone.\n\n```ts\nimport { NgZone } from '@angular/core';\n\nconstructor(private ngZone: NgZone){}\n\n// Callback running outside of NgZone brought back in NgZone.\nfunction callbackOutsideNgZone(){\n  this.ngZone.run(() => {\n    this.store.dispatch(clearTodos());\n  }\n}\n```\n\n### strictActionTypeUniqueness\n\nThe `strictActionTypeUniqueness` guards you against registering the same action type more than once.\n\nExample violation of the rule:\n\n```ts\nexport const customerPageLoaded = createAction(\n  '[Customers Page] Loaded'\n);\nexport const customerPageRefreshed = createAction(\n  '[Customers Page] Loaded'\n);\n```\n\nThe fix of the violation is to create unique action types:\n\n<ngrx-code-example>\n\n```ts\nexport const customerPageLoaded = createAction(\n  '[Customers Page] Loaded'\n);\nexport const customerPageRefreshed = createAction(\n  '[Customers Page] Refreshed'\n);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/feature-creators.md",
    "content": "# Feature Creators\n\n<div class=\"video-container\">\n  <div class=\"video-responsive-wrapper\">\n    <iframe\n      src=\"https://www.youtube.com/embed/bHw8SV4SNUU\"\n      allow=\"accelerometer; encrypted-media; gyroscope; picture-in-picture\"\n      allowfullscreen\n      frameborder=\"0\"\n    ></iframe>\n  </div>\n</div>\n\n## What is an NgRx feature?\n\nThere are three main building blocks of global state management with `@ngrx/store`: actions, reducers, and selectors.\nFor a particular feature state, we create a reducer for handling state transitions based on the dispatched actions\nand selectors to obtain slices of the feature state. Also, we need to define a feature name needed to register\nthe feature reducer in the NgRx store. Therefore, we can consider the NgRx feature as a grouping of the feature name,\nfeature reducer, and selectors for the particular feature state.\n\n## Using feature creator\n\nThe `createFeature` function reduces repetitive code in selector files by generating a feature selector and child selectors\nfor each feature state property. It accepts an object containing a feature name and a feature reducer as the input argument:\n\n<ngrx-code-example header=\"books.reducer.ts\">\n\n```ts\nimport { createFeature, createReducer, on } from '@ngrx/store';\nimport { Book } from './book.model';\n\nimport * as BookListPageActions from './book-list-page.actions';\nimport * as BooksApiActions from './books-api.actions';\n\ninterface State {\n  books: Book[];\n  loading: boolean;\n}\n\nconst initialState: State = {\n  books: [],\n  loading: false,\n};\n\nexport const booksFeature = createFeature({\n  name: 'books',\n  reducer: createReducer(\n    initialState,\n    on(BookListPageActions.enter, (state) => ({\n      ...state,\n      loading: true,\n    })),\n    on(BooksApiActions.loadBooksSuccess, (state, { books }) => ({\n      ...state,\n      books,\n      loading: false,\n    }))\n  ),\n});\n\nexport const {\n  name, // feature name\n  reducer, // feature reducer\n  selectBooksState, // feature selector\n  selectBooks, // selector for `books` property\n  selectLoading, // selector for `loading` property\n} = booksFeature;\n```\n\n</ngrx-code-example>\n\nAn object created with the `createFeature` function contains a feature name, a feature reducer, a feature selector,\nand a selector for each feature state property. All generated selectors have the \"select\" prefix, and the feature selector has\nthe \"State\" suffix. In this example, the name of the feature selector is `selectBooksState`, where \"books\" is the feature name.\nThe names of the child selectors are `selectBooks` and `selectLoading`, based on the property names of the books feature state.\n\nThe generated selectors can be used independently or to create other selectors:\n\n<ngrx-code-example header=\"books.selectors.ts\">\n\n```ts\nimport { createSelector } from '@ngrx/store';\nimport { booksFeature } from './books.reducer';\n\nexport const selectBookListPageViewModel = createSelector(\n  booksFeature.selectBooks,\n  booksFeature.selectLoading,\n  (books, loading) => ({ books, loading })\n);\n```\n\n</ngrx-code-example>\n\n## Providing Extra Selectors\n\n`createFeature` also can be used to provide extra selectors for the feature state with the `extraSelectors` option:\n\n<ngrx-code-example header=\"books.feature.ts\">\n\n```ts\nimport { createFeature, createReducer, on } from '@ngrx/store';\nimport { Book } from './book.model';\n\nimport * as BookListPageActions from './book-list-page.actions';\n\ninterface State {\n  books: Book[];\n  query: string;\n}\n\nconst initialState: State = {\n  books: [],\n  query: '',\n};\n\nexport const booksFeature = createFeature({\n  name: 'books',\n  reducer: createReducer(\n    initialState,\n    on(BookListPageActions.search, (state, action) => ({\n      ...state,\n      query: action.query,\n    }))\n  ),\n  extraSelectors: ({ selectQuery, selectBooks }) => ({\n    selectFilteredBooks: createSelector(\n      selectQuery,\n      selectBooks,\n      (query, books) =>\n        books.filter((book) => book.title.includes(query))\n    ),\n  }),\n});\n```\n\n</ngrx-code-example>\n\nThe `extraSelectors` option accepts a function that takes the generated selectors as input arguments and returns an object of all the extra selectors. We can use it to define as many extra selectors as we need.\n\n### Reusing Extra Selectors\n\nReusing extra selectors can be done by defining `extraSelectors` factory in the following way:\n\n<ngrx-code-example header=\"books.feature.ts\">\n\n```ts\nimport { createFeature, createReducer, on } from '@ngrx/store';\nimport { Book } from './book.model';\n\nimport * as BookListPageActions from './book-list-page.actions';\n\ninterface State {\n  books: Book[];\n  query: string;\n}\n\nconst initialState: State = {\n  books: [],\n  query: '',\n};\n\nexport const booksFeature = createFeature({\n  name: 'books',\n  reducer: createReducer(\n    initialState,\n    on(BookListPageActions.search, (state, action) => ({\n      ...state,\n      query: action.query,\n    }))\n  ),\n  extraSelectors: ({ selectQuery, selectBooks }) => {\n    const selectFilteredBooks = createSelector(\n      selectQuery,\n      selectBooks,\n      (query, books) =>\n        books.filter((book) => book.title.includes(query))\n    );\n    const selectFilteredBooksWithRating = createSelector(\n      selectFilteredBooks,\n      (books) => books.filter((book) => book.ratingsCount >= 1)\n    );\n\n    return { selectFilteredBooks, selectFilteredBooksWithRating };\n  },\n});\n```\n\n</ngrx-code-example>\n\n## Feature registration\n\nRegistering the feature reducer in the store can be done by passing the entire feature object to the `StoreModule.forFeature` method:\n\n<ngrx-code-example header=\"books.module.ts\">\n\n```ts\nimport { NgModule } from '@angular/core';\nimport { StoreModule } from '@ngrx/store';\n\nimport { booksFeature } from './books.reducer';\n\n@NgModule({\n  imports: [StoreModule.forFeature(booksFeature)],\n})\nexport class BooksModule {}\n```\n\n</ngrx-code-example>\n\n### Using the Standalone API\n\nRegistering the feature can be done using the standalone APIs if you are bootstrapping an Angular application using standalone features.\n\n<ngrx-code-example header=\"main.ts\">\n\n```ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideStore, provideState } from '@ngrx/store';\n\nimport { AppComponent } from './app.component';\nimport { booksFeature } from './books.reducer';\n\nbootstrapApplication(AppComponent, {\n  providers: [provideStore(), provideState(booksFeature)],\n});\n```\n\n</ngrx-code-example>\n\nFeature states can also be registered in the `providers` array of the route config.\n\n<ngrx-code-example header=\"books-routes.ts\">\n\n```ts\nimport { Route } from '@angular/router';\nimport { provideState } from '@ngrx/store';\n\nimport { booksFeature } from './books.reducer';\n\nexport const routes: Route[] = [\n  {\n    path: 'books',\n    providers: [provideState(booksFeature)],\n  },\n];\n```\n\n</ngrx-code-example>\n\n## Restrictions\n\nThe `createFeature` function cannot be used for features whose state contains optional properties.\nIn other words, all state properties have to be passed to the initial state object.\n\nSo, if the state contains optional properties:\n\n<ngrx-code-example header=\"books.reducer.ts\">\n\n```ts\ninterface State {\n  books: Book[];\n  activeBookId?: string;\n}\n\nconst initialState: State = {\n  books: [],\n};\n```\n\n</ngrx-code-example>\n\nEach optional symbol (`?`) have to be replaced with `| null` or `| undefined`:\n\n<ngrx-code-example header=\"books.reducer.ts\">\n\n```ts\ninterface State {\n  books: Book[];\n  activeBookId: string | null;\n  // or activeBookId: string | undefined;\n}\n\nconst initialState: State = {\n  books: [],\n  activeBookId: null,\n  // or activeBookId: undefined,\n};\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/index.md",
    "content": "# @ngrx/store\n\nStore is RxJS powered global state management for Angular applications, inspired by Redux. Store is a controlled state container designed to help write performant, consistent applications on top of Angular.\n\n## Key concepts\n\n- [Actions](guide/store/actions) describe unique events that are dispatched from components and services.\n- State changes are handled by pure functions called [reducers](guide/store/reducers) that take the current state and the latest action to compute a new state.\n- [Selectors](guide/store/selectors) are pure functions used to select, derive and compose pieces of state.\n- State is accessed with the `Store`, an observable of state and an observer of actions.\n\n## Local state management\n\nNgRx Store is mainly for managing global state across an entire application. In cases where you need to manage temporary or local component state, consider using [NgRx Signals](guide/signals).\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/store/install) page.\n\n## Diagram\n\nThe following diagram represents the overall general flow of application state in NgRx.\n\n<figure>\n  <img src=\"images/guide/store/state-management-lifecycle.png\" alt=\"NgRx State Management Lifecycle Diagram\" width=\"100%\" height=\"100%\" />\n</figure>\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** All `Actions` that are dispatched within an application state are always first processed by the `Reducers` before being handled by the `Effects` of the application state.\n\n</ngrx-docs-alert>\n\n## Tutorial\n\nThe following tutorial shows you how to manage the state of a counter, and how to select and display it within an Angular component.\n\n<ngrx-docs-stackblitz name=\"store\" embedded></ngrx-docs-stackblitz>\n\n1.  Generate a new project using the <ngrx-docs-stackblitz name=\"ngrx-start\"></ngrx-docs-stackblitz>.\n\n2.  Right click on the `src` folder in StackBlitz and create a new file named `counter.actions.ts` to describe the counter actions to increment, decrement, and reset its value.\n\n<ngrx-code-example header=\"src/counter.actions.ts\" path=\"store/src/counter.actions.ts\">\n\n</ngrx-code-example>\n\n3.  Define a reducer function to handle changes in the counter value based on the provided actions.\n\n<ngrx-code-example header=\"src/counter.reducer.ts\" path=\"store/src/counter.reducer.ts\">\n\n</ngrx-code-example>\n\n4.  Add the `provideStore` function in the `providers` array of your `ApplicationConfig` (within `app.config.ts`) with an object containing the `count` and the `counterReducer` that manages the state of the counter. The `provideStore` method registers the global providers needed to access the `Store` throughout your application.\n\n<ngrx-code-example header=\"src/app.config.ts\" path=\"store/src/app.config.ts\">\n\n</ngrx-code-example>\n\n5.  Create a new file called `my-counter.component.ts` in a folder named `my-counter` within the `app` folder that defines a new component called `MyCounterComponent`. This component renders buttons that allow the user to change the count state. Also add the component's HTML template to the component using the `template` property. If you prefer to use an external HTML file for the template, create a file named `my-counter.component.html` in the same folder and move the template code there, then update the `templateUrl` property of the component accordingly.\n\n<ngrx-code-example header=\"src/my-counter/my-counter.component.todo.ts\" path=\"store/src/my-counter/my-counter.component.todo.ts\">\n\n</ngrx-code-example>\n\n6.  Add the new component to your AppComponent's imports and declare it in the template:\n\n<ngrx-code-example header=\"src/app.component.ts\" path=\"store/src/app.component.ts\">\n\n</ngrx-code-example>\n\n7.  Inject the store into `MyCounterComponent` and connect the `count$` stream to the store's `count` state. Implement the `increment`, `decrement`, and `reset` methods by dispatching actions to the store.\n\n<ngrx-code-example header=\"src/my-counter/my-counter.component.ts\" path=\"store/src/my-counter/my-counter.component.ts\">\n\n</ngrx-code-example>\n\nAnd that's it! Click the increment, decrement, and reset buttons to change the state of the counter.\n\nLet's cover what you did:\n\n- Defined actions to express events.\n- Defined a reducer function to manage the state of the counter.\n- Registered the global state container that is available throughout your application.\n- Injected the `Store` service to dispatch actions and select the current state of the counter.\n\n## Next Steps\n\nLearn about the architecture of an NgRx application through [actions](guide/store/actions), [reducers](guide/store/reducers), and [selectors](guide/store/selectors).\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/install.md",
    "content": "# Installation\n\n## Installing with `ng add`\n\nYou can install the Store to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/store@latest\n```\n\n### Optional `ng add` flags\n\n| flag               | description                                                                                                                                                                         | value type | default value |\n| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- |\n| `--path`           | Path to the module that you wish to add the import for the StoreModule to.                                                                                                          | `string`   |\n| `--project`        | Name of the project defined in your `angular.json` to help locating the module to add the `StoreModule` to.                                                                         | `string`   |\n| `--module`         | Name of file containing the module that you wish to add the import for the `StoreModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts`.  | `string`   | `app`         |\n| `--minimal`        | Flag to only provide minimal setup for the root state management. Only registers `StoreModule.forRoot()` in the provided `module` with an empty object, and default runtime checks. | `boolean`  | `true`        |\n| `--statePath`      | The file path to create the state in.                                                                                                                                               | `string`   | `reducers`    |\n| `--stateInterface` | The type literal of the defined interface for the state.                                                                                                                            | `string`   | `State`       |\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/store`.\n2. Run `npm install` to install those dependencies.\n3. Update your `src/app/app.module.ts` > `imports` array with `StoreModule.forRoot({})`\n4. If the project is using a `standalone bootstrap`, it adds `provideStore()` into the application config.\n\n```sh\nng add @ngrx/store@latest --no-minimal\n```\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/store`.\n2. Run `npm install` to install those dependencies.\n3. Create a `src/app/reducers` folder, unless the `statePath` flag is provided, in which case this would be created based on the flag.\n4. Create a `src/app/reducers/index.ts` file with an empty `State` interface, an empty `reducers` map, and an empty `metaReducers` array. This may be created under a different directory if the `statePath` flag is provided.\n5. Update your `src/app/app.module.ts` > `imports` array with `StoreModule.forRoot(reducers, { metaReducers })`. If you provided flags then the command will attempt to locate and update module found by the flags.\n\n## Manual Installation\n\nYou can also install `@ngrx/store` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/store\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/metareducers.md",
    "content": "# Meta-reducers\n\n`@ngrx/store` composes your map of reducers into a single reducer.\n\nDevelopers can think of meta-reducers as hooks into the action->reducer pipeline. Meta-reducers allow developers to pre-process actions before _normal_ reducers are invoked.\n\nUse the `metaReducers` configuration option to provide an array of meta-reducers that are composed from right to left.\n\n**Note:** Meta-reducers in NgRx are similar to middleware used in Redux.\n\n### Using a meta-reducer to log all actions\n\n<ngrx-code-example header=\"app.module.ts\">\n\n```ts\nimport { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store';\nimport { reducers } from './reducers';\n\n// console.log all actions\nexport function debug(\n  reducer: ActionReducer<any>\n): ActionReducer<any> {\n  return function (state, action) {\n    console.log('state', state);\n    console.log('action', action);\n\n    return reducer(state, action);\n  };\n}\n\nexport const metaReducers: MetaReducer<any>[] = [debug];\n\n@NgModule({\n  imports: [StoreModule.forRoot(reducers, { metaReducers })],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/recipes/downgrade.md",
    "content": "# Using Store in AngularJS\n\nIf you are working on an AngularJS to Angular conversion, you can use\n`@ngrx/store` to provide global state to your hybrid application.\n\n## Downgrading Store service\n\nIf you want to **dispatch** an action or **select** some slice of your store\nstate, you will need to downgrade the Store service to use it in the AngularJS\nparts of your application.\n\n<ngrx-code-example header=\"app.module.js\">\n\n```ts\nimport { Store } from '@ngrx/store';\nimport { downgradeInjectable } from '@angular/upgrade/static';\nimport { module as ngModule } from 'angular';\n// app\nimport { MyActionClass } from 'path/to.my/file.action';\nimport { mySelectorFunction } from 'path/to.my/file.selector';\n\n// Using the `downgradeInjectable` to create the `ngrxStoreService` factory in AngularJS\nngModule('appName').factory(\n  'ngrxStoreService',\n  downgradeInjectable(Store)\n);\n\n// AngularJS controller\nexport default ngModule('appName').controller('AngularJSController', [\n  '$scope',\n  '$controller',\n  'ngrxStoreService',\n  function ($scope, $controller, ngrxStoreService) {\n    // ...\n    ngrxStoreService.dispatch(new MyActionClass(myPayload));\n    ngrxStoreService.select(mySelectorFunction).subscribe(/*...*/);\n    // ...\n  },\n]);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/recipes/injecting.md",
    "content": "# Using Dependency Injection\n\n## Injecting Reducers\n\nTo inject the root reducers into your application, use an `InjectionToken` and a `Provider` to register the reducers through dependency injection.\n\n<ngrx-code-example header=\"app.module.ts\">\n\n```ts\nimport { NgModule, inject, InjectionToken } from '@angular/core';\nimport { StoreModule, ActionReducerMap } from '@ngrx/store';\n\nimport { SomeService } from './some.service';\nimport * as fromRoot from './reducers';\n\nexport const REDUCER_TOKEN = new InjectionToken<\n  ActionReducerMap<fromRoot.State>\n>('Registered Reducers', {\n  factory: () => {\n    const serv = inject(SomeService);\n    // return reducers synchronously\n    return serv.getReducers();\n  },\n});\n\n@NgModule({\n  imports: [StoreModule.forRoot(REDUCER_TOKEN)],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n\nReducers are also injected when composing state through feature modules.\n\n<ngrx-code-example header=\"feature.module.ts\">\n\n```ts\nimport { NgModule, InjectionToken } from '@angular/core';\nimport { StoreModule, ActionReducerMap } from '@ngrx/store';\n\nimport * as fromFeature from './reducers';\n\nexport const FEATURE_REDUCER_TOKEN = new InjectionToken<\n  ActionReducerMap<fromFeature.State>\n>('Feature Reducers');\n\nexport function getReducers(): ActionReducerMap<fromFeature.State> {\n  // map of reducers\n  return {};\n}\n\n@NgModule({\n  imports: [\n    StoreModule.forFeature(\n      fromFeature.featureKey,\n      FEATURE_REDUCER_TOKEN\n    ),\n  ],\n  providers: [\n    {\n      provide: FEATURE_REDUCER_TOKEN,\n      useFactory: getReducers,\n    },\n  ],\n})\nexport class FeatureModule {}\n```\n\n</ngrx-code-example>\n\n## Injecting Meta-Reducers\n\nTo inject 'middleware' meta reducers, use the `META_REDUCERS` injection token exported in\nthe Store API and a `Provider` to register the meta reducers through dependency\ninjection.\n\n<ngrx-code-example header=\"app.module.ts\">\n\n```ts\nimport {\n  ActionReducer,\n  MetaReducer,\n  META_REDUCERS,\n} from '@ngrx/store';\nimport { SomeService } from './some.service';\nimport * as fromRoot from './reducers';\n\nexport function metaReducerFactory(): MetaReducer<fromRoot.State> {\n  return (reducer: ActionReducer<any>) => (state, action) => {\n    console.log('state', state);\n    console.log('action', action);\n    return reducer(state, action);\n  };\n}\n\n@NgModule({\n  providers: [\n    {\n      provide: META_REDUCERS,\n      deps: [SomeService],\n      useFactory: metaReducerFactory,\n      multi: true,\n    },\n  ],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\nCareful attention should be called to the use of the `multi`\nproperty in the provider here for `META_REDUCERS`. As this injection token may be utilized\nby many libraries concurrently, specifying `multi: true` is critical to ensuring that all\nlibrary meta reducers are applied to any project that consumes multiple NgRx libraries with\nregistered meta reducers.\n\n</ngrx-docs-alert>\n\n## Injecting Feature Config\n\nTo inject the feature store configuration into your module, use an `InjectionToken` and a `Provider` to register the feature config object through dependency injection.\n\n<ngrx-code-example header=\"feature.module.ts\">\n\n```ts\nimport { NgModule, InjectionToken } from '@angular/core';\nimport { StoreModule, StoreConfig } from '@ngrx/store';\nimport { SomeService } from './some.service';\n\nimport * as fromFeature from './reducers';\n\nexport const FEATURE_CONFIG_TOKEN = new InjectionToken<\n  StoreConfig<fromFeature.State>\n>('Feature Config');\n\nexport function getConfig(\n  someService: SomeService\n): StoreConfig<fromFeature.State> {\n  // return the config synchronously.\n  return {\n    initialState: someService.getInitialState(),\n\n    metaReducers: [\n      fromFeature.loggerFactory(someService.loggerConfig()),\n    ],\n  };\n}\n\n@NgModule({\n  imports: [\n    StoreModule.forFeature(\n      fromFeature.featureKey,\n      fromFeature.reducers,\n      FEATURE_CONFIG_TOKEN\n    ),\n  ],\n  providers: [\n    {\n      provide: FEATURE_CONFIG_TOKEN,\n      deps: [SomeService],\n      useFactory: getConfig,\n    },\n  ],\n})\nexport class FeatureModule {}\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/reducers.md",
    "content": "# Reducers\n\nReducers in NgRx are responsible for handling transitions from one state to the next state in your application. Reducer functions handle these transitions by determining which [actions](guide/store/actions) to handle based on the action's type.\n\n## Introduction\n\nReducers are pure functions in that they produce the same output for a given input. They are without side effects and handle each state transition synchronously. Each reducer function takes the latest `Action` dispatched, the current state, and determines whether to return a newly modified state or the original state. This guide shows you how to write reducer functions, register them in your `Store`, and compose feature states.\n\n## The reducer function\n\nThere are a few consistent parts of every piece of state managed by a reducer.\n\n- An interface or type that defines the shape of the state.\n- The arguments including the initial state or current state and the current action.\n- The functions that handle state changes for their associated action(s).\n\nBelow is an example of a set of actions to handle the state of a scoreboard,\nand the associated reducer function.\n\nFirst, define some actions for interacting with a piece of state.\n\n<ngrx-code-example header=\"scoreboard-page.actions.ts\">\n\n```ts\nimport { createAction, props } from '@ngrx/store';\n\nexport const homeScore = createAction('[Scoreboard Page] Home Score');\nexport const awayScore = createAction('[Scoreboard Page] Away Score');\nexport const resetScore = createAction(\n  '[Scoreboard Page] Score Reset'\n);\nexport const setScores = createAction(\n  '[Scoreboard Page] Set Scores',\n  props<{ game: Game }>()\n);\n```\n\n</ngrx-code-example>\n\nNext, create a reducer file that imports the actions and define\na shape for the piece of state.\n\n### Defining the state shape\n\nEach reducer function is a listener of actions. The scoreboard actions defined above describe the possible transitions handled by the reducer. Import multiple sets of actions to handle additional state transitions within a reducer.\n\n<ngrx-code-example header=\"scoreboard.reducer.ts\">\n\n```ts\nimport { Action, createReducer, on } from '@ngrx/store';\nimport * as ScoreboardPageActions from '../actions/scoreboard-page.actions';\n\nexport interface State {\n  home: number;\n  away: number;\n}\n```\n\n</ngrx-code-example>\n\nYou define the shape of the state according to what you are capturing, whether it be a single type such as a number, or a more complex object with multiple properties.\n\n### Setting the initial state\n\nThe initial state gives the state an initial value, or provides a value if the current state is `undefined`. You set the initial state with defaults for your required state properties.\n\nCreate and export a variable to capture the initial state with one or\nmore default values.\n\n<ngrx-code-example header=\"scoreboard.reducer.ts\">\n\n```ts\nexport const initialState: State = {\n  home: 0,\n  away: 0,\n};\n```\n\n</ngrx-code-example>\n\nThe initial values for the `home` and `away` properties of the state are 0.\n\n### Creating the reducer function\n\nThe reducer function's responsibility is to handle the state transitions in an immutable way. Create a reducer function that handles the actions for managing the state of the scoreboard using the `createReducer` function.\n\n<ngrx-code-example header=\"scoreboard.reducer.ts\">\n\n```ts\nexport const scoreboardReducer = createReducer(\n  initialState,\n  on(ScoreboardPageActions.homeScore, (state) => ({\n    ...state,\n    home: state.home + 1,\n  })),\n  on(ScoreboardPageActions.awayScore, (state) => ({\n    ...state,\n    away: state.away + 1,\n  })),\n  on(ScoreboardPageActions.resetScore, (state) => ({\n    home: 0,\n    away: 0,\n  })),\n  on(ScoreboardPageActions.setScores, (state, { game }) => ({\n    home: game.home,\n    away: game.away,\n  }))\n);\n```\n\n</ngrx-code-example>\n\nIn the example above, the reducer is handling 4 actions: `[Scoreboard Page] Home Score`, `[Scoreboard Page] Away Score`, `[Scoreboard Page] Score Reset` and `[Scoreboard Page] Set Scores`. Each action is strongly-typed. Each action handles the state transition immutably. This means that the state transitions are not modifying the original state, but are returning a new state object using the spread operator. The spread syntax copies the properties from the current state into the object, creating a new reference. This ensures that a new state is produced with each change, preserving the purity of the change. This also promotes referential integrity, guaranteeing that the old reference was discarded when a state change occurred.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** The [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) only does shallow copying and does not handle deeply nested objects. You need to copy each level in the object to ensure immutability. There are libraries that handle deep copying including [lodash](https://lodash.com) and [immer](https://github.com/mweststrate/immer).\n\n</ngrx-docs-alert>\n\nWhen an action is dispatched, _all registered reducers_ receive the action. Whether they handle the action is determined by the `on` functions that associate one or more actions with a given state change.\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** You can also write reducers using switch statements, which was the previously defined way before reducer creators were introduced in NgRx. If you are looking for examples of reducers using switch statements, visit the documentation for [versions 7.x and prior](https://v7.ngrx.io/guide/store/reducers).\n\n</ngrx-docs-alert>\n\n## Registering root state\n\nThe state of your application is defined as one large object. Registering reducer functions to manage parts of your state only defines keys with associated values in the object. To register the global `Store` within your application, use the `StoreModule.forRoot()` method with a map of key/value pairs that define your state. The `StoreModule.forRoot()` registers the global providers for your application, including the `Store` service you inject into your components and services to dispatch actions and select pieces of state.\n\n<ngrx-code-example header=\"app.module.ts\">\n\n```ts\nimport { NgModule } from '@angular/core';\nimport { StoreModule } from '@ngrx/store';\nimport { scoreboardReducer } from './reducers/scoreboard.reducer';\n\n@NgModule({\n  imports: [StoreModule.forRoot({ game: scoreboardReducer })],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n\nRegistering states with `StoreModule.forRoot()` ensures that the states are defined upon application startup. In general, you register root states that always need to be available to all areas of your application immediately.\n\n### Using the Standalone API\n\nRegistering the root store and state can also be done using the standalone APIs if you are bootstrapping an Angular application using standalone features.\n\n<ngrx-code-example header=\"main.ts\">\n\n```ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideStore, provideState } from '@ngrx/store';\n\nimport { AppComponent } from './app.component';\nimport { scoreboardReducer } from './reducers/scoreboard.reducer';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideStore(),\n    provideState({ name: 'game', reducer: scoreboardReducer }),\n  ],\n});\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** Although you can register reducers in the `provideStore()` function, we recommend keeping `provideStore()` empty and using the `provideState()` function to register feature states in the root `providers` array.\n\n</ngrx-docs-alert>\n\n## Registering feature state\n\nFeature states behave in the same way root states do, but allow you to define them with specific feature areas in your application. Your state is one large object, and feature states register additional keys and values in that object.\n\nLooking at an example state object, you see how a feature state allows your state to be built up incrementally. Let's start with an empty state object.\n\n<ngrx-code-example header=\"app.module.ts\">\n\n```ts\nimport { NgModule } from '@angular/core';\nimport { StoreModule } from '@ngrx/store';\n\n@NgModule({\n  imports: [StoreModule.forRoot({})],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n\nUsing the Standalone API:\n\n<ngrx-code-example header=\"main.ts\">\n\n```ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideStore } from '@ngrx/store';\n\nimport { AppComponent } from './app.component';\n\nbootstrapApplication(AppComponent, {\n  providers: [provideStore()],\n});\n```\n\n</ngrx-code-example>\n\nThis registers your application with an empty object for the root state.\n\n```json\n{}\n```\n\nNow use the `scoreboard` reducer with a feature `NgModule` named `ScoreboardModule` to register additional state.\n\n<ngrx-code-example header=\"scoreboard.reducer.ts\">\n\n```ts\nexport const scoreboardFeatureKey = 'game';\n```\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"scoreboard.module.ts\">\n\n```ts\nimport { NgModule } from '@angular/core';\nimport { StoreModule } from '@ngrx/store';\nimport {\n  scoreboardFeatureKey,\n  scoreboardReducer,\n} from './reducers/scoreboard.reducer';\n\n@NgModule({\n  imports: [\n    StoreModule.forFeature(scoreboardFeatureKey, scoreboardReducer),\n  ],\n})\nexport class ScoreboardModule {}\n```\n\n</ngrx-code-example>\n\n### Using the Standalone API\n\nFeature states are registered in the `providers` array of the route config.\n\n<ngrx-code-example header=\"game-routes.ts\">\n\n```ts\nimport { Route } from '@angular/router';\nimport { provideState } from '@ngrx/store';\n\nimport {\n  scoreboardFeatureKey,\n  scoreboardReducer,\n} from './reducers/scoreboard.reducer';\n\nexport const routes: Route[] = [\n  {\n    path: 'scoreboard',\n    providers: [\n      provideState({\n        name: scoreboardFeatureKey,\n        reducer: scoreboardReducer,\n      }),\n    ],\n  },\n];\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\n**Note:** It is recommended to abstract a feature key string to prevent hardcoding strings when registering feature state and calling `createFeatureSelector`. Alternatively, you can use a [Feature Creator](guide/store/feature-creators) which automatically generates selectors for your feature state.\n\n</ngrx-docs-alert>\n\nAdd the `ScoreboardModule` to the `AppModule` to load the state eagerly.\n\n<ngrx-code-example header=\"app.module.ts\">\n\n```ts\nimport { NgModule } from '@angular/core';\nimport { StoreModule } from '@ngrx/store';\nimport { ScoreboardModule } from './scoreboard/scoreboard.module';\n\n@NgModule({\n  imports: [StoreModule.forRoot({}), ScoreboardModule],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n\nUsing the Standalone API, register the feature state on application bootstrap:\n\n<ngrx-code-example header=\"main.ts\">\n\n```ts\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideStore } from '@ngrx/store';\n\nimport { AppComponent } from './app.component';\nimport {\n  scoreboardFeatureKey,\n  scoreboardReducer,\n} from './reducers/scoreboard.reducer';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideStore({ [scoreboardFeatureKey]: scoreboardReducer }),\n  ],\n});\n```\n\n</ngrx-code-example>\n\nAfter the feature is loaded, the `game` key becomes a property in the object and is now managed in the state.\n\n```json\n{\n  \"game\": { \"home\": 0, \"away\": 0 }\n}\n```\n\nWhether your feature states are loaded eagerly or lazily depends on the needs of your application. You use feature states to build up your state object over time and through different feature areas.\n\n## Standalone API in module-based apps\n\nIf you have a module-based Angular application, you can still use standalone components. NgRx standalone APIs support this workflow as well.\n\nFor module-based apps, you have the `StoreModule.forRoot({...})` included in the `imports` array of your `AppModule`, which registers the root store for dependency injection. Standalone components look for a different injection token that can only be provided by the `provideStore({...})` function detailed above. In order to use NgRx in a standalone component, you must first add the `provideStore({...})` function to the `providers` array in your `AppModule` with the same configuration you have inside of your `forRoot({...})`. For module-based apps with standalone components, you will simply have both.\n\n<ngrx-code-example header=\"app.module.ts\">\n\n```ts\nimport { NgModule } from '@angular/core';\nimport { StoreModule, provideStore } from '@ngrx/store';\nimport { scoreboardReducer } from './reducers/scoreboard.reducer';\n\n@NgModule({\n  imports: [StoreModule.forRoot({ game: scoreboardReducer })],\n  providers: [provideStore({ game: scoreboardReducer })],\n})\nexport class AppModule {}\n```\n\n</ngrx-code-example>\n\nNote: Similarly, if you are using effects, you will need to register both `EffectsModule.forRoot([...])` and `provideEffects([...])`. For more info, see [Effects](guide/effects).\n\n## Next Steps\n\nReducers are only responsible for deciding which state transitions need to occur for a given action.\n\nIn an application there is also a need to handle impure actions, such as AJAX requests, in NgRx we call them [Effects](guide/effects).\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/selectors.md",
    "content": "# Selectors\n\nSelectors are pure functions used for obtaining slices of store state. @ngrx/store provides a few helper functions for optimizing this selection. Selectors provide many features when selecting slices of state:\n\n- Portability\n- Memoization\n- Composition\n- Testability\n- Type Safety\n\nWhen using the `createSelector` and `createFeatureSelector` functions @ngrx/store keeps track of the latest arguments in which your selector function was invoked. Because selectors are [pure functions](https://en.wikipedia.org/wiki/Pure_function), the last result can be returned when the arguments match without reinvoking your selector function. This can provide performance benefits, particularly with selectors that perform expensive computation. This practice is known as [memoization](https://en.wikipedia.org/wiki/Memoization).\n\n### Using a selector for one piece of state\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nimport { createSelector } from '@ngrx/store';\n\nexport interface FeatureState {\n  counter: number;\n}\n\nexport interface AppState {\n  feature: FeatureState;\n}\n\nexport const selectFeature = (state: AppState) => state.feature;\n\nexport const selectFeatureCount = createSelector(\n  selectFeature,\n  (state: FeatureState) => state.counter\n);\n```\n\n</ngrx-code-example>\n\n### Using selectors for multiple pieces of state\n\nThe `createSelector` can be used to select some data from the state based on several slices of the same state.\n\nThe `createSelector` function can take up to 8 selector functions for more complete state selections.\n\nFor example, imagine you have a `selectedUser` object in the state. You also have an `allBooks` array of book objects.\n\nAnd you want to show all books for the current user.\n\nYou can use `createSelector` to achieve just that. Your visible books will always be up to date even if you update them in `allBooks`. They will always show the books that belong to your user if there is one selected and will show all the books when there is no user selected.\n\nThe result will be just some of your state filtered by another section of the state. And it will be always up to date.\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nimport { createSelector } from '@ngrx/store';\n\nexport interface User {\n  id: number;\n  name: string;\n}\n\nexport interface Book {\n  id: number;\n  userId: number;\n  name: string;\n}\n\nexport interface AppState {\n  selectedUser: User;\n  allBooks: Book[];\n}\n\nexport const selectUser = (state: AppState) => state.selectedUser;\nexport const selectAllBooks = (state: AppState) => state.allBooks;\n\nexport const selectVisibleBooks = createSelector(\n  selectUser,\n  selectAllBooks,\n  (selectedUser: User, allBooks: Book[]) => {\n    if (selectedUser && allBooks) {\n      return allBooks.filter(\n        (book: Book) => book.userId === selectedUser.id\n      );\n    } else {\n      return allBooks;\n    }\n  }\n);\n```\n\n</ngrx-code-example>\n\nThe `createSelector` function also provides the ability to pass a dictionary of selectors without a projector.\nIn this case, `createSelector` will generate a projector function that maps the results of the input selectors to a dictionary.\n\n```ts\n// result type - { books: Book[]; query: string }\nconst selectBooksPageViewModel = createSelector({\n  books: selectBooks, // result type - Book[]\n  query: selectQuery, // result type - string\n});\n```\n\n### Using selectors with props\n\n<ngrx-docs-alert type=\"error\">\n\nSelectors with props are [deprecated](https://github.com/ngrx/platform/issues/2980).\n\n</ngrx-docs-alert>\n\nTo select a piece of state based on data that isn't available in the store you can pass `props` to the selector function. These `props` gets passed through every selector and the projector function.\nTo do so we must specify these `props` when we use the selector inside our component.\n\nFor example if we have a counter and we want to multiply its value, we can add the multiply factor as a `prop`:\n\nThe last argument of a selector or a projector is the `props` argument, for our example it looks as follows:\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nexport const selectCount = createSelector(\n  selectCounterValue,\n  (counter, props) => counter * props.multiply\n);\n```\n\n</ngrx-code-example>\n\nInside the component we can define the `props`:\n\n<ngrx-code-example header=\"app.component.ts\">\n\n```ts\nngOnInit() {\n  this.counter = this.store.select(fromRoot.selectCount, { multiply: 2 })\n}\n```\n\n</ngrx-code-example>\n\nKeep in mind that a selector only keeps the previous input arguments in its cache. If you reuse this selector with another multiply factor, the selector would always have to re-evaluate its value. This is because it's receiving both of the multiply factors (e.g. one time `2`, the other time `4`). In order to correctly memoize the selector, wrap the selector inside a factory function to create different instances of the selector.\n\nThe following is an example of using multiple counters differentiated by `id`.\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nexport const selectCount = () =>\n  createSelector(\n    (state, props) => state.counter[props.id],\n    (counter, props) => counter * props.multiply\n  );\n```\n\n</ngrx-code-example>\n\nThe component's selectors are now calling the factory function to create different selector instances:\n\n<ngrx-code-example header=\"app.component.ts\">\n\n```ts\nngOnInit() {\n  this.counter2 = this.store.select(fromRoot.selectCount(), { id: 'counter2', multiply: 2 });\n  this.counter4 = this.store.select(fromRoot.selectCount(), { id: 'counter4', multiply: 4 });\n  this.counter6 = this.store.select(fromRoot.selectCount(), { id: 'counter6', multiply: 6 });\n}\n```\n\n</ngrx-code-example>\n\n## Selecting Feature States\n\nThe `createFeatureSelector` is a convenience method for returning a top level feature state. It returns a typed selector function for a feature slice of state.\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nimport { createSelector, createFeatureSelector } from '@ngrx/store';\n\nexport const featureKey = 'feature';\n\nexport interface FeatureState {\n  counter: number;\n}\n\nexport const selectFeature =\n  createFeatureSelector<FeatureState>(featureKey);\n\nexport const selectFeatureCount = createSelector(\n  selectFeature,\n  (state: FeatureState) => state.counter\n);\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"inform\">\n\nUsing a [Feature Creator](guide/store/feature-creators) generates the top-level selector and child selectors for each feature state property.\n\n</ngrx-docs-alert>\n\n## Resetting Memoized Selectors\n\nThe selector function returned by calling `createSelector` or `createFeatureSelector` initially has a memoized value of `null`. After a selector is invoked the first time its memoized value is stored in memory. If the selector is subsequently invoked with the same arguments it will return the memoized value. If the selector is then invoked with different arguments it will recompute and update its memoized value. Consider the following:\n\n<ngrx-code-example header=\"example.ts\">\n\n```ts\nimport { createSelector } from '@ngrx/store';\n\nexport interface State {\n  counter1: number;\n  counter2: number;\n}\n\nexport const selectCounter1 = (state: State) => state.counter1;\nexport const selectCounter2 = (state: State) => state.counter2;\nexport const selectTotal = createSelector(\n  selectCounter1,\n  selectCounter2,\n  (counter1, counter2) => counter1 + counter2\n); // selectTotal has a memoized value of null, because it has not yet been invoked.\n\nlet state = { counter1: 3, counter2: 4 };\n\nselectTotal(state); // computes the sum of 3 & 4, returning 7. selectTotal now has a memoized value of 7\nselectTotal(state); // does not compute the sum of 3 & 4. selectTotal instead returns the memoized value of 7\n\nstate = { ...state, counter2: 5 };\n\nselectTotal(state); // computes the sum of 3 & 5, returning 8. selectTotal now has a memoized value of 8\n```\n\n</ngrx-code-example>\n\nA selector's memoized value stays in memory indefinitely. If the memoized value is, for example, a large dataset that is no longer needed it's possible to reset the memoized value to null so that the large dataset can be removed from memory. This can be accomplished by invoking the `release` method on the selector.\n\n<ngrx-code-example header=\"example.ts\">\n\n```ts\nselectTotal(state); // returns the memoized value of 8\nselectTotal.release(); // memoized value of selectTotal is now null\n```\n\n</ngrx-code-example>\n\nReleasing a selector also recursively releases any ancestor selectors. Consider the following:\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nexport interface State {\n  evenNums: number[];\n  oddNums: number[];\n}\n\nexport const selectSumEvenNums = createSelector(\n  (state: State) => state.evenNums,\n  (evenNums) => evenNums.reduce((prev, curr) => prev + curr)\n);\nexport const selectSumOddNums = createSelector(\n  (state: State) => state.oddNums,\n  (oddNums) => oddNums.reduce((prev, curr) => prev + curr)\n);\nexport const selectTotal = createSelector(\n  selectSumEvenNums,\n  selectSumOddNums,\n  (evenSum, oddSum) => evenSum + oddSum\n);\n\nselectTotal({\n  evenNums: [2, 4],\n  oddNums: [1, 3],\n});\n\n/**\n * Memoized Values before calling selectTotal.release()\n *   selectSumEvenNums  6\n *   selectSumOddNums   4\n *   selectTotal        10\n */\n\nselectTotal.release();\n\n/**\n * Memoized Values after calling selectTotal.release()\n *   selectSumEvenNums  null\n *   selectSumOddNums   null\n *   selectTotal        null\n */\n```\n\n</ngrx-code-example>\n\n## Using Store Without Type Generic\n\nThe most common way to select information from the store is to use a selector function defined with `createSelector`. TypeScript is able to automatically infer types from `createSelector`, which reduces the need to provide the shape of the state to `Store` via a generic argument.\n\nSo, when injecting `Store` into components and other injectables, the generic type can be omitted. If injected without the generic, the default generic applied is `Store<T = object>`.\n\n<ngrx-docs-alert type=\"inform\">\n\nIt is important to continue to provide a Store type generic if you are using the string version of selectors as types cannot be inferred automatically in those instances.\n\n</ngrx-docs-alert>\n\nThe follow example demonstrates the use of `Store` without providing a generic:\n\n<ngrx-code-example header=\"app.component.ts\">\n\n```ts\nexport class AppComponent {\n  counter$ = this.store.select(fromCounter.selectCounter);\n\n  constructor(private readonly store: Store) {}\n}\n```\n\n</ngrx-code-example>\n\nWhen using strict mode, the `select` method will expect to be passed a selector whose base selects from an `object`.\n\nThis is the default behavior of `createFeatureSelector` when providing only one generic argument:\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nimport { createSelector, createFeatureSelector } from '@ngrx/store';\n\nexport const featureKey = 'feature';\n\nexport interface FeatureState {\n  counter: number;\n}\n\n// selectFeature will have the type MemoizedSelector<object, FeatureState>\nexport const selectFeature =\n  createFeatureSelector<FeatureState>(featureKey);\n\n// selectFeatureCount will have the type MemoizedSelector<object, number>\nexport const selectFeatureCount = createSelector(\n  selectFeature,\n  (state) => state.counter\n);\n```\n\n</ngrx-code-example>\n\n## Using Signal Selector\n\nThe `selectSignal` method expects a selector as an input argument and returns a signal of the selected state slice. It has a similar signature to the `select` method, but unlike `select`, `selectSignal` returns a signal instead of an observable.\n\n### Example Usage in Components\n\n```typescript\nimport { Component, inject } from '@angular/core';\nimport { NgFor } from '@angular/common';\nimport { Store } from '@ngrx/store';\n\nimport { selectUsers } from './users.selectors';\n\n@Component({\n  standalone: true,\n  imports: [NgFor],\n  template: `\n    <h1>Users</h1>\n    <ul>\n      <li *ngFor=\"let user of users()\">\n        {{ user.name }}\n      </li>\n    </ul>\n  `,\n})\nexport class UsersComponent {\n  private readonly store = inject(Store);\n\n  // type: Signal<User[]>\n  readonly users = this.store.selectSignal(selectUsers);\n}\n```\n\n### Selecting with Equality Function\n\nSimilar to the `computed` function, the `selectSignal` method also accepts the equality function to stop the recomputation of the deeper dependency chain if two values are determined to be equal.\n\n## Advanced Usage\n\nSelectors empower you to compose a [read model for your application state](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs#solution).\nIn terms of the CQRS architectural pattern, NgRx separates the read model (selectors) from the write model (reducers).\nAn advanced technique is to combine selectors with [RxJS pipeable operators](https://rxjs.dev/guide/v6/pipeable-operators).\n\nThis section covers some basics of how selectors compare to pipeable operators and demonstrates how `createSelector` and `scan` are utilized to display a history of state transitions.\n\n### Breaking Down the Basics\n\n#### Select a non-empty state using pipeable operators\n\nLet's pretend we have a selector called `selectValues` and the component for displaying the data is only interested in defined values, i.e., it should not display empty states.\n\nWe can achieve this behaviour by using only RxJS pipeable operators:\n\n<ngrx-code-example header=\"app.component.ts\">\n\n```ts\nimport { map, filter } from 'rxjs/operators';\n\nstore\n  .pipe(\n    map((state) => selectValues(state)),\n    filter((val) => val !== undefined)\n  )\n  .subscribe(/* .. */);\n```\n\n</ngrx-code-example>\n\nThe above can be further rewritten to use the `select()` utility function from NgRx:\n\n<ngrx-code-example header=\"app.component.ts\">\n\n```ts\nimport { select } from '@ngrx/store';\nimport { map, filter } from 'rxjs/operators';\n\nstore\n  .pipe(\n    select(selectValues),\n    filter((val) => val !== undefined)\n  )\n  .subscribe(/* .. */);\n```\n\n</ngrx-code-example>\n\n#### Solution: Extracting a pipeable operator\n\nTo make the `select()` and `filter()` behaviour a reusable piece of code, we extract a [pipeable operator](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md) using the RxJS `pipe()` utility function:\n\n<ngrx-code-example header=\"app.component.ts\">\n\n```ts\nimport { select } from '@ngrx/store';\nimport { pipe } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\nexport const selectFilteredValues = pipe(\n  select(selectValues),\n  filter((val) => val !== undefined)\n);\n\nstore.pipe(selectFilteredValues).subscribe(/* .. */);\n```\n\n</ngrx-code-example>\n\n### Advanced Example: Select the last {n} state transitions\n\nLet's examine the technique of combining NgRx selectors and RxJS operators in an advanced example.\n\nIn this example, we will write a selector function that projects values from two different slices of the application state.\nThe projected state will emit a value when both slices of state have a value.\nOtherwise, the selector will emit an `undefined` value.\n\n<ngrx-code-example header=\"index.ts\">\n\n```ts\nexport const selectProjectedValues = createSelector(\n  selectFoo,\n  selectBar,\n  (foo, bar) => {\n    if (foo && bar) {\n      return { foo, bar };\n    }\n\n    return undefined;\n  }\n);\n```\n\n</ngrx-code-example>\n\nThen, the component should visualize the history of state transitions.\nWe are not only interested in the current state but rather like to display the last `n` pieces of state.\nMeaning that we will map a stream of state values (`1`, `2`, `3`) to an array of state values (`[1, 2, 3]`).\n\n<ngrx-code-example header=\"select-last-state-transition.ts\">\n\n```ts\n// The number of state transitions is given by the user (subscriber)\nexport const selectLastStateTransitions = (count: number) => {\n  return pipe(\n    // Thanks to `createSelector` the operator will have memoization \"for free\"\n    select(selectProjectedValues), // Combines the last `count` state values in array\n    scan(\n      (acc, curr) => {\n        return [curr, ...acc].filter(\n          (val, index) => index < count && val !== undefined\n        );\n      },\n      [] as { foo: number; bar: string }[]\n    ) // XX: Explicit type hint for the array.\n    // Equivalent to what is emitted by the selector\n  );\n};\n```\n\n</ngrx-code-example>\n\nFinally, the component will subscribe to the store, telling the number of state transitions it wishes to display:\n\n<ngrx-code-example header=\"app.component.ts\">\n\n```ts\n// Subscribe to the store using the custom pipeable operator\nstore.pipe(selectLastStateTransitions(3)).subscribe(/* .. */);\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/testing.md",
    "content": "# Testing\n\n### Using a Mock Store\n\nThe `provideMockStore()` function registers providers that allow you to mock out the `Store` for testing functionality that has a dependency on `Store` without setting up reducers.\nYou can write tests validating behaviors corresponding to the specific state snapshot easily.\n\n<ngrx-docs-alert type=\"help\">\n\n**Note:** All dispatched actions don't affect the state, but you can see them in the `scannedActions$` stream.\n\n</ngrx-docs-alert>\n\nUsage:\n\n<ngrx-code-example header=\"auth.guard.spec.ts\">\n\n```ts\nimport { TestBed } from '@angular/core/testing';\nimport { provideMockStore, MockStore } from '@ngrx/store/testing';\nimport { cold } from 'jasmine-marbles';\n\nimport { AuthGuard } from '../guards/auth.guard';\n\ndescribe('Auth Guard', () => {\n  let guard: AuthGuard;\n  let store: MockStore;\n  const initialState = { loggedIn: false };\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        // any modules needed\n      ],\n      providers: [\n        AuthGuard,\n        provideMockStore({ initialState }),\n        // other providers\n      ],\n    });\n\n    store = TestBed.inject(MockStore);\n    guard = TestBed.inject(AuthGuard);\n  });\n\n  it('should return false if the user state is not logged in', () => {\n    const expected = cold('(a|)', { a: false });\n\n    expect(guard.canActivate()).toBeObservable(expected);\n  });\n\n  it('should return true if the user state is logged in', () => {\n    store.setState({ loggedIn: true });\n\n    const expected = cold('(a|)', { a: true });\n\n    expect(guard.canActivate()).toBeObservable(expected);\n  });\n});\n```\n\n</ngrx-code-example>\n\n### Using Mock Selectors\n\n`MockStore` also provides the ability to mock individual selectors to return a passed value using the `overrideSelector()` method. When the selector is invoked by the `select` method, the returned value is overridden by the passed value, regardless of the current state in the store.\n\n`overrideSelector()` returns a `MemoizedSelector`. To update the mock selector to return a different value, use the `MemoizedSelector`'s `setResult()` method. Updating a selector's mock value will not cause it to emit automatically. To trigger an emission from all selectors, use the `MockStore.refreshState()` method after updating the desired selectors.\n\n`overrideSelector()` supports mocking the `select` method (used in RxJS pipe) and the `Store` `select` instance method using a string or selector.\n\nUsage:\n\n<ngrx-code-example header=\"src/app/state/books.selectors.ts\" path=\"testing-store/src/app/state/books.selectors.ts\">\n\n</ngrx-code-example>\n\n<ngrx-code-example header=\"src/app/app.component.spec.ts (Using Mock Selectors) \" path=\"store-walkthrough/src/app/tests/app.component.1.spec.ts\" region=\"mockSelector\">\n\n</ngrx-code-example>\n\nIn this example based on the [walkthrough](guide/store/walkthrough), we mock the `selectBooks` selector by using `overrideSelector`, passing in the `selectBooks` selector with a default mocked return value of an array of books. Similarly, we mock the `selectBookCollection` selector and pass the selector together with another array. In the test, we use `setResult()` to update the mock selectors to return new array values, then we use `MockStore.refreshState()` to trigger an emission from the `selectBooks` and `selectBookCollection` selectors.\n\nYou can reset selectors by calling the `MockStore.resetSelectors()` method in the `afterEach()` hook.\n\n<ngrx-code-example header=\"src/app/app.component.spec.ts (Reset Mock Selector) \" path=\"store-walkthrough/src/app/tests/app.component.1.spec.ts\" region=\"resetMockSelector\">\n\n</ngrx-code-example>\n\nTry the <ngrx-docs-stackblitz name=\"testing-store\"></ngrx-docs-stackblitz>.\n\n### Integration Testing\n\nAn integration test should verify that the `Store` coherently works together with our components and services that inject `Store`. An integration test will not mock the store or individual selectors, as unit tests do, but will instead integrate a `Store` by using `StoreModule.forRoot` in your `TestBed` configuration. Here is part of an integration test for the `AppComponent` introduced in the [walkthrough](guide/store/walkthrough).\n\n<ngrx-code-example header=\"src/app/tests/integration.spec.ts (Integrate Store)\" path=\"store-walkthrough/src/app/tests/integration.spec.ts\" region=\"integrate\">\n\n</ngrx-code-example>\n\nThe integration test sets up the dependent `Store` by importing the `StoreModule`. In this part of the example, we assert that clicking the `add` button dispatches the corresponding action and is correctly emitted by the `collection` selector.\n\n<ngrx-code-example header=\"src/app/tests/integration.spec.ts (addButton Test)\" path=\"store-walkthrough/src/app/tests/integration.spec.ts\" region=\"addTest\">\n\n</ngrx-code-example>\n\n### Testing selectors\n\nYou can use the projector function used by the selector by accessing the `.projector` property. The following example tests the `books` selector from the [walkthrough](guide/store/walkthrough).\n\n<ngrx-code-example header=\"src/app/state/books.selectors.spec.ts\" path=\"testing-store/src/app/state/books.selectors.spec.ts\">\n\n</ngrx-code-example>\n\n### Testing reducers\n\nThe following example tests the `booksReducer` from the [walkthrough](guide/store/walkthrough). In the first test we check that the state returns the same reference when the reducer is not supposed to handle the action (unknown action). The second test checks that `retrievedBookList` action updates the state and returns the new instance of it.\n\n<ngrx-code-example header=\"src/app/state/books.reducer.spec.ts\" path=\"testing-store/src/app/state/books.reducer.spec.ts\">\n\n</ngrx-code-example>\n\n### Testing without `TestBed`\n\nThe `provideMockStore()` function can be also used with `Injector.create`:\n\n<ngrx-code-example header=\"books.component.spec.ts\">\n\n```ts\nimport { MockStore, provideMockStore } from '@ngrx/store/testing';\nimport { Injector } from '@angular/core';\n\ndescribe('Books Component', () => {\n  let store: MockStore;\n  const initialState = { books: ['Book 1', 'Book 2', 'Book 3'] };\n\n  beforeEach(() => {\n    const injector = Injector.create({\n      providers: [provideMockStore({ initialState })],\n    });\n\n    store = injector.get(MockStore);\n  });\n});\n```\n\n</ngrx-code-example>\n\nAnother option to create the `MockStore` without `TestBed` is by calling the `createMockStore()` function:\n\n<ngrx-code-example header=\"books.component.spec.ts\">\n\n```ts\nimport { MockStore, createMockStore } from '@ngrx/store/testing';\n\ndescribe('Books Component', () => {\n  let store: MockStore;\n  const initialState = { books: ['Book 1', 'Book 2', 'Book 3'] };\n\n  beforeEach(() => {\n    store = createMockStore({ initialState });\n  });\n});\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/walkthrough.md",
    "content": "# Walkthrough\n\nThe following example more extensively utilizes the key concepts of store to manage the state of book list, and how the user can add a book to and remove it from their collection within an Angular component. To build the example, follow the steps in the tutorial below.\n\nThe full example is available on StackBlitz:\n\n<ngrx-docs-stackblitz name=\"store-walkthrough\" embedded=\"true\"></ngrx-docs-stackblitz>\n\n## Tutorial\n\n1. Generate a new project using the <ngrx-docs-stackblitz name=\"ngrx-start\"></ngrx-docs-stackblitz> and create a folder named `book-list` inside the `src` folder. This folder is used to hold the book list component later in the tutorial. For now, let's start with adding a file named `book.ts` to reference different aspects of a book in the book list.\n\n<ngrx-code-example header=\"src/book-list/book.ts\" path=\"store-walkthrough/src/book-list/book.ts\">\n</ngrx-code-example>\n\n1. Right click on the `app` folder to create a state management folder `state`. Within the new folder, create a new file `books.actions.ts` to describe the book actions. Book actions include the book list retrieval, and the add and remove book actions.\n\n<ngrx-code-example header=\"src/state/books.actions.ts\" path=\"store-walkthrough/src/state/books.actions.ts\">\n</ngrx-code-example>\n\n3. Right click on the `state` folder and create a new file labeled `books.reducer.ts`. Within this file, define a reducer function to handle the retrieval of the book list from the state and consequently, update the state.\n\n<ngrx-code-example header=\"src/state/books.reducer.ts\" path=\"store-walkthrough/src/state/books.reducer.ts\">\n</ngrx-code-example>\n\n4. Create another file named `collection.reducer.ts` in the `state` folder to handle actions that alter the user's book collection. Define a reducer function that handles the add action by appending the book's ID to the collection, including a condition to avoid duplicate book IDs. Define the same reducer to handle the remove action by filtering the collection array with the book ID.\n\n<ngrx-code-example header=\"src/state/collection.reducer.ts\" path=\"store-walkthrough/src/state/collection.reducer.ts\">\n</ngrx-code-example>\n\n5. Add the `provideStore` function in the `providers` array of your `app.config.ts` with an object containing the `books` and `booksReducer`, as well as the `collection` and `collectionReducer` that manage the state of the book list and the collection. The `provideStore` function registers the global providers needed to access the `Store` throughout your application.\n\n<ngrx-code-example header=\"src/app.config.ts\" path=\"store-walkthrough/src/app.config.ts\">\n</ngrx-code-example>\n\n6. Create the book list and collection selectors to ensure we get the correct information from the store. As you can see, the `selectBookCollection` selector combines two other selectors in order to build its return value.\n\n<ngrx-code-example header=\"src/state/books.selectors.ts\" path=\"store-walkthrough/src/state/books.selectors.ts\">\n</ngrx-code-example>\n\n7. In the `book-list` folder, we want to have a service that fetches the data needed for the book list from an API. Create a file in the `book-list` folder named `books.service.ts`, which will call the Google Books API and return a list of books.\n\n<ngrx-code-example header=\"src/book-list/books-service.ts\" path=\"store-walkthrough/src/book-list/books-service.ts\">\n</ngrx-code-example>\n\n8. Add the `HttpClient` module using the `provideHttpClient` provider in the `app.config.ts` file in order to make HTTP requests using the `HttpClient`.\n\n<ngrx-code-example header=\"src/app.config.ts\" path=\"store-walkthrough/src/app.config.ts\">\n</ngrx-code-example>\n\n9. In the same folder (`book-list`), create the `BookList` with the following template. Update the `BookList` class to dispatch the `add` event.\n\n<ngrx-code-example header=\"src/book-list/book-list.ts\" path=\"store-walkthrough/src/book-list/book-list.ts\">\n</ngrx-code-example>\n\n10. Create a new _Component_ named `book-collection` in the `app` folder. Update the `BookCollection` template and class.\n\n<ngrx-code-example header=\"src/book-collection/book-collection.ts\" path=\"store-walkthrough/src/book-collection/book-collection.ts\">\n</ngrx-code-example>\n\n11. Add `BookList` and `BookCollection` to your `App` template, and to your imports in `app.ts` as well.\n\n<ngrx-code-example header=\"src/app.ts\" path=\"store-walkthrough/src/app.ts\">\n</ngrx-code-example>\n\n12. In the `App` class, add the selectors and corresponding actions to dispatch on `add` or `remove` method calls. Then subscribe to the Google Books API in order to update the state. (This should probably be handled by NgRx Effects, which you can read about [here](guide/effects). For the sake of this demo, NgRx Effects is not being included).\n\n<ngrx-code-example header=\"src/app.ts\" path=\"store-walkthrough/src/app.ts\">\n</ngrx-code-example>\n\nAnd that's it! Click the add and remove buttons to change the state.\n\nLet's cover what you did:\n\n- Defined actions to express events.\n- Defined two reducer functions to manage different parts of the state.\n- Registered the global state container that is available throughout your application.\n- Defined the state, as well as selectors that retrieve specific parts of the state.\n- Created two distinct components, as well as a service that fetches from the Google Books API.\n- Injected the `Store` and Google Books API services to dispatch actions and select the current state.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store/why.md",
    "content": "# Why use NgRx Store for State Management?\n\nNgRx Store provides state management for creating maintainable, explicit applications through the use of single state and actions in order to express state changes. In cases where you don't need a global, application-wide solution to manage state, consider using [NgRx Signals](guide/signals) which provides a solution for local state management.\n\n## When Should I Use NgRx Store for State Management?\n\nIn particular, you might use NgRx when you build an application with a lot of user interactions and multiple data sources, or when managing state in services are no longer sufficient.\n\nA good guideline that might help answer the question, \"Do I need NgRx Store?\" is the\n<a href=\"https://youtu.be/omnwu_etHTY\" target=\"_blank\">**SHARI**</a> principle:\n\n- **S**hared: state that is accessed by many components and services.\n\n- **H**ydrated: state that is persisted and rehydrated from external storage.\n\n- **A**vailable: state that needs to be available when re-entering routes.\n\n- **R**etrieved: state that must be retrieved with a side-effect.\n\n- **I**mpacted: state that is impacted by actions from other sources.\n\nHowever, realizing that using NgRx Store comes with some tradeoffs is also crucial. It is not meant to be the shortest or quickest way to write code. It also encourages the usage of many files.\n\nIt's also important to consider the patterns implemented with NgRx Store. A solid understanding of [`RxJS`](https://rxjs.dev) and [`Redux`](https://redux.js.org/) will be very beneficial before learning to use NgRx Store and the other state management libraries.\n\n## Key Concepts\n\n### Type Safety\n\nType safety is promoted throughout the architecture with reliance on the TypeScript compiler for program correctness. In addition to this, NgRx's strictness of type safety and the use of patterns lends itself well to the creation of higher quality code.\n\n### Immutability and Performance\n\n[Store](guide/store) is built on a single, immutable data structure which makes change detection a relatively straightforward task using the [`OnPush`](https://angular.dev/api/core/ChangeDetectionStrategy#OnPush) strategy. NgRx Store also provides APIs for creating memoized selector functions that optimize retrieving data from your state.\n\n### Encapsulation\n\nUsing NgRx [Effects](guide/effects) and [Store](guide/store), any interaction with external resources side effects such as network requests or web sockets, as well as any business logic, can be isolated from the UI. This isolation allows for more pure and simple components and upholds the single responsibility principle.\n\n### Serializability\n\nBy normalizing state changes and passing them through observables, NgRx provides serializability and ensures the state is predictably stored. This allows the state to be saved to external storage such as `localStorage`.\n\nThis also allows the inspection, download, upload, and the dispatch of actions all from the [Store Devtools](guide/store-devtools).\n\n### Testable\n\nBecause [Store](guide/store) uses pure functions for changing and selecting data from state, as well as the ability to isolate side effects from the UI, testing becomes very straightforward.\nNgRx also provides test resources such as `provideMockStore` and `provideMockActions` for isolated tests and an overall better test experience.\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store-devtools/config.md",
    "content": "# Instrumentation options\n\nWhen you call the instrumentation, you can give an optional configuration object. As stated, each property in the object provided is optional.\n\n## Configuration Object Properties\n\n### `maxAge`\n\nnumber (>1) | `false` - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. Default is `false` (infinite).\n\n### `logOnly`\n\nboolean - connect to the Devtools Extension in log-only mode. Default is `false` which enables all extension [features](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#features).\n\n### `autoPause`\n\nboolean - Pauses recording actions and state changes when the extension window is not open. Default is `false`.\n\n### `name`\n\nstring - the instance name to show on the monitor page. Default value is NgRx Store DevTools.\n\n### `monitor`\n\nfunction - the monitor function configuration that you want to hook.\n\n### `actionSanitizer`\n\nfunction - takes `action` object and id number as arguments, and should return an `action` object.\n\n### `stateSanitizer`\n\nfunction - takes `state` object and index as arguments, and should return a `state` object.\n\n### `serialize`\n\n- options\n  - `undefined` - will use regular `JSON.stringify` to send data\n  - `false` - will handle also circular references\n  - `true` - will handle also date, regex, undefined, primitives, error objects, symbols, maps, sets and functions\n  - object - which contains `date`, `regex`, `undefined`, `NaN`, `infinity`, `Error`, `Symbol`, `Map`, `Set` and `function` keys. For each of them, you can indicate if they have to be included by setting them to `true`. For function keys, you can also specify a custom function which handles serialization.\n\nFor more detailed information see [Redux DevTools Serialize](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#serialize)\n\n### `actionsSafelist` / `actionsBlocklist`\n\narray of strings as regex - actions types to be hidden / shown in the monitors, [more information here](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#actionsblacklist--actionswhitelist).\n\n### `predicate`\n\nfunction - called for every action before sending, takes state and action object, and returns `true` in case it allows sending the current data to the monitor, [more information here](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#predicate).\n\n### `connectInZone`\n\nboolean - property determines whether the extension connection is established within the Angular zone or not. When `false`, the connection is established outside the Angular zone to prevent unnecessary change detection cycles. Default is `false`.\n\n### `features`\n\nconfiguration object - containing properties for features than can be enabled or disabled in the browser extension Redux DevTools. These options are passed through to the browser extension verbatim. By default, all features are enabled. For more information visit the [Redux DevTools Docs](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#features)\n\n```typescript\nfeatures: {\n  pause: true, // start/pause recording of dispatched actions\n  lock: true, // lock/unlock dispatching actions and side effects\n  persist: true, // persist states on page reloading\n  export: true, // export history of actions in a file\n  import: 'custom', // import history of actions from a file\n  jump: true, // jump back and forth (time travelling)\n  skip: true, // skip (cancel) actions\n  reorder: true, // drag and drop actions in the history list\n  dispatch: true, // dispatch custom actions or action creators\n  test: true // generate tests for the selected actions\n},\n```\n\n## Example Object as provided in module imports\n\n<ngrx-code-example header=\"app.config.ts\">\n\n```ts\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideStoreDevtools({\n      maxAge: 25,\n      logOnly: false,\n      autoPause: true,\n      features: {\n        pause: false,\n        lock: true,\n        persist: true,\n      },\n    }),\n  ],\n};\n```\n\n</ngrx-code-example>\n\n<ngrx-docs-alert type=\"help\">\n\nAn example of the `@ngrx/store-devtools` setup in module-based applications is available at the [following link](https://v17.ngrx.io/guide/store-devtools).\n\n</ngrx-docs-alert>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store-devtools/index.md",
    "content": "# @ngrx/store-devtools\n\nStore Devtools provides developer tools and instrumentation for [Store](guide/store).\n\n## Installation\n\nDetailed installation instructions can be found on the [Installation](guide/store-devtools/install) page.\n\n## Setup\n\nInstrumentation with the Chrome / Firefox Extension\n\n1.  Download the [Redux Devtools Extension](https://github.com/reduxjs/redux-devtools/)\n\n2.  Provide the `provideStoreDevtools` to the application config:\n\n<ngrx-code-example header=\"main.ts\">\n\n```ts\nimport { isDevMode } from '@angular/core';\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { provideStore } from '@ngrx/store';\nimport { provideStoreDevtools } from '@ngrx/store-devtools';\n\nimport { AppComponent } from './app.component';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideStore(),\n    provideStoreDevtools({\n      maxAge: 25, // Retains last 25 states\n      logOnly: !isDevMode(), // Restrict extension to log-only mode\n      autoPause: true, // Pauses recording actions and state changes when the extension window is not open\n      trace: false, //  If set to true, will include stack trace for every dispatched action, so you can see it in trace tab jumping directly to that part of code\n      traceLimit: 75, // maximum stack trace frames to be stored (in case trace option was provided as true)\n      connectInZone: true, // If set to true, the connection is established within the Angular zone\n    }),\n  ],\n});\n```\n\n</ngrx-code-example>\n\n> More extension options and explanation, refer to [Redux Devtools Documentation](https://github.com/reduxjs/redux-devtools#documentation)\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store-devtools/install.md",
    "content": "# Installation\n\n## Installing with `ng add`\n\nYou can install the Store Devtools to your project with the following `ng add` command <a href=\"https://angular.dev/cli/add\" target=\"_blank\">(details here)</a>:\n\n```sh\nng add @ngrx/store-devtools@latest\n```\n\n### Optional `ng add` flags\n\n| flag        | description                                                                                                                                                                                 | value type | default value |\n| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- |\n| `--path`    | Path to the module that you wish to add the import for the `StoreDevtoolsModule` to.                                                                                                        | `string`   |\n| `--project` | Name of the project defined in your `angular.json` to help locating the module to add the `StoreDevtoolsModule` to.                                                                         | `string`   |\n| `--module`  | Name of file containing the module that you wish to add the import for the `StoreDevtoolsModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts`.  | `string`   | `app`         |\n| `--maxAge`  | Maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. 0 is infinite. Must be greater than 1 or 0. | `number`   | `25`          |\n\nThis command will automate the following steps:\n\n1. Update `package.json` > `dependencies` with `@ngrx/store-devtools`.\n2. Run `npm install` to install those dependencies.\n3. Add the devtools to the application config provider's using `provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() })`. The `maxAge` property will be set to the flag `maxAge` if provided.\n\n## Manual Installation\n\nYou can also install `@ngrx/store-devtools` manually using one of the following commands:\n\n<ngrx-docs-install package-name=\"@ngrx/store-devtools\"></ngrx-docs-install>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide/store-devtools/recipes/exclude.md",
    "content": "# Excluding Store Devtools In Production\n\nTo prevent Store Devtools from being included in your bundle, you can exclude it from the build process.\n\n## Step 1: Put Store Devtools In `environment.ts`\n\nTo exclude the DevTools, put `@ngrx/store-devtools` into an `environment.ts` file, which is replaced with `environment.prod.ts`.\n\n<ngrx-docs-alert type=\"help\">\n\nIf the environment files don't exist in your project you can use the `ng generate environments` command to create them.\n\n</ngrx-docs-alert>\n\nGiven the below example:\n\n<ngrx-code-example header=\"environments/environment.ts\">\n\n```ts\nimport { provideStoreDevtools } from '@ngrx/store-devtools';\n\nexport const environment = {\n  production: false,\n  providers: [provideStoreDevtools({ maxAge: 25 })],\n};\n```\n\n</ngrx-code-example>\n\n## Step 2: Import Environment File\n\nModify the `app.config.ts` file, where your application configuration resides, to specify `environment.providers`:\n\n<ngrx-code-example header=\"app.config.ts\">\n\n```ts\nimport { environment } from '../environments/environment';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [provideStore(), environment.providers],\n};\n```\n\n</ngrx-code-example>\n"
  },
  {
    "path": "projects/www/src/app/pages/guide.page.ts",
    "content": "import { Component, computed, inject } from '@angular/core';\nimport { NavigationEnd, Router, RouterOutlet } from '@angular/router';\nimport { MarkdownArticleComponent } from '../components/docs/markdown-article.component';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { filter, map, startWith } from 'rxjs';\nimport { Location } from '@angular/common';\nimport { GuideMenuService } from '../services/guide-menu.service';\nimport { GuideFooterComponent } from '../components/guide-footer.component';\n\n@Component({\n  selector: 'ngrx-guide-page',\n  imports: [RouterOutlet, MarkdownArticleComponent, GuideFooterComponent],\n  template: `\n    <ngrx-markdown-article>\n      <router-outlet></router-outlet>\n      <ngrx-guide-footer\n        [previousLink]=\"previousLink()\"\n        [nextLink]=\"nextLink()\"\n      ></ngrx-guide-footer>\n    </ngrx-markdown-article>\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n      }\n    `,\n  ],\n})\nexport default class GuidePageComponent {\n  router = inject(Router);\n  guideMenuService = inject(GuideMenuService);\n  location = inject(Location);\n  path = toSignal(\n    this.router.events.pipe(\n      filter((event) => event instanceof NavigationEnd),\n      map(() => {\n        return this.location.path(false);\n      }),\n      startWith(this.location.path(false))\n    )\n  );\n  previousLink = computed(() => {\n    const path = this.path();\n\n    return path ? this.guideMenuService.getPreviousLink(path) : null;\n  });\n  nextLink = computed(() => {\n    const path = this.path();\n\n    return path ? this.guideMenuService.getNextLink(path) : null;\n  });\n}\n"
  },
  {
    "path": "projects/www/src/app/pages/workshops.page.ts",
    "content": "import { Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { StyledBoxComponent } from '../components/styled-box.component';\n\n@Component({\n  selector: 'ngrx-workshops-page',\n  imports: [StyledBoxComponent, RouterLink],\n  template: `\n    <div class=\"article-width\">\n      <h1>Workshops</h1>\n\n      <p>\n        Are you struggling to maintain and enhance your existing Angular\n        applications? Do you want to harness the power of NgRx for state\n        management and explore the cutting-edge features in Angular? Join us for\n        in-depth workshops that will empower you to architect and modernize your\n        apps with confidence!\n      </p>\n\n      <p>\n        Take your Angular knowledge to the next level with\n        <a routerLink=\".\" fragment=\"team\">participating NgRx team members</a>.\n      </p>\n\n      <a routerLink=\".\" fragment=\"upcoming\" class=\"cta-button\">\n        View upcoming dates ↓\n      </a>\n\n      <h2>\n        Enterprise Angular Architectures with NgRx, Signals, and AI Assistants\n      </h2>\n\n      <p>\n        In the ever-evolving world of web development, staying ahead is crucial.\n        This workshop offers a hands-on approach to mastering architecture and\n        state management in Angular applications using both traditional NgRx\n        patterns and the modern Signal-based approach. It is designed for\n        developers, architects, and teams who want to revamp their existing\n        Angular applications, leverage the latest advancements in Angular and\n        NgRx ecosystems, and learn how to collaborate effectively with AI\n        assistants to implement predictable state management logic.\n      </p>\n\n      <h2>Workshop Highlights</h2>\n\n      <div class=\"features\">\n        <ngrx-styled-box>\n          <h3>Architectural Excellence</h3>\n          <p>\n            Learn how to design scalable and maintainable Angular applications\n            using proven architectural patterns. We will explore how to\n            structure applications, organize state management logic, and make\n            architectural decisions that support long-term sustainability.\n          </p>\n        </ngrx-styled-box>\n        <ngrx-styled-box>\n          <h3>Global NgRx Store</h3>\n          <p>\n            Master the powerful state management solution for complex Angular\n            applications. Learn how actions, reducers, effects, and selectors\n            work together to create a robust global state architecture that\n            scales with your application needs.\n          </p>\n        </ngrx-styled-box>\n        <ngrx-styled-box>\n          <h3>Angular Signals</h3>\n          <p>\n            Gain an in-depth understanding of Angular's powerful reactivity\n            system. Learn how to leverage Signals to create efficient\n            applications while maintaining clean, readable code and optimal\n            performance.\n          </p>\n        </ngrx-styled-box>\n        <ngrx-styled-box>\n          <h3>NgRx SignalStore</h3>\n          <p>\n            Explore the fastest-growing state management solution in the Angular\n            ecosystem. From foundational concepts to advanced techniques, learn\n            how to leverage SignalStore's robust and extensible design to\n            efficiently manage application state.\n          </p>\n        </ngrx-styled-box>\n        <ngrx-styled-box>\n          <h3>State Management Patterns</h3>\n          <p>\n            Understand when and how to apply local and global state management\n            strategies. Learn how to clearly separate responsibilities between\n            different layers of application state to build maintainable\n            architectures.\n          </p>\n        </ngrx-styled-box>\n        <ngrx-styled-box>\n          <h3>AI-Assisted Development</h3>\n          <p>\n            Discover how to collaborate effectively with modern AI assistants to\n            accelerate development without sacrificing architectural clarity.\n            Through practical examples, learn how structured prompts and\n            reusable AI skills can help generate predictable and maintainable\n            state management logic.\n          </p>\n        </ngrx-styled-box>\n      </div>\n\n      <h2>Agenda</h2>\n\n      <div class=\"agenda\">\n        <div class=\"day\">\n          <div class=\"day-header\">\n            <h4>Day 1</h4>\n            <h3>Global Store Foundation</h3>\n          </div>\n          <ul>\n            <li>Introduction to State Management and Redux Architecture</li>\n            <li>Building Blocks: Actions, Reducers, and Selectors</li>\n            <li>Modern Store Features: Action Group and Feature Creators</li>\n            <li>Effects Management: Functional and Class-Based Approach</li>\n            <li>\n              Enhancing DX: Leveraging Devtools, Router Store, and Entities\n            </li>\n            <li>Store Testing: Validating Predictable Application Behavior</li>\n          </ul>\n        </div>\n        <div class=\"day\">\n          <div class=\"day-header\">\n            <h4>Day 2</h4>\n            <h3>Signals and SignalStore</h3>\n          </div>\n          <ul>\n            <li>Angular Signals: Core Concepts and Benefits</li>\n            <li>\n              Signal Extensions: Deep Signals, SignalState, and SignalMethod\n            </li>\n            <li>State Manipulation: Building Custom Updaters</li>\n            <li>Bridging Worlds: RxJS Integration with Signals</li>\n            <li>SignalStore: Core Concepts and Architecture</li>\n            <li>\n              State Management Patterns: Local and Global State Strategies\n            </li>\n          </ul>\n        </div>\n        <div class=\"day\">\n          <div class=\"day-header\">\n            <h4>Day 3</h4>\n            <h3>Advanced SignalStore and AI Assistants</h3>\n          </div>\n          <ul>\n            <li>SignalStore Extensibility: Building Custom Store Features</li>\n            <li>\n              Entity Management: Working with Collections Using the Entities\n              Plugin\n            </li>\n            <li>\n              Resource Integration: Managing Data Fetching with Angular\n              Resources\n            </li>\n            <li>\n              Event-Based Workflows: Coordinating Complex Logic with the Events\n              Plugin\n            </li>\n            <li>\n              Testing SignalStore: Best Practices for Reliable Store Logic\n            </li>\n            <li>\n              AI-Assisted Development: Using Skills to Generate Predictable\n              State Management Logic\n            </li>\n          </ul>\n        </div>\n      </div>\n\n      <h2>Flexible Attendance Options</h2>\n\n      <p>Choose the workshop option that best fits your team's needs:</p>\n\n      <ul>\n        <li>\n          <strong>Full NgRx Experience:</strong> Attend all three days to learn\n          NgRx from basics to the most advanced topics!\n        </li>\n        <li>\n          <strong>Traditional NgRx:</strong> Day 1 focuses on the proven\n          patterns of global state management with NgRx Store.\n        </li>\n        <li>\n          <strong>SignalStore Bundle:</strong> Days 2 and 3 explore the exciting\n          world of Signal-based state management and AI-assisted development.\n        </li>\n      </ul>\n\n      <p>\n        All days are designed to be independent and can be attended separately,\n        although the complete three-day workshop provides the most comprehensive\n        learning experience.\n      </p>\n\n      <h2 id=\"upcoming\">Upcoming Workshops</h2>\n\n      <div class=\"upcoming\">\n        <div class=\"workshop-item\">\n          <div class=\"workshop-meta\">\n            <span class=\"workshop-date\">May 13&ndash;15, 2026</span>\n            <span class=\"workshop-tz\">US-friendly timezone</span>\n            <span class=\"workshop-time\">\n              Start time: 8 AM (PT) / 11 AM (ET)\n            </span>\n            <span class=\"workshop-duration\">8 hours / day</span>\n          </div>\n          <a\n            href=\"https://ti.to/ngrx/workshop-may-2025-us?source=ngrx_io\"\n            target=\"_blank\"\n            class=\"workshop-register\"\n          >\n            Register\n          </a>\n        </div>\n        <div class=\"workshop-item\">\n          <div class=\"workshop-meta\">\n            <span class=\"workshop-date\">May 20&ndash;22, 2026</span>\n            <span class=\"workshop-tz\">EU-friendly timezone</span>\n            <span class=\"workshop-time\">Start time: 10 AM (CET)</span>\n            <span class=\"workshop-duration\">8 hours / day</span>\n          </div>\n          <a\n            href=\"https://ti.to/ngrx/workshop-may-2025-eu?source=ngrx_io\"\n            target=\"_blank\"\n            class=\"workshop-register\"\n          >\n            Register\n          </a>\n        </div>\n      </div>\n\n      <h2 id=\"team\">Participating NgRx Team Members</h2>\n\n      <div class=\"team\">\n        <div class=\"member\">\n          <div class=\"member-photo\">\n            <img src=\"/images/bios/alex-okrushko.jpg\" alt=\"Alex Okrushko\" />\n          </div>\n          <div class=\"member-info\">\n            <h3>Alex Okrushko</h3>\n            <div class=\"member-bio\">\n              Alex Okrushko is a core member of the NgRx team, a Google\n              Developer Expert in Angular, an AngularToronto organizer, and a\n              co-organizer of the official\n              <a href=\"https://discord.gg/angular\" target=\"_blank\"\n                >Angular Discord</a\n              >.\n              <p>\n                Alex has been contributing to NgRx since 2018. Among his\n                contributions are creator factories (such as\n                <code>createAction()</code> and <code>createReducer()</code>),\n                the overall type strictness of the NgRx code and the\n                introduction of the <code>&#64;ngrx/component-store</code>\n                library.\n              </p>\n              <p>\n                Over the last 17 years Alex worked at companies such as Google,\n                Cisco and Snowflake where he built modern web apps, processes\n                and teams.\n              </p>\n              <p>\n                In his free time, he loves to learn and share the knowledge,\n                provides NgRx workshops, and helps with\n                <a href=\"https://ts.dev/style\" target=\"_blank\">ts.dev/style</a>\n                - the popular TypeScript style guide.\n              </p>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"member\">\n          <div class=\"member-photo\">\n            <img src=\"/images/bios/marko.jpg\" alt=\"Marko Stanimirović\" />\n          </div>\n          <div class=\"member-info\">\n            <h3>Marko Stanimirović</h3>\n            <div class=\"member-bio\">\n              Marko Stanimirović is a core member of the NgRx team (contributing\n              since 2020), a Google Developer Expert in Angular, and a\n              co-organizer of the NG Belgrade conference.\n              <p>\n                Marko's contributions include <code>createFeature()</code>,\n                <code>createActionGroup()</code>, functional effects, an\n                overhaul of the <code>&#64;ngrx/component</code> library, and\n                continuous maintenance of the NgRx platform. He is also a lead\n                author of the <code>&#64;ngrx/signals</code> library.\n              </p>\n              <p>\n                He enjoys contributing to open-source software, sharing\n                knowledge through technical articles and talks, and playing\n                guitar.\n              </p>\n              <p>\n                Marko holds a Master of Science in Software Engineering from the\n                University of Belgrade.\n              </p>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"member\">\n          <div class=\"member-photo\">\n            <img src=\"/images/bios/rainer.jpg\" alt=\"Rainer Hahnekamp\" />\n          </div>\n          <div class=\"member-info\">\n            <h3>Rainer Hahnekamp</h3>\n            <div class=\"member-bio\">\n              Rainer Hahnekamp is a core member of the NgRx team (contributing\n              since 2023), a Google Developer Expert in Angular, a trainer and\n              consultant in the Angular Architects expert network, and the\n              author of ng-news, a weekly Angular newsletter.\n              <p>\n                His work on the <code>&#64;ngrx/signals</code> package spans\n                several key features, including <code>signalMethod()</code>,\n                <code>withLinkedState()</code>, and <code>withFeature()</code>,\n                alongside continuous maintenance of the NgRx platform. Rainer\n                also maintains NgRx Toolkit, a community-driven collection of\n                plugins for NgRx SignalStore.\n              </p>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <h2>Contact Us</h2>\n\n      <p>\n        Are you interested in a dedicated in-person or online workshop for your\n        team? Any questions about the workshops? Please reach out to us directly\n        at\n        <a href=\"mailto:marko@ngrx.io\">marko&#64;ngrx.io</a>\n      </p>\n    </div>\n  `,\n  styles: [\n    `\n      :host {\n        display: block;\n        padding: 0 24px 24px;\n\n        @media only screen and (max-width: 1280px) {\n          padding-top: 62px;\n        }\n      }\n\n      .article-width {\n        max-width: 960px;\n        margin: 0 auto;\n      }\n\n      .cta-button {\n        display: inline-block;\n        margin: 24px 0 24px;\n        padding: 12px 28px;\n        border-radius: 8px;\n        background-color: rgba(170, 27, 182, 0.15);\n        border: 1px solid rgba(170, 27, 182, 0.35);\n        color: var(--ngrx-link);\n        font-weight: 600;\n        font-size: 1rem;\n        text-decoration: none;\n        transition:\n          background-color 0.15s ease,\n          border-color 0.15s ease;\n      }\n\n      .cta-button:hover {\n        background-color: rgba(170, 27, 182, 0.25);\n        border-color: rgba(170, 27, 182, 0.55);\n      }\n\n      ngrx-styled-box {\n        padding: 16px 24px 24px;\n      }\n\n      .features {\n        display: grid;\n        grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n        gap: 24px;\n        margin: 24px 0;\n      }\n\n      .agenda {\n        display: flex;\n        flex-direction: column;\n        gap: 16px;\n        padding: 16px 0;\n      }\n\n      .agenda .day {\n        display: grid;\n        grid-template-columns: 200px 1fr;\n        gap: 0 32px;\n        padding: 24px 28px;\n        border-radius: 8px;\n        border-left: 4px solid rgba(170, 27, 182, 0.5);\n      }\n\n      .agenda .day:nth-child(1) {\n        background-color: rgba(170, 27, 182, 0.06);\n        border-left-color: rgba(170, 27, 182, 0.3);\n      }\n\n      .agenda .day:nth-child(2) {\n        background-color: rgba(170, 27, 182, 0.12);\n        border-left-color: rgba(170, 27, 182, 0.5);\n      }\n\n      .agenda .day:nth-child(3) {\n        background-color: rgba(170, 27, 182, 0.2);\n        border-left-color: rgba(170, 27, 182, 0.7);\n      }\n\n      .agenda .day-header {\n        display: flex;\n        flex-direction: column;\n        justify-content: flex-start;\n        padding-top: 4px;\n        border-right: 1px solid rgba(170, 27, 182, 0.2);\n        padding-right: 32px;\n      }\n\n      .agenda h4 {\n        text-transform: uppercase;\n        color: var(--ngrx-link);\n        margin: 0 0 6px;\n        font-size: 0.75rem;\n        letter-spacing: 0.08em;\n      }\n\n      .agenda h3 {\n        margin: 0;\n        font-size: 1rem;\n        line-height: 1.4;\n      }\n\n      .agenda ul {\n        padding: 0 0 0 20px;\n        margin: 0;\n        line-height: 1.8;\n      }\n\n      @media only screen and (max-width: 640px) {\n        .agenda .day {\n          grid-template-columns: 1fr;\n        }\n\n        .agenda .day-header {\n          border-right: none;\n          border-bottom: 1px solid rgba(170, 27, 182, 0.2);\n          padding-right: 0;\n          padding-bottom: 12px;\n          margin-bottom: 12px;\n        }\n      }\n\n      .upcoming {\n        display: flex;\n        flex-direction: column;\n        gap: 12px;\n        padding: 16px 0;\n      }\n\n      .workshop-item {\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n        gap: 24px;\n        padding: 20px 28px;\n        border-radius: 8px;\n        border-left: 4px solid rgba(170, 27, 182, 0.5);\n        background-color: rgba(170, 27, 182, 0.06);\n      }\n\n      .workshop-meta {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        gap: 6px 8px;\n      }\n\n      .workshop-date {\n        flex: 0 0 100%;\n        font-weight: 600;\n        font-size: 1rem;\n        margin-bottom: 2px;\n      }\n\n      .workshop-tz,\n      .workshop-time,\n      .workshop-duration {\n        font-size: 0.8rem;\n        padding: 2px 10px;\n        border-radius: 20px;\n        background-color: rgba(170, 27, 182, 0.1);\n        border: 1px solid rgba(170, 27, 182, 0.2);\n        color: var(--ngrx-link);\n        opacity: 0.85;\n      }\n\n      .workshop-register {\n        flex-shrink: 0;\n        display: inline-block;\n        padding: 8px 20px;\n        border-radius: 6px;\n        background-color: rgba(170, 27, 182, 0.15);\n        color: var(--ngrx-link);\n        font-weight: 600;\n        font-size: 0.875rem;\n        text-decoration: none;\n        border: 1px solid rgba(170, 27, 182, 0.3);\n        transition:\n          background-color 0.15s ease,\n          border-color 0.15s ease;\n      }\n\n      .workshop-register:hover {\n        background-color: rgba(170, 27, 182, 0.25);\n        border-color: rgba(170, 27, 182, 0.5);\n      }\n\n      @media only screen and (max-width: 640px) {\n        .workshop-item {\n          flex-direction: column;\n          align-items: flex-start;\n        }\n      }\n\n      .team {\n        display: flex;\n        flex-direction: column;\n        gap: 24px;\n        padding: 16px 0;\n      }\n\n      .member {\n        display: grid;\n        grid-template-columns: 160px 1fr;\n        gap: 32px;\n        align-items: start;\n        padding: 28px;\n        border-radius: 8px;\n        background-color: rgba(170, 27, 182, 0.06);\n        border-left: 4px solid rgba(170, 27, 182, 0.35);\n      }\n\n      .member-photo {\n        width: 160px;\n        height: 160px;\n        border-radius: 50%;\n        overflow: hidden;\n        flex-shrink: 0;\n        border: 3px solid rgba(170, 27, 182, 0.3);\n      }\n\n      .member-photo img {\n        width: 100%;\n        height: 100%;\n        object-fit: cover;\n        object-position: top center;\n        display: block;\n      }\n\n      .member-info h3 {\n        margin: 0 0 12px;\n        font-size: 1.2rem;\n      }\n\n      .member-bio {\n        line-height: 1.7;\n        font-size: 0.95rem;\n      }\n\n      .member-bio p {\n        margin: 10px 0 0;\n      }\n\n      @media only screen and (max-width: 640px) {\n        .member {\n          grid-template-columns: 1fr;\n        }\n\n        .member-photo {\n          width: 120px;\n          height: 120px;\n        }\n      }\n    `,\n  ],\n})\nexport default class WorkshopsPageComponent {}\n"
  },
  {
    "path": "projects/www/src/app/reference/reference.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport {\n  ApiMemberSummary,\n  CanonicalReference,\n  MinimizedApiPackageReport,\n  ParsedCanonicalReference,\n} from '@ngrx-io/shared';\nimport { packageNames, packages } from './api-report.min.json';\n\nconst modules = import.meta.glob('./**/*.json');\n\n@Injectable({ providedIn: 'root' })\nexport class ReferenceService {\n  getMinifiedApiReport(): MinimizedApiPackageReport {\n    return { packageNames, packages } as unknown as MinimizedApiPackageReport;\n  }\n\n  loadReferenceData(pkg: string, symbol: string): Promise<ApiMemberSummary> {\n    /**\n     * Wrapping this up in a Zone-aware promise for server-side rendering\n     */\n    return new Promise<ApiMemberSummary>((resolve, reject) => {\n      const path = `${pkg}/${symbol}`;\n\n      if (!modules[`./${path}.json`]) {\n        throw new Error(\n          `Module not found: ${pkg}/${path}. Tried loading from ${path}.`\n        );\n      }\n\n      modules[`./${path}.json`]()\n        .then((module) => {\n          resolve((module as { default: ApiMemberSummary }).default);\n        })\n        .catch(reject);\n    });\n  }\n\n  loadFromCanonicalReference(\n    canonicalReference: CanonicalReference\n  ): Promise<ApiMemberSummary> {\n    const parsed = new ParsedCanonicalReference(canonicalReference);\n    const [_ngrx, ...rest] = parsed.package.split('/');\n\n    return this.loadReferenceData(rest.join('/'), parsed.name);\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/services/contributors.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { map, of } from 'rxjs';\nimport contributorsData from '../data/contributors.json';\n\nexport interface ContributorGroup {\n  name: string;\n  order: number;\n  contributors: Contributor[];\n}\n\nexport interface Contributor {\n  group: string;\n  name: string;\n  picture?: string;\n  website?: string;\n  twitter?: string;\n  bio?: string;\n  isFlipped?: boolean;\n  pictureUrl?: string;\n}\n\nexport interface GroupNav {\n  name: string;\n  order: number;\n}\n\nconst knownGroups = ['Core', 'Alumni'];\n\n@Injectable({ providedIn: 'root' })\nexport class ContributorsService {\n  getContributors() {\n    const contribsObj = contributorsData as { [key: string]: Contributor };\n    return of(contribsObj).pipe(\n      map((contribs) => {\n        const contribMap: { [name: string]: Contributor[] } = {};\n        Object.keys(contribs).forEach((key) => {\n          const contributor = contribs[key];\n          const group = contributor.group;\n          const contribGroup = contribMap[group];\n          if (contribGroup) {\n            contribGroup.push(contributor);\n          } else {\n            contribMap[group] = [contributor];\n          }\n        });\n        return contribMap;\n      }),\n      map((cmap) => {\n        return Object.keys(cmap)\n          .map((key) => {\n            const order = knownGroups.indexOf(key);\n            return {\n              name: key,\n              order: order === -1 ? knownGroups.length : order,\n              contributors: cmap[key].sort(this.compareContributors),\n            } as ContributorGroup;\n          })\n          .sort(this.compareGroups);\n      })\n    );\n  }\n  compareContributors(l: Contributor, r: Contributor) {\n    return l.name.toUpperCase() > r.name.toUpperCase() ? 1 : -1;\n  }\n\n  compareGroups(l: ContributorGroup, r: ContributorGroup) {\n    return l.order === r.order\n      ? l.name > r.name\n        ? 1\n        : -1\n      : l.order > r.order\n      ? 1\n      : -1;\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/services/guide-menu.service.ts",
    "content": "import { Injectable } from '@angular/core';\n\nexport type Link = { kind: 'link'; url: string; text: string };\nexport type Section = {\n  kind: 'section';\n  title: string;\n  children: (Link | Section | LineBreak)[];\n};\nexport type LineBreak = { kind: 'break' };\nexport type FlattenedLink = { url: string; text: string; parents: string[] };\n\nexport const link = (text: string, url: string): Link => ({\n  kind: 'link',\n  url,\n  text,\n});\nexport const section = (\n  title: string,\n  children: (Link | Section | LineBreak)[]\n): Section => ({\n  kind: 'section',\n  title,\n  children,\n});\nexport const lineBreak = (): LineBreak => ({ kind: 'break' });\n\nconst MORE_PACKAGES = 'More Packages';\n\n@Injectable({ providedIn: 'root' })\nexport class GuideMenuService {\n  private readonly links = section('NgRx Guide', [\n    section('Store', [\n      link('Why use Store?', '/guide/store/why'),\n      link('Getting Started', '/guide/store'),\n      link('Walkthrough', '/guide/store/walkthrough'),\n      link('Installation', '/guide/store/install'),\n      section('Architecture', [\n        link('Actions', '/guide/store/actions'),\n        link('Reducers', '/guide/store/reducers'),\n        link('Selectors', '/guide/store/selectors'),\n      ]),\n      section('Advanced', [\n        link('Meta-Reducers', '/guide/store/metareducers'),\n        link('Feature Creators', '/guide/store/feature-creators'),\n        link('Action Groups', '/guide/store/action-groups'),\n      ]),\n      section('Recipes', [\n        link('Injecting Reducers', '/guide/store/recipes/injecting'),\n        link('Downgrade for AngularJS', '/guide/store/recipes/downgrade'),\n      ]),\n      section('Devtools', [\n        link('Overview', '/guide/store-devtools'),\n        link('Installation', '/guide/store-devtools/install'),\n        link('Configuration', '/guide/store-devtools/config'),\n        section('Recipes', [\n          link(\n            'Exclude from Production',\n            '/guide/store-devtools/recipes/exclude'\n          ),\n        ]),\n      ]),\n      link('Runtime Checks', '/guide/store/configuration/runtime-checks'),\n      link('Testing', '/guide/store/testing'),\n    ]),\n    section('Effects', [\n      link('Overview', '/guide/effects'),\n      link('Installation', '/guide/effects/install'),\n      link('Testing', '/guide/effects/testing'),\n      link('Lifecycle', '/guide/effects/lifecycle'),\n      link('Operators', '/guide/effects/operators'),\n    ]),\n    section('Signals', [\n      link('Overview', '/guide/signals'),\n      link('Installation', '/guide/signals/install'),\n      section('SignalStore', [\n        link('Core Concepts', '/guide/signals/signal-store'),\n        link('Lifecycle Hooks', '/guide/signals/signal-store/lifecycle-hooks'),\n        link(\n          'Custom Store Properties',\n          '/guide/signals/signal-store/custom-store-properties'\n        ),\n        link('Linked State', '/guide/signals/signal-store/linked-state'),\n        link('State Tracking', '/guide/signals/signal-store/state-tracking'),\n        link(\n          'Private Store Members',\n          '/guide/signals/signal-store/private-store-members'\n        ),\n        link(\n          'Custom Store Features',\n          '/guide/signals/signal-store/custom-store-features'\n        ),\n        link(\n          'Entity Management',\n          '/guide/signals/signal-store/entity-management'\n        ),\n        link('Events', '/guide/signals/signal-store/events'),\n        link('Testing', '/guide/signals/signal-store/testing'),\n      ]),\n      link('SignalState', '/guide/signals/signal-state'),\n      link('DeepComputed', '/guide/signals/deep-computed'),\n      link('SignalMethod', '/guide/signals/signal-method'),\n      link('RxJS Integration', '/guide/signals/rxjs-integration'),\n      link('FAQ', '/guide/signals/faq'),\n    ]),\n    section('Entity', [\n      link('Overview', '/guide/entity'),\n      link('Installation', '/guide/entity/install'),\n      link('Entity Interfaces', '/guide/entity/interfaces'),\n      link('Entity Adapter', '/guide/entity/adapter'),\n      section('Recipes', [\n        link(\n          'Additional State Properties',\n          '/guide/entity/recipes/additional-state-properties'\n        ),\n        link(\n          'Entity Adapter with Feature Creator',\n          '/guide/entity/recipes/entity-adapter-with-feature-creator'\n        ),\n      ]),\n    ]),\n    section('Router Store', [\n      link('Overview', '/guide/router-store'),\n      link('Installation', '/guide/router-store/install'),\n      link('Actions', '/guide/router-store/actions'),\n      link('Selectors', '/guide/router-store/selectors'),\n      link('Configuration', '/guide/router-store/configuration'),\n    ]),\n    section('Operators', [\n      link('Overview', '/guide/operators'),\n      link('Installation', '/guide/operators/install'),\n      link('Operators', '/guide/operators/operators'),\n    ]),\n    section(MORE_PACKAGES, [\n      section('Component Store', [\n        link('Overview', '/guide/component-store'),\n        link('Installation', '/guide/component-store/install'),\n        section('Architecture', [\n          link('Initialization', '/guide/component-store/initialization'),\n          link('Read', '/guide/component-store/read'),\n          link('Write', '/guide/component-store/write'),\n          link('Effects', '/guide/component-store/effect'),\n        ]),\n        link('Lifecycle', '/guide/component-store/lifecycle'),\n        link('ComponentStore vs Store', '/guide/component-store/comparison'),\n        link('Usage', '/guide/component-store/usage'),\n      ]),\n      section('Component', [\n        link('Overview', '/guide/component'),\n        link('Installation', '/guide/component/install'),\n        link('Let Directive', '/guide/component/let'),\n        link('Push Pipe', '/guide/component/push'),\n      ]),\n      section('Data', [\n        link('Overview', '/guide/data'),\n        link('Installation', '/guide/data/install'),\n        section('Architecture', [\n          link('Overview', '/guide/data/architecture-overview'),\n          link('Entity Metadata', '/guide/data/entity-metadata'),\n          link('Entity Actions', '/guide/data/entity-actions'),\n          link('Entity Collection', '/guide/data/entity-collection'),\n          link(\n            'Entity Collection Service',\n            '/guide/data/entity-collection-service'\n          ),\n          link('Entity Dataservice', '/guide/data/entity-dataservice'),\n          link('Entity Effects', '/guide/data/entity-effects'),\n          link('Entity Reducer', '/guide/data/entity-reducer'),\n          link('Entity Services', '/guide/data/entity-services'),\n        ]),\n        section('Advanced', [\n          link('Save Multiple Entities', '/guide/data/save-entities'),\n          link('Entity Change Tracking', '/guide/data/entity-change-tracker'),\n          link('Extension Points', '/guide/data/extension-points'),\n        ]),\n        link('FAQ', '/guide/data/faq'),\n        link('Limitations', '/guide/data/limitations'),\n      ]),\n    ]),\n    lineBreak(),\n    section('Schematics', [\n      link('Overview', '/guide/schematics'),\n      link('Installation', '/guide/schematics/install'),\n      section('Collection', [\n        link('Store', '/guide/schematics/store'),\n        link('Action', '/guide/schematics/action'),\n        link('Reducer', '/guide/schematics/reducer'),\n        link('Selector', '/guide/schematics/selector'),\n        link('Container', '/guide/schematics/container'),\n        link('Effect', '/guide/schematics/effect'),\n        link('Entity', '/guide/schematics/entity'),\n        link('Feature', '/guide/schematics/feature'),\n      ]),\n    ]),\n    section('ESLint Plugin', [\n      link('Overview', '/guide/eslint-plugin'),\n      link('Installation', '/guide/eslint-plugin/install'),\n    ]),\n    lineBreak(),\n    section('Developer Resources', [\n      link('Nightlies', '/guide/nightlies'),\n      section('Migrations', [\n        link('V21', '/guide/migration/v21'),\n        link('V20', '/guide/migration/v20'),\n        link('V19', '/guide/migration/v19'),\n        link('V18', '/guide/migration/v18'),\n        link('V17', '/guide/migration/v17'),\n        link('V16', '/guide/migration/v16'),\n        link('V15', '/guide/migration/v15'),\n        link('V14', '/guide/migration/v14'),\n        link('V13', '/guide/migration/v13'),\n        link('V12', '/guide/migration/v12'),\n        link('V11', '/guide/migration/v11'),\n        link('V10', '/guide/migration/v10'),\n        link('V9', '/guide/migration/v9'),\n        link('V8', '/guide/migration/v8'),\n        link('V7', '/guide/migration/v7'),\n        link('V4', '/guide/migration/v4'),\n      ]),\n    ]),\n  ]);\n\n  private readonly flattenedLinks = this.links.children.flatMap((section) =>\n    section.kind === 'section' ? this.flattenLinks(section) : []\n  );\n\n  private flattenLinks(\n    section: Section,\n    parents: string[] = [section.title]\n  ): FlattenedLink[] {\n    const links: FlattenedLink[] = [];\n\n    for (const child of section.children) {\n      if (child.kind === 'link') {\n        links.push({\n          url: child.url,\n          text: child.text,\n          parents: parents.filter((parent) => parent !== MORE_PACKAGES),\n        });\n      } else if (child.kind === 'section') {\n        links.push(...this.flattenLinks(child, [...parents, child.title]));\n      }\n    }\n\n    return links;\n  }\n\n  getMenu(): Section {\n    return this.links;\n  }\n\n  getNextLink(url: string): FlattenedLink | null {\n    const index = this.flattenedLinks.findIndex((link) => link.url === url);\n\n    if (index === -1) {\n      return null;\n    }\n\n    return this.flattenedLinks[index + 1] ?? null;\n  }\n\n  getPreviousLink(url: string): FlattenedLink | null {\n    const index = this.flattenedLinks.findIndex((link) => link.url === url);\n\n    if (index === -1) {\n      return null;\n    }\n\n    return this.flattenedLinks[index - 1] ?? null;\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/services/markdown.service.ts",
    "content": "import { MarkedSetupService } from '@analogjs/content';\nimport { Injectable } from '@angular/core';\nimport { CanonicalReference, ParsedCanonicalReference } from '@ngrx-io/shared';\nimport hljs from 'highlight.js';\nimport { Marked, marked } from 'marked';\nimport { markedHighlight } from 'marked-highlight';\n\nexport const CanonicalReferenceExtension = {\n  name: 'canonicalReference',\n  level: 'inline',\n  tokenizer(this: any, src: string): any {\n    const rule = /@?[\\w/-]+![\\w]+:[\\w]+/;\n    const match = rule.exec(src);\n    if (match) {\n      const parsed = new ParsedCanonicalReference(\n        match[0] as CanonicalReference\n      );\n\n      const index = src.indexOf(match[0]);\n      const before = src.slice(0, index);\n      const after = src.slice(index + match[0].length);\n\n      const token = {\n        type: 'canonicalReference',\n        raw: src,\n        text: match[0],\n        name: parsed.name,\n        canonicalReference: parsed.referenceString,\n        before,\n        after,\n        beforeTokens: [],\n        afterTokens: [],\n      };\n\n      this.lexer.inline(token.before, token.beforeTokens);\n      this.lexer.inline(token.after, token.afterTokens);\n\n      return token;\n    }\n  },\n  renderer(this: any, token: any) {\n    return `${this.parser.parseInline(\n      token.beforeTokens\n    )}<ngrx-docs-symbol-link reference=\"${\n      token.canonicalReference\n    }\"></ngrx-docs-symbol-link>${this.parser.parseInline(token.afterTokens)}`;\n  },\n  childTokens: [],\n};\n\n@Injectable()\nexport class NgRxMarkedSetupService extends MarkedSetupService {\n  private _marked = new Marked(\n    markedHighlight({\n      highlight: (code, lang) => {\n        const language = hljs.getLanguage(lang) ? lang : 'plaintext';\n        return hljs.highlight(code, { language }).value;\n      },\n    }),\n    {\n      extensions: [CanonicalReferenceExtension],\n    }\n  );\n\n  constructor() {\n    super();\n\n    this.getMarkedInstance().use();\n  }\n\n  override getMarkedInstance(): typeof marked {\n    // eslint-disable-next-line no-underscore-dangle\n    return this._marked as unknown as typeof marked;\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/services/theme.service.ts",
    "content": "import { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { effect, inject, Injectable, PLATFORM_ID, signal } from '@angular/core';\n\nexport type Theme = 'light' | 'dark';\n\n@Injectable({ providedIn: 'root' })\nexport class ThemeService {\n  private platformId = inject(PLATFORM_ID);\n  private document = inject(DOCUMENT);\n\n  theme = signal<Theme>('dark');\n\n  constructor() {\n    if (isPlatformBrowser(this.platformId)) {\n      this.initializeTheme();\n    }\n\n    effect(() => {\n      if (isPlatformBrowser(this.platformId)) {\n        this.document.body.classList.toggle(\n          'light-theme',\n          this.theme() === 'light'\n        );\n        try {\n          localStorage.setItem('ngrx-theme', this.theme());\n        } catch {\n          // localStorage may be blocked\n        }\n      }\n    });\n  }\n\n  private initializeTheme(): void {\n    try {\n      const stored = localStorage.getItem('ngrx-theme') as Theme | null;\n      if (stored === 'light' || stored === 'dark') {\n        this.theme.set(stored);\n      } else {\n        this.theme.set(\n          matchMedia?.('(prefers-color-scheme: dark)').matches\n            ? 'dark'\n            : 'light'\n        );\n      }\n    } catch {\n      this.theme.set('dark');\n    }\n  }\n\n  toggle(): void {\n    this.theme.set(this.theme() === 'dark' ? 'light' : 'dark');\n  }\n}\n"
  },
  {
    "path": "projects/www/src/app/services/versionInfo.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport versionInfoData from '../data/versionInfo.json';\n\ninterface PreviousVersion {\n  url: string;\n  title: string;\n}\n\ninterface VersionInfo {\n  currentVersion: string;\n  docVersions: PreviousVersion[];\n}\n\n@Injectable({ providedIn: 'root' })\nexport class VersionInfoService {\n  readonly #versionInfo = versionInfoData as VersionInfo;\n  readonly currentVersion = this.#versionInfo.currentVersion;\n  readonly previousVersions = this.#versionInfo.docVersions;\n}\n"
  },
  {
    "path": "projects/www/src/main.server.ts",
    "content": "import '@angular/platform-server/init';\n\nimport { render } from '@analogjs/router/server';\n\nimport { AppComponent } from './app/app.component';\nimport { config } from './app/app.config.server';\n\nexport default render(AppComponent, config);\n"
  },
  {
    "path": "projects/www/src/main.ts",
    "content": "import { bootstrapApplication } from '@angular/platform-browser';\nimport { AppComponent } from './app/app.component';\nimport { config } from './app/app.config.browser';\n\n// Deactivate service worker from old ngrx.io app\n// TODO: Remove after 6 months\nif (typeof window !== 'undefined') {\n  window.navigator.serviceWorker.getRegistrations().then((registrations) => {\n    for (const registration of registrations) {\n      registration.unregister();\n    }\n  });\n}\n\nbootstrapApplication(AppComponent, config).catch((err) => console.error(err));\n"
  },
  {
    "path": "projects/www/src/shared/api-report.models.ts",
    "content": "export enum ApiMemberKind {\n  EntryPoint = 'EntryPoint',\n  Function = 'Function',\n  Class = 'Class',\n  TypeAlias = 'TypeAlias',\n  Interface = 'Interface',\n  Enum = 'Enum',\n  Variable = 'Variable',\n  Property = 'Property',\n  Method = 'Method',\n}\n\nexport enum ApiExcerptTokenKind {\n  Content = 'Content',\n  Reference = 'Reference',\n}\n\nexport enum ApiReleaseTag {\n  Public = 'Public',\n  Beta = 'Beta',\n  Alpha = 'Alpha',\n  Internal = 'Internal',\n}\n\nexport type CanonicalReferenceKind =\n  | 'var'\n  | 'function'\n  | 'class'\n  | 'interface'\n  | 'enum'\n  | 'type'\n  | 'member';\n\nexport type CanonicalReference =\n  | `${string}!${string}:${CanonicalReferenceKind}`\n  | `${string}!${string}:${CanonicalReferenceKind}(${number})`\n  | `${string}!~${string}:${CanonicalReferenceKind}`\n  | `${string}!~${string}:${CanonicalReferenceKind}(${number})`;\n\nexport class ParsedCanonicalReference {\n  readonly package: string;\n  readonly name: string;\n  readonly kind: CanonicalReferenceKind;\n  readonly index: number | undefined;\n  readonly isPrivate: boolean;\n\n  constructor(readonly referenceString: CanonicalReference) {\n    const [packagePart, restOfParts] = referenceString.split('!');\n\n    this.package = packagePart === '' ? '@@internal' : packagePart;\n\n    const [symbolName, kindParts] = restOfParts.split(':');\n    if (!symbolName || !kindParts) {\n      throw new Error(`Invalid reference: ${referenceString}`);\n    }\n\n    this.name = symbolName;\n    const [memberKind, countWithClosingParens] = kindParts.split('(');\n\n    this.kind = memberKind as CanonicalReferenceKind;\n\n    if (countWithClosingParens) {\n      this.index = parseInt(countWithClosingParens.slice(0, -1));\n    }\n\n    this.isPrivate = this.name.startsWith('~') || this.package === '@@internal';\n  }\n}\n\nexport interface ApiContentExcerptToken {\n  kind: ApiExcerptTokenKind.Content;\n  text: string;\n}\n\nexport interface ApiReferenceExcerptToken {\n  kind: ApiExcerptTokenKind.Reference;\n  text: string;\n  canonicalReference: CanonicalReference;\n}\n\nexport type ApiExcerptToken = ApiContentExcerptToken | ApiReferenceExcerptToken;\n\nexport interface ApiDocs {\n  modifiers: {\n    isInternal: boolean;\n    isPublic: boolean;\n    isAlpha: boolean;\n    isBeta: boolean;\n    isOverride: boolean;\n    isExperimental: boolean;\n  };\n  summary: string;\n  usageNotes: string;\n  remarks: string;\n  deprecated: string;\n  returns: string;\n  see: string[];\n  params: { name: string; description: string }[];\n}\n\nexport interface ApiTokenRange {\n  startIndex: number;\n  endIndex: number;\n}\n\nexport interface ApiMemberParam {\n  parameterName: string;\n  isOptional: boolean;\n  parameterTypeTokenRange: ApiTokenRange;\n}\n\nexport interface ApiMemberTypeParam {\n  typeParameterName: string;\n  constraintTokenRange: ApiTokenRange;\n  defaultTypeTokenRange: ApiTokenRange;\n}\n\nexport interface ApiMember {\n  kind: ApiMemberKind;\n  name: string;\n  canonicalReference: CanonicalReference;\n  docComment: string;\n  fileUrlPath: string;\n  isStatic?: boolean;\n  returnTypeTokenRange?: ApiTokenRange;\n  typeTokenRange?: ApiTokenRange;\n  variableTypeTokenRange?: ApiTokenRange;\n  releaseTag: ApiReleaseTag;\n  excerptTokens: ApiExcerptToken[];\n  members?: ApiMember[];\n  parameters?: ApiMemberParam[];\n  typeParameters?: ApiMemberTypeParam[];\n  docs: ApiDocs;\n}\n\nexport interface ApiMemberSummary {\n  kind: ApiMemberKind;\n  name: string;\n  canonicalReference: CanonicalReference;\n  fileUrlPath: string;\n  isDeprecated: boolean;\n  members: ApiMember[];\n}\n\nexport type MinimizedApiMemberSummary = Omit<\n  ApiMemberSummary,\n  'members' | 'fileUrlPath'\n>;\n\nexport interface ApiReport {\n  symbolNames: string[];\n  symbols: {\n    [symbolName: string]: ApiMemberSummary;\n  };\n}\n\nexport interface ApiPackageReport {\n  packageNames: string[];\n  packages: {\n    [packageName: string]: ApiReport;\n  };\n}\n\nexport interface MinimizedApiReport {\n  symbolNames: string[];\n  symbols: {\n    [symbolName: string]: MinimizedApiMemberSummary;\n  };\n}\n\nexport interface MinimizedApiPackageReport {\n  packageNames: string[];\n  packages: {\n    [packageName: string]: MinimizedApiReport;\n  };\n}\n"
  },
  {
    "path": "projects/www/src/shared/index.ts",
    "content": "export * from './api-report.models';\n"
  },
  {
    "path": "projects/www/src/shared/ngrx-shiki-theme.ts",
    "content": "export const ngrxTheme = {\n  name: 'ngrx-theme',\n  fg: '#abb2bf',\n  bg: '#rgba(0, 0, 0, 0.24)',\n  settings: [\n    {\n      name: 'Comments / Quotes',\n      scope: ['comment', 'punctuation.definition.comment', 'markup.quote'],\n      settings: { foreground: '#5c6370', fontStyle: 'italic' },\n    },\n    {\n      name: 'Keywords / Doctags / Formula',\n      scope: ['keyword', 'storage.type', 'storage.modifier', 'storage.control'],\n      settings: { foreground: '#fface6' },\n    },\n    {\n      name: 'Sections / Deletions / Tags / Function name',\n      scope: [\n        'entity.name.section',\n        'markup.heading',\n        'markup.deleted',\n        'variable.language',\n        'entity.name.function',\n        'entity.name.tag',\n      ],\n      settings: { foreground: '#e06c75' },\n    },\n    {\n      name: 'Literals',\n      scope: ['constant.language', 'support.constant'],\n      settings: { foreground: '#56b6c2' },\n    },\n    {\n      name: 'Strings / Regex / Added / Attributes',\n      scope: [\n        'string',\n        'string.regexp',\n        'constant.character.escape',\n        'markup.inserted',\n        'entity.other.attribute-name',\n        'string.template',\n      ],\n      settings: { foreground: '#98c379' },\n    },\n    {\n      name: 'Numbers / Types / Classes / Vars',\n      scope: [\n        'constant.numeric',\n        'variable.other.readwrite',\n        'support.type',\n        'support.class',\n        'variable.other.constant',\n      ],\n      settings: { foreground: '#ffb871' },\n    },\n    {\n      name: 'Symbols / Meta / Links',\n      scope: [\n        'entity.name.type',\n        'meta.import',\n        'meta.export',\n        'markup.list.bullet',\n        'markup.link',\n        'string.other.link',\n      ],\n      settings: { foreground: '#61aeee' },\n    },\n    {\n      name: 'Builtins / Class Titles',\n      scope: [\n        'support.function.builtin',\n        'support.type.builtin',\n        'entity.name.type.class',\n        'meta.class',\n      ],\n      settings: { foreground: '#ffdcbe' },\n    },\n    {\n      name: 'Emphasis',\n      scope: ['markup.italic'],\n      settings: { fontStyle: 'italic' },\n    },\n    {\n      name: 'Strong',\n      scope: ['markup.bold'],\n      settings: { fontStyle: 'bold' },\n    },\n    {\n      name: 'Underline Link',\n      scope: ['markup.underline.link'],\n      settings: { fontStyle: 'underline', foreground: '#61aeee' },\n    },\n    {\n      name: 'Default',\n      scope: ['source', 'text'],\n      settings: { foreground: '#abb2bf' },\n    },\n  ],\n};\n"
  },
  {
    "path": "projects/www/src/styles.scss",
    "content": "// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n@use './theme.scss' as theme;\n@use './code_theme.scss';\n\nhtml {\n  color-scheme: dark;\n  /* Prevent font size inflation */\n  -moz-text-size-adjust: none;\n  -webkit-text-size-adjust: none;\n  text-size-adjust: none;\n  @include mat.theme(\n    (\n      color: mat.$violet-palette,\n      typography: Roboto,\n      density: 0,\n    )\n  );\n}\n\nbody.light-theme {\n  color-scheme: light;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\n/* Remove default margin in favour of better control in authored CSS */\nbody,\nh1,\nh2,\nh3,\nh4,\np,\nfigure,\nblockquote,\ndl,\ndd {\n  margin-block-end: 0;\n}\n\n/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */\nul[role='list'],\nol[role='list'] {\n  list-style: none;\n}\n\n/* Set core body defaults */\nbody {\n  min-height: 100vh;\n  line-height: 1.5;\n}\n\n/* Set shorter line heights on headings and interactive elements */\nh1,\nh2,\nh3,\nh4,\nbutton,\ninput,\nlabel {\n  line-height: 1.1;\n}\n\n/* Balance text wrapping on headings */\nh1,\nh2,\nh3,\nh4 {\n  text-wrap: balance;\n}\n\n/* Make images easier to work with */\nimg,\npicture {\n  max-width: 100%;\n  display: block;\n}\n\n/* Inherit fonts for inputs and buttons */\ninput,\nbutton,\ntextarea,\nselect {\n  font-family: inherit;\n  font-size: inherit;\n}\n\n/* Make sure textareas without a rows attribute are not tiny */\ntextarea:not([rows]) {\n  min-height: 10em;\n}\n\n/* Anything that has been anchored to should have extra scroll margin */\n:target {\n  scroll-margin-block: 5ex;\n}\n\nhtml,\nbody {\n  height: 100%;\n}\nbody.mat-app-background {\n  margin: 0;\n  font-family: Roboto, 'Helvetica Neue', sans-serif;\n  background-color: var(--ngrx-bg);\n  color: var(--ngrx-text);\n  transition: background-color 0.3s, color 0.3s;\n}\n\ncode {\n  font-family: 'Space Mono', monospace;\n  font-variant-ligatures: none;\n}\n\na {\n  text-decoration: none;\n  color: var(--ngrx-link);\n  cursor: pointer;\n}\n\n// Set up Angular Material\n@include mat.core();\n\n:root {\n  @include mat.all-component-themes(theme.$dark-theme);\n\n  // Theme CSS variables (dark theme defaults)\n  --ngrx-bg: #17111a;\n  --ngrx-bg-surface: #17111a;\n  --ngrx-bg-elevated: #241b28;\n  --ngrx-bg-elevated-hover: #2b1f31;\n  --ngrx-bg-overlay: #0d0a0f;\n  --ngrx-bg-content-menu: #201a23;\n  --ngrx-bg-content-menu-hover: #262029;\n  --ngrx-text: #fff;\n  --ngrx-text-primary: rgba(255, 255, 255, 0.87);\n  --ngrx-text-secondary: rgba(255, 255, 255, 0.72);\n  --ngrx-text-muted: rgba(255, 255, 255, 0.56);\n  --ngrx-text-faint: rgba(255, 255, 255, 0.54);\n  --ngrx-text-very-faint: rgba(255, 255, 255, 0.32);\n  --ngrx-border-color: rgba(255, 255, 255, 0.12);\n  --ngrx-link: #fface6;\n  --ngrx-accent: #cf8fc5;\n  --ngrx-accent-strong: #a91794;\n  --ngrx-active-border: rgba(207, 143, 197, 0.54);\n  --ngrx-table-header-bg: rgba(0, 0, 0, 0.36);\n}\n\nbody.light-theme {\n  @include mat.all-component-colors(theme.$light-theme);\n\n  --ngrx-bg: #f5f3f4;\n  --ngrx-bg-surface: #f5f3f4;\n  --ngrx-bg-elevated: #dcdadb;\n  --ngrx-bg-elevated-hover: #cfccce;\n  --ngrx-bg-overlay: #e5e3e4;\n  --ngrx-bg-content-menu: #e0dedf;\n  --ngrx-bg-content-menu-hover: #d5d3d4;\n  --ngrx-text: #1f1a1d;\n  --ngrx-text-primary: rgba(0, 0, 0, 0.87);\n  --ngrx-text-secondary: rgba(0, 0, 0, 0.72);\n  --ngrx-text-muted: rgba(0, 0, 0, 0.56);\n  --ngrx-text-faint: rgba(0, 0, 0, 0.54);\n  --ngrx-text-very-faint: rgba(0, 0, 0, 0.32);\n  --ngrx-border-color: #dbdbdb;\n  --ngrx-link: #a91794;\n  --ngrx-accent: #a91794;\n  --ngrx-accent-strong: #850074;\n  --ngrx-active-border: rgba(169, 23, 148, 0.54);\n  --ngrx-table-header-bg: rgba(0, 0, 0, 0.06);\n  --ngrx-code-bg: rgba(241, 241, 241, 0.5);\n\n  // Light theme code blocks\n  .shiki,\n  pre code.shiki,\n  ngrx-code-example pre code {\n    background-color: #f6f8fa !important;\n  }\n\n  ngrx-code-example {\n    border-color: #d0d7de !important;\n  }\n\n  ngrx-code-example .header {\n    background-color: #f6f8fa !important;\n    color: #1f2328 !important;\n    border-color: #d0d7de !important;\n  }\n\n  // Keywords (pink → magenta)\n  span[style*=\"#fface6\"], span[style*=\"#FFACE6\"] { color: #a626a4 !important; }\n  // Strings (green → dark green)\n  span[style*=\"#98c379\"], span[style*=\"#98C379\"] { color: #50a14f !important; }\n  // Numbers/variables (orange → dark orange)\n  span[style*=\"#ffb871\"], span[style*=\"#FFB871\"] { color: #986801 !important; }\n  // Functions (red → dark red)\n  span[style*=\"#e06c75\"], span[style*=\"#E06C75\"] { color: #c18401 !important; }\n  // Comments (gray → gray)\n  span[style*=\"#5c6370\"], span[style*=\"#5C6370\"] { color: #717277 !important; }\n  // Types (blue → blue)\n  span[style*=\"#61aeee\"], span[style*=\"#61AEEE\"] { color: #4078f2 !important; }\n  // Classes/builtins (tan → teal)\n  span[style*=\"#ffdcbe\"], span[style*=\"#FFDCBE\"], span[style*=\"ffdcbe\"] { color: #0184bc !important; }\n  // Literals (cyan → dark cyan)\n  span[style*=\"#56b6c2\"], span[style*=\"#56B6C2\"] { color: #0997b3 !important; }\n  // Plain text (gray → black)\n  span[style*=\"#abb2bf\"], span[style*=\"#ABB2BF\"] { color: #383a42 !important; }\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-family: 'Oxanium', sans-serif;\n}\n\n@keyframes entrance {\n  0% {\n    opacity: 0;\n    transform: translateX(-20px);\n  }\n  100% {\n    opacity: 1;\n    transform: translateX(0);\n  }\n}\n\n@keyframes exit {\n  0% {\n    opacity: 1;\n    transform: translateX(0);\n  }\n  100% {\n    opacity: 0;\n    transform: translateX(-20px);\n  }\n}\n\n::view-transition-old(markdown-article) {\n  animation: exit 0.25s ease-out 0s 1 normal forwards;\n}\n\n::view-transition-new(markdown-article) {\n  animation: entrance 0.25s ease-out 0s 1 normal forwards;\n}\n\nanalog-markdown-route > div {\n  view-transition-name: markdown-article;\n}\n\n.no-scroll {\n  overflow: hidden;\n  width: 100%;\n}\n\n.video-container {\n  width: 100%;\n  max-width: 750px;\n  justify-content: center;\n  margin: 1rem auto;\n}\n\n.video-responsive-wrapper {\n  position: relative;\n  padding-bottom: 56.25%; /* 16:9 */\n  height: 0;\n\n  > iframe {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n  }\n}\n\n\n"
  },
  {
    "path": "projects/www/src/test-setup.ts",
    "content": "import '@angular/compiler';\nimport '@analogjs/vitest-angular/setup-snapshots';\n\nimport { provideZonelessChangeDetection, NgModule } from '@angular/core';\nimport {\n  BrowserTestingModule,\n  platformBrowserTesting,\n} from '@angular/platform-browser/testing';\nimport { getTestBed } from '@angular/core/testing';\n\n@NgModule({\n  providers: [provideZonelessChangeDetection()],\n})\nexport class ZonelessTestModule {}\n\ngetTestBed().initTestEnvironment(\n  [BrowserTestingModule, ZonelessTestModule],\n  platformBrowserTesting()\n);\n"
  },
  {
    "path": "projects/www/src/tools/api-extractor.json",
    "content": "{\n  \"$schema\": \"https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json\",\n  \"mainEntryPointFilePath\": \"\",\n  \"bundledPackages\": [],\n  \"apiReport\": {\n    \"enabled\": false\n  },\n  \"docModel\": {\n    \"enabled\": false\n  },\n  \"dtsRollup\": {\n    \"enabled\": false\n  },\n  \"messages\": {\n    \"compilerMessageReporting\": {\n      \"default\": {\n        \"logLevel\": \"warning\"\n      }\n    },\n    \"extractorMessageReporting\": {\n      \"default\": {\n        \"logLevel\": \"warning\"\n      }\n    },\n    \"tsdocMessageReporting\": {\n      \"default\": {\n        \"logLevel\": \"warning\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/www/src/tools/extract-docs-content.ts",
    "content": "import {\n  DocExcerpt,\n  DocNode,\n  TSDocConfiguration,\n  TSDocParser,\n  TSDocTagDefinition,\n  TSDocTagSyntaxKind,\n} from '@microsoft/tsdoc';\nimport path from 'path';\nimport fs from 'fs';\nimport prettier from 'prettier';\nimport {\n  Extractor,\n  ExtractorConfig,\n  IConfigFile,\n} from '@microsoft/api-extractor';\nimport {\n  ApiDocs,\n  ApiMember,\n  ApiMemberSummary,\n  MinimizedApiReport,\n  MinimizedApiPackageReport,\n  ApiPackageReport,\n  ApiReport,\n  CanonicalReference,\n  MinimizedApiMemberSummary,\n} from '@ngrx-io/shared';\n\ninterface ApiPackage {\n  name: string;\n  rewriteName?: string;\n  alias: string;\n  entryPoint: string;\n}\n\nconst MONOREPO_ROOT = path.join(process.cwd(), '../../');\nconst ANGULAR_THETA_CHAR = 'ɵ';\n\nasync function formatJsonWithPrettier(jsonContent: any): Promise<string> {\n  const prettierConfig = await prettier.resolveConfig(MONOREPO_ROOT);\n  return prettier.format(JSON.stringify(jsonContent, null, 2), {\n    ...prettierConfig,\n    parser: 'json',\n  });\n}\n\nconst PACKAGES_TO_PARSE: ApiPackage[] = [\n  {\n    name: '@ngrx/store',\n    alias: 'store',\n    entryPoint: 'dist/modules/store/types/ngrx-store.d.ts',\n  },\n  {\n    name: '@ngrx/store-testing',\n    rewriteName: '@ngrx/store/testing',\n    alias: 'store-testing',\n    entryPoint: 'dist/modules/store/types/ngrx-store-testing.d.ts',\n  },\n  {\n    name: '@ngrx/store-devtools',\n    alias: 'store-devtools',\n    entryPoint: 'dist/modules/store-devtools/types/ngrx-store-devtools.d.ts',\n  },\n  {\n    name: '@ngrx/signals',\n    alias: 'signals',\n    entryPoint: 'dist/modules/signals/types/ngrx-signals.d.ts',\n  },\n  {\n    name: '@ngrx/signals-entities',\n    rewriteName: '@ngrx/signals/entities',\n    alias: 'signals-entities',\n    entryPoint: 'dist/modules/signals/types/ngrx-signals-entities.d.ts',\n  },\n  {\n    name: '@ngrx/signals-events',\n    rewriteName: '@ngrx/signals/events',\n    alias: 'signals-events',\n    entryPoint: 'dist/modules/signals/types/ngrx-signals-events.d.ts',\n  },\n  {\n    name: '@ngrx/signals-rxjs-interop',\n    rewriteName: '@ngrx/signals/rxjs-interop',\n    alias: 'signals-rxjs-interop',\n    entryPoint: 'dist/modules/signals/types/ngrx-signals-rxjs-interop.d.ts',\n  },\n  {\n    name: '@ngrx/signals-testing',\n    rewriteName: '@ngrx/signals/testing',\n    alias: 'signals-testing',\n    entryPoint: 'dist/modules/signals/types/ngrx-signals-testing.d.ts',\n  },\n  {\n    name: '@ngrx/effects',\n    alias: 'effects',\n    entryPoint: 'dist/modules/effects/types/ngrx-effects.d.ts',\n  },\n  {\n    name: '@ngrx/effects-testing',\n    rewriteName: '@ngrx/effects/testing',\n    alias: 'effects-testing',\n    entryPoint: 'dist/modules/effects/types/ngrx-effects-testing.d.ts',\n  },\n  {\n    name: '@ngrx/entity',\n    alias: 'entity',\n    entryPoint: 'dist/modules/entity/types/ngrx-entity.d.ts',\n  },\n  {\n    name: '@ngrx/component-store',\n    alias: 'component-store',\n    entryPoint: 'dist/modules/component-store/types/ngrx-component-store.d.ts',\n  },\n  {\n    name: '@ngrx/operators',\n    alias: 'operators',\n    entryPoint: 'dist/modules/operators/types/ngrx-operators.d.ts',\n  },\n  {\n    name: '@ngrx/router-store',\n    alias: 'router-store',\n    entryPoint: 'dist/modules/router-store/types/ngrx-router-store.d.ts',\n  },\n  {\n    name: '@ngrx/data',\n    alias: 'data',\n    entryPoint: 'dist/modules/data/types/ngrx-data.d.ts',\n  },\n];\n\nfunction loadExtractorConfig(\n  pkg: ApiPackage,\n  cb: (config: IConfigFile) => void\n) {\n  const configFilePath = path.join(\n    MONOREPO_ROOT,\n    './projects/www/src/tools/api-extractor.json'\n  );\n  const configFile = ExtractorConfig.loadFile(configFilePath);\n\n  configFile.mainEntryPointFilePath = path.join(MONOREPO_ROOT, pkg.entryPoint);\n\n  configFile.compiler = {\n    tsconfigFilePath: path.join(MONOREPO_ROOT, 'tsconfig.docs.json'),\n  };\n\n  cb(configFile);\n\n  return ExtractorConfig.prepare({\n    configObject: configFile,\n    configObjectFullPath: configFilePath,\n    packageJsonFullPath: path.join(MONOREPO_ROOT, 'package.json'),\n    packageJson: {\n      name: pkg.name,\n    },\n  });\n}\n\nfunction createApiReport(pkg: ApiPackage) {\n  const config = loadExtractorConfig(pkg, (configFile) => {\n    configFile.docModel = {\n      enabled: true,\n      apiJsonFilePath: path.join(\n        MONOREPO_ROOT,\n        `dist/reports/${pkg.alias}.api.json`\n      ),\n    };\n  });\n\n  const extractorResult = Extractor.invoke(config, {\n    localBuild: true,\n    showVerboseMessages: true,\n  });\n\n  if (!extractorResult.succeeded) {\n    throw new Error(\n      `API Extractor completed with ${extractorResult.errorCount} errors` +\n        ` and ${extractorResult.warningCount} warnings`\n    );\n  }\n\n  // Rewrite the package name if needed\n  if (pkg.rewriteName) {\n    const content = fs.readFileSync(config.apiJsonFilePath, 'utf-8');\n    fs.writeFileSync(\n      config.apiJsonFilePath,\n      content.replace(new RegExp(pkg.name, 'g'), pkg.rewriteName)\n    );\n  }\n}\n\nfunction filterMembersForAngularThetaChar(members: ApiMember[]): ApiMember[] {\n  return members.reduce((acc, member) => {\n    if (member.name && member.name.startsWith(ANGULAR_THETA_CHAR)) {\n      return acc;\n    }\n\n    return [\n      ...acc,\n      {\n        ...member,\n        members: member.members\n          ? filterMembersForAngularThetaChar(member.members)\n          : undefined,\n      },\n    ];\n  }, [] as ApiMember[]);\n}\n\nfunction rollupApiReport(pkg: ApiPackage): ApiReport {\n  const apiReportPath = path.join(\n    MONOREPO_ROOT,\n    `dist/reports/${pkg.alias}.api.json`\n  );\n  const apiReport: ApiMember = JSON.parse(\n    fs.readFileSync(apiReportPath, 'utf-8')\n  );\n\n  function recursivelyParseDocs(apiMember: ApiMember) {\n    apiMember.docs = parseTSDoc(apiMember.docComment ?? '');\n\n    if (apiMember.members) {\n      apiMember.members.forEach((member) => recursivelyParseDocs(member));\n    }\n  }\n\n  recursivelyParseDocs(apiReport);\n\n  const entryPoint = apiReport.members?.find(\n    (member) => member.kind === 'EntryPoint'\n  );\n\n  const symbols = new Map<string, ApiMember[]>();\n  const members = filterMembersForAngularThetaChar(entryPoint?.members ?? []);\n\n  members.forEach((member) => {\n    if (!symbols.has(member.name)) {\n      symbols.set(member.name, []);\n    }\n\n    symbols.get(member.name)?.push(member);\n  });\n\n  return {\n    symbolNames: Array.from(symbols.keys()),\n    symbols: Array.from(symbols.entries()).reduce(\n      (acc, [name, members]): Record<string, ApiMemberSummary> => {\n        const firstMember = members[0];\n        const [simplifiedReference] = firstMember.canonicalReference.split('(');\n        const isDeprecated = members.every((member) => member.docs.deprecated);\n\n        return {\n          ...acc,\n          [name]: {\n            name,\n            canonicalReference: simplifiedReference as CanonicalReference,\n            kind: firstMember.kind,\n            fileUrlPath: firstMember.fileUrlPath,\n            isDeprecated,\n            members,\n          },\n        };\n      },\n      {}\n    ),\n  };\n}\n\nfunction renderDocNode(annotation: string, docNode?: DocNode): string {\n  let result = '';\n  if (docNode) {\n    if (docNode instanceof DocExcerpt) {\n      result += docNode.content.toString();\n    }\n    for (const childNode of docNode.getChildNodes()) {\n      result += renderDocNode(annotation, childNode);\n    }\n  }\n  return result.replace(annotation, '');\n}\n\nfunction parseTSDoc(foundComment: string): ApiDocs {\n  const customConfiguration = new TSDocConfiguration();\n  const usageNotesDefinition = new TSDocTagDefinition({\n    tagName: '@usageNotes',\n    syntaxKind: TSDocTagSyntaxKind.BlockTag,\n  });\n\n  customConfiguration.addTagDefinitions([usageNotesDefinition]);\n\n  const tsdocParser = new TSDocParser(customConfiguration);\n  const parserContext = tsdocParser.parseString(foundComment);\n  const docComment = parserContext.docComment;\n  const usageNotesBlock = docComment.customBlocks.find(\n    (block) => block.blockTag.tagName === '@usageNotes'\n  );\n\n  return {\n    modifiers: {\n      isInternal: docComment.modifierTagSet.isInternal(),\n      isPublic: docComment.modifierTagSet.isPublic(),\n      isAlpha: docComment.modifierTagSet.isAlpha(),\n      isBeta: docComment.modifierTagSet.isBeta(),\n      isOverride: docComment.modifierTagSet.isOverride(),\n      isExperimental: docComment.modifierTagSet.isExperimental(),\n    },\n    summary: renderDocNode('@description', docComment.summarySection),\n    usageNotes: renderDocNode('@usageNotes', usageNotesBlock),\n    remarks: renderDocNode('@remarks', docComment.remarksBlock),\n    deprecated: renderDocNode('@deprecated', docComment.deprecatedBlock),\n    returns: renderDocNode('@returns', docComment.returnsBlock),\n    see: docComment.seeBlocks.map((block) => renderDocNode('@see', block)),\n    params: docComment.params.blocks.map((block) => ({\n      name: block.parameterName,\n      description: renderDocNode('@param', block.content),\n    })),\n  };\n}\n\nfunction parsePackages(): ApiPackageReport {\n  PACKAGES_TO_PARSE.forEach((pkg) => createApiReport(pkg));\n\n  const packages = PACKAGES_TO_PARSE.reduce(\n    (acc, pkg) => ({\n      ...acc,\n      [pkg.rewriteName ?? pkg.name]: rollupApiReport(pkg),\n    }),\n    {} as Record<string, ApiReport>\n  );\n  const packageNames = Object.keys(packages);\n\n  return { packageNames, packages };\n}\n\nfunction minimizeApiReport(\n  apiReport: ApiPackageReport\n): MinimizedApiPackageReport {\n  const packages = apiReport.packageNames.reduce(\n    (acc, packageName): Record<string, MinimizedApiReport> => {\n      const { symbols, symbolNames } = apiReport.packages[packageName];\n\n      const minimalSymbols = symbolNames.map(\n        (symbolName): MinimizedApiMemberSummary => {\n          const symbol = symbols[symbolName];\n\n          return {\n            kind: symbol.kind,\n            name: symbol.name,\n            canonicalReference: symbol.canonicalReference,\n            isDeprecated: symbol.isDeprecated,\n          };\n        }\n      );\n\n      return {\n        ...acc,\n        [packageName]: {\n          symbolNames,\n          symbols: minimalSymbols.reduce(\n            (acc, symbol): Record<string, MinimizedApiMemberSummary> => {\n              return {\n                ...acc,\n                [symbol.name]: symbol,\n              };\n            },\n            {} as Record<string, MinimizedApiMemberSummary>\n          ),\n        },\n      };\n    },\n    {} as Record<string, MinimizedApiReport>\n  );\n\n  return { packageNames: apiReport.packageNames, packages };\n}\n\nasync function writeFinalizedApiReport() {\n  const report = parsePackages();\n  const minimizedReport = minimizeApiReport(report);\n\n  const minimizedReportPath = path.join(\n    MONOREPO_ROOT,\n    'projects/www/src/app/reference/api-report.min.json'\n  );\n\n  for (const packageName of report.packageNames) {\n    const packageReport = report.packages[packageName];\n    const [_ngrx, ...packagePath] = packageName.split('/');\n\n    for (const symbolName of packageReport.symbolNames) {\n      const symbol = packageReport.symbols[symbolName];\n      const basePath = path.join(\n        MONOREPO_ROOT,\n        `projects/www/src/app/reference`\n      );\n\n      packagePath.forEach((dir, index) => {\n        const previousPath = path.join(\n          basePath,\n          ...packagePath.slice(0, index)\n        );\n        const currentPath = path.join(previousPath, dir);\n\n        if (!fs.existsSync(currentPath)) {\n          fs.mkdirSync(currentPath);\n        }\n      });\n\n      const symbolPath = path.join(\n        MONOREPO_ROOT,\n        `projects/www/src/app/reference/${packagePath.join('/')}/${\n          symbol.name\n        }.json`\n      );\n\n      const formattedContent = await formatJsonWithPrettier(symbol);\n      fs.writeFileSync(symbolPath, formattedContent);\n    }\n  }\n\n  const formattedMinimizedReport =\n    await formatJsonWithPrettier(minimizedReport);\n  fs.writeFileSync(minimizedReportPath, formattedMinimizedReport);\n}\n\nwriteFinalizedApiReport().catch(console.error);\n"
  },
  {
    "path": "projects/www/src/tools/prepare-examples.ts",
    "content": "import { globSync } from 'tinyglobby';\nimport * as fs from 'node:fs';\n\nfunction copyTsFilesToTxt() {\n  const files = globSync(['./src/app/examples/**/*.ts']);\n\n  files.forEach((file) => {\n    const txtFile = file.replace('.ts', '.txt');\n    fs.writeFileSync(txtFile, fs.readFileSync(file));\n  });\n}\n\ncopyTsFilesToTxt();\n"
  },
  {
    "path": "projects/www/src/tools/vite-ngrx-stackblitz.plugin.ts",
    "content": "import { Plugin } from 'vite';\nimport { parse as parseYaml } from 'yaml';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface StackblitzConfig {\n  name: string;\n  description: string;\n  open: string;\n  extends?: string;\n  files: Record<string, string>;\n}\n\nconst EMPTY_CONFIG: StackblitzConfig = {\n  name: '',\n  description: '',\n  open: 'app/main.ts',\n  files: {},\n};\n\nconst packageJson = JSON.parse(\n  fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8')\n);\n\nconst NGRX_VERSION = packageJson.version;\nconst ANGULAR_VERSION = packageJson.dependencies['@angular/core'];\nconst RXJS_VERSION = packageJson.dependencies['rxjs'];\nconst ZONE_JS_VERSION = packageJson.dependencies['zone.js'];\nconst TYPESCRIPT_VERSION = packageJson.devDependencies['typescript'];\n\nexport default function ngrxStackblitzPlugin(): Plugin {\n  function resolveFiles(\n    id: string,\n    files: Record<string, string>\n  ): Record<string, string> {\n    return Object.keys(files).reduce(\n      (acc, file) => {\n        const filePath = path.resolve(path.dirname(id), files[file]);\n        const content = fs.readFileSync(filePath, 'utf-8');\n\n        if (file === 'package.json') {\n          acc[file] = content\n            .replaceAll('<angular-version>', ANGULAR_VERSION)\n            .replaceAll('<rxjs-version>', RXJS_VERSION)\n            .replaceAll('<zone-js-version>', ZONE_JS_VERSION)\n            .replaceAll('<ngrx-version>', NGRX_VERSION)\n            .replaceAll('<typescript-version>', TYPESCRIPT_VERSION);\n\n          return acc;\n        }\n\n        acc[file] = content;\n        return acc;\n      },\n      {} as Record<string, string>\n    );\n  }\n\n  function getBaseConfig(\n    id: string,\n    config: StackblitzConfig\n  ): StackblitzConfig {\n    if (!config.extends) {\n      return EMPTY_CONFIG;\n    }\n\n    const pathToBase = path.resolve(path.dirname(id), config.extends);\n    const baseContents = fs.readFileSync(pathToBase, 'utf-8');\n    const base = parseYaml(baseContents) as StackblitzConfig;\n    const baseFiles = resolveFiles(pathToBase, base.files);\n\n    return {\n      ...base,\n      files: baseFiles,\n    };\n  }\n\n  function loadConfig(id: string, config: StackblitzConfig): StackblitzConfig {\n    const base = getBaseConfig(id, config);\n\n    return {\n      ...base,\n      ...config,\n      files: {\n        ...base.files,\n        ...resolveFiles(id, config.files),\n      },\n    };\n  }\n\n  return {\n    name: 'ngrx-stacklbitz-plugin',\n\n    transform(src, id) {\n      if (!id.includes('stackblitz.yml')) {\n        return;\n      }\n\n      if (src.startsWith('export default')) {\n        return src;\n      }\n\n      const parsed = parseYaml(src);\n      const config = loadConfig(id, parsed as StackblitzConfig);\n\n      return `export default ${JSON.stringify(config)};`;\n    },\n  };\n}\n"
  },
  {
    "path": "projects/www/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\nimport type { StackblitzConfig } from './tools/vite-ngrx-stackblitz.plugin';\n\ndeclare module '*/stackblitz.yml' {\n  const value: StackblitzConfig;\n  export default value;\n}\n"
  },
  {
    "path": "projects/www/tsconfig.app.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"types\": [],\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": false\n  },\n  \"files\": [\"src/main.ts\", \"src/main.server.ts\"],\n  \"include\": [\"src/**/*.d.ts\", \"src/app/pages/**/*.page.ts\"],\n  \"exclude\": [\"**/*.test.ts\", \"**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "projects/www/tsconfig.editor.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"include\": [\"**/*.ts\", \"src/app/examples/__base/vite.config.js\"],\n  \"compilerOptions\": {\n    \"types\": [\"node\", \"vitest/globals\"]\n  }\n}\n"
  },
  {
    "path": "projects/www/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"files\": [],\n  \"include\": [],\n  \"exclude\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.app.json\"\n    },\n    {\n      \"path\": \"./tsconfig.spec.json\"\n    },\n    {\n      \"path\": \"./tsconfig.editor.json\"\n    },\n    {\n      \"path\": \"./tsconfig.tools.json\"\n    }\n  ],\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"module\": \"ES2022\",\n    \"resolveJsonModule\": true,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"moduleResolution\": \"bundler\"\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true\n  }\n}\n"
  },
  {
    "path": "projects/www/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"types\": [\"node\", \"vitest/globals\"]\n  },\n  \"files\": [\"src/test-setup.ts\"],\n  \"include\": [\"src/**/*.spec.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "projects/www/tsconfig.tools.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"include\": [\"src/tools/**/*.ts\"],\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true\n  }\n}\n"
  },
  {
    "path": "projects/www/vite.config.ts",
    "content": "/// <reference types=\"vitest\" />\nimport {\n  defineConfig,\n  defaultClientConditions,\n  defaultServerConditions,\n} from 'vite';\nimport analog from '@analogjs/platform';\nimport { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';\nimport ngrxStackblitzPlugin from './src/tools/vite-ngrx-stackblitz.plugin';\nimport { ngrxTheme } from './src/shared/ngrx-shiki-theme';\nimport { configDefaults } from 'vitest/config';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nexport default defineConfig(({ mode }) => ({\n  root: dirname(fileURLToPath(import.meta.url)),\n  cacheDir: '../../node_modules/.vite/www',\n\n  resolve: {\n    conditions: [...defaultClientConditions],\n  },\n\n  ssr: {\n    resolve: {\n      conditions: [...defaultServerConditions],\n    },\n  },\n\n  build: {\n    outDir: '../../dist/projects/www/client',\n    reportCompressedSize: true,\n    target: 'es2020',\n  },\n\n  server: {\n    fs: {\n      allow: ['.'],\n    },\n    headers: {\n      'Cross-Origin-Embedder-Policy': 'require-corp',\n      'Cross-Origin-Opener-Policy': 'same-origin',\n    },\n  },\n\n  plugins: [\n    analog({\n      static: true,\n      content: {\n        highlighter: 'shiki',\n        shikiOptions: {\n          highlight: {\n            theme: 'ngrx-theme',\n          },\n          highlighter: {\n            additionalLangs: ['sh'],\n            themes: [ngrxTheme],\n          },\n        },\n      },\n      vite: {\n        inlineStylesExtension: 'scss',\n      },\n    }),\n    nxViteTsPaths(),\n    ngrxStackblitzPlugin(),\n  ],\n\n  test: {\n    globals: true,\n    environment: 'jsdom',\n    setupFiles: ['src/test-setup.ts'],\n    include: ['**/*.spec.ts'],\n    exclude: [...configDefaults.exclude, 'src/app/examples/**'],\n    reporters: ['default'],\n  },\n\n  define: {\n    'import.meta.vitest': mode !== 'production',\n  },\n}));\n"
  },
  {
    "path": "setup-jest.ts",
    "content": "import 'jest-preset-angular';\n(global as any)['CSS'] = null;\n\n/**\n * ISSUE: https://github.com/angular/material2/issues/7101\n * Workaround for JSDOM missing transform property\n */\nObject.defineProperty(document.body.style, 'transform', {\n  value: () => {\n    return {\n      enumerable: true,\n      configurable: true,\n    };\n  },\n});\n"
  },
  {
    "path": "tsconfig.docs.json",
    "content": "{\n  \"compilerOptions\": {\n    \"importHelpers\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"experimentalDecorators\": true,\n    \"downlevelIteration\": true,\n    \"emitDecoratorMetadata\": true,\n    \"noImplicitOverride\": true,\n    \"useDefineForClassFields\": false,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"outDir\": \"../out-tsc/app\",\n    \"target\": \"ES2022\",\n    \"module\": \"commonjs\",\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"strict\": true,\n    \"paths\": {},\n    \"typeRoots\": [\"node_modules/@types\"]\n  },\n  \"exclude\": [\n    \"dist\",\n    \"node_modules\",\n    \"**/*/node_modules\",\n    \"modules/**/*\",\n    \"projects/**/*\"\n  ]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"importHelpers\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"experimentalDecorators\": true,\n    \"downlevelIteration\": true,\n    \"emitDecoratorMetadata\": true,\n    \"noImplicitOverride\": true,\n    \"useDefineForClassFields\": false,\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"outDir\": \"../out-tsc/app\",\n    \"target\": \"ES2022\",\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"strict\": true,\n    \"paths\": {\n      \"@example-app/*\": [\"./projects/example-app/src/app/*\"],\n      \"@ngrx-io/*\": [\"./projects/www/src/*\"],\n      \"@ngrx/component\": [\"./modules/component\"],\n      \"@ngrx/component-store\": [\"./modules/component-store\"],\n      \"@ngrx/component-store/schematics-core\": [\n        \"./modules/component-store/schematics-core\"\n      ],\n      \"@ngrx/component/schematics-core\": [\n        \"./modules/component/schematics-core\"\n      ],\n      \"@ngrx/data\": [\"./modules/data\"],\n      \"@ngrx/data/schematics-core\": [\"./modules/data/schematics-core\"],\n      \"@ngrx/effects\": [\"./modules/effects\"],\n      \"@ngrx/effects/schematics-core\": [\"./modules/effects/schematics-core\"],\n      \"@ngrx/effects/testing\": [\"./modules/effects/testing\"],\n      \"@ngrx/entity\": [\"./modules/entity\"],\n      \"@ngrx/entity/schematics-core\": [\"./modules/entity/schematics-core\"],\n      \"@ngrx/operators\": [\"./modules/operators\"],\n      \"@ngrx/router-store\": [\"./modules/router-store\"],\n      \"@ngrx/router-store/schematics-core\": [\n        \"./modules/router-store/schematics-core\"\n      ],\n      \"@ngrx/schematics\": [\"./modules/schematics\"],\n      \"@ngrx/schematics-core/*\": [\"./modules/schematics-core/*\"],\n      \"@ngrx/schematics/schematics-core\": [\n        \"./modules/schematics/schematics-core\"\n      ],\n      \"@ngrx/signals\": [\"./modules/signals\"],\n      \"@ngrx/signals/entities\": [\"./modules/signals/entities\"],\n      \"@ngrx/signals/events\": [\"./modules/signals/events\"],\n      \"@ngrx/signals/rxjs-interop\": [\"./modules/signals/rxjs-interop\"],\n      \"@ngrx/signals/schematics-core\": [\"./modules/signals/schematics-core\"],\n      \"@ngrx/signals/testing\": [\"./modules/signals/testing\"],\n      \"@ngrx/store\": [\"./modules/store\"],\n      \"@ngrx/store-devtools\": [\"./modules/store-devtools\"],\n      \"@ngrx/store-devtools/schematics-core\": [\n        \"./modules/store-devtools/schematics-core\"\n      ],\n      \"@ngrx/store/schematics-core\": [\"./modules/store/schematics-core\"],\n      \"@ngrx/store/testing\": [\"./modules/store/testing\"]\n    },\n    \"typeRoots\": [\"node_modules/@types\"]\n  },\n  \"exclude\": [\n    \"dist\",\n    \"node_modules\",\n    \"**/*/node_modules\",\n    \"modules/schematics/src/*/files/**/*\"\n  ],\n  \"compileOnSave\": false,\n  \"buildOnSave\": false,\n  \"atom\": {\n    \"rewriteTsconfig\": false\n  }\n}\n"
  },
  {
    "path": "tsdoc-metadata.json",
    "content": "// This file is read by tools that parse documentation comments conforming to the TSDoc standard.\n// It should be published with your NPM package.  It should not be tracked by Git.\n{\n  \"tsdocVersion\": \"0.12\",\n  \"toolPackages\": [\n    {\n      \"packageName\": \"@microsoft/api-extractor\",\n      \"packageVersion\": \"7.55.1\"\n    }\n  ]\n}\n"
  }
]