Full Code of ionic-team/stencil-store for AI

main 4a5628ade709 cached
52 files
94.3 KB
26.0k tokens
42 symbols
1 requests
Download .txt
Repository: ionic-team/stencil-store
Branch: main
Commit: 4a5628ade709
Files: 52
Total size: 94.3 KB

Directory structure:
gitextract_14714bwu/

├── .github/
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   ├── ionic-issue-bot.yml
│   └── workflows/
│       ├── build.yml
│       ├── main.yml
│       ├── publish-npm.yml
│       ├── release-dev.yml
│       ├── release-orchestrator.yml
│       └── release-production.yml
├── .gitignore
├── .nvmrc
├── .prettierrc.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src/
│   ├── index.test.ts
│   ├── index.ts
│   ├── observable-map.test.ts
│   ├── observable-map.ts
│   ├── store.test.ts
│   ├── store.ts
│   ├── subscriptions/
│   │   ├── stencil.test.ts
│   │   └── stencil.ts
│   ├── types.ts
│   ├── utils.test.ts
│   └── utils.ts
├── test-app/
│   ├── .editorconfig
│   ├── .gitignore
│   ├── .prettierrc.json
│   ├── LICENSE
│   ├── package.json
│   ├── readme.md
│   ├── src/
│   │   ├── components/
│   │   │   ├── change-store/
│   │   │   │   └── change-store.tsx
│   │   │   ├── display-store/
│   │   │   │   ├── display-store.e2e.ts
│   │   │   │   └── display-store.tsx
│   │   │   └── simple-store/
│   │   │       ├── display-store.spec.ts
│   │   │       └── display-store.tsx
│   │   ├── components.d.ts
│   │   ├── index.html
│   │   ├── index.ts
│   │   └── utils/
│   │       └── greeting-store.ts
│   ├── stencil.config.ts
│   └── tsconfig.json
├── tsconfig.json
└── vitest.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/CODEOWNERS
================================================
* @stenciljs/technical-steering-committee


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [johnjenkins]


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: 🐛 Bug Report
description: Create a report to help us improve Stencil Store
title: 'bug: '
body:
  - type: checkboxes
    attributes:
      label: Prerequisites
      description: Please ensure you have completed all of the following.
      options:
        - label: I have read the [Contributing Guidelines](https://github.com/stenciljs/core/blob/main/.github/CONTRIBUTING.md).
          required: true
        - label: I agree to follow the [Code of Conduct](https://github.com/stenciljs/.github/blob/main/CODE_OF_CONDUCT.md).
          required: true
        - label: I have searched for [existing issues](https://github.com/stenciljs/store/issues) that already report this problem, without success.
          required: true
  - type: input
    attributes:
      label: Stencil Store Version
      description: The version number of Stencil Store where the issue is occurring.
    validations:
      required: true
  - type: input
    attributes:
      label: Stencil Version
      description: The version number of Stencil where the issue is occurring.
    validations:
      required: true
  - type: textarea
    attributes:
      label: Current Behavior
      description: A clear description of what the bug is and how it manifests.
    validations:
      required: true
  - type: textarea
    attributes:
      label: Expected Behavior
      description: A clear description of what you expected to happen.
    validations:
      required: true
  - type: textarea
    attributes:
      label: Steps to Reproduce
      description: Please explain the steps required to duplicate this issue.
    validations:
      required: true
  - type: input
    attributes:
      label: Code Reproduction URL
      description: Please reproduce this issue in a blank Stencil starter application and provide a link to the repo. Run `npm init stencil` to quickly spin up a Stencil project. This is the best way to ensure this issue is triaged quickly. Issues without a code reproduction may be closed if the Stencil Team cannot reproduce the issue you are reporting.
      placeholder: https://github.com/...
    validations:
      required: true
  - type: textarea
    attributes:
      label: Additional Information
      description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
contact_links:
  - name: 💻 Stencil
    url: https://github.com/stenciljs/core/issues/new/choose
    about: This issue tracker is not for Stencil compiler issues. Please file compiler issues on the Stencil repo.
  - name: 📚 Documentation
    url: https://github.com/stenciljs/site/issues/new/choose
    about: This issue tracker is not for documentation issues. Please file documentation issues on the Stencil site repo.
  - name: 📝 Create Stencil CLI
    url: https://github.com/stenciljs/create-stencil/issues/new/choose
    about: This issue tracker is not for Create Stencil CLI issues. Please file CLI issues on the Create Stencil CLI repo.
  - name: 🤔 Support Question
    url: https://forum.ionicframework.com/
    about: This issue tracker is not for support questions. Please post your question on the Ionic Forums.

================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: 💡 Feature Request
description: Suggest an idea for Stencil Store
title: 'feat: '
body:
  - type: checkboxes
    attributes:
      label: Prerequisites
      description: Please ensure you have completed all of the following.
      options:
        - label: I have read the [Contributing Guidelines](https://github.com/stenciljs/core/blob/main/.github/CONTRIBUTING.md).
          required: true
        - label: I agree to follow the [Code of Conduct](https://github.com/stenciljs/.github/blob/main/CODE_OF_CONDUCT.md).
          required: true
        - label: I have searched for [existing issues](https://github.com/stenciljs/store/issues) that already include this feature request, without success.
          required: true
  - type: textarea
    attributes:
      label: Describe the Feature Request
      description: A clear and concise description of what the feature does.
    validations:
      required: true
  - type: textarea
    attributes:
      label: Describe the Use Case
      description: A clear and concise use case for what problem this feature would solve.
    validations:
      required: true
  - type: textarea
    attributes:
      label: Describe Preferred Solution
      description: A clear and concise description of how you want this feature to be added to Stencil Store.
  - type: textarea
    attributes:
      label: Describe Alternatives
      description: A clear and concise description of any alternative solutions or features you have considered.
  - type: textarea
    attributes:
      label: Related Code
      description: If you are able to illustrate the feature request with an example, please provide a sample Stencil component(s). Run `npm init stencil` to quickly spin up a Stencil project.
  - type: textarea
    attributes:
      label: Additional Information
      description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to implement, Stack Overflow links, forum links, etc.


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Please refer to our contributing documentation for any questions on submitting a pull request, or let us know here if you need any help: https://github.com/stenciljs/core/blob/main/.github/CONTRIBUTING.md -->

## Pull request checklist

Please check if your PR fulfills the following requirements:
- [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features)
- [ ] Build (`npm run build`) was run locally and any changes were pushed
- [ ] Tests (`npm test`) were run locally and passed
- [ ] Prettier (`npm run prettier`) was run locally and passed

## Pull request type

<!-- Please do not submit updates to dependencies unless it fixes an issue. --> 

<!-- Please try to limit your pull request to one type, submit multiple pull requests if needed. --> 

Please check the type of change your PR introduces:
- [ ] Bugfix
- [ ] Feature
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] Documentation content changes
- [ ] Other (please describe):


## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

GitHub Issue Number: N/A


## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by this PR. -->

-
-
-

## Does this introduce a breaking change?

- [ ] Yes
- [ ] No

<!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. -->

## Testing

<!-- Please describe the steps you took to test the changes in this PR. These steps can be programmatic (e.g. unit tests) and/or manual. -->

## Other information

<!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
  directory: "/"
  schedule:
    interval: weekly
  open-pull-requests-limit: 10
  groups:
    patch-deps-updates-main:
      update-types:
        - "patch"
    minor-deps-updates-main:
      update-types:
        - "minor"
    major-deps-updates-main:
      update-types:
        - "major"

- package-ecosystem: github-actions
  directory: "/"
  schedule:
    interval: weekly
    time: "11:00"
  open-pull-requests-limit: 10
  groups:
    patch-deps-updates:
      update-types:
        - "patch"
    minor-deps-updates:
      update-types:
        - "minor"
    major-deps-updates:
      update-types:
        - "major"

================================================
FILE: .github/ionic-issue-bot.yml
================================================
triage:
  label: triage
  dryRun: false

closeAndLock:
  labels:
    - label: "ionitron: support"
      message: >
        Thanks for the issue! This issue appears to be a support request. We use this issue tracker exclusively for
        bug reports and feature requests. Please use our [discord channel](https://chat.stenciljs.com/)
        for questions about Stencil Store.


        Thank you for using Stencil Store!
    - label: "ionitron: missing template"
      message: >
        Thanks for the issue! It appears that you have not filled out the provided issue template. We use this issue
        template in order to gather more information and further assist you. Please create a new issue and ensure the
        template is fully filled out.


        Thank you for using Stencil Store!
  close: true
  lock: true
  dryRun: false

comment:
  labels:
    - label: "ionitron: needs reproduction"
      message: >
        Thanks for the issue! This issue has been labeled as `needs reproduction`. This label is added to issues that
        need a code reproduction.


        Please reproduce this issue in an Stencil starter component library and Stencil Store. Please provide a way for
        us to access it (GitHub repo, StackBlitz, etc). Without a reliable code reproduction, it is unlikely we will be
        able to resolve the issue, leading to it being closed.


        If you have already provided a code snippet and are seeing this message, it is likely that the code snippet was
        not enough for our team to reproduce the issue.


        For a guide on how to create a good reproduction, see our
        [Contributing Guide](https://github.com/stenciljs/core/blob/main/.github/CONTRIBUTING.md).
  dryRun: false

noReply:
  maxIssuesPerRun: 100
  includePullRequests: false
  label: Awaiting Reply
  close: false
  lock: false
  dryRun: false

noReproduction:
  days: 14
  maxIssuesPerRun: 100
  label: "ionitron: needs reproduction"
  responseLabel: triage
  exemptProjects: true
  exemptMilestones: true
  message: >
    Thanks for the issue! This issue is being closed due to the lack of a code reproduction. If this is still
    an issue with the latest version of Stencil & Stencil Store, please create a new issue and ensure the template is
    fully filled out.


    Thank you for using Stencil Store!
  close: true
  lock: true
  dryRun: false

stale:
  days: 30
  maxIssuesPerRun: 100
  exemptLabels:
    - "Bug: Validated"
    - "Feature: Want this? Upvote it!"
    - good first issue
    - help wanted
    - Reply Received
    - Request For Comments
    - "Resolution: Needs Investigation"
    - "Resolution: Refine"
    - triage
  exemptAssigned: true
  exemptProjects: true
  exemptMilestones: true
  label: "ionitron: stale issue"
  message: >
    Thanks for the issue! This issue is being closed due to inactivity. If this is still
    an issue with the latest version of Stencil, please create a new issue and ensure the
    template is fully filled out.


    Thank you for using Stencil Store!
  close: true
  lock: true
  dryRun: false


================================================
FILE: .github/workflows/build.yml
================================================
name: Build Stencil Store

on:
  workflow_call:
  # Make this a reusable workflow, no value needed
  # https://docs.github.com/en/actions/using-workflows/reusing-workflows

jobs:
  build_stencil_store:
    name: Build Stencil Store
    runs-on: 'ubuntu-latest'
    steps:
      - name: 📥 Checkout Code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          # the pull_request_target event will consider the HEAD of `main` to be the SHA to use.
          # attempt to use the SHA associated with a pull request and fallback to HEAD of `main`
          ref: ${{ github.event_name == 'pull_request_target' && format('refs/pull/{0}/merge', github.event.number) || '' }}
          persist-credentials: false

      - name: 🕸️ Get Core Dependencies
        uses: stenciljs/.github/actions/get-core-dependencies@main

      - name: 📦 Stencil Store Build
        run: npm run build
        shell: bash

      - name: 🧪 Unit Tests
        run: npm test
        shell: bash

      - name: 📤 Upload Build Artifacts
        uses: stenciljs/.github/actions/upload-archive@main



================================================
FILE: .github/workflows/main.yml
================================================
name: CI

on:
  push:
    branches:
      - 'main'
  pull_request:
    branches:
      - '**'

jobs:
  build:
    name: (build stencil ${{ matrix.stencil_version }})
    strategy:
      fail-fast: false
      matrix:
        # Run with multiple different versions of Stencil in parallel:
        # 1. DEFAULT - uses the version of Stencil written in `package-lock.json`, keeping the same version used by the
        # Stencil team as a source of truth
        # 2. 2 - will install the latest release under major version 2 of Stencil. This should be kept as long as this
        # library supports Stencil v2.Y.Z
        # 3. 3 - will install the latest release under major version 3 of Stencil. This should be kept as long as this
        # library supports Stencil v3.Y.Z
        # 4. 4 - will install the latest release under major version 4 of Stencil. This should be kept as long as this
        # library supports Stencil v4.Y.Z
        stencil_version: ['DEFAULT', '2', '3', '4']

    runs-on: ubuntu-latest

    steps:
      - name: 📥 Checkout Code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: 🕸️ Get Core Dependencies
        uses: stenciljs/.github/actions/get-core-dependencies@main

      - name: 🧪 Test
        run: npm run test

      - name: 📦 Install Stencil ${{matrix.stencil_version}}
        run: npm install --save-dev @stencil/core@${{matrix.stencil_version}}
        shell: bash
        if: ${{ matrix.stencil_version != 'DEFAULT' }}

      - name: 📊 Report Stencil Version
        run: npm ls @stencil/core
        shell: bash

      - name: 🏗️ Build
        run: npm run build
        shell: bash

      - name: 🔗 Create Symlink
        run: npm link
        shell: bash

      - name: 📂 Enter test-app Directory
        run: cd test-app
        shell: bash

      - name: 📦 Install test-app Dependencies
        run: npm install
        shell: bash

      - name: 🔗 Link Stencil Store
        run: npm link @stencil/store

      - name: 🏗️ Build test-app
        run: npm run build
        shell: bash

      - name: 🧪 Test test-app
        run: npm test
        shell: bash



================================================
FILE: .github/workflows/publish-npm.yml
================================================
name: 'Publish Stencil Store'

on:
  workflow_call:
    inputs:
      version:
        description: 'Version or semver bump to publish.'
        required: true
        type: string
      tag:
        description: 'npm dist-tag to publish under.'
        required: true
        type: string
      node-version:
        description: 'Node.js version used for publishing.'
        required: false
        type: string
        default: '20'
      registry-url:
        description: 'npm registry URL.'
        required: false
        type: string
        default: 'https://registry.npmjs.org'
      scope:
        description: 'npm scope that should use trusted publisher auth.'
        required: false
        type: string
        default: '@stencil'
      package-directory:
        description: 'Directory that contains the package to publish.'
        required: false
        type: string
        default: '.'
      install-command:
        description: 'Command used to install dependencies. Leave blank to skip.'
        required: false
        type: string
        default: 'npm ci'
      install-working-directory:
        description: 'Working directory for the install command.'
        required: false
        type: string
        default: '.'
      build-command:
        description: 'Command used to build the package. Leave blank to skip.'
        required: false
        type: string
        default: 'npm run build'
      build-working-directory:
        description: 'Working directory for the build command.'
        required: false
        type: string
        default: '.'

permissions:
  contents: write
  id-token: write

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: 📥 Checkout Code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      - name: 🟢 Configure Node for Publish
        if: ${{ inputs.scope == '' }}
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ inputs.node-version }}
          registry-url: ${{ inputs.registry-url }}

      - name: 🟢 Configure Node for Publish (Scoped)
        if: ${{ inputs.scope != '' }}
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ inputs.node-version }}
          registry-url: ${{ inputs.registry-url }}
          scope: ${{ inputs.scope }}

      - name: 🔄 Ensure Latest npm
        run: npm install -g npm@latest
        shell: bash

      - name: 📦 Install Dependencies
        if: ${{ inputs.install-command != '' }}
        run: ${{ inputs.install-command }}
        shell: bash
        working-directory: ${{ inputs.install-working-directory }}

      - name: 🛠️ Build Package
        if: ${{ inputs.build-command != '' }}
        run: ${{ inputs.build-command }}
        shell: bash
        working-directory: ${{ inputs.build-working-directory }}

      - name: 🏷️ Set Version
        run: npm version --no-git-tag-version --allow-same-version ${{ inputs.version }}
        shell: bash
        working-directory: ${{ inputs.package-directory }}

      - name: 🔎 Get Version from package.json
        id: get-version
        run: |
          # Normalize package directory path to prevent path traversal issues
          PKG_DIR="${{ inputs.package-directory }}"
          if [ "$PKG_DIR" = "." ] || [ -z "$PKG_DIR" ]; then
            PKG_DIR="."
          fi
          # Use path.resolve to safely construct the absolute path
          VERSION=$(node -e "const path = require('path'); const pkgPath = path.resolve('$PKG_DIR', 'package.json'); const pkg = require(pkgPath); console.log(pkg.version);")
          if [ -z "$VERSION" ]; then
            echo "❌ Failed to extract version from $PKG_DIR/package.json. Ensure the file exists and contains a valid 'version' field." >&2
            exit 1
          fi
          echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
          echo "Extracted version: $VERSION from $PKG_DIR/package.json"
        shell: bash

      - name: 📝 Commit Version Changes
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          if [ "${{ inputs.package-directory }}" = "." ]; then
            git add package.json package-lock.json
          else
            git add ${{ inputs.package-directory }}/package.json ${{ inputs.package-directory }}/package-lock.json 2>/dev/null || true
          fi
          # Only commit if there are changes
          if ! git diff --staged --quiet; then
            git commit -m "chore: bump version to ${{ steps.get-version.outputs.VERSION }}"
          else
            echo "No changes to commit"
          fi
        shell: bash

      - name: 🏷️ Create Git Tag
        # Skip tag creation for dev builds to avoid cluttering the repository
        if: ${{ inputs.tag != 'dev' }}
        run: |
          TAG_NAME="v${{ steps.get-version.outputs.VERSION }}"
          echo "Checking for existing tag: $TAG_NAME"
          
          # Check if tag exists locally
          if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
            echo "Tag $TAG_NAME already exists locally, skipping creation"
            exit 0
          fi
          
          # Check if tag exists remotely
          if git ls-remote --tags origin "$TAG_NAME" | grep -q "$TAG_NAME"; then
            echo "Tag $TAG_NAME already exists on remote, skipping creation"
            exit 0
          fi
          
          # Create the tag
          echo "Creating annotated tag $TAG_NAME"
          git tag -a "$TAG_NAME" -m "Release $TAG_NAME"
          echo "✓ Tag $TAG_NAME created successfully"
        shell: bash

      - name: 📤 Push Changes and Tag
        run: |
          # Push the commit to the current branch
          git push origin HEAD
          # Push the tag (only for non-dev builds)
          if [ "${{ inputs.tag }}" != "dev" ]; then
            git push origin "v${{ steps.get-version.outputs.VERSION }}"
          else
            echo "Skipping tag push for dev build"
          fi
        shell: bash

      - name: 🚀 Publish to npm
        run: npm publish --tag ${{ inputs.tag }} --provenance
        shell: bash
        working-directory: ${{ inputs.package-directory }}



================================================
FILE: .github/workflows/release-dev.yml
================================================
name: 'Stencil Store Dev Release'

on:
  workflow_call:

permissions:
  contents: write
  id-token: write

jobs:
  build_stencil_store:
    name: 🏗️ Build Stencil Store
    uses: ./.github/workflows/build.yml

  get_dev_version:
    name: 🔍 Get Dev Build Version
    runs-on: ubuntu-latest
    needs: build_stencil_store
    outputs:
      dev-version: ${{ steps.generate-dev-version.outputs.DEV_VERSION }}
    steps:
      - name: 📥 Checkout Code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: ⚙️ Generate Dev Version
        id: generate-dev-version
        run: |
          PKG_JSON_VERSION=$(cat package.json | jq -r '.version')
          GIT_HASH=$(git rev-parse --short HEAD)
          DEV_VERSION=$PKG_JSON_VERSION-dev.$(date +"%s").$GIT_HASH
          echo "DEV_VERSION=$DEV_VERSION" >> $GITHUB_OUTPUT
        shell: bash

  publish_dev:
    name: 🚀 Publish Dev Build
    needs: get_dev_version
    uses: ./.github/workflows/publish-npm.yml
    with:
      tag: dev
      version: ${{ needs.get_dev_version.outputs.dev-version }}



================================================
FILE: .github/workflows/release-orchestrator.yml
================================================
name: 'Release - Stencil Store'

on:
  workflow_dispatch:
    inputs:
      channel:
        description: 'Which release workflow should run?'
        required: true
        type: choice
        default: dev
        options:
          - dev
          - production
      bump:
        description: 'Semver bump for production releases.'
        required: false
        type: choice
        default: patch
        options:
          - patch
          - minor
          - major

permissions:
  contents: read
  id-token: write

jobs:
  run-dev:
    if: ${{ inputs.channel == 'dev' }}
    uses: ./.github/workflows/release-dev.yml
    secrets: inherit

  run-production:
    if: ${{ inputs.channel == 'production' }}
    uses: ./.github/workflows/release-production.yml
    secrets: inherit
    with:
      bump: ${{ inputs.bump }}



================================================
FILE: .github/workflows/release-production.yml
================================================
name: 'Stencil Store Production Release'

on:
  workflow_call:
    inputs:
      bump:
        description: 'Semver bump to apply (patch, minor, major).'
        required: true
        type: string
        default: patch

permissions:
  contents: write
  id-token: write

jobs:
  validate_bump:
    name: ✅ Validate Bump Input
    runs-on: ubuntu-latest
    steps:
      - name: 🔎 Ensure bump is allowed
        env:
          BUMP: ${{ inputs.bump }}
        run: |
          case "$BUMP" in
            patch|minor|major)
              exit 0
              ;;
            *)
              echo "::error::Invalid bump input: '$BUMP'. Allowed values: patch, minor, major."
              exit 1
              ;;
          esac

  build_stencil_store:
    name: 🏗️ Build Stencil Store
    uses: ./.github/workflows/build.yml

  publish_production:
    name: 🚀 Publish Production Build
    needs:
      - build_stencil_store
      - validate_bump
    uses: ./.github/workflows/publish-npm.yml
    with:
      tag: latest
      version: ${{ inputs.bump }}



================================================
FILE: .gitignore
================================================
dist/
www/
loader/
build/

*~
*.sw[mnpcod]
*.log
*.lock
*.tmp
*.tmp.*
log.txt
*.sublime-project
*.sublime-workspace
*.tgz

.stencil/
.idea/
.vscode/
.sass-cache/
.versions/
node_modules/
$RECYCLE.BIN/

.DS_Store
Thumbs.db
UserInterfaceState.xcuserstate
.env
coverage

================================================
FILE: .nvmrc
================================================
v22.14.0

================================================
FILE: .prettierrc.json
================================================
{
  "parser": "typescript",
  "printWidth": 100,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5"
}

================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to @stencil/store

Thank you for your interest in contributing to @stencil/store! This document provides guidelines and information for contributors.

## Table of Contents

- [Code of Conduct](#code-of-conduct)
- [Getting Started](#getting-started)
- [Development Setup](#development-setup)
- [Making Changes](#making-changes)
- [Testing](#testing)
- [Pull Request Process](#pull-request-process)
- [Release Process](#release-process)

## Code of Conduct

This project follows the same code of conduct as the main StencilJS project. Please be respectful and inclusive in all interactions.

## Getting Started

1. Fork the repository on GitHub
2. Clone your fork locally:
   ```bash
   git clone https://github.com/YOUR_USERNAME/store.git
   cd store
   ```
3. Add the upstream repository:
   ```bash
   git remote add upstream https://github.com/stenciljs/store.git
   ```

## Development Setup

### Prerequisites

- Node.js >= 22.14.0
- npm >= 9.x

### Installation

```bash
npm install
```

### Available Scripts

- `npm run build` - Build the project
- `npm test` - Run all tests
- `npm run test.unit` - Run unit tests with Vitest
- `npm run test.prettier` - Check code formatting
- `npm run prettier` - Format code with Prettier

### Project Structure

```
src/
├── index.ts              # Main entry point
├── store.ts              # Core store implementation
├── observable-map.ts     # Observable map utilities
├── types.ts              # TypeScript type definitions
├── utils.ts              # Utility functions
└── subscriptions/
    └── stencil.ts        # Stencil-specific subscriptions
```

## Making Changes

1. Create a new branch from `main`:
   ```bash
   git checkout -b feature/your-feature-name
   ```

2. Make your changes following these guidelines:
   - Write clear, readable code
   - Add tests for new functionality
   - Update documentation if needed
   - Follow the existing code style

3. Run tests to ensure everything works:
   ```bash
   npm test
   ```

4. Commit your changes with a clear message:
   ```bash
   git commit -m "feat: add new feature description"
   ```

### Commit Message Guidelines

Use conventional commit format:
- `feat:` - New features
- `fix:` - Bug fixes
- `docs:` - Documentation changes
- `style:` - Code style changes (formatting, etc.)
- `refactor:` - Code refactoring
- `test:` - Adding or updating tests
- `chore:` - Build process or auxiliary tool changes

## Testing

### Unit Tests

The project uses Vitest for unit testing. Tests are located alongside the source files with `.test.ts` extensions.

```bash
npm run test.unit
```

### Test App

There's a test application in the `test-app/` directory that demonstrates the store functionality:

```bash
cd test-app
npm install
npm start
```

### Writing Tests

When adding new features or fixing bugs:

1. Add unit tests in the appropriate `.test.ts` file
2. Test both positive and negative cases
3. Include edge cases
4. Ensure tests are isolated and don't depend on external state

## Pull Request Process

1. Push your branch to your fork:
   ```bash
   git push origin feature/your-feature-name
   ```

2. Create a Pull Request on GitHub with:
   - Clear title and description
   - Reference any related issues
   - Include tests for new functionality
   - Update documentation if needed

3. Ensure all checks pass:
   - All tests must pass
   - Code must be properly formatted
   - No linting errors

4. Address any review feedback

5. Once approved, a maintainer will merge your PR

## Release Process

### Release Types

We follow [Semantic Versioning (SemVer)](https://semver.org/) for releases. Choose the appropriate release type based on your changes:

#### Patch Release (x.x.X)
Use for:
- Bug fixes
- Documentation updates
- Internal refactoring that doesn't change the API
- Performance improvements without API changes

#### Minor Release (x.X.x)
Use for:
- New features that are backward compatible
- New API methods or options
- Deprecating functionality (without removing)
- Significant internal improvements

#### Major Release (X.x.x)
Use for:
- Breaking changes to the public API
- Removing deprecated functionality
- Changes that require users to modify their code
- Major architectural changes

### Development Releases

Development Releases (or "Dev Releases", "Dev Builds") are installable instances of Stencil Store that are:
- Published to the npm registry for distribution within and outside the Stencil team
- Built using the same infrastructure as production releases, with less safety checks
- Used to verify a fix or change to the project prior to a production release

#### How to Publish Dev Releases

Only members of the Stencil team may create dev builds of Stencil Store.
To publish a dev build:
1. Navigate to the [Stencil Store Release GitHub Action](https://github.com/stenciljs/store/actions/workflows/release.yml) in your browser.
2. Click the 'Run Workflow' dropdown on the right hand side of the page
3. Configure the workflow inputs:
   - **Release type**: Select `patch`, `minor`, or `major` (this won't affect the dev build version)
   - **Dev Release**: Select `yes` to create a dev build
4. Select the branch you want to build from
5. Click 'Run Workflow'
6. Allow the workflow to run. Upon completion, the output of the 'Publish Dev Build' action will report the published version string.

Following a successful run of the workflow, the package can be installed from the npm registry like any other package.

#### Dev Release Format

Dev Builds are published to the NPM registry under the `@stencil/store` scope.
Unlike production builds, dev builds use a specially formatted version string to express its origins.
Dev builds follow the format `BASE_VERSION-dev.EPOCH_DATE.SHA`, where:
- `BASE_VERSION` is the latest production release changes to the build were based off of
- `EPOCH_DATE` is the number of seconds since January 1st, 1970 in UTC
- `SHA` is the git short SHA of the commit used in the release

As an example: `2.1.0-dev.1677185104.7c87e34` was built:
- With v2.1.0 as the latest production build at the time of the dev build
- On Fri, 26 Jan 2024 13:48:17 UTC
- With the commit `7c87e34`

### Production Releases

Only members of the Stencil team may create production releases of Stencil Store.

#### How to Publish Production Releases

1. Navigate to the [Stencil Store Release GitHub Action](https://github.com/stenciljs/store/actions/workflows/release.yml) in your browser.
2. Click the 'Run Workflow' dropdown on the right hand side of the page
3. Configure the workflow inputs:
   - **Release type**: Select the appropriate type (`patch`, `minor`, or `major`) based on your changes
   - **Dev Release**: Select `no` for production releases
4. Select the `main` branch (production releases should only be made from main)
5. Click 'Run Workflow'
6. The workflow will:
   - Build and test the package
   - Bump the version according to the selected release type
   - Create a Git tag
   - Publish to NPM with the `latest` tag
   - Create a GitHub release

#### Release Checklist

Before creating a production release:
- [ ] Ensure all intended changes are merged to `main`
- [ ] All CI checks are passing
- [ ] Update any relevant documentation
- [ ] Consider if this should be a dev release first for testing
- [ ] Choose the correct release type (patch/minor/major)
- [ ] Notify the team of the planned release

## Questions or Issues?

- Create an issue on GitHub for bugs or feature requests
- Check existing issues before creating new ones
- Provide detailed information including steps to reproduce for bugs

Thank you for contributing to @stencil/store! 🎉 

================================================
FILE: LICENSE
================================================
Copyright 2015-present Drifty Co.
http://drifty.com/

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# @stencil/store

Store is a lightweight shared state library by the [StencilJS](https://stenciljs.com/) core team. It implements a simple key/value map that efficiently re-renders components when necessary.

**Highlights:**

- 🪶 Lightweight
- ⚡ Zero dependencies
- 📦 Simple API, like a reactive Map
- 🚀 Best performance

## Installation

```
npm install @stencil/store --save-dev
```

## Example

**store.ts:**

```ts
import { createStore } from "@stencil/store";

const { state, onChange } = createStore({
  clicks: 0,
  seconds: 0,
  squaredClicks: 0
});

onChange('clicks', value => {
  state.squaredClicks = value ** 2;
});

export default state;
```

**component.tsx:**

```tsx
import { Component, h } from '@stencil/core';
import state from '../store';

@Component({
  tag: 'app-profile',
})
export class AppProfile {

  componentWillLoad() {
    setInterval(() => state.seconds++, 1000);
  }

  render() {
    return (
      <div>
        <p>
          <MyGlobalCounter />
          <p>
            Seconds: {state.seconds}
            <br />
            Squared Clicks: {state.squaredClicks}
          </p>
        </p>
      </div>
    );
  }
}

const MyGlobalCounter = () => {
  return (
    <button onClick={() => state.clicks++}>
      {state.clicks}
    </button>
  );
};
```

## API

### `createStore<T>(initialState?: T | (() => T), shouldUpdate?)`

Create a new store with the given initial state. The type is inferred from `initialState`, or can be passed as the generic type `T`.
`initialState` can be a function that returns the actual initial state. This feature is just in case you have deep objects that mutate
as otherwise we cannot keep track of those.

```ts
const { reset, state } = createStore(() => ({
  pageA: {
    count: 1
  },
  pageB: {
    count: 1
  }
}));

state.pageA.count = 2;
state.pageB.count = 3;

reset();

state.pageA.count; // 1
state.pageB.count; // 1
```

Please, bear in mind that the object needs to be created inside the function, not just referenced. The following example won't work
as you might want it to, as the returned object is always the same one.

```ts
const object = {
  pageA: {
    count: 1
  },
  pageB: {
    count: 1
  }
};
const { reset, state } = createStore(() => object);

state.pageA.count = 2;
state.pageB.count = 3;

reset();

state.pageA.count; // 2
state.pageB.count; // 3
```

By default, store performs a exact comparison (`===`) between the new value, and the previous one in order to prevent unnecessary rerenders, however, this behaviour can be changed by providing a `shouldUpdate` function through the second argument. When this function returns `false`, the value won't be updated. By providing a custom `shouldUpdate()` function, applications can create their own fine-grained change detection logic, beyond the default `===`. This may be useful for certain use-cases to avoid any expensive re-rendering.

```ts
const shouldUpdate = (newValue, oldValue, propChanged) => {
  return JSON.stringify(newValue) !== JSON.stringify(oldValue);
}
```

Returns a `store` object with the following properties.

#### `store.state`

The state object is proxied, i. e. you can directly get and set properties. If you access the state object in the `render` function of your component, Store will automatically re-render it when the state object is changed.

Note: [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) objects are not supported by IE11 (not even with a polyfill), so you need to use the `store.get` and `store.set` methods of the API if you wish to support IE11.

#### `store.on(event, listener)`

Add a listener to the store for a certain action.

#### `store.onChange(propName, listener)`

Add a listener that is called when a specific property changes (either from a `set` or `reset`).

#### `store.get(propName)`

Get a property's value from the store.

#### `store.set(propName, value)`

Set a property's value in the store.

#### `store.reset()`

Reset the store to its initial state.

#### `store.use(...subscriptions)`

Use the given subscriptions in the store. A subscription is an object that defines one or more of the properties `get`, `set` or `reset`.

```ts
const { reset, state, use } = createStore({ a: 1, b: 2});

const unlog = use({
  get: (key) => {
    console.log(`Someone's reading prop ${key}`);
  },
  set: (key, newValue, oldValue) => {
    console.log(`Prop ${key} changed from ${oldValue} to ${newValue}`);
  },
  reset: () => {
    console.log('Store got reset');
  },
  dispose: () => {
    console.log('Store got disposed');
  },
})

state.a; // Someone's reading prop a
state.b = 3; // Prop b changed from 2 to 3
reset(); // Store got reset

unlog();

state.a; // Nothing is logged
state.b = 5; // Nothing is logged
reset(); // Nothing is logged
```

#### `store.dispose()`

Resets the store and all the internal state of the store that should not survive between tests.


## Testing

Like any global state library, state should be `dispose`d between each spec test.
Use the `dispose()` API in the `beforeEach` hook.

```ts
import store from '../store';

beforeEach(() => {
  store.dispose();
});
```


================================================
FILE: package.json
================================================
{
  "name": "@stencil/store",
  "author": "StencilJS Team",
  "version": "2.2.2",
  "description": "Store is a lightweight shared state library by the StencilJS core team. Implements a simple key/value map that efficiently re-renders components when necessary.",
  "license": "MIT",
  "homepage": "https://stenciljs.com/docs/stencil-store",
  "repository": {
    "type": "git",
    "url": "git://github.com/stenciljs/store.git"
  },
  "keywords": [
    "stencil",
    "redux",
    "global",
    "state",
    "tunnel",
    "hooks"
  ],
  "type": "module",
  "module": "dist/index.js",
  "main": "dist/index.cjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  },
  "engines": {
    "node": ">=18.0.0",
    "npm": ">=6.0.0"
  },
  "scripts": {
    "build": "run-s build.clean build.rollup",
    "build.clean": "rimraf dist",
    "build.rollup": "rollup -c rollup.config.js",
    "prettier": "npm run prettier.base -- --write",
    "prettier.base": "prettier --cache \"src/**/*.ts\"",
    "prettier.dry-run": "npm run prettier.base -- --list-different",
    "release": "np",
    "test": "run-s test.*",
    "test.prettier": "npm run prettier.dry-run",
    "test.unit": "vitest",
    "version": "npm run build"
  },
  "files": [
    "dist"
  ],
  "peerDependencies": {
    "@stencil/core": ">=2.0.0 || >=3.0.0 || >= 4.0.0-beta.0 || >= 4.0.0"
  },
  "devDependencies": {
    "@ionic/prettier-config": "^4.0.0",
    "@rollup/plugin-typescript": "^12.3.0",
    "@stencil/core": "^4.38.2",
    "@types/node": "^25.0.2",
    "@vitest/coverage-v8": "^4.0.5",
    "np": "^11.0.1",
    "npm-run-all2": "^8.0.4",
    "prettier": "^3.6.2",
    "rimraf": "^6.0.1",
    "rollup": "^4.52.5",
    "typescript": "~6.0.2",
    "vitest": "^4.0.5"
  },
  "prettier": "@ionic/prettier-config"
}


================================================
FILE: rollup.config.js
================================================
import typescript from '@rollup/plugin-typescript';

import pkg from './package.json' with { type: 'json' };

export default {
  input: 'src/index.ts',
  output: [
    {
      format: 'cjs',
      file: pkg.main
    },
    {
      format: 'esm',
      file: pkg.module
    },
  ],
  external: ['@stencil/core'],
  plugins: [typescript({
    outDir: 'dist'
  })],
};


================================================
FILE: src/index.test.ts
================================================
import { describe, it, expect } from 'vitest';
import { createStore, createObservableMap } from './index';

describe('store', () => {
  it('exports createStore and createObservableMap', () => {
    expect(createStore).toBeDefined();
    expect(createObservableMap).toBeDefined();
  });
});


================================================
FILE: src/index.ts
================================================
export { createStore } from './store';
export { createObservableMap } from './observable-map';

// Types
export { ObservableMap, Subscription } from './types';


================================================
FILE: src/observable-map.test.ts
================================================
import { describe, expect, test, vi } from 'vitest';
import { createObservableMap } from './observable-map';

describe.each([
  ['reset', 'reset'],
  ['dispose calls reset', 'dispose'],
] as [string, 'reset' | 'dispose'][])('%s', (_, methodName) => {
  test('returns all variable to their original state', () => {
    const { [methodName]: method, state } = createObservableMap({
      hola: 'hola',
      name: 'Sergio',
    });

    state.hola = 'hello';
    state.name = 'Manu';

    expect(state.hola).toBe('hello');
    expect(state.name).toBe('Manu');

    method();

    expect(state.hola).toBe('hola');
    expect(state.name).toBe('Sergio');
  });

  test('extra properties get removed', () => {
    const { [methodName]: method, state } = createObservableMap<Record<string, string>>({});

    state['hola'] = 'hello';

    expect(state).toHaveProperty('hola');
    expect(state['hola']).toBe('hello');

    method();

    expect('hola' in state).toBe(false);
  });

  test('calls on', () => {
    const { [methodName]: method, on } = createObservableMap({ hola: 'hello' });
    const subscription = vi.fn();

    on('reset', subscription);

    method();

    expect(subscription).toHaveBeenCalledTimes(1);
  });
});

test('falls back to plain objects when Proxy is unavailable', () => {
  const originalProxy = globalThis.Proxy;
  // @ts-expect-error - simulating environments without Proxy support
  globalThis.Proxy = undefined;

  try {
    const store = createObservableMap({ count: 1 });

    store.set('count', 2);
    store.set('extra', 3);

    expect(store.get('count')).toBe(2);
    expect('extra' in store.state).toBe(true);

    store.reset();

    expect(store.get('count')).toBe(1);
    expect('extra' in store.state).toBe(false);
  } finally {
    globalThis.Proxy = originalProxy;
  }
});

test('reset restores empty state when no default state is provided', () => {
  const store = createObservableMap<Record<string, number>>();

  store.set('num', 1);
  expect(store.get('num')).toBe(1);

  store.reset();

  expect(store.get('num')).toBeUndefined();
});

describe('dispose', () => {
  test('calls on', () => {
    const { dispose, on } = createObservableMap({ hola: 'hello' });
    const subscription = vi.fn();

    on('dispose', subscription);

    dispose();

    expect(subscription).toHaveBeenCalledTimes(1);
  });
});

describe.each([
  ['proxy', (state, _get, property) => state[property]],
  ['get fn', (_state, get, property) => get(property)],
] as [string, <T, K extends keyof T>(s: T, get: (prop: K) => T[K], prop: K) => T[K]][])('get (%s)', (_, getter) => {
  test('returns the value for the property in the store', () => {
    const { get, state } = createObservableMap({
      hola: 'hello',
    });

    expect(getter(state, get, 'hola')).toBe('hello');
  });

  test('returns the modified value after being set', () => {
    const { get, state } = createObservableMap({
      hola: 'hello',
    });

    state.hola = 'ola';

    expect(getter(state, get, 'hola')).toBe('ola');
  });

  test('calls on', () => {
    const { get, on, state } = createObservableMap({
      hola: 'hello',
    });
    const subscription = vi.fn();
    on('get', subscription);

    getter(state, get, 'hola');

    expect(subscription).toHaveBeenCalledWith('hola');
  });
});

describe.each([
  ['proxy', (state, _set, prop, value) => (state[prop] = value)],
  ['set fn', (_state, set, prop, value) => set(prop, value)],
] as [string, <T, K extends keyof T>(s: T, set: (prop: K, value: T[K]) => void, prop: K, value: T[K]) => void][])(
  'set (%s)',
  (_, setter) => {
    test('sets the value for a property', () => {
      const { set, state } = createObservableMap({
        hola: 'hello',
      });

      setter(state, set, 'hola', 'ola');

      expect(state.hola).toBe('ola');
    });

    test('calls on', () => {
      const { set, on, state } = createObservableMap({
        hola: 'hello',
      });
      const subscription = vi.fn();
      on('set', subscription);

      setter(state, set, 'hola', 'ola');

      expect(subscription).toHaveBeenCalledWith('hola', 'ola', 'hello');
    });

    test('calls onChange', () => {
      const { set, onChange, state } = createObservableMap({
        hola: 'hello',
      });
      const subscription = vi.fn();
      onChange('hola', subscription);

      setter(state, set, 'hola', 'ola');

      expect(subscription).toHaveBeenCalledWith('ola');
    });

    test('enumerable keys', () => {
      const { state } = createObservableMap<any>({});
      expect(Object.keys(state)).toEqual([]);
      state.hello = 'hola';
      expect(Object.keys(state)).toEqual(['hello']);
      expect(Object.getOwnPropertyNames(state)).toEqual(['hello']);
      const copy = { ...state };
      expect(copy).toEqual({ hello: 'hola' });
    });

    test('in operator', () => {
      const { state } = createObservableMap<any>({});
      expect('hello' in state).toBe(false);
      state.hello = 'hola';
      expect('hello' in state).toBe(true);
    });
  },
);

describe('using a function as initial value', () => {
  test('function gets invoked', () => {
    const fn = vi.fn().mockReturnValue({ a: 1 });

    createObservableMap(fn);

    // We should not need to call this more than once
    // when creating the proxy.
    expect(fn).toHaveBeenCalledTimes(1);
  });

  test('returned value is used as object', () => {
    const fn = vi.fn().mockReturnValue({ a: 1 });

    const { state } = createObservableMap(fn);

    expect(state).toHaveProperty('a', 1);
  });

  test('resetting resets deep objects', () => {
    const fn = () => ({
      a: {
        b: 1,
        c: 2,
      },
      d: [1, 2],
    });
    const { reset, state } = createObservableMap(fn);
    state.a.b = 3;
    state.a.c = 5;
    state.d.push(1, 2);

    reset();

    expect(state.a).toHaveProperty('b', 1);
    expect(state.a).toHaveProperty('c', 2);
    expect(state).toHaveProperty('d', [1, 2]);
  });

  test('if the function does not create a new object, there is nothing we can do', () => {
    const object = {
      a: {
        b: 1,
        c: 2,
      },
      d: [1, 2],
    };
    const fn = () => object;
    const { reset, state } = createObservableMap(fn);
    state.a.b = 3;
    state.a.c = 5;
    state.d.push(1, 2);

    reset();

    expect(state.a).toHaveProperty('b', 3);
    expect(state.a).toHaveProperty('c', 5);
    expect(state).toHaveProperty('d', [1, 2, 1, 2]);
  });
});

test('unregister events', () => {
  const { reset, state, on, onChange } = createObservableMap({
    hola: 'hola',
    name: 'Sergio',
  });
  const SET = vi.fn();
  const GET = vi.fn();
  const RESET = vi.fn();
  const CHANGE = vi.fn();

  const unset = on('set', SET);
  const unget = on('get', GET);
  const unreset = on('reset', RESET);
  const unChange = onChange('hola', CHANGE);

  state.hola = 'hola2';
  state.name = 'hola2';
  expect(SET).toHaveBeenCalledTimes(2);
  unset();
  state.hola = 'hola3';
  expect(SET).toHaveBeenCalledTimes(2);

  state.hola;
  state.name;
  expect(GET).toHaveBeenCalledTimes(2);
  unget();
  state.name;
  expect(GET).toHaveBeenCalledTimes(2);

  reset();
  reset();
  expect(RESET).toHaveBeenCalledTimes(2);
  unreset();
  reset();
  expect(RESET).toHaveBeenCalledTimes(2);

  expect(CHANGE).toHaveBeenCalledTimes(5);
  unChange();
  reset();
  state.hola = 'hola';
  expect(CHANGE).toHaveBeenCalledTimes(5);
});

test('default change detector', () => {
  const store = createObservableMap({
    str: 'hola',
  });
  const SET = vi.fn();
  store.on('set', SET);
  store.state.str = 'hola';
  expect(SET).not.toBeCalled();
  store.state.str = 'hola2';
  expect(SET).toBeCalledWith('str', 'hola2', 'hola');
});

test('custom change detector, values', () => {
  const comparer = vi.fn((a, b) => a !== b);
  const store = createObservableMap(
    {
      str: 'hola',
    },
    comparer,
  );
  store.state.str = 'hola';
  expect(comparer).toBeCalledWith('hola', 'hola', 'str');
  store.state.str = 'hola2';
  expect(comparer).toBeCalledWith('hola2', 'hola', 'str');
  store.state.str = 'hola3';
  expect(comparer).toBeCalledWith('hola3', 'hola2', 'str');
});

test('custom change detector, prevent all mutations', () => {
  const store = createObservableMap(
    {
      str: 'hola',
    },
    () => false,
  );
  const SET = vi.fn();
  store.on('set', SET);
  store.state.str = 'hola';
  expect(SET).not.toBeCalled();
  store.state.str = 'hola2';
  expect(SET).not.toBeCalled();
  expect(store.state.str).toEqual('hola');
});

describe('use subscriptions', () => {
  test('get is called whenever we get a prop', () => {
    const store = createObservableMap({ str: 'hola' });
    const get = vi.fn();
    store.use({ get });

    store.state.str;

    expect(get).toHaveBeenCalledTimes(1);
    expect(get).toHaveBeenCalledWith('str');
  });

  test('get is unregistered', () => {
    const store = createObservableMap({ str: 'hola' });
    const get = vi.fn();
    const unregister = store.use({ get });
    store.state.str;
    expect(get).toHaveBeenCalledTimes(1);
    get.mockClear();

    unregister();
    store.state.str;

    expect(get).not.toHaveBeenCalled();
  });

  test('set is called whenever we set a prop', () => {
    const store = createObservableMap({ str: 'hola' });
    const set = vi.fn();
    store.use({ set });

    store.state.str = 'adios';

    expect(set).toHaveBeenCalledTimes(1);
    expect(set).toHaveBeenCalledWith('str', 'adios', 'hola');
  });

  test('set is unregistered', () => {
    const store = createObservableMap({ str: 'hola' });
    const set = vi.fn();
    const unregister = store.use({ set });
    store.state.str = 'adios';
    expect(set).toHaveBeenCalledTimes(1);
    set.mockClear();

    unregister();
    store.state.str = 'hello';

    expect(set).not.toHaveBeenCalled();
  });

  test('reset is called when we reset the store', () => {
    const store = createObservableMap({ str: 'hola' });
    const reset = vi.fn();
    store.use({ reset });

    store.reset();

    expect(reset).toHaveBeenCalledTimes(1);
  });

  test('reset is unregistered', () => {
    const store = createObservableMap({ str: 'hola' });
    const reset = vi.fn();
    const unregister = store.use({ reset });
    store.reset();
    expect(reset).toHaveBeenCalledTimes(1);
    reset.mockClear();

    unregister();
    store.reset();

    expect(reset).not.toHaveBeenCalled();
  });

  test('dispose is called when we dispose the store', () => {
    const store = createObservableMap({ str: 'hola' });
    const dispose = vi.fn();
    store.use({ dispose });

    store.dispose();

    expect(dispose).toHaveBeenCalledTimes(1);
  });

  test('dispose is unregistered', () => {
    const store = createObservableMap({ str: 'hola' });
    const dispose = vi.fn();
    const unregister = store.use({ dispose });
    store.dispose();
    expect(dispose).toHaveBeenCalledTimes(1);
    dispose.mockClear();

    unregister();
    store.dispose();

    expect(dispose).not.toHaveBeenCalled();
  });

  test('subscription with several properties subscribes to all of them', () => {
    const store = createObservableMap({ str: 'hola' });
    const subscription = {
      dispose: vi.fn(),
      get: vi.fn(),
      reset: vi.fn(),
      set: vi.fn(),
    };
    store.use(subscription);

    store.state.str;
    expect(subscription.get).toHaveBeenCalledTimes(1);

    store.state.str = 'adios';
    expect(subscription.set).toHaveBeenCalledTimes(1);

    store.reset();
    expect(subscription.reset).toHaveBeenCalledTimes(1);

    store.dispose();
    expect(subscription.dispose).toHaveBeenCalledTimes(1);
  });

  test('subscription with several properties can be unregistered', () => {
    const store = createObservableMap({ str: 'hola' });
    const subscription = {
      dispose: vi.fn(),
      get: vi.fn(),
      reset: vi.fn(),
      set: vi.fn(),
    };
    const unregister = store.use(subscription);

    store.state.str;
    expect(subscription.get).toHaveBeenCalledTimes(1);
    store.state.str = 'adios';
    expect(subscription.set).toHaveBeenCalledTimes(1);
    store.reset();
    expect(subscription.reset).toHaveBeenCalledTimes(1);
    store.dispose();
    expect(subscription.dispose).toHaveBeenCalledTimes(1);
    vi.clearAllMocks();

    unregister();

    store.state.str;
    expect(subscription.get).not.toHaveBeenCalled();
    store.state.str = 'adios';
    expect(subscription.set).not.toHaveBeenCalled();
    store.reset();
    expect(subscription.reset).not.toHaveBeenCalled();
    store.dispose();
    expect(subscription.dispose).not.toHaveBeenCalled();
  });

  test('use can be passed several subscriptions', () => {
    const store = createObservableMap({ str: 'hola' });
    const subscription = {
      dispose: vi.fn(),
      get: vi.fn(),
      reset: vi.fn(),
      set: vi.fn(),
    };
    const subscription2 = {
      dispose: vi.fn(),
      get: vi.fn(),
      reset: vi.fn(),
      set: vi.fn(),
    };
    store.use(subscription, subscription2);

    store.state.str;
    expect(subscription.get).toHaveBeenCalledTimes(1);
    expect(subscription2.get).toHaveBeenCalledTimes(1);

    store.state.str = 'adios';
    expect(subscription.set).toHaveBeenCalledTimes(1);
    expect(subscription2.set).toHaveBeenCalledTimes(1);

    store.reset();
    expect(subscription.reset).toHaveBeenCalledTimes(1);
    expect(subscription2.reset).toHaveBeenCalledTimes(1);

    store.dispose();
    expect(subscription.dispose).toHaveBeenCalledTimes(1);
    expect(subscription2.dispose).toHaveBeenCalledTimes(1);
  });

  test('use can be passed several subscriptions and unregisters them all', () => {
    const store = createObservableMap({ str: 'hola' });
    const subscription = {
      dispose: vi.fn(),
      get: vi.fn(),
      reset: vi.fn(),
      set: vi.fn(),
    };
    const subscription2 = {
      dispose: vi.fn(),
      get: vi.fn(),
      reset: vi.fn(),
      set: vi.fn(),
    };
    const unregister = store.use(subscription, subscription2);
    store.state.str;
    expect(subscription.get).toHaveBeenCalledTimes(1);
    expect(subscription2.get).toHaveBeenCalledTimes(1);
    store.state.str = 'adios';
    expect(subscription.set).toHaveBeenCalledTimes(1);
    expect(subscription2.set).toHaveBeenCalledTimes(1);
    store.reset();
    expect(subscription.reset).toHaveBeenCalledTimes(1);
    expect(subscription2.reset).toHaveBeenCalledTimes(1);
    store.dispose();
    expect(subscription.dispose).toHaveBeenCalledTimes(1);
    expect(subscription2.dispose).toHaveBeenCalledTimes(1);
    vi.clearAllMocks();

    unregister();

    store.state.str;
    expect(subscription.get).not.toHaveBeenCalled();
    expect(subscription2.get).not.toHaveBeenCalled();
    store.state.str = 'adios';
    expect(subscription.set).not.toHaveBeenCalled();
    expect(subscription2.set).not.toHaveBeenCalled();
    store.reset();
    expect(subscription.reset).not.toHaveBeenCalled();
    expect(subscription2.reset).not.toHaveBeenCalled();
    store.dispose();
    expect(subscription.dispose).not.toHaveBeenCalled();
    expect(subscription2.dispose).not.toHaveBeenCalled();
  });
});

describe('removeListener', () => {
  test('removes a listener from the set event', () => {
    const store = createObservableMap({ str: 'hola' });
    const listener = vi.fn();

    store.onChange('str', listener);
    store.state.str = 'adios';
    expect(listener).toHaveBeenCalledWith('adios');

    store.removeListener('str', listener);
    store.state.str = 'hello';
    expect(listener).toHaveBeenCalledTimes(1);
  });

  test('removes a listener from the reset event', () => {
    const store = createObservableMap({ str: 'hola' });
    const listener = vi.fn();

    store.onChange('str', listener);
    store.reset();
    expect(listener).toHaveBeenCalledWith('hola');

    store.removeListener('str', listener);
    store.reset();
    expect(listener).toHaveBeenCalledTimes(1);
  });

  test('ignores unknown listeners', () => {
    const store = createObservableMap({ str: 'hola' });
    const listener = vi.fn();

    store.removeListener('str', listener);

    // should not throw and should not start tracking the listener
    store.set('str', 'hola2');

    expect(listener).not.toHaveBeenCalled();
  });
});

test('forceUpdate', () => {
  const store = createObservableMap({
    str: 'hola',
  });
  const SET = vi.fn();
  store.on('set', SET);
  store.forceUpdate('str');
  store.forceUpdate('str');
  expect(SET).toHaveBeenCalledTimes(2);
  expect(SET).toBeCalledWith('str', 'hola', 'hola');
});


================================================
FILE: src/observable-map.ts
================================================
import { OnHandler, OnChangeHandler, Subscription, ObservableMap, Handlers } from './types';

type Invocable<T> = T | (() => T);

const unwrap = <T>(val: Invocable<T>): T => (typeof val === 'function' ? (val as () => T)() : val);

export const createObservableMap = <T extends { [key: string]: any }>(
  defaultState?: Invocable<T>,
  shouldUpdate: (newV: any, oldValue, prop: keyof T) => boolean = (a, b) => a !== b,
): ObservableMap<T> => {
  const resolveDefaultState = (): T => (unwrap(defaultState) ?? {}) as T;
  const initialState = resolveDefaultState();
  let states = new Map<string, any>(Object.entries(initialState));
  const proxyAvailable = typeof Proxy !== 'undefined';
  const plainState: T | null = proxyAvailable ? null : ({} as T);
  const handlers: Handlers<T> = {
    dispose: [],
    get: [],
    set: [],
    reset: [],
  };

  // Track onChange listeners to enable removeListener functionality
  const changeListeners = new Map<Function, { setHandler: Function; resetHandler: Function; propName: keyof T }>();

  const reset = (): void => {
    // When resetting the state, the default state may be a function - unwrap it to invoke it.
    // otherwise, the state won't be properly reset
    states = new Map<string, any>(Object.entries(resolveDefaultState()));
    if (!proxyAvailable) {
      syncPlainStateKeys();
    }

    handlers.reset.forEach((cb) => cb());
  };

  const dispose = (): void => {
    // Call first dispose as resetting the state would
    // cause less updates ;)
    handlers.dispose.forEach((cb) => cb());
    reset();
  };

  const get = <P extends keyof T>(propName: P & string): T[P] => {
    handlers.get.forEach((cb) => cb(propName));

    return states.get(propName);
  };

  const set = <P extends keyof T>(propName: P & string, value: T[P]) => {
    const oldValue = states.get(propName);
    if (shouldUpdate(value, oldValue, propName)) {
      states.set(propName, value);
      if (!proxyAvailable) {
        ensurePlainProperty(propName as string);
      }

      handlers.set.forEach((cb) => cb(propName, value, oldValue));
    }
  };
  const state = (
    proxyAvailable
      ? new Proxy(initialState, {
          get(_, propName) {
            return get(propName as any);
          },
          ownKeys(_) {
            return Array.from(states.keys());
          },
          getOwnPropertyDescriptor() {
            return {
              enumerable: true,
              configurable: true,
            };
          },
          has(_, propName) {
            return states.has(propName as any);
          },
          set(_, propName, value) {
            set(propName as any, value);
            return true;
          },
        })
      : (() => {
          syncPlainStateKeys();
          return plainState!;
        })()
  ) as T;

  const on: OnHandler<T> = (eventName, callback) => {
    handlers[eventName].push(callback);
    return () => {
      removeFromArray(handlers[eventName], callback);
    };
  };

  const onChange: OnChangeHandler<T> = (propName, cb) => {
    const setHandler = (key, newValue) => {
      if (key === propName) {
        cb(newValue);
      }
    };

    const resetHandler = () => {
      const snapshot = resolveDefaultState();
      cb(snapshot[propName]);
    };

    // Register the handlers
    const unSet = on('set', setHandler);
    const unReset = on('reset', resetHandler);

    // Track the relationship between the user callback and internal handlers
    changeListeners.set(cb, { setHandler, resetHandler, propName });

    return () => {
      unSet();
      unReset();
      changeListeners.delete(cb);
    };
  };

  const use = (...subscriptions: Subscription<T>[]): (() => void) => {
    const unsubs = subscriptions.reduce((unsubs, subscription) => {
      if (subscription.set) {
        unsubs.push(on('set', subscription.set));
      }
      if (subscription.get) {
        unsubs.push(on('get', subscription.get));
      }
      if (subscription.reset) {
        unsubs.push(on('reset', subscription.reset));
      }
      if (subscription.dispose) {
        unsubs.push(on('dispose', subscription.dispose));
      }

      return unsubs;
    }, []);

    return () => unsubs.forEach((unsub) => unsub());
  };

  const forceUpdate = (key: string) => {
    const oldValue = states.get(key);
    handlers.set.forEach((cb) => cb(key, oldValue, oldValue));
  };

  const removeListener = (propName: keyof T, listener: (value: any) => void) => {
    const listenerInfo = changeListeners.get(listener);
    if (listenerInfo && listenerInfo.propName === propName) {
      // Remove the specific handlers that were created for this listener
      removeFromArray(handlers.set, listenerInfo.setHandler);
      removeFromArray(handlers.reset, listenerInfo.resetHandler);
      changeListeners.delete(listener);
    }
  };

  function ensurePlainProperty(key: string): void {
    if (proxyAvailable || !plainState) {
      return;
    }

    if (Object.prototype.hasOwnProperty.call(plainState, key)) {
      return;
    }

    Object.defineProperty(plainState, key, {
      configurable: true,
      enumerable: true,
      get() {
        return get(key as keyof T & string);
      },
      set(value: any) {
        set(key as keyof T & string, value);
      },
    });
  }

  function syncPlainStateKeys(): void {
    if (proxyAvailable || !plainState) {
      return;
    }

    const knownKeys = new Set(states.keys());

    for (const key of Object.keys(plainState as object)) {
      if (!knownKeys.has(key)) {
        delete (plainState as Record<string, unknown>)[key];
      }
    }

    for (const key of knownKeys) {
      ensurePlainProperty(key);
    }
  }

  return {
    state,
    get,
    set,
    on,
    onChange,
    use,
    dispose,
    reset,
    forceUpdate,
    removeListener,
  };
};
const removeFromArray = (array: any[], item: any) => {
  const index = array.indexOf(item);
  if (index >= 0) {
    array[index] = array[array.length - 1];
    array.length--;
  }
};


================================================
FILE: src/store.test.ts
================================================
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { Mock } from 'vitest';

vi.mock('./observable-map', () => {
  return {
    createObservableMap: vi.fn(),
  };
});

vi.mock('./subscriptions/stencil', () => {
  return {
    stencilSubscription: vi.fn(),
  };
});

afterEach(() => {
  vi.clearAllMocks();
});

describe('createStore', () => {
  it('creates an observable map and wires the stencil subscription', async () => {
    const map = { use: vi.fn(), state: {} } as const;
    const subscription = { get: vi.fn() };

    const { createObservableMap } = await import('./observable-map');
    const { stencilSubscription } = await import('./subscriptions/stencil');

    (createObservableMap as unknown as Mock).mockReturnValue(map as any);
    (stencilSubscription as unknown as Mock).mockReturnValue(subscription as any);

    const { createStore } = await import('./store');

    const defaultState = { value: 1 };
    const shouldUpdate = vi.fn();

    const result = createStore(defaultState, shouldUpdate);

    expect(createObservableMap).toHaveBeenCalledWith(defaultState, shouldUpdate);
    expect(map.use).toHaveBeenCalledWith(subscription);
    expect(result).toBe(map);
  });
});


================================================
FILE: src/store.ts
================================================
import { stencilSubscription } from './subscriptions/stencil';
import { createObservableMap } from './observable-map';
import { ObservableMap } from './types';

export const createStore = <T extends { [key: string]: any }>(
  defaultState?: T | (() => T),
  shouldUpdate?: (newV: any, oldValue, prop: keyof T) => boolean,
): ObservableMap<T> => {
  const map = createObservableMap(defaultState, shouldUpdate);
  map.use(stencilSubscription());
  return map;
};


================================================
FILE: src/subscriptions/stencil.test.ts
================================================
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

const coreMock = vi.hoisted(() => ({
  exports: {} as {
    forceUpdate?: ReturnType<typeof vi.fn>;
    getRenderingRef?: ReturnType<typeof vi.fn>;
  },
}));

vi.mock('@stencil/core', () => coreMock.exports);

describe('stencilSubscription', () => {
  beforeEach(() => {
    coreMock.exports.forceUpdate = undefined;
    coreMock.exports.getRenderingRef = undefined;
  });

  afterEach(() => {
    vi.clearAllMocks();
    vi.resetModules();
    vi.useRealTimers();
  });

  it('returns an empty subscription when stencil internals are unavailable', async () => {
    coreMock.exports.forceUpdate = vi.fn();
    coreMock.exports.getRenderingRef = undefined;

    const { stencilSubscription } = await import('./stencil');

    expect(stencilSubscription()).toEqual({});
  });

  it('tracks stencil elements and triggers updates', async () => {
    vi.useFakeTimers();

    const connectedElm = { isConnected: true, id: 'connected' };
    const disconnectedElm = { isConnected: false, id: 'disconnected' };
    const legacyElm = { id: 'legacy' } as { id: string; isConnected?: boolean };

    const forceUpdate = vi.fn((elm: typeof connectedElm) => elm.isConnected !== false);
    const getRenderingRef = vi
      .fn()
      .mockReturnValueOnce(connectedElm)
      .mockReturnValueOnce(disconnectedElm)
      .mockReturnValueOnce(legacyElm)
      .mockReturnValueOnce(undefined);

    coreMock.exports.forceUpdate = forceUpdate as any;
    coreMock.exports.getRenderingRef = getRenderingRef as any;

    const { stencilSubscription } = await import('./stencil');

    const subscription = stencilSubscription();

    subscription.set?.('missing');
    expect(forceUpdate).not.toHaveBeenCalled();

    subscription.get?.('prop');
    subscription.get?.('prop');
    subscription.get?.('prop');
    subscription.get?.('prop');

    expect(getRenderingRef).toHaveBeenCalledTimes(4);

    subscription.set?.('prop');

    expect(forceUpdate).toHaveBeenCalledTimes(3);
    expect(forceUpdate.mock.calls[0]?.[0]).toBe(connectedElm);
    expect(forceUpdate.mock.calls[1]?.[0]).toBe(disconnectedElm);
    expect(forceUpdate.mock.calls[2]?.[0]).toBe(legacyElm);

    vi.runAllTimers();

    forceUpdate.mockClear();

    subscription.reset?.();

    expect(forceUpdate).toHaveBeenCalledTimes(2);
    expect(forceUpdate.mock.calls[0]?.[0]).toBe(connectedElm);
    expect(forceUpdate.mock.calls[1]?.[0]).toBe(legacyElm);

    vi.runAllTimers();

    forceUpdate.mockClear();
    subscription.dispose?.();
    subscription.reset?.();

    expect(forceUpdate).not.toHaveBeenCalled();
  });

  it('prevents duplicate subscriptions for the same element', async () => {
    const elm = { isConnected: true, id: 'unique' };
    const forceUpdate = vi.fn();
    const getRenderingRef = vi.fn().mockReturnValue(elm);

    coreMock.exports.forceUpdate = forceUpdate as any;
    coreMock.exports.getRenderingRef = getRenderingRef as any;

    const { stencilSubscription } = await import('./stencil');
    const subscription = stencilSubscription();

    subscription.get?.('prop');
    subscription.get?.('prop');

    expect(getRenderingRef).toHaveBeenCalledTimes(2);

    subscription.set?.('prop');

    expect(forceUpdate).toHaveBeenCalledTimes(1);
  });

  it('handles garbage collected elements', async () => {
    const originalWeakRef = global.WeakRef;
    const gcedElm = { id: 'gced' };
    const keptElm = { id: 'kept', isConnected: true };

    class MockWeakRef {
      target: any;
      constructor(target: any) {
        this.target = target;
      }
      deref() {
        if (this.target === gcedElm) return undefined;
        return this.target;
      }
    }
    (global as any).WeakRef = MockWeakRef;

    const forceUpdate = vi.fn(() => true);
    const getRenderingRef = vi.fn().mockReturnValueOnce(gcedElm).mockReturnValueOnce(keptElm);

    coreMock.exports.forceUpdate = forceUpdate as any;
    coreMock.exports.getRenderingRef = getRenderingRef as any;

    try {
      const { stencilSubscription } = await import('./stencil');
      const subscription = stencilSubscription();

      subscription.get?.('prop');
      subscription.get?.('prop');

      subscription.set?.('prop');

      expect(forceUpdate).toHaveBeenCalledTimes(1);
      expect(forceUpdate).toHaveBeenCalledWith(keptElm);

      forceUpdate.mockClear();
      subscription.reset?.();
      expect(forceUpdate).toHaveBeenCalledTimes(1);
      expect(forceUpdate).toHaveBeenCalledWith(keptElm);
    } finally {
      global.WeakRef = originalWeakRef;
    }
  });
});


================================================
FILE: src/subscriptions/stencil.ts
================================================
import * as StencilCore from '@stencil/core';
import { Subscription } from '../types';
import { appendToMap, debounce } from '../utils';

/**
 * Check if a possible element isConnected.
 * The property might not be there, so we check for it.
 *
 * We want it to return true if isConnected is not a property,
 * otherwise we would remove these elements and would not update.
 *
 * Better leak in Edge than to be useless.
 */
const isConnected = (maybeElement: any) => !('isConnected' in maybeElement) || maybeElement.isConnected;

const cleanupElements = debounce((map: Map<string, WeakRef<any>[]>) => {
  for (let key of map.keys()) {
    const refs = map.get(key).filter((ref) => {
      const elm = ref.deref();
      return elm && isConnected(elm);
    });
    map.set(key, refs);
  }
}, 2_000);

const core = StencilCore as unknown as {
  forceUpdate?: (elm: any) => boolean;
  getRenderingRef?: () => any;
};

const forceUpdate = core.forceUpdate;
const getRenderingRef = core.getRenderingRef;

export const stencilSubscription = <T>(): Subscription<T> => {
  if (typeof getRenderingRef !== 'function' || typeof forceUpdate !== 'function') {
    // If we are not in a stencil project, we do nothing.
    // This function is not really exported by @stencil/core.
    return {};
  }

  const ensureForceUpdate = forceUpdate;
  const ensureGetRenderingRef = getRenderingRef;
  const elmsToUpdate = new Map<string, WeakRef<any>[]>();

  return {
    dispose: () => elmsToUpdate.clear(),
    get: (propName) => {
      const elm = ensureGetRenderingRef();
      if (elm) {
        appendToMap(elmsToUpdate, propName as string, elm);
      }
    },
    set: (propName) => {
      const refs = elmsToUpdate.get(propName as string);
      if (refs) {
        const nextRefs = refs.filter((ref) => {
          const elm = ref.deref();
          if (!elm) return false;
          return ensureForceUpdate(elm);
        });
        elmsToUpdate.set(propName as string, nextRefs);
      }
      cleanupElements(elmsToUpdate);
    },
    reset: () => {
      elmsToUpdate.forEach((refs) => {
        refs.forEach((ref) => {
          const elm = ref.deref();
          if (elm) ensureForceUpdate(elm);
        });
      });
      cleanupElements(elmsToUpdate);
    },
  };
};


================================================
FILE: src/types.ts
================================================
export interface Handlers<T> {
  dispose: DisposeEventHandler[];
  get: GetEventHandler<T>[];
  reset: ResetEventHandler[];
  set: SetEventHandler<T>[];
}

export type SetEventHandler<StoreType> = (key: keyof StoreType, newValue: any, oldValue: any) => void;
export type GetEventHandler<StoreType> = (key: keyof StoreType) => void;
export type ResetEventHandler = () => void;
export type DisposeEventHandler = () => void;

export interface OnHandler<StoreType> {
  (eventName: 'set', callback: SetEventHandler<StoreType>): () => void;
  (eventName: 'get', callback: GetEventHandler<StoreType>): () => void;
  (eventName: 'dispose', callback: DisposeEventHandler): () => void;
  (eventName: 'reset', callback: ResetEventHandler): () => void;
}

export interface OnChangeHandler<StoreType> {
  <Key extends keyof StoreType>(propName: Key, cb: (newValue: StoreType[Key]) => void): () => void;
}

export interface Subscription<StoreType> {
  dispose?(): void;
  get?<KeyFromStoreType extends keyof StoreType>(key: KeyFromStoreType): void;
  set?<KeyFromStoreType extends keyof StoreType>(
    key: KeyFromStoreType,
    newValue: StoreType[KeyFromStoreType],
    oldValue: StoreType[KeyFromStoreType],
  ): void;
  reset?(): void;
}

export interface Getter<T> {
  <P extends keyof T>(propName: P & string): T[P];
}

export interface Setter<T> {
  <P extends keyof T>(propName: P & string, value: T[P]): void;
}

export interface ObservableMap<T> {
  /**
   * Proxied object that will detect dependencies and call
   * the subscriptions and computed properties.
   *
   * If available, it will detect from which Stencil Component
   * it was called and rerender it when the property changes.
   *
   * Note: Proxy objects are not supported by IE11 (not even with a polyfill)
   * so you need to use the store.get and store.set methods of the API if you wish to support IE11.
   *
   * @returns The proxied object
   */
  state: T;

  /**
   * Only useful if you need to support IE11.
   *
   * @param propName - The property name to get
   * @returns The value of the property
   *
   * @example
   * const { state, get } = createStore({ hola: 'hello', adios: 'goodbye' });
   * console.log(state.hola); // If you don't need to support IE11, use this way.
   * console.log(get('hola')); // If you need to support IE11, use this other way.
   */
  get: Getter<T>;

  /**
   * Only useful if you need to support IE11.
   *
   * @param propName - The property name to set
   * @param value - The value to set
   * @returns void
   *
   * @example
   * const { state, get } = createStore({ hola: 'hello', adios: 'goodbye' });
   * state.hola = 'ola'; // If you don't need to support IE11, use this way.
   * set('hola', 'ola')); // If you need to support IE11, use this other way.
   */
  set: Setter<T>;

  /**
   * Register a event listener, you can listen to `set`, `get` and `reset` events.
   *
   * @param eventName - The event name to listen for
   * @param callback - The callback to call when the event occurs
   * @returns A function to unsubscribe from the listener
   *
   * @example
   * store.on('set', (prop, value) => {
   *   console.log(`Prop ${prop} changed to: ${value}`);
   * });
   */
  on: OnHandler<T>;

  /**
   * Easily listen for value changes of the specified key.
   *
   * @param propName - The property name to listen for
   * @param cb - The callback to call when the property changes
   * @returns A function to unsubscribe from the listener
   */
  onChange: OnChangeHandler<T>;

  /**
   * Resets the state to its original state and
   * signals a dispose event to all the plugins.
   *
   * This method is intended for plugins to reset
   * all their internal state between tests.
   *
   * @returns void
   */
  dispose(): void;

  /**
   * Resets the state to its original state.
   *
   * @returns void
   */
  reset(): void;

  /**
   * Registers a subscription that will be called whenever the user gets, sets, or
   * resets a value.
   *
   * @param plugins - The plugins to use
   * @returns A function to unsubscribe from the plugins
   */
  use(...plugins: Subscription<T>[]): () => void;

  /**
   * Force a rerender of the specified key, just like the value changed.
   *
   * @param key - The property name to force an update for
   * @returns void
   */
  forceUpdate(key: keyof T): void;

  /**
   * Remove a listener
   *
   * @param propName - The property name to remove the listener from
   * @param listener - The listener to remove
   * @returns void
   */
  removeListener(propName: keyof T, listener: (value: any) => void): void;
}


================================================
FILE: src/utils.test.ts
================================================
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { appendToMap, debounce } from './utils';

describe('appendToMap', () => {
  let testMap: Map<string, WeakRef<Object>[]>;

  beforeEach(() => {
    testMap = new Map();
  });

  it('should add value to empty map', () => {
    const obj = { id: 1 };
    appendToMap(testMap, 'key1', obj);

    const refs = testMap.get('key1');
    expect(refs).toHaveLength(1);
    expect(refs![0].deref()).toBe(obj);
  });

  it('should append value to existing array', () => {
    const obj1 = { id: 1 };
    const obj2 = { id: 3 };

    appendToMap(testMap, 'key1', obj1);
    appendToMap(testMap, 'key1', obj2);

    const refs = testMap.get('key1');
    expect(refs).toHaveLength(2);
    expect(refs![0].deref()).toBe(obj1);
    expect(refs![1].deref()).toBe(obj2);
  });

  it('should not append duplicate value', () => {
    const obj1 = { id: 1 };
    const obj2 = { id: 2 };

    appendToMap(testMap, 'key1', obj1);
    appendToMap(testMap, 'key1', obj2);
    appendToMap(testMap, 'key1', obj1); // Duplicate

    const refs = testMap.get('key1');
    expect(refs).toHaveLength(2);
    expect(refs![0].deref()).toBe(obj1);
    expect(refs![1].deref()).toBe(obj2);
  });
});

describe('debounce', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should debounce function calls', () => {
    const mockFn = vi.fn();
    const debouncedFn = debounce(mockFn, 1000);

    // Call the debounced function multiple times
    debouncedFn(1);
    debouncedFn(2);
    debouncedFn(3);

    // Function should not have been called yet
    expect(mockFn).not.toHaveBeenCalled();

    // Fast forward time
    vi.runAllTimers();

    // Function should have been called once with the last arguments
    expect(mockFn).toHaveBeenCalledTimes(1);
    expect(mockFn).toHaveBeenCalledWith(3);
  });

  it('should cancel previous timeout on new calls', () => {
    const mockFn = vi.fn();
    const debouncedFn = debounce(mockFn, 1000);

    debouncedFn(1);

    // Advance timer halfway
    vi.advanceTimersByTime(500);

    debouncedFn(2);

    // Advance to just before the second call would trigger
    vi.advanceTimersByTime(999);
    expect(mockFn).not.toHaveBeenCalled();

    // Advance the remaining time
    vi.advanceTimersByTime(1);
    expect(mockFn).toHaveBeenCalledTimes(1);
    expect(mockFn).toHaveBeenCalledWith(2);
  });
});


================================================
FILE: src/utils.ts
================================================
export const appendToMap = <K, V extends Object>(map: Map<K, WeakRef<V>[]>, propName: K, value: V) => {
  let refs = map.get(propName);
  if (!refs) {
    refs = [];
    map.set(propName, refs);
  }
  if (!refs.some((ref) => ref.deref() === value)) {
    refs.push(new WeakRef(value));
  }
};

export const debounce = <T extends (...args: any[]) => any>(fn: T, ms: number): ((...args: Parameters<T>) => void) => {
  let timeoutId: any;
  return (...args: Parameters<T>) => {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    timeoutId = setTimeout(() => {
      timeoutId = 0;
      fn(...args);
    }, ms);
  };
};


================================================
FILE: test-app/.editorconfig
================================================
# http://editorconfig.org

root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false


================================================
FILE: test-app/.gitignore
================================================
dist/
www/
loader/

*~
*.sw[mnpcod]
*.log
*.lock
*.tmp
*.tmp.*
log.txt
*.sublime-project
*.sublime-workspace

.stencil/
.idea/
.vscode/
.sass-cache/
.versions/
node_modules/
$RECYCLE.BIN/

.DS_Store
Thumbs.db
UserInterfaceState.xcuserstate
.env


================================================
FILE: test-app/.prettierrc.json
================================================
{
  "parser": "typescript",
  "printWidth": 100,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5",
  "jsxBracketSameLine": false,
  "jsxSingleQuote": false
}


================================================
FILE: test-app/LICENSE
================================================
MIT License

Copyright (c) 2018

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: test-app/package.json
================================================
{
  "name": "stencil-store-tests",
  "version": "0.0.1",
  "description": "Stencil Component Starter",
  "main": "./dist/index.cjs.js",
  "module": "./dist/index.js",
  "es2015": "dist/esm/index.mjs",
  "es2017": "dist/esm/index.mjs",
  "types": "dist/types/components.d.ts",
  "collection": "dist/collection/collection-manifest.json",
  "collection:main": "dist/collection/index.js",
  "unpkg": "dist/stencil-store-tests/stencil-store-tests.js",
  "files": [
    "dist/",
    "loader/"
  ],
  "scripts": {
    "build": "stencil build",
    "start": "stencil build --dev --watch --serve",
    "lint": "npm run lint.prettier",
    "test": "stencil test --spec --e2e",
    "test.spec": "stencil test --spec",
    "test.ci": "npm run test",
    "test.watch": "stencil test --spec --e2e --watchAll",
    "generate": "stencil generate"
  },
  "devDependencies": {
    "@stencil/core": "^4.0.0",
    "@stencil/store": "latest",
    "@types/node": "^20.11.7",
    "puppeteer": "^22.15.0"
  },
  "license": "MIT"
}


================================================
FILE: test-app/readme.md
================================================
![Built With Stencil](https://img.shields.io/badge/-Built%20With%20Stencil-16161d.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BCgkuc3Qwe2ZpbGw6I0ZGRkZGRjt9Cjwvc3R5bGU%2BCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00MjQuNywzNzMuOWMwLDM3LjYtNTUuMSw2OC42LTkyLjcsNjguNkgxODAuNGMtMzcuOSwwLTkyLjctMzAuNy05Mi43LTY4LjZ2LTMuNmgzMzYuOVYzNzMuOXoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQyNC43LDI5Mi4xSDE4MC40Yy0zNy42LDAtOTIuNy0zMS05Mi43LTY4LjZ2LTMuNkgzMzJjMzcuNiwwLDkyLjcsMzEsOTIuNyw2OC42VjI5Mi4xeiIvPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNDI0LjcsMTQxLjdIODcuN3YtMy42YzAtMzcuNiw1NC44LTY4LjYsOTIuNy02OC42SDMzMmMzNy45LDAsOTIuNywzMC43LDkyLjcsNjguNlYxNDEuN3oiLz4KPC9zdmc%2BCg%3D%3D&colorA=16161d&style=flat-square)

# Stencil Component Starter

> This is a starter project for building a standalone Web Component using Stencil.

Stencil is a compiler for building fast web apps using Web Components.

Stencil combines the best concepts of the most popular frontend frameworks into a compile-time rather than run-time tool.  Stencil takes TypeScript, JSX, a tiny virtual DOM layer, efficient one-way data binding, an asynchronous rendering pipeline (similar to React Fiber), and lazy-loading out of the box, and generates 100% standards-based Web Components that run in any browser supporting the Custom Elements v1 spec.

Stencil components are just Web Components, so they work in any major framework or with no framework at all.

## Getting Started

To start building a new web component using Stencil, clone this repo to a new directory:

```bash
git clone https://github.com/stenciljs/component-starter.git my-component
cd my-component
git remote rm origin
```

and run:

```bash
npm install
npm start
```

To build the component for production, run:

```bash
npm run build
```

To run the unit tests for the components, run:

```bash
npm test
```

Need help? Check out our docs [here](https://stenciljs.com/docs/my-first-component).


## Naming Components

When creating new component tags, we recommend _not_ using `stencil` in the component name (ex: `<stencil-datepicker>`). This is because the generated component has little to nothing to do with Stencil; it's just a web component!

Instead, use a prefix that fits your company or any name for a group of related components. For example, all of the Ionic generated web components use the prefix `ion`.


## Using this component

### Script tag

- [Publish to NPM](https://docs.npmjs.com/getting-started/publishing-npm-packages)
- Put a script tag similar to this `<script src='https://unpkg.com/my-component@0.0.1/dist/mycomponent.js'></script>` in the head of your index.html
- Then you can use the element anywhere in your template, JSX, html etc

### Node Modules
- Run `npm install my-component --save`
- Put a script tag similar to this `<script src='node_modules/my-component/dist/mycomponent.js'></script>` in the head of your index.html
- Then you can use the element anywhere in your template, JSX, html etc

### In a stencil-starter app
- Run `npm install my-component --save`
- Add an import to the npm packages `import my-component;`
- Then you can use the element anywhere in your template, JSX, html etc


================================================
FILE: test-app/src/components/change-store/change-store.tsx
================================================
import { Component, Prop, Host, h } from '@stencil/core';
import { state } from '../../utils/greeting-store';

@Component({
  tag: 'change-store',
  shadow: false,
})
export class ChangeStore {
  @Prop() storeKey: 'hola' | 'adios';
  @Prop() storeValue: string;

  changeValue() {
    state[this.storeKey] = this.storeValue;
  }

  render() {
    return (
      <Host>
        <button onClick={() => this.changeValue()}>Change!</button>
      </Host>
    );
  }
}


================================================
FILE: test-app/src/components/display-store/display-store.e2e.ts
================================================
import { newE2EPage, E2EPage } from '@stencil/core/testing';

describe('display-store', () => {
  it('re-renders', async () => {
    const page = await newE2EPage();
    await page.setContent(
      '<display-store store-key="hello"></display-store><change-store store-key="hello" store-value="other-value"></change-store>'
    );

    expect(await displayedValue(page)).toEqual('hola');
    expect(await renderCount(page)).toEqual(1);

    await changeValue(page);

    expect(await displayedValue(page)).toEqual('other-value');
    expect(await renderCount(page)).toEqual(2);
  });

  it('does not rerender if the key changed is a different one', async () => {
    const page = await newE2EPage();
    await page.setContent(
      '<display-store class="hello" store-key="hello"></display-store><display-store class="goodbye" store-key="goodbye"></display-store><change-store store-key="hello" store-value="other-value"></change-store>'
    );

    expect(await displayedValue(page, '.hello')).toEqual('hola');
    expect(await renderCount(page, '.hello')).toEqual(1);
    expect(await displayedValue(page, '.goodbye')).toEqual('adiós');
    expect(await renderCount(page, '.goodbye')).toEqual(1);

    await changeValue(page);

    expect(await displayedValue(page, '.hello')).toEqual('other-value');
    expect(await renderCount(page, '.hello')).toEqual(2);
    expect(await displayedValue(page, '.goodbye')).toEqual('adiós');
    expect(await renderCount(page, '.goodbye')).toEqual(1);
  });
});

const displayedValue = async (page: E2EPage, parentSelector?: string): Promise<string> => {
  const parentElement = parentSelector === undefined ? page : await page.find(parentSelector);
  const element = await parentElement.find('.value');

  return element.textContent;
};

const renderCount = async (page: E2EPage, parentSelector?: string): Promise<number> => {
  const parentElement = parentSelector === undefined ? page : await page.find(parentSelector);
  const element = await parentElement.find('.counter');

  return parseInt(element.textContent, 10);
};

const changeValue = async (page: E2EPage, newValue?: string): Promise<void> => {
  if (newValue !== undefined) {
    const element = await page.find('change-store');

    element.setProperty('store-value', newValue);
  }
  const button = await page.find('button');

  await button.click();

  await page.waitForChanges();
};


================================================
FILE: test-app/src/components/display-store/display-store.tsx
================================================
import { Component, Prop, h, Host } from '@stencil/core';
import { state } from '../../utils/greeting-store';

@Component({
  tag: 'display-store',
  shadow: false,
})
export class DisplayStore {
  @Prop() storeKey: 'hello' | 'goodbye';

  i = 0;

  render() {
    this.i++;
    return (
      <Host>
        <span class="counter">{this.i}</span>
        <span class="value">{state[this.storeKey]}</span>
      </Host>
    );
  }
}


================================================
FILE: test-app/src/components/simple-store/display-store.spec.ts
================================================
import { newSpecPage } from '@stencil/core/testing';
import { SimpleStore } from './display-store';
import { dispose, reset } from '../../utils/greeting-store';

describe('some-store', () => {
  beforeEach(() => dispose());
  it('updates', async () => {
    reset();
    const { root, waitForChanges } = await newSpecPage({
      components: [SimpleStore],
      html: `<simple-store></simple-store>`,
    });
    expect(root).toEqualHtml(`
      <simple-store>
        hola
        <span>0</span>
        <span>0</span>
      </simple-store>
    `);
    await root.next();
    await waitForChanges();
    expect(root).toEqualHtml(`
      <simple-store>
        hola
        <span>1</span>
        <span>1</span>
      </simple-store>
    `);
    await root.next();
    await waitForChanges();
    expect(root).toEqualHtml(`
      <simple-store>
        hola
        <span>2</span>
        <span>4</span>
      </simple-store>
    `);
    reset();
    await waitForChanges();
    expect(root).toEqualHtml(`
      <simple-store>
        hola
        <span>0</span>
        <span>0</span>
      </simple-store>
    `);
  });

  it('resetting in a second test does not crash', async () => {
    reset();
    const { root, waitForChanges } = await newSpecPage({
      components: [SimpleStore],
      html: `<simple-store></simple-store>`,
    });
    expect(root).toEqualHtml(`
      <simple-store>
        hola
        <span>0</span>
        <span>0</span>
      </simple-store>
    `);
    await root.next();
    await waitForChanges();
    expect(root).toEqualHtml(`
      <simple-store>
        hola
        <span>1</span>
        <span>1</span>
      </simple-store>
    `);
    await root.next();
    await waitForChanges();
    expect(root).toEqualHtml(`
      <simple-store>
        hola
        <span>2</span>
        <span>4</span>
      </simple-store>
    `);
    reset();
    await waitForChanges();
    expect(root).toEqualHtml(`
      <simple-store>
        hola
        <span>0</span>
        <span>0</span>
      </simple-store>
    `);
  });
});


================================================
FILE: test-app/src/components/simple-store/display-store.tsx
================================================
import { Component, h, Host, Method } from '@stencil/core';
import { state } from '../../utils/greeting-store';

@Component({
  tag: 'simple-store',
  shadow: false,
})
export class SimpleStore {

  @Method()
  async next() {
    state.clicks++;
  }

  render() {
    return (
      <Host>
        {state.hello}
        <span>{state.clicks}</span>
        <span>{state.squaredClicks}</span>
      </Host>
    );
  }
}


================================================
FILE: test-app/src/components.d.ts
================================================
/* eslint-disable */
/* tslint:disable */
/**
 * This is an autogenerated file created by the Stencil compiler.
 * It contains typing information for all components that exist in this project.
 */
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
export namespace Components {
    interface ChangeStore {
        "storeKey": 'hola' | 'adios';
        "storeValue": string;
    }
    interface DisplayStore {
        "storeKey": 'hello' | 'goodbye';
    }
    interface SimpleStore {
        "next": () => Promise<void>;
    }
}
declare global {
    interface HTMLChangeStoreElement extends Components.ChangeStore, HTMLStencilElement {
    }
    var HTMLChangeStoreElement: {
        prototype: HTMLChangeStoreElement;
        new (): HTMLChangeStoreElement;
    };
    interface HTMLDisplayStoreElement extends Components.DisplayStore, HTMLStencilElement {
    }
    var HTMLDisplayStoreElement: {
        prototype: HTMLDisplayStoreElement;
        new (): HTMLDisplayStoreElement;
    };
    interface HTMLSimpleStoreElement extends Components.SimpleStore, HTMLStencilElement {
    }
    var HTMLSimpleStoreElement: {
        prototype: HTMLSimpleStoreElement;
        new (): HTMLSimpleStoreElement;
    };
    interface HTMLElementTagNameMap {
        "change-store": HTMLChangeStoreElement;
        "display-store": HTMLDisplayStoreElement;
        "simple-store": HTMLSimpleStoreElement;
    }
}
declare namespace LocalJSX {
    interface ChangeStore {
        "storeKey"?: 'hola' | 'adios';
        "storeValue"?: string;
    }
    interface DisplayStore {
        "storeKey"?: 'hello' | 'goodbye';
    }
    interface SimpleStore {
    }
    interface IntrinsicElements {
        "change-store": ChangeStore;
        "display-store": DisplayStore;
        "simple-store": SimpleStore;
    }
}
export { LocalJSX as JSX };
declare module "@stencil/core" {
    export namespace JSX {
        interface IntrinsicElements {
            "change-store": LocalJSX.ChangeStore & JSXBase.HTMLAttributes<HTMLChangeStoreElement>;
            "display-store": LocalJSX.DisplayStore & JSXBase.HTMLAttributes<HTMLDisplayStoreElement>;
            "simple-store": LocalJSX.SimpleStore & JSXBase.HTMLAttributes<HTMLSimpleStoreElement>;
        }
    }
}


================================================
FILE: test-app/src/index.html
================================================
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
  <title>Stencil Component Starter</title>

  <script type="module" src="/build/stencil-store-tests.esm.js"></script>
  <script nomodule src="/build/stencil-store-tests.js"></script>

</head>
<body>

  <display-store store-key="hello"></display-store>
  <change-store store-key="hello" store-value="other-value"></change-store>
</body>
</html>


================================================
FILE: test-app/src/index.ts
================================================
export * from './components';


================================================
FILE: test-app/src/utils/greeting-store.ts
================================================
import { createStore } from '@stencil/store';

const store = createStore({
  hello: 'hola',
  goodbye: 'adiós',
  clicks: 0,
  squaredClicks: 0,
});

store.onChange('clicks', (value) => {
  state.squaredClicks = value ** 2;
});

export const dispose = store.dispose;
export const state = store.state;
export const reset = store.reset;


================================================
FILE: test-app/stencil.config.ts
================================================
import { Config } from '@stencil/core';

export const config: Config = {
  namespace: 'stencil-store-tests',
  outputTargets: [
    {
      type: 'dist',
      esmLoaderPath: '../loader'
    },
    {
      type: 'www',
      serviceWorker: null // disable service workers
    }
  ]
};


================================================
FILE: test-app/tsconfig.json
================================================
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "allowUnreachableCode": false,
    "declaration": false,
    "experimentalDecorators": true,
    "lib": [
      "dom",
      "es2017"
    ],
    "moduleResolution": "node",
    "module": "esnext",
    "target": "es2017",
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "jsx": "react",
    "jsxFactory": "h"
  },
  "include": [
    "src",
    "types/jsx.d.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "allowUnreachableCode": false,
    "declaration": true,
    "experimentalDecorators": true,
    "lib": [
      "dom",
      "es2022"
    ],
    "outDir": "build",
    "declarationDir": "dist",
    "moduleResolution": "bundler",
    "module": "esnext",
    "target": "es2022",
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noUncheckedIndexedAccess": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "jsx": "react",
    "jsxFactory": "h",
    "useUnknownInCatchVariables": true
  },
  "files": [
    "src/index.ts"
  ],
  "exclude": [
    "node_modules",
    "src/**/*.test.ts"
  ]
}


================================================
FILE: vitest.config.ts
================================================
import { defineConfig } from 'vitest/config';

const exclude = ['node_modules', 'dist', 'test-app'];

export default defineConfig({
  test: {
    exclude,
    coverage: {
      enabled: true,
      exclude: [
        ...exclude,
        'vitest.config.ts',
        'rollup.config.js'
      ],
      provider: 'v8',
      thresholds: {
        branches: 93,
        functions: 86,
        lines: 80,
        statements: 80,
      },
    },
  },
});
Download .txt
gitextract_14714bwu/

├── .github/
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   ├── ionic-issue-bot.yml
│   └── workflows/
│       ├── build.yml
│       ├── main.yml
│       ├── publish-npm.yml
│       ├── release-dev.yml
│       ├── release-orchestrator.yml
│       └── release-production.yml
├── .gitignore
├── .nvmrc
├── .prettierrc.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src/
│   ├── index.test.ts
│   ├── index.ts
│   ├── observable-map.test.ts
│   ├── observable-map.ts
│   ├── store.test.ts
│   ├── store.ts
│   ├── subscriptions/
│   │   ├── stencil.test.ts
│   │   └── stencil.ts
│   ├── types.ts
│   ├── utils.test.ts
│   └── utils.ts
├── test-app/
│   ├── .editorconfig
│   ├── .gitignore
│   ├── .prettierrc.json
│   ├── LICENSE
│   ├── package.json
│   ├── readme.md
│   ├── src/
│   │   ├── components/
│   │   │   ├── change-store/
│   │   │   │   └── change-store.tsx
│   │   │   ├── display-store/
│   │   │   │   ├── display-store.e2e.ts
│   │   │   │   └── display-store.tsx
│   │   │   └── simple-store/
│   │   │       ├── display-store.spec.ts
│   │   │       └── display-store.tsx
│   │   ├── components.d.ts
│   │   ├── index.html
│   │   ├── index.ts
│   │   └── utils/
│   │       └── greeting-store.ts
│   ├── stencil.config.ts
│   └── tsconfig.json
├── tsconfig.json
└── vitest.config.ts
Download .txt
SYMBOL INDEX (42 symbols across 7 files)

FILE: src/observable-map.ts
  type Invocable (line 3) | type Invocable<T> = T | (() => T);
  method get (line 64) | get(_, propName) {
  method ownKeys (line 67) | ownKeys(_) {
  method getOwnPropertyDescriptor (line 70) | getOwnPropertyDescriptor() {
  method has (line 76) | has(_, propName) {
  method set (line 79) | set(_, propName, value) {
  function ensurePlainProperty (line 159) | function ensurePlainProperty(key: string): void {
  function syncPlainStateKeys (line 180) | function syncPlainStateKeys(): void {

FILE: src/subscriptions/stencil.test.ts
  class MockWeakRef (line 117) | class MockWeakRef {
    method constructor (line 119) | constructor(target: any) {
    method deref (line 122) | deref() {

FILE: src/types.ts
  type Handlers (line 1) | interface Handlers<T> {
  type SetEventHandler (line 8) | type SetEventHandler<StoreType> = (key: keyof StoreType, newValue: any, ...
  type GetEventHandler (line 9) | type GetEventHandler<StoreType> = (key: keyof StoreType) => void;
  type ResetEventHandler (line 10) | type ResetEventHandler = () => void;
  type DisposeEventHandler (line 11) | type DisposeEventHandler = () => void;
  type OnHandler (line 13) | interface OnHandler<StoreType> {
  type OnChangeHandler (line 20) | interface OnChangeHandler<StoreType> {
  type Subscription (line 24) | interface Subscription<StoreType> {
  type Getter (line 35) | interface Getter<T> {
  type Setter (line 39) | interface Setter<T> {
  type ObservableMap (line 43) | interface ObservableMap<T> {

FILE: test-app/src/components.d.ts
  type ChangeStore (line 9) | interface ChangeStore {
  type DisplayStore (line 13) | interface DisplayStore {
  type SimpleStore (line 16) | interface SimpleStore {
  type HTMLChangeStoreElement (line 21) | interface HTMLChangeStoreElement extends Components.ChangeStore, HTMLSte...
  type HTMLDisplayStoreElement (line 27) | interface HTMLDisplayStoreElement extends Components.DisplayStore, HTMLS...
  type HTMLSimpleStoreElement (line 33) | interface HTMLSimpleStoreElement extends Components.SimpleStore, HTMLSte...
  type HTMLElementTagNameMap (line 39) | interface HTMLElementTagNameMap {
  type ChangeStore (line 46) | interface ChangeStore {
  type DisplayStore (line 50) | interface DisplayStore {
  type SimpleStore (line 53) | interface SimpleStore {
  type IntrinsicElements (line 55) | interface IntrinsicElements {
  type IntrinsicElements (line 64) | interface IntrinsicElements {

FILE: test-app/src/components/change-store/change-store.tsx
  class ChangeStore (line 8) | class ChangeStore {
    method changeValue (line 12) | changeValue() {
    method render (line 16) | render() {

FILE: test-app/src/components/display-store/display-store.tsx
  class DisplayStore (line 8) | class DisplayStore {
    method render (line 13) | render() {

FILE: test-app/src/components/simple-store/display-store.tsx
  class SimpleStore (line 8) | class SimpleStore {
    method next (line 11) | async next() {
    method render (line 15) | render() {
Condensed preview — 52 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (104K chars).
[
  {
    "path": ".github/CODEOWNERS",
    "chars": 42,
    "preview": "* @stenciljs/technical-steering-committee\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 69,
    "preview": "# These are supported funding model platforms\n\ngithub: [johnjenkins]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 2391,
    "preview": "name: 🐛 Bug Report\ndescription: Create a report to help us improve Stencil Store\ntitle: 'bug: '\nbody:\n  - type: checkbox"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 823,
    "preview": "contact_links:\n  - name: 💻 Stencil\n    url: https://github.com/stenciljs/core/issues/new/choose\n    about: This issue tr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 1999,
    "preview": "name: 💡 Feature Request\ndescription: Suggest an idea for Stencil Store\ntitle: 'feat: '\nbody:\n  - type: checkboxes\n    at"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 1803,
    "preview": "<!-- Please refer to our contributing documentation for any questions on submitting a pull request, or let us know here "
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 668,
    "preview": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: weekly\n  open-pull-requests-limi"
  },
  {
    "path": ".github/ionic-issue-bot.yml",
    "chars": 3086,
    "preview": "triage:\n  label: triage\n  dryRun: false\n\ncloseAndLock:\n  labels:\n    - label: \"ionitron: support\"\n      message: >\n     "
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 1115,
    "preview": "name: Build Stencil Store\n\non:\n  workflow_call:\n  # Make this a reusable workflow, no value needed\n  # https://docs.gith"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 2203,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - 'main'\n  pull_request:\n    branches:\n      - '**'\n\njobs:\n  build:\n    name: "
  },
  {
    "path": ".github/workflows/publish-npm.yml",
    "chars": 6354,
    "preview": "name: 'Publish Stencil Store'\n\non:\n  workflow_call:\n    inputs:\n      version:\n        description: 'Version or semver b"
  },
  {
    "path": ".github/workflows/release-dev.yml",
    "chars": 1088,
    "preview": "name: 'Stencil Store Dev Release'\n\non:\n  workflow_call:\n\npermissions:\n  contents: write\n  id-token: write\n\njobs:\n  build"
  },
  {
    "path": ".github/workflows/release-orchestrator.yml",
    "chars": 829,
    "preview": "name: 'Release - Stencil Store'\n\non:\n  workflow_dispatch:\n    inputs:\n      channel:\n        description: 'Which release"
  },
  {
    "path": ".github/workflows/release-production.yml",
    "chars": 1053,
    "preview": "name: 'Stencil Store Production Release'\n\non:\n  workflow_call:\n    inputs:\n      bump:\n        description: 'Semver bump"
  },
  {
    "path": ".gitignore",
    "chars": 266,
    "preview": "dist/\nwww/\nloader/\nbuild/\n\n*~\n*.sw[mnpcod]\n*.log\n*.lock\n*.tmp\n*.tmp.*\nlog.txt\n*.sublime-project\n*.sublime-workspace\n*.tg"
  },
  {
    "path": ".nvmrc",
    "chars": 8,
    "preview": "v22.14.0"
  },
  {
    "path": ".prettierrc.json",
    "chars": 114,
    "preview": "{\n  \"parser\": \"typescript\",\n  \"printWidth\": 100,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\"\n}"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 7662,
    "preview": "# Contributing to @stencil/store\n\nThank you for your interest in contributing to @stencil/store! This document provides "
  },
  {
    "path": "LICENSE",
    "chars": 1090,
    "preview": "Copyright 2015-present Drifty Co.\nhttp://drifty.com/\n\nMIT License\n\nPermission is hereby granted, free of charge, to any "
  },
  {
    "path": "README.md",
    "chars": 5165,
    "preview": "# @stencil/store\n\nStore is a lightweight shared state library by the [StencilJS](https://stenciljs.com/) core team. It i"
  },
  {
    "path": "package.json",
    "chars": 1898,
    "preview": "{\n  \"name\": \"@stencil/store\",\n  \"author\": \"StencilJS Team\",\n  \"version\": \"2.2.2\",\n  \"description\": \"Store is a lightweig"
  },
  {
    "path": "rollup.config.js",
    "chars": 366,
    "preview": "import typescript from '@rollup/plugin-typescript';\n\nimport pkg from './package.json' with { type: 'json' };\n\nexport def"
  },
  {
    "path": "src/index.test.ts",
    "chars": 290,
    "preview": "import { describe, it, expect } from 'vitest';\nimport { createStore, createObservableMap } from './index';\n\ndescribe('st"
  },
  {
    "path": "src/index.ts",
    "chars": 160,
    "preview": "export { createStore } from './store';\nexport { createObservableMap } from './observable-map';\n\n// Types\nexport { Observ"
  },
  {
    "path": "src/observable-map.test.ts",
    "chars": 16562,
    "preview": "import { describe, expect, test, vi } from 'vitest';\nimport { createObservableMap } from './observable-map';\n\ndescribe.e"
  },
  {
    "path": "src/observable-map.ts",
    "chars": 6016,
    "preview": "import { OnHandler, OnChangeHandler, Subscription, ObservableMap, Handlers } from './types';\n\ntype Invocable<T> = T | (("
  },
  {
    "path": "src/store.test.ts",
    "chars": 1219,
    "preview": "import { afterEach, describe, expect, it, vi } from 'vitest';\nimport type { Mock } from 'vitest';\n\nvi.mock('./observable"
  },
  {
    "path": "src/store.ts",
    "chars": 461,
    "preview": "import { stencilSubscription } from './subscriptions/stencil';\nimport { createObservableMap } from './observable-map';\ni"
  },
  {
    "path": "src/subscriptions/stencil.test.ts",
    "chars": 4619,
    "preview": "import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nconst coreMock = vi.hoisted(() => ({\n  export"
  },
  {
    "path": "src/subscriptions/stencil.ts",
    "chars": 2268,
    "preview": "import * as StencilCore from '@stencil/core';\nimport { Subscription } from '../types';\nimport { appendToMap, debounce } "
  },
  {
    "path": "src/types.ts",
    "chars": 4584,
    "preview": "export interface Handlers<T> {\n  dispose: DisposeEventHandler[];\n  get: GetEventHandler<T>[];\n  reset: ResetEventHandler"
  },
  {
    "path": "src/utils.test.ts",
    "chars": 2466,
    "preview": "import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';\nimport { appendToMap, debounce } from './utils"
  },
  {
    "path": "src/utils.ts",
    "chars": 628,
    "preview": "export const appendToMap = <K, V extends Object>(map: Map<K, WeakRef<V>[]>, propName: K, value: V) => {\n  let refs = map"
  },
  {
    "path": "test-app/.editorconfig",
    "chars": 244,
    "preview": "# http://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert"
  },
  {
    "path": "test-app/.gitignore",
    "chars": 245,
    "preview": "dist/\nwww/\nloader/\n\n*~\n*.sw[mnpcod]\n*.log\n*.lock\n*.tmp\n*.tmp.*\nlog.txt\n*.sublime-project\n*.sublime-workspace\n\n.stencil/\n"
  },
  {
    "path": "test-app/.prettierrc.json",
    "chars": 173,
    "preview": "{\n  \"parser\": \"typescript\",\n  \"printWidth\": 100,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\",\n  \"jsx"
  },
  {
    "path": "test-app/LICENSE",
    "chars": 1056,
    "preview": "MIT License\n\nCopyright (c) 2018\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this so"
  },
  {
    "path": "test-app/package.json",
    "chars": 1007,
    "preview": "{\n  \"name\": \"stencil-store-tests\",\n  \"version\": \"0.0.1\",\n  \"description\": \"Stencil Component Starter\",\n  \"main\": \"./dist"
  },
  {
    "path": "test-app/readme.md",
    "chars": 3636,
    "preview": "![Built With Stencil](https://img.shields.io/badge/-Built%20With%20Stencil-16161d.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bba"
  },
  {
    "path": "test-app/src/components/change-store/change-store.tsx",
    "chars": 464,
    "preview": "import { Component, Prop, Host, h } from '@stencil/core';\nimport { state } from '../../utils/greeting-store';\n\n@Componen"
  },
  {
    "path": "test-app/src/components/display-store/display-store.e2e.ts",
    "chars": 2392,
    "preview": "import { newE2EPage, E2EPage } from '@stencil/core/testing';\n\ndescribe('display-store', () => {\n  it('re-renders', async"
  },
  {
    "path": "test-app/src/components/display-store/display-store.tsx",
    "chars": 432,
    "preview": "import { Component, Prop, h, Host } from '@stencil/core';\nimport { state } from '../../utils/greeting-store';\n\n@Componen"
  },
  {
    "path": "test-app/src/components/simple-store/display-store.spec.ts",
    "chars": 2061,
    "preview": "import { newSpecPage } from '@stencil/core/testing';\nimport { SimpleStore } from './display-store';\nimport { dispose, re"
  },
  {
    "path": "test-app/src/components/simple-store/display-store.tsx",
    "chars": 418,
    "preview": "import { Component, h, Host, Method } from '@stencil/core';\nimport { state } from '../../utils/greeting-store';\n\n@Compon"
  },
  {
    "path": "test-app/src/components.d.ts",
    "chars": 2265,
    "preview": "/* eslint-disable */\n/* tslint:disable */\n/**\n * This is an autogenerated file created by the Stencil compiler.\n * It co"
  },
  {
    "path": "test-app/src/index.html",
    "chars": 530,
    "preview": "<!DOCTYPE html>\n<html dir=\"ltr\" lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device"
  },
  {
    "path": "test-app/src/index.ts",
    "chars": 30,
    "preview": "export * from './components';\n"
  },
  {
    "path": "test-app/src/utils/greeting-store.ts",
    "chars": 335,
    "preview": "import { createStore } from '@stencil/store';\n\nconst store = createStore({\n  hello: 'hola',\n  goodbye: 'adiós',\n  clicks"
  },
  {
    "path": "test-app/stencil.config.ts",
    "chars": 285,
    "preview": "import { Config } from '@stencil/core';\n\nexport const config: Config = {\n  namespace: 'stencil-store-tests',\n  outputTar"
  },
  {
    "path": "test-app/tsconfig.json",
    "chars": 491,
    "preview": "{\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"allowUnreachableCode\": false,\n    \"declaration\":"
  },
  {
    "path": "tsconfig.json",
    "chars": 713,
    "preview": "{\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"allowUnreachableCode\": false,\n    \"declaration\":"
  },
  {
    "path": "vitest.config.ts",
    "chars": 448,
    "preview": "import { defineConfig } from 'vitest/config';\n\nconst exclude = ['node_modules', 'dist', 'test-app'];\n\nexport default def"
  }
]

About this extraction

This page contains the full source code of the ionic-team/stencil-store GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 52 files (94.3 KB), approximately 26.0k tokens, and a symbol index with 42 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!