[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: dfahlander\n"
  },
  {
    "path": ".github/workflows/dexie-cloud-common.yml",
    "content": "name: dexie-cloud-common Tests\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - master\n    paths:\n      - 'libs/dexie-cloud-common/**'\n      - '.github/workflows/dexie-cloud-common.yml'\n  pull_request:\n    types: [opened, synchronize, reopened]\n    paths:\n      - 'libs/dexie-cloud-common/**'\n      - '.github/workflows/dexie-cloud-common.yml'\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v3\n\n      - name: Set up pnpm\n        uses: pnpm/action-setup@v2\n        with:\n          version: 9\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: 18\n          cache: \"pnpm\"\n\n      - name: Install dependencies\n        run: pnpm install --no-frozen-lockfile\n\n      - name: Build dexie-cloud-common\n        run: pnpm --filter dexie-cloud-common run build\n\n      - name: Run tests\n        run: pnpm --filter dexie-cloud-common test\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Build and Test\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - master # Add also master-4 when dexie@4 is stable and master will represent dexie@5. dexie@3 has its own workflow.\n  pull_request:\n      types: [opened, synchronize, reopened]\nenv:\n  LAMBDATEST: \"true\"\n  GH_ACTIONS: \"true\"\n  LT_USERNAME: ${{ secrets.LT_USERNAME }}\n  LT_ACCESS_KEY: ${{ secrets.LT_ACCESS_KEY }}\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        TF:\n          - test\n          - addons/Dexie.Observable/test\n          - addons/Dexie.Syncable/test\n          - addons/dexie-export-import/test\n          - addons/y-dexie/test\n          - libs/dexie-react-hooks/test\n      fail-fast: true # If one test fails, abort the rest of the tests\n      max-parallel: 6 # At least for browserstack, this seems to be needed to avoid timeouts\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v3\n      - name: Set up pnpm\n        uses: pnpm/action-setup@v2\n        with:\n          version: 9\n      - name: Set up Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: 18\n          cache: \"pnpm\"\n      - name: Install dependencies\n        run: pnpm install --no-frozen-lockfile\n      - name: Build\n        run: pnpm run build\n      - name: Set LT_TUNNEL_NAME\n        run: echo \"LT_TUNNEL_NAME=${{ matrix.TF }}-${{ github.run_id }}-${{ github.run_attempt }}\" >> $GITHUB_ENV\n      - name: Run headless test\n        uses: coactions/setup-xvfb@v1\n        with:\n          run: bash -e ./gh-actions.sh\n          working-directory: ${{ matrix.TF }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Ignore node_modules\nnode_modules/\n\n# Ignore all build output\ndist/*.js\ndist/*.map\ndist/*.ts\ndist/*.gz\ndist/*.mjs\ndist/**/*.js\ndist/**/*.map\ndist/**/*.ts\ndist/**/*.gz\ndist/**/*.mjs\n\n# Other ignores\ntmp/\n.idea/\n.eslintcache\n\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.sln.docstates\n\n# Build results\n\n[Dd]ebug/\n[Rr]elease/\nx64/\n[Bb]in/\n[Oo]bj/\n\n# Enable \"build/\" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets\n!packages/*/build/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n*_i.c\n*_p.c\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.log\n*.scc\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n*.ncrunch*\n.*crunch*.local.xml\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.Publish.xml\n*.pubxml\n\n# NuGet Packages Directory\n## TODO: If you have NuGet Package Restore enabled, uncomment the next line\n#packages/\n\n# Windows Azure Build Output\ncsx\n*.build.csdef\n\n# Windows Store app package directory\nAppPackages/\n\n# Others\nsql/\n*.Cache\nClientBin/\n[Ss]tyle[Cc]op.*\n~$*\n*~\n*.dbmdl\n*.[Pp]ublish.xml\n*.pfx\n*.publishsettings\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file to a newer\n# Visual Studio version. Backup files are not needed, because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\nApp_Data/*.mdf\nApp_Data/*.ldf\n\n# =========================\n# Windows detritus\n# =========================\n\n# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Mac crap\n.DS_Store\n\n*.csproj\nDexie.sln\nsamples/typescript/appdb.js\nsamples/typescript/app.js\nsamples/typescript/app.js.map\nsamples/typescript/appdb.js.map\nsamples/typescript/console.js\nsamples/typescript/console.js.map\nsamples/typescript/utils.js\nsamples/typescript/utils.js.map\n/.vscode\n/jsconfig.json\n\n/.ntvs_analysis.dat\n/*.njsproj\nlibs/dexie-cloud-common/tsconfig.tsbuildinfo\n\n#lambdatest tunnel binary\n.lambdatest\ntunnel.pid\n"
  },
  {
    "path": ".npmignore",
    "content": "**/tmp/\nsamples/\naddons/\nlibs/\n*.njsproj\n.*\n*.log\ntest/\ntools/\nbower.json\nsrc/\n.lambdatest\ntunnel.pid\ntsconfig.json\npnpm-workspace.yaml\n"
  },
  {
    "path": ".npmrc",
    "content": "auto-install-peers=true\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"trailingComma\": \"es5\",\n  \"tabWidth\": 2,\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": ".prettierrc.yml",
    "content": "trailingComma: 'none'\ntabWidth: 2\nsemi: true\nsingleQuote: true\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at code@dexie.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "HOW TO CONTRIBUTE\n=================\n\nWe appreciate contributions in forms of:\n\n* issues\n* help answering questions in [issues](https://github.com/dexie/Dexie.js/issues) and on [stackoverflow](https://stackexchange.com/filters/233583/dexie-stackoverflow)\n* fixing bugs via pull-requests\n* developing addons or other [derived work](https://dexie.org/docs/DerivedWork)\n* promoting Dexie.js\n* sharing ideas\n\nContribute while developing your own app\n========================================\n\nDexie uses pnpm package manager. Refer to [pnpm.io/installation](https://pnpm.io/installation) for how to install pnpm.\n\nHere is a little cheat-sheet for how to symlink your app's `node_modules/dexie` to a place where you can edit the source, version control your changes and create pull requests back to Dexie. Assuming you've already ran `npm install dexie` for the app your are developing.\n\n1. Fork Dexie.js from the web gui on github\n2. Clone your fork locally by launching a shell/command window and cd to a neutral place (like `~repos/`, `c:\\repos` or whatever)\n3. Run the following commands:\n\n    ```\n    git clone https://github.com/YOUR-USERNAME/Dexie.js.git dexie\n    cd dexie\n    pnpm install\n    pnpm run build\n    npm link # Or yarn link or pnpm link --global depending on what package manager you are using.\n    ```\n3. cd to your app directory and write:\n    ```\n    npm link dexie # Or yarn link dexie / pnpm link dexie depending on your package manager.\n    ```\n\nYour app's `node_modules/dexie/` is now sym-linked to the Dexie.js clone on your hard drive so any change you do there will propagate to your app. Build dexie.js using `pnpm run build` or `pnpm run watch`. The latter will react on any source file change and rebuild the dist files.\n\nThat's it. Now you're up and running to test and commit changes to files under dexie/src/* or dexie/test/* and the changes will instantly affect the app you are developing.\n\nIf you're on yarn or pnpm, do the same procedures using yarn link / pnpm link.\n\nPull requests are more than welcome. Some advices are:\n\n* Run pnpm test before making a pull request.\n* If you find an issue, a unit test that reproduces it is lovely ;). If you don't know where to put it, put it in `test/tests-misc.js`. We use qunit. Just look at existing tests in `tests-misc.js` to see how they should be written. Tests are transpiled in the build script so you can use ES6 if you like.\n\nBuild\n-----\n```\n# To install pnpm, see https://pnpm.io/installation\npnpm install\npnpm run build\n```\n\nTest\n----\n```\npnpm test\n```\n\nWatch\n-----\n```\npnpm run watch\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "NOTICE",
    "content": "Dexie.js\n\nCopyright (c) 2014-2017 David Fahlander\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Dexie.js\n\n[![NPM Version][npm-image]][npm-url] ![Build Status](https://github.com/dexie/Dexie.js/actions/workflows/main.yml/badge.svg) [![Join our Discord](https://img.shields.io/discord/1328303736363421747?label=Discord&logo=discord&style=badge)](https://discord.gg/huhre7MHBF)\n\nDexie.js is a wrapper library for indexedDB - the standard database in the browser. https://dexie.org.\n\n#### Why Dexie.js?\n\nIndexedDB is the portable database for all browser engines. Dexie.js makes it fun and easy to work with.\n\nBut also:\n\n* Dexie.js is widely used by 100,000 of web sites, apps and other projects and supports all browsers, Electron for Desktop apps, Capacitor for iOS / Android apps and of course pure PWAs.\n* Dexie.js works around bugs in the IndexedDB implementations, giving a more stable user experience.\n* Need sync? [Dexie Cloud](https://dexie.org/cloud/) adds real-time sync, auth, and collaboration on top of Dexie.js — no backend needed.\n\n#### Hello World (vanilla JS)\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script type=\"module\">\n      // Import Dexie\n      import { Dexie } from 'https://unpkg.com/dexie/dist/modern/dexie.mjs';\n\n      //\n      // Declare Database\n      //\n      const db = new Dexie('FriendDatabase');\n      db.version(1).stores({\n        friends: '++id, age'\n      });\n\n      //\n      // Play with it\n      //\n      try {\n        await db.friends.add({ name: 'Alice', age: 21 });\n\n        const youngFriends = await db.friends\n            .where('age')\n            .below(30)\n            .toArray();\n\n        alert(`My young friends: ${JSON.stringify(youngFriends)}`);\n      } catch (e) {\n        alert(`Oops: ${e}`);\n      }\n    </script>\n  </head>\n</html>\n```\n\nYes, it's that simple. Read [the docs](https://dexie.org/docs/) to get into the details.\n\n#### Hello World (legacy script tags)\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <script src=\"https://unpkg.com/dexie/dist/dexie.js\"></script>\n    <script>\n\n      //\n      // Declare Database\n      //\n      const db = new Dexie('FriendDatabase');\n      db.version(1).stores({\n        friends: '++id, age'\n      });\n\n      //\n      // Play with it\n      //\n      db.friends.add({ name: 'Alice', age: 21 }).then(() => {\n        return db.friends\n          .where('age')\n          .below(30)\n          .toArray();\n      }).then(youngFriends => {\n        alert (`My young friends: ${JSON.stringify(youngFriends)}`);\n      }).catch (e => {\n        alert(`Oops: ${e}`);\n      });\n\n    </script>\n  </head>\n</html>\n```\n\n#### Hello World (React + Typescript)\n\nReal-world apps are often built using components in various frameworks. Here's a version of Hello World written for React and Typescript. There are also links below this sample to more tutorials for different frameworks...\n\n```tsx\nimport React from 'react';\nimport { Dexie, type EntityTable } from 'dexie';\nimport { useLiveQuery } from 'dexie-react-hooks';\n\n// Typing for your entities (hint is to move this to its own module)\nexport interface Friend {\n  id: number;\n  name: string;\n  age: number;\n}\n\n// Database declaration (move this to its own module also)\nexport const db = new Dexie('FriendDatabase') as Dexie & {\n  friends: EntityTable<Friend, 'id'>;\n};\ndb.version(1).stores({\n  friends: '++id, age',\n});\n\n// Component:\nexport function MyDexieReactComponent() {\n  const youngFriends = useLiveQuery(() =>\n    db.friends\n      .where('age')\n      .below(30)\n      .toArray()\n  );\n\n  return (\n    <>\n      <h3>My young friends</h3>\n      <ul>\n        {youngFriends?.map((f) => (\n          <li key={f.id}>\n            Name: {f.name}, Age: {f.age}\n          </li>\n        ))}\n      </ul>\n      <button\n        onClick={() => {\n          db.friends.add({ name: 'Alice', age: 21 });\n        }}\n      >\n        Add another friend\n      </button>\n    </>\n  );\n}\n```\n\n[Tutorials for React, Svelte, Vue, Angular and vanilla JS](https://dexie.org/docs/Tutorial/Getting-started)\n\n[API Reference](https://dexie.org/docs/API-Reference)\n\n[Samples](https://dexie.org/docs/Samples)\n\n### Performance\n\nDexie has kick-ass performance. Its [bulk methods](<https://dexie.org/docs/Table/Table.bulkPut()>) take advantage of a lesser-known feature in IndexedDB that makes it possible to store stuff without listening to every onsuccess event. This speeds up the performance to a maximum.\n\n#### Supported operations\n\n```js\nabove(key): Collection;\naboveOrEqual(key): Collection;\nadd(item, key?): Promise;\nand(filter: (x) => boolean): Collection;\nanyOf(keys[]): Collection;\nanyOfIgnoreCase(keys: string[]): Collection;\nbelow(key): Collection;\nbelowOrEqual(key): Collection;\nbetween(lower, upper, includeLower?, includeUpper?): Collection;\nbulkAdd(items: Array): Promise;\nbulkDelete(keys: Array): Promise;\nbulkPut(items: Array): Promise;\nclear(): Promise;\ncount(): Promise;\ndelete(key): Promise;\ndistinct(): Collection;\neach(callback: (obj) => any): Promise;\neachKey(callback: (key) => any): Promise;\neachPrimaryKey(callback: (key) => any): Promise;\neachUniqueKey(callback: (key) => any): Promise;\nequals(key): Collection;\nequalsIgnoreCase(key): Collection;\nfilter(fn: (obj) => boolean): Collection;\nfirst(): Promise;\nget(key): Promise;\ninAnyRange(ranges): Collection;\nkeys(): Promise;\nlast(): Promise;\nlimit(n: number): Collection;\nmodify(changeCallback: (obj: T, ctx:{value: T}) => void): Promise;\nmodify(changes: { [keyPath: string]: any } ): Promise;\nnoneOf(keys: Array): Collection;\nnotEqual(key): Collection;\noffset(n: number): Collection;\nor(indexOrPrimayKey: string): WhereClause;\norderBy(index: string): Collection;\nprimaryKeys(): Promise;\nput(item: T, key?: Key): Promise;\nreverse(): Collection;\nsortBy(keyPath: string): Promise;\nstartsWith(key: string): Collection;\nstartsWithAnyOf(prefixes: string[]): Collection;\nstartsWithAnyOfIgnoreCase(prefixes: string[]): Collection;\nstartsWithIgnoreCase(key: string): Collection;\ntoArray(): Promise;\ntoCollection(): Collection;\nuniqueKeys(): Promise;\nuntil(filter: (value) => boolean, includeStopEntry?: boolean): Collection;\nupdate(key: Key, changes: { [keyPath: string]: any }): Promise;\n```\n\nThis is a mix of methods from [WhereClause](https://dexie.org/docs/WhereClause/WhereClause), [Table](https://dexie.org/docs/Table/Table) and [Collection](https://dexie.org/docs/Collection/Collection). Dive into the [API reference](https://dexie.org/docs/API-Reference) to see the details.\n\n## Dexie Cloud\n\n[Dexie Cloud](https://dexie.org/cloud/) is the easiest way to add sync, authentication, and real-time collaboration to your Dexie app. You keep writing frontend code with Dexie.js — Dexie Cloud handles the rest.\n\n**What you get:**\n- 🔄 **Sync across devices** — changes propagate in real time, no polling needed\n- 🔐 **Authentication** — built-in user auth, no identity provider required\n- 🛡️ **Access control** — share data between users with fine-grained permissions\n- 📁 **File & blob storage** — store attachments alongside your structured data\n- ✈️ **Offline-first** — works fully offline, syncs when back online\n\n**Getting started is just a few lines:**\n\n```bash\nnpm install dexie-cloud-addon\n```\n\n```ts\nimport Dexie from 'dexie';\nimport dexieCloud from 'dexie-cloud-addon';\n\nconst db = new Dexie('MyDatabase', { addons: [dexieCloud] });\ndb.version(1).stores({ items: '@id, title' });\ndb.cloud.configure({ databaseUrl: 'https://<your-db>.dexie.cloud' });\n```\n\nThat's it. Your existing Dexie app now syncs. Hosted cloud or self-hosted on your own infrastructure. 👋\n\n→ [Quickstart guide](https://dexie.org/cloud/docs/quickstart)\n\n**Sample app:**\n\nSource: [Dexie Cloud To-do app](https://github.com/dexie/Dexie.js/tree/master/samples/dexie-cloud-todo-app)\n\nLive demo: https://dexie.github.io/Dexie.js/dexie-cloud-todo-app/\n\n## Samples\n\nhttps://dexie.org/docs/Samples\n\nhttps://github.com/dexie/Dexie.js/tree/master/samples\n\n## Knowledge Base\n\n[https://dexie.org/docs/Questions-and-Answers](https://dexie.org/docs/Questions-and-Answers)\n\n## Website\n\n[https://dexie.org](https://dexie.org)\n\n## Install via npm\n\n```\nnpm install dexie\n```\n\n## Download\n\nFor those who don't like package managers, here's the download links:\n\n### UMD (for legacy script includes as well as commonjs require):\n\nhttps://unpkg.com/dexie@latest/dist/dexie.min.js\n\nhttps://unpkg.com/dexie@latest/dist/dexie.min.js.map\n\n### Modern (ES module):\n\nhttps://unpkg.com/dexie@latest/dist/modern/dexie.min.mjs\n\nhttps://unpkg.com/dexie@latest/dist/modern/dexie.min.mjs.map\n\n### Typings:\n\nhttps://unpkg.com/dexie@latest/dist/dexie.d.ts\n\n# Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md)\n\n## Build\n\n```\npnpm install\npnpm run build\n```\n\n## Test\n\n```\npnpm test\n```\n\n## Watch\n\n```\npnpm run watch\n```\n\n<br/>\n\n[![Browser testing via LAMDBATEST](https://dexie.org/assets/images/lambdatest2.png)](https://www.lambdatest.com/)\n\n[npm-image]: https://img.shields.io/npm/v/dexie.svg?style=flat\n[npm-url]: https://npmjs.org/package/dexie\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          | Branch\n| ------- | ------------------ | --------\n| 4.x     | :white_check_mark: | master\n| 3.x     | :white_check_mark: | master-3\n| 2.0.x   | :x: | master-2\n| 1.5.x   | :x: | master-1\n| < 1.5.1 | :x:                |\n\n## Reporting a Vulnerability\n\nTo report a security vulnerability in Dexie.js, please send an email to code@dexie.org describing the vulnerability and how to reproduce it.\n\nIf we find an issue to be regarded as a security vulnerability, we will patch and release a new version in all the supported versions as soon as possible.\nKeep in mind though that this is an uncommercial open source project which means that sometimes you might have to be the one that\n*fixes* the issue and not just report it.\n\n## Fixing a Vulnerability\n\nFix the issue in the corresponding branch for the major version according to the table above where it applies and\ncreate pull requests. Make sure that you have the words \"security\" or \"vulnerability\" in the title of the Pull Request\nin order to get the correct attention for it to be merged and released as soon as possible.\n\n"
  },
  {
    "path": "addons/Dexie.Observable/.gitignore",
    "content": "dist/*.js\ndist/*.map\ndist/*.ts\ndist/*.gz\n**/tmp/\n"
  },
  {
    "path": "addons/Dexie.Observable/.npmignore",
    "content": "tools/\nsrc/\n.*\ntmp/\n**/tmp/\ntest\n*.log\n"
  },
  {
    "path": "addons/Dexie.Observable/README.md",
    "content": "# Dexie.Observable.js\n\n*NOTE! Dexie's liveQuery feature is NOT dependent on this old package. This package has been unmaintained for a looong time and might be retired*\n\nObserve changes to database - even when they happen in another browser window.\n\n### Install\n```\nnpm install dexie --save\nnpm install dexie-observable --save\n```\n\n### Use\n```js\nimport Dexie from 'dexie';\nimport 'dexie-observable';\n\n// Use Dexie as normally - but you can also subscribe to db.on('changes').\n\n```\n\n#### Usage with existing DB\n\nIn case you want to use Dexie.Observable with your existing database, you will have to do a schema upgrade. Without it Dexie.Observable will not be able to properly work.\n\n```javascript\nimport Dexie from 'dexie';\nimport 'dexie-observable';\n\nvar db = new Dexie('myExistingDb');\ndb.version(1).stores(... existing schema ...);\n\n// Now, add another version, just to trigger an upgrade for Dexie.Observable\ndb.version(2).stores({}); // No need to add / remove tables. This is just to allow the addon to install its tables.\n```\n\n### Dependency Tree\n\n * [Dexie.Syncable.js](https://dexie.org/docs/Syncable/Dexie.Syncable.js)\n   * **Dexie.Observable.js**\n     * [Dexie.js](https://dexie.org/docs/Dexie/Dexie.js)\n       * [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)\n\n### Source\n\n[Dexie.Observable.js](https://github.com/dexie/Dexie.js/blob/master/addons/Dexie.Observable/src/Dexie.Observable.js)\n\n### Description\n\nDexie.Observable is an add-on to Dexie.js makes it possible to listen for changes on the database even if the changes are made in a foreign window. The addon provides a \"storage\" event for IndexedDB, much like the storage event (onstorage) for localStorage.\n\nIn contrary to the [Dexie CRUD hooks](https://dexie.org/docs/Tutorial/Design#the-crud-hooks-create-read-update-delete), this event reacts not only on changes made on the current db instance but also on changes occurring on db instances in other browser windows. <u>This enables a Web Apps to react to database changes and update their views accordingly.</u>\n\nDexie.Observable is also the base of [Dexie.Syncable.js](https://dexie.org/docs/Syncable//Dexie.Syncable.js) - an add-on that enables two-way replication with a remote server.\n\n### Extended Methods, Properties and Events\n\n#### UUID key generator\nWhen defining your stores in [Version.stores()](https://dexie.org/docs/Version/Version.stores()) you may use the $$ (double dollar) prefix to your primary key. This will make it auto-generated to a UUID string. See sample below.\n\n#### Dexie.Observable.createUUID()\nA static method added to Dexie that creates a UUID. This method is used internally when using the $$ prefix to primary keys. To change the format of $$ primary keys, just override Dexie.createUUID by setting it to your desired function instead.\n\n#### db.on('changes') event\nSubscribe to any database changes no matter if they occur locally or in other browser window.\n\nParameters to your callback:\n\n<table>\n<tr><td>changes : Array&lt;<a href=\"https://dexie.org/docs/Observable/Dexie.Observable.DatabaseChange\">DatabaseChange</a>&gt;</td><td>Array of changes that have occured in database (locally or in other window) since last time event was triggered, or the time of starting subscribing to changes.</td></tr>\n<tr><td>partial: Boolean</td><td>True in case the array does not contain all changes. In this case, your callback will soon be called again with the additional changes and partial=false when all changes are delivered.</td></tr>\n</table>\n\n#### Example (here we're using plain ES6 script tags):\n```html\n<html>\n    <head>\n    <script src=\"dexie.min.js\"></script>\n    <script src=\"dexie-observable.min.js\"></script> <!-- Enable DB observation -->\n    <script>\n        var db = new Dexie(\"ObservableTest\");\n        db.version(1).stores({\n            friends: \"$$uuid,name\"\n        });\n        db.on('changes', function (changes) {\n            changes.forEach(function (change) {\n                switch (change.type) {\n                    case 1: // CREATED\n                        console.log('An object was created: ' + JSON.stringify(change.obj);\n                        break;\n                    case 2: // UPDATED\n                        console.log('An object with key ' + change.key + ' was updated with modifications: ' + JSON.stringify(change.mods));\n                        break;\n                    case 3: // DELETED\n                        console.log('An object was deleted: ' + JSON.stringify(change.oldObj);\n                        break;\n            });\n        });\n        db.open();\n        // Make an initial put() - will result in a CREATE-change:\n        db.friends.put({name: \"Kalle\"}).then(function(primKey) {\n            // Call put() with existing primary key - will result in an UPDATE-change:\n            db.friends.put({uuid: primKey, name: \"Olle\"}).then (function () {\n                // Call delete() will result in a DELETE-change:\n                db.friends.delete(primKey);\n            });\n        });\n\n        // Result that will be logged:\n        // An object was created: {\"uuid\": \"23bada36-d27a-4e78-a978-1ab3c4129cd0\", name: \"Kalle\"}\n        // An object with key: 23bada36-d27a-4e78-a978-1ab3c4129cd0 was updated with modifications: {\"name\": \"Olle\"}\n        // An object was deleted: {\"uuid\": \"23bada36-d27a-4e78-a978-1ab3c4129cd0\", name: \"Olle\"}\n    </script>\n    </head>\n    <body>\n    </body>\n</html>\n```\n"
  },
  {
    "path": "addons/Dexie.Observable/api.d.ts",
    "content": "/**\n * API for Dexie.Observable.\n *\n * Contains interfaces used by dexie-observable.\n *\n * By separating module 'dexie-observable' from 'dexie-observable/api' we\n * distinguish that:\n *\n *   import {...} from 'dexie-observable/api' is only for getting access to its\n *                                            interfaces and has no side-effects.\n *                                            Typescript-only import.\n *\n *   import 'dexie-observable' is only for side effects - to extend Dexie with\n *                             functionality of dexie-observable.\n *                             Javascript / Typescript import.\n *\n */\nexport const enum DatabaseChangeType {\n    Create = 1,\n    Update = 2,\n    Delete = 3,\n}\n\nexport interface ICreateChange {\n    type: DatabaseChangeType.Create;\n    table: string;\n    key: any;\n    obj: any;\n    source?: string;\n}\n\nexport interface IUpdateChange {\n    type: DatabaseChangeType.Update;\n    table: string;\n    key: any;\n    mods: { [keyPath: string]: any | undefined };\n    obj: any;\n    oldObj: any;\n    source?: string;\n}\n\nexport interface IDeleteChange {\n    type: DatabaseChangeType.Delete;\n    table: string;\n    key: any;\n    oldObj: any;\n    source?: string;\n}\n\nexport type IDatabaseChange = ICreateChange | IUpdateChange | IDeleteChange;\n"
  },
  {
    "path": "addons/Dexie.Observable/api.js",
    "content": "// This file is deliberatly left empty to allow the api.d.ts to contain the definitions for Dexie.Observable without generating an error on webpack\n"
  },
  {
    "path": "addons/Dexie.Observable/dist/README.md",
    "content": "## Can't find dexie-observable.js?\nTranspiled code (dist version) IS ONLY checked in to\nthe [releases](https://github.com/dexie/Dexie.js/tree/releases/addons/Dexie.Observable/dist)\nbranch.\n\n## Download\n[unpkg.com/dexie-observable/dist/dexie-observable.js](https://unpkg.com/dexie-observable/dist/dexie-observable.js)\n\n[unpkg.com/dexie-observable/dist/dexie-observable.min.js](https://unpkg.com/dexie-observable/dist/dexie-observable.min.js)\n\n[unpkg.com/dexie-observable/dist/dexie-observable.js.map](https://unpkg.com/dexie-observable/dist/dexie-observable.js.map)\n\n[unpkg.com/dexie-observable/dist/dexie-observable.min.js.map](https://unpkg.com/dexie-observable/dist/dexie-observable.min.js.map)\n\n## npm\n```\nnpm install dexie-observable --save\n```\n## bower\nSince Dexie v1.3.4, addons are included in the dexie bower package. \n```\n$ bower install dexie --save\n$ ls bower_components/dexie/addons/Dexie.Observable/dist\ndexie-observable.js  dexie-observable.js.map  dexie-observable.min.js  dexie-observable.min.js.map\n\n```\n## Or build them yourself...\nFork Dexie.js, then:\n```\ngit clone https://github.com/YOUR-USERNAME/Dexie.js.git\ncd Dexie.js\nnpm install\ncd addons/Dexie.Observable\nnpm run build       # or npm run watch\n\n```\nIf you're on windows, you need to use an elevated command prompt of some reason to get `npm install` to work.\n"
  },
  {
    "path": "addons/Dexie.Observable/package.json",
    "content": "{\n  \"name\": \"dexie-observable\",\n  \"version\": \"4.0.1-beta.13\",\n  \"description\": \"Addon to Dexie that makes it possible to observe database changes no matter if they occur on other db instance or other window.\",\n  \"main\": \"dist/dexie-observable.js\",\n  \"module\": \"dist/dexie-observable.es.js\",\n  \"jsnext:main\": \"dist/dexie-observable.es.js\",\n  \"typings\": \"dist/dexie-observable.d.ts\",\n  \"jspm\": {\n    \"format\": \"cjs\",\n    \"ignore\": [\n      \"src/\"\n    ]\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dexie/Dexie.js.git\"\n  },\n  \"keywords\": [\n    \"indexeddb\",\n    \"browser\",\n    \"dexie\",\n    \"addon\"\n  ],\n  \"author\": \"David Fahlander\",\n  \"contributors\": [\n    \"Nikolas Poniros <https://github.com/nponiros>\",\n    \"Yury Solovyov <https://github.com/YurySolovyov>\",\n    \"Martin Diphoorn <https://github.com/martindiphoorn>\",\n    \"Corbin Crutchley <https://github.com/crutchcorn>\"\n  ],\n  \"license\": \"Apache-2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/dexie/Dexie.js/issues\"\n  },\n  \"scripts\": {\n    \"build\": \"just-build\",\n    \"watch\": \"just-build --watch\",\n    \"test\": \"pnpm run build && pnpm run test:typings && pnpm run test:unit && pnpm run test:integration\",\n    \"test:unit\": \"karma start test/unit/karma.conf.js --single-run\",\n    \"test:integration\": \"karma start test/integration/karma.conf.js --single-run\",\n    \"test:typings\": \"just-build test-typings\",\n    \"test:unit:debug\": \"karma start test/unit/karma.conf.js --log-level debug\",\n    \"test:integration:debug\": \"karma start test/integrations/karma.conf.js --log-level debug\",\n    \"test:ltcloud\": \"cross-env LAMBDATEST=true pnpm run test:ltTunnel & sleep 10 && pnpm run test:unit; UNIT_STATUS=$?; exit $UNIT_STATUS\",\n    \"test:ltTunnel\": \"node ../../test/lt-local\",\n    \"test:ltcloud:integration\": \"cross-env LAMBDATEST=true pnpm run test:integration; UNIT_STATUS=$?; kill $(cat tunnel.pid); exit $UNIT_STATUS\"\n  },\n  \"just-build\": {\n    \"default\": [\n      \"just-build release test\"\n    ],\n    \"dev\": [\n      \"just-build dexie-observable test\"\n    ],\n    \"dexie-observable\": [\n      \"# Build UMD module\",\n      \"tsc --allowJs -t es5 -m es2015 --outDir tools/tmp/es5/src/ --sourceMap --skipLibCheck src/Dexie.Observable.js [--watch 'Compilation complete.']\",\n      \"rollup -c tools/build-configs/rollup.config.mjs\",\n      \"node tools/replaceVersionAndDate.js dist/dexie-observable.js\",\n      \"# eslint \",\n      \"eslint src --cache\"\n    ],\n    \"release\": [\n      \"just-build dexie-observable\",\n      \"# Copy Dexie.Observable.d.ts to dist and replace version in it\",\n      \"node -e \\\"fs.writeFileSync('dist/dexie-observable.d.ts', fs.readFileSync('src/Dexie.Observable.d.ts'))\\\"\",\n      \"node tools/replaceVersionAndDate.js dist/dexie-observable.d.ts\",\n      \"# Minify the default ES5 UMD module\",\n      \"cd dist\",\n      \"uglifyjs dexie-observable.js -m -c negate_iife=0 -o dexie-observable.min.js --source-map\"\n    ],\n    \"test\": [\n      \"# Build the unit tests (integration tests need no build)\",\n      \"tsc --allowJs --moduleResolution node --lib es2018,dom -t es5 -m es2015 --outDir tools/tmp/es5/test --rootDir ../.. --sourceMap --skipLibCheck test/unit/unit-tests-all.js [--watch 'Compilation complete.']\",\n      \"rollup -c tools/build-configs/rollup.tests.config.mjs\"\n    ],\n    \"test-typings\": [\n      \"tsc -p test/typings/\"\n    ]\n  },\n  \"homepage\": \"https://dexie.org\",\n  \"peerDependencies\": {\n    \"dexie\": \"workspace:^\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^18.11.18\",\n    \"dexie\": \"workspace:^\",\n    \"eslint\": \"^7.27.0\",\n    \"just-build\": \"^0.9.24\",\n    \"qunit\": \"2.10.0\",\n    \"qunitjs\": \"1.23.1\",\n    \"typescript\": \"^5.3.3\",\n    \"uglify-js\": \"^3.5.6\",\n    \"undici-types\": \"^6.21.0\"\n  }\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/.eslintrc.json",
    "content": "{\n  \"extends\": \"eslint:recommended\",\n  \"parserOptions\": {\n    \"ecmaVersion\": 2020,\n    \"sourceType\": \"module\",\n    \"ecmaFeatures\": {\n    }\n  },\n  \"rules\": {\n    \"no-undef\": [\"error\"],\n    \"no-unused-vars\": 1,\n    \"no-console\": 0,\n    \"no-empty\": 0\n  },\n  \"globals\": {\n    \"indexedDB\": false,\n    \"IDBKeyRange\": false,\n    \"setTimeout\": false,\n    \"clearTimeout\": false,\n    \"Symbol\": false,\n    \"setImmediate\": false,\n    \"console\": false,\n    \"self\": false,\n    \"window\": false,\n    \"global\": false,\n    \"navigator\": false,\n    \"location\": false,\n    \"chrome\": false,\n    \"document\": false,\n    \"MutationObserver\": false,\n    \"CustomEvent\": false,\n    \"dispatchEvent\": false,\n    \"localStorage\": false\n  }\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/Dexie.Observable.d.ts",
    "content": "// Type definitions for dexie-observable v{version}\n// Project: https://github.com/dexie/Dexie.js/tree/master/addons/Dexie.Observable\n// Definitions by: David Fahlander <http://github.com/dfahlander>\n\nimport Dexie, { DexieEventSet } from 'dexie';\nimport { IDatabaseChange } from '../api';\n\nexport interface SyncNodeConstructor {\n    new() : SyncNode;\n}\n\n//\n// Interfaces of Dexie.Observable\n//            \n\n\n/**\n * A SyncNode represents a local database instance that subscribes\n * to changes made on the database.\n * SyncNodes are stored in the _syncNodes table.\n * \n * Dexie.Syncable extends this interface and allows 'remote' nodes to be stored\n * as well.\n */\nexport interface SyncNode {\n    id?: number,\n    myRevision: number,\n    type: 'local' | 'remote',\n    lastHeartBeat: number,\n    deleteTimeStamp: number, // In case lastHeartBeat is too old, a value of now + HIBERNATE_GRACE_PERIOD will be set here. If reached before node wakes up, node will be deleted.\n    isMaster: number // 1 if true. Not using Boolean because it's not possible to index Booleans.\n}\n\nexport interface ObservableEventSet extends DexieEventSet {\n    (eventName: 'latestRevisionIncremented', subscriber: (dbName: string, latestRevision: number) => void): void;\n    (eventName: 'suicideNurseCall', subscriber: (dbName: string, nodeID: number) => void): void;\n    (eventName: 'intercomm', subscriber: (dbName: string) => void): void;\n    (eventName: 'beforeunload', subscriber: () => void): void;\n}\n\n// Object received by on('message') after sendMessage() or broadcastMessage()\ninterface MessageEvent {\n    id: number;\n    type: string;\n    message: any;\n    destinationNode: number;\n    wantReply?: boolean;\n    resolve(result: any): void;\n    reject(error: any): void;\n}\n\n//\n// Extend Dexie interface\n//\ndeclare module 'dexie' {\n    // Extend methods on db (db.sendMessage(), ...)\n    interface Dexie {\n        // Placeholder where to access the SyncNode class constructor.\n        // (makes it valid to do new db.observable.SyncNode())\n        observable: {\n            version: string;\n            SyncNode: SyncNodeConstructor;\n            sendMessage(\n                type: string, // Don't use 'response' as it is used internally by the framework\n                message: any, // anything that can be saved by IndexedDB\n                destinationNode: number,\n                options: {\n                    wantReply?: boolean;\n                }\n            ): Promise<any> | void; // When wantReply is undefined or false return is void\n\n            broadcastMessage(\n                type: string,\n                message: any, // anything that can be saved by IndexedDB\n                bIncludeSelf: boolean\n            ): void;\n        }\n\n        readonly _localSyncNode: SyncNode;\n\n        _changes: Dexie.Table<IDatabaseChange & {rev: number}, number>;\n        _syncNodes: Dexie.Table<SyncNode, number>;\n        _intercomm: Dexie.Table<any, number>;\n    }\n\n    // Extended events db.on('changes', subscriber), ...\n    interface DbEvents {\n        (eventName: 'changes', subscriber: (changes: IDatabaseChange[], partial: boolean)=>void): void;\n        (eventName: 'cleanup', subscriber: ()=>any): void;\n        (eventName: 'message', subscriber: (msg: MessageEvent)=>any): void;\n    }\n\n    // Extended IndexSpec with uuid boolean for primary key.\n    interface IndexSpec {\n        uuid: boolean;\n    }\n\n    interface DexieConstructor {\n        Observable: {\n            (db: Dexie) : void;\n\n            version: string;\n            createUUID: () => string;\n            on: ObservableEventSet;\n            localStorageImpl: {\n                setItem(key: string, value: string): void,\n                getItem(key: string): string,\n                removeItem(key: string): void; \n            };\n            _onStorage: (event: StorageEvent) => void;\n        }\n\n    }\n}\n\nexport default Dexie.Observable;\n"
  },
  {
    "path": "addons/Dexie.Observable/src/Dexie.Observable.js",
    "content": "/* ========================================================================== \n *                           dexie-observable.js\n * ==========================================================================\n *\n * Dexie addon for observing database changes not just on local db instance\n * but also on other instances, tabs and windows.\n *\n * Comprises a base framework for dexie-syncable.js\n *\n * By David Fahlander, david.fahlander@gmail.com,\n *    Nikolas Poniros, https://github.com/nponiros\n *\n * ==========================================================================\n *\n * Version {version}, {date}\n *\n * https://dexie.org\n *\n * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/\n * \n */\nimport Dexie from 'dexie';\nimport { nop, promisableChain, createUUID } from './utils';\n\nimport initOverrideCreateTransaction from './override-create-transaction';\nimport initWakeupObservers from './wakeup-observers';\nimport initCrudMonitor from './hooks/crud-monitor';\nimport initOnStorage from './on-storage';\nimport initOverrideOpen from './override-open';\nimport initIntercomm from './intercomm';\n\nimport overrideParseStoresSpec from './override-parse-stores-spec';\n\nimport deleteOldChanges from './delete-old-changes';\n\nvar global = self;\n\n/** class DatabaseChange\n    *\n    *  Object contained by the _changes table.\n    */\nvar DatabaseChange = Dexie.defineClass({\n    rev: Number, // Auto-incremented primary key\n    source: String, // Optional source creating the change. Set if transaction.source was set when doing the operation.\n    table: String, // Table name\n    key: Object, // Primary key. Any type.\n    type: Number, // 1 = CREATE, 2 = UPDATE, 3 = DELETE\n    obj: Object, // CREATE: obj contains the object created.\n    mods: Object, // UPDATE: mods contains the modifications made to the object.\n    oldObj: Object // DELETE: oldObj contains the object deleted. UPDATE: oldObj contains the old object before updates applied.\n});\n\n// Import some usable helper functions\nvar override = Dexie.override;\nvar Promise = Dexie.Promise;\nvar browserIsShuttingDown = false;\n\n/** Dexie addon for change tracking and real-time observation.\n * \n * @param {Dexie} db \n */\nfunction Observable(db) {\n    if (!/^(3|4)\\./.test(Dexie.version))\n        throw new Error(`Missing dexie version 3.x or 4.x`);\n    if (db.observable) {\n        if (db.observable.version !== \"{version}\") throw new Error(`Mixed versions of dexie-observable`);\n        return; // Addon already active.\n    }\n\n    var NODE_TIMEOUT = 20000, // 20 seconds before local db instances are timed out. This is so that old changes can be deleted when not needed and to garbage collect old _syncNodes objects.\n        HIBERNATE_GRACE_PERIOD = 20000, // 20 seconds\n        // LOCAL_POLL: The time to wait before polling local db for changes and cleaning up old nodes. \n        // Polling for changes is a fallback only needed in certain circomstances (when the onstorage event doesnt reach all listeners - when different browser windows doesnt share the same process)\n        LOCAL_POLL = 500, // 500 ms. In real-world there will be this value + the time it takes to poll(). A small value is needed in Workers where we cannot rely on storage event.\n        HEARTBEAT_INTERVAL = NODE_TIMEOUT - 5000;\n\n    var localStorage = Observable.localStorageImpl;\n\n    /** class SyncNode\n        *\n        * Object contained in the _syncNodes table.\n        */\n    var SyncNode = Dexie.defineClass({\n        //id: Number,\n        myRevision: Number,\n        type: String, // \"local\" or \"remote\"\n        lastHeartBeat: Number,\n        deleteTimeStamp: Number, // In case lastHeartBeat is too old, a value of now + HIBERNATE_GRACE_PERIOD will be set here. If reached before node wakes up, node will be deleted.\n        url: String, // Only applicable for \"remote\" nodes. Only used in Dexie.Syncable.\n        isMaster: Number, // 1 if true. Not using Boolean because it's not possible to index Booleans in IE implementation of IDB.\n\n        // Below properties should be extended in Dexie.Syncable. Not here. They apply to remote nodes only (type == \"remote\"):\n        syncProtocol: String, // Tells which implementation of ISyncProtocol to use for remote syncing. \n        syncContext: null,\n        syncOptions: Object,\n        connected: false, // FIXTHIS: Remove! Replace with status.\n        status: Number,\n        appliedRemoteRevision: null,\n        remoteBaseRevisions: [{ local: Number, remote: null }],\n        dbUploadState: {\n            tablesToUpload: [String],\n            currentTable: String,\n            currentKey: null,\n            localBaseRevision: Number\n        }\n    });\n\n    db.observable = {version: \"{version}\"};\n    db.observable.SyncNode = SyncNode;\n\n    const wakeupObservers = initWakeupObservers(db, Observable, localStorage);\n    const overrideCreateTransaction = initOverrideCreateTransaction(db, wakeupObservers);\n    const crudMonitor = initCrudMonitor(db);\n    const overrideOpen = initOverrideOpen(db, SyncNode, crudMonitor);\n\n    var mySyncNode = {node: null};\n\n    const intercomm = initIntercomm(db, Observable, SyncNode, mySyncNode, localStorage);\n    const onIntercomm = intercomm.onIntercomm;\n    const consumeIntercommMessages = intercomm.consumeIntercommMessages;\n\n    // Allow other addons to access the local sync node. May be needed by Dexie.Syncable.\n    Object.defineProperty(db, \"_localSyncNode\", {\n        get: function() { return mySyncNode.node; }\n    });\n\n    var pollHandle = null,\n        heartbeatHandle = null;\n\n    if (Dexie.fake) {\n        // This code will never run.\n        // It's here just to enable auto-complete in visual studio - helps a lot when writing code.\n        db.version(1).stores({\n            _syncNodes: \"++id,myRevision,lastHeartBeat\",\n            _changes: \"++rev\",\n            _intercomm: \"++id,destinationNode\",\n            _uncommittedChanges: \"++id,node\"\n        });\n        db._syncNodes.mapToClass(SyncNode);\n        db._changes.mapToClass(DatabaseChange);\n        mySyncNode.node = new SyncNode({\n            myRevision: 0,\n            type: \"local\",\n            lastHeartBeat: Date.now(),\n            deleteTimeStamp: null\n        });\n    }\n\n    //\n    // Override parsing the stores to add \"_changes\" and \"_syncNodes\" tables.\n    // It also adds UUID support for the primary key and sets tables as observable tables.\n    //\n    db.Version.prototype._parseStoresSpec = override(db.Version.prototype._parseStoresSpec, overrideParseStoresSpec);\n\n    // changes event on db:\n    db.on.addEventType({\n        changes: 'asap',\n        cleanup: [promisableChain, nop], // fire (nodesTable, changesTable, trans). Hook called when cleaning up nodes. Subscribers may return a Promise to to more stuff. May do additional stuff if local sync node is master.\n        message: 'asap'\n    });\n\n    //\n    // Override transaction creation to always include the \"_changes\" store when any observable store is involved.\n    //\n    db._createTransaction = override(db._createTransaction, overrideCreateTransaction);\n\n    // If Observable.latestRevsion[db.name] is undefined, set it to 0 so that comparing against it always works.\n    // You might think that it will always be undefined before this call, but in case another Dexie instance in the same\n    // window with the same database name has been created already, this static property will already be set correctly.\n    Observable.latestRevision[db.name] = Observable.latestRevision[db.name] || 0;\n\n    //\n    // Override open to setup hooks for db changes and map the _syncNodes table to class\n    //\n    db.open = override(db.open, overrideOpen);\n\n    db.close = override(db.close, function(origClose) {\n        return function () {\n            if (db.dynamicallyOpened()) return origClose.apply(this, arguments); // Don't observe dynamically opened databases.\n            // Teardown our framework.\n            if (wakeupObservers.timeoutHandle) {\n                clearTimeout(wakeupObservers.timeoutHandle);\n                delete wakeupObservers.timeoutHandle;\n            }\n            Observable.on('latestRevisionIncremented').unsubscribe(onLatestRevisionIncremented);\n            Observable.on('suicideNurseCall').unsubscribe(onSuicide);\n            Observable.on('intercomm').unsubscribe(onIntercomm);\n            Observable.on('beforeunload').unsubscribe(onBeforeUnload);\n            // Inform other db instances in same window that we are dying:\n            if (mySyncNode.node && mySyncNode.node.id) {\n                Observable.on.suicideNurseCall.fire(db.name, mySyncNode.node.id);\n                // Inform other windows as well:\n                if (localStorage) {\n                    localStorage.setItem('Dexie.Observable/deadnode:' + mySyncNode.node.id.toString() + '/' + db.name, \"dead\"); // In IE, this will also wakeup our own window. cleanup() may trigger twice per other db instance. But that doesnt to anything.\n                }\n                mySyncNode.node.deleteTimeStamp = 1; // One millisecond after 1970. Makes it occur in the past but still keeps it truthy.\n                mySyncNode.node.lastHeartBeat = 0;\n                db._syncNodes.put(mySyncNode.node); // This async operation may be cancelled since the browser is closing down now.\n                mySyncNode.node = null;\n            }\n\n            if (pollHandle) clearTimeout(pollHandle);\n            pollHandle = null;\n            if (heartbeatHandle) clearTimeout(heartbeatHandle);\n            heartbeatHandle = null;\n            return origClose.apply(this, arguments);\n        };\n    });\n\n    // Override Dexie.delete() in order to delete Observable.latestRevision[db.name].\n    db.delete = override(db.delete, function(origDelete) {\n        return function() {\n            return origDelete.apply(this, arguments).then(function(result) {\n                // Reset Observable.latestRevision[db.name]\n                Observable.latestRevision[db.name] = 0;\n                return result;\n            });\n        };\n    });\n\n    // When db opens, make sure to start monitor any changes before other db operations will start.\n    db.on(\"ready\", function startObserving() {\n        if (db.dynamicallyOpened()) return db; // Don't observe dynamically opened databases.\n        \n        return db.table(\"_changes\").orderBy(\"rev\").last(function(lastChange) {\n            // Since startObserving() is called before database open() method, this will be the first database operation enqueued to db.\n            // Therefore we know that the retrieved value will be This query will\n            var latestRevision = (lastChange ? lastChange.rev : 0);\n            mySyncNode.node = new SyncNode({\n                myRevision: latestRevision,\n                type: \"local\",\n                lastHeartBeat: Date.now(),\n                deleteTimeStamp: null,\n                isMaster: 0\n            });\n            if (Observable.latestRevision[db.name] < latestRevision) {\n                // Side track . For correctness whenever setting Observable.latestRevision[db.name] we must make sure the event is fired if increased:\n                // There are other db instances in same window that hasnt yet been informed about a new revision\n                Observable.latestRevision[db.name] = latestRevision;\n                Dexie.ignoreTransaction(function() {\n                    Observable.on.latestRevisionIncremented.fire(latestRevision);\n                });\n            }\n            // Add new sync node or if this is a reopening of the database after a close() call, update it.\n            return db._syncNodes.put(mySyncNode.node).then(Dexie.ignoreTransaction(() => {\n                // By default, this node will become master unless we discover an existing, up-to-date master\n                var mySyncNodeShouldBecomeMaster = 1;\n                return db._syncNodes.orderBy('isMaster').reverse().modify(existingNode => {\n                    if (existingNode.isMaster) {\n                        if (existingNode.lastHeartBeat < Date.now() - NODE_TIMEOUT) {\n                            // Existing master record is out-of-date; demote it\n                            existingNode.isMaster = 0;\n                        } else {\n                            // An existing up-to-date master record exists, so it will remain master\n                            mySyncNodeShouldBecomeMaster = 0;\n                        }\n                    }\n\n                    // The local node reference may be unassigned at any point by a database close() operation\n                    if (!mySyncNode.node) return;\n\n                    // Assign the local node state\n                    // This is guaranteed to apply *after* any existing master records have been inspected, due to the orderBy clause\n                    if (existingNode.id === mySyncNode.node.id) {\n                        existingNode.isMaster = mySyncNode.node.isMaster = mySyncNodeShouldBecomeMaster;\n                    }\n                });\n            })).then(() => {\n                Observable.on('latestRevisionIncremented', onLatestRevisionIncremented); // Wakeup when a new revision is available.\n                Observable.on('beforeunload', onBeforeUnload);\n                Observable.on('suicideNurseCall', onSuicide);\n                Observable.on('intercomm', onIntercomm);\n                // Start polling for changes and do cleanups:\n                pollHandle = setTimeout(poll, LOCAL_POLL);\n                // Start heartbeat\n                heartbeatHandle = setTimeout(heartbeat, HEARTBEAT_INTERVAL);\n            }).then(function () {\n                cleanup();\n            });\n        });\n    }, true); // True means the on(ready) event will survive a db reopening (db.close() / db.open()).\n\n    var handledRevision = 0;\n\n    function onLatestRevisionIncremented(dbname, latestRevision) {\n        if (dbname === db.name) {\n            if (handledRevision >= latestRevision) return; // Make sure to only run once per revision. (Workaround for IE triggering storage event on same window)\n            handledRevision = latestRevision;\n            Dexie.vip(function() {\n                readChanges(latestRevision).catch('DatabaseClosedError', ()=>{\n                    // Handle database closed error gracefully while reading changes.\n                    // Don't trigger 'unhandledrejection'.\n                    // Even though we intercept the close() method, it might be called when in the middle of\n                    // reading changes and then that flow will cancel with DatabaseClosedError.\n                });\n            });\n        }\n    }\n\n    function readChanges(latestRevision, recursion, wasPartial) {\n        // Whenever changes are read, fire db.on(\"changes\") with the array of changes. Eventually, limit the array to 1000 entries or so (an entire database is\n        // downloaded from server AFTER we are initiated. For example, if first sync call fails, then after a while we get reconnected. However, that scenario\n        // should be handled in case database is totally empty we should fail if sync is not available)\n        if (!recursion && readChanges.ongoingOperation) {\n            // We are already reading changes. Prohibit a parallell execution of this which would lead to duplicate trigging of 'changes' event.\n            // Instead, the callback in toArray() will always check Observable.latestRevision[db.name] to see if it has changed and if so, re-launch readChanges().\n            // The caller should get the Promise instance from the ongoing operation so that the then() method will resolve when operation is finished.\n            return readChanges.ongoingOperation;\n        }\n\n        var partial = false;\n        var ourSyncNode = mySyncNode.node; // Because mySyncNode can suddenly be set to null on database close, and worse, can be set to a new value if database is reopened.\n        if (!ourSyncNode) {\n            return Promise.reject(new Dexie.DatabaseClosedError());\n        }\n        var LIMIT = 1000;\n        var promise = db._changes.where(\"rev\").above(ourSyncNode.myRevision).limit(LIMIT).toArray(function (changes) {\n            if (changes.length > 0) {\n                var lastChange = changes[changes.length - 1];\n                partial = (changes.length === LIMIT);\n                db.on('changes').fire(changes, partial);\n                ourSyncNode.myRevision = lastChange.rev;\n            } else if (wasPartial) {\n                // No more changes, BUT since we have triggered on('changes') with partial = true,\n                // we HAVE TO trigger changes again with empty list and partial = false\n                db.on('changes').fire([], false);\n            }\n\n            let ourNodeStillExists = false;\n            return db._syncNodes.where(':id').equals(ourSyncNode.id).modify(syncNode => {\n                ourNodeStillExists = true;\n                syncNode.lastHeartBeat = Date.now(); // Update heart beat (not nescessary, but why not!)\n                syncNode.deleteTimeStamp = null; // Reset \"deleteTimeStamp\" flag if it was there.\n                syncNode.myRevision = Math.max(syncNode.myRevision, ourSyncNode.myRevision);\n            }).then(()=>ourNodeStillExists);\n        }).then(ourNodeStillExists =>{\n            if (!ourNodeStillExists) {\n                // My node has been deleted. We must have been lazy and got removed by another node.\n                if (browserIsShuttingDown) {\n                    throw new Error(\"Browser is shutting down\");\n                } else {\n                    db.close();\n                    console.error(\"Out of sync\"); // TODO: What to do? Reload the page?\n                    if (global.location) global.location.reload(true);\n                    throw new Error(\"Out of sync\"); // Will make current promise reject\n                }\n            }\n\n            // Check if more changes have come since we started reading changes in the first place. If so, relaunch readChanges and let the ongoing promise not\n            // resolve until all changes have been read.\n            if (partial || Observable.latestRevision[db.name] > ourSyncNode.myRevision) {\n                // Either there were more than 1000 changes or additional changes where added while we were reading these changes,\n                // In either case, call readChanges() again until we're done.\n                return readChanges(Observable.latestRevision[db.name], (recursion || 0) + 1, partial);\n            }\n\n        }).finally(function() {\n            delete readChanges.ongoingOperation;\n        });\n\n        if (!recursion) {\n            readChanges.ongoingOperation = promise;\n        }\n        return promise;\n    }\n\n    /**\n     * The reason we need heartbeat in parallell with poll() is due to the risk of long-running\n     * transactions while syncing changes from server to client in Dexie.Syncable. That transaction will\n     * include _changes (which will block readChanges()) but not _syncNodes. So this heartbeat will go on\n     * during that changes are being applied and update our lastHeartBeat property while poll() is waiting.\n     * When cleanup() (who also is blocked by the sync) wakes up, it won't kill the master node because this\n     * heartbeat job will have updated the master node's heartbeat during the long-running sync transaction.\n     * \n     * If we did not have this heartbeat, and a server send lots of changes that took more than NODE_TIMEOUT\n     * (20 seconds), another node waking up after the sync would kill the master node and take over because\n     * it would believe it was dead.\n     */\n    function heartbeat() {\n        heartbeatHandle = null;\n        var currentInstance = mySyncNode.node && mySyncNode.node.id;\n        if (!currentInstance) return;\n        db.transaction('rw!', db._syncNodes, ()=>{\n            db._syncNodes.where({id: currentInstance}).first(ourSyncNode => {\n                if (!ourSyncNode) {\n                    // We do not exist anymore. Call db.close() to teardown polls etc.\n                    if (db.isOpen()) db.close();\n                    return;\n                }\n                ourSyncNode.lastHeartBeat = Date.now();\n                ourSyncNode.deleteTimeStamp = null; // Reset \"deleteTimeStamp\" flag if it was there.\n                return db._syncNodes.put(ourSyncNode);\n            });\n        }).catch('DatabaseClosedError', () => {\n            // Ignore silently\n        }).finally(() => {\n            if (mySyncNode.node && mySyncNode.node.id === currentInstance && db.isOpen()) {\n                heartbeatHandle = setTimeout(heartbeat, HEARTBEAT_INTERVAL);\n            }\n        });\n    }\n\n    function poll() {\n        pollHandle = null;\n        var currentInstance = mySyncNode.node && mySyncNode.node.id;\n        if (!currentInstance) return;\n        Dexie.vip(function() { // VIP ourselves. Otherwise we might not be able to consume intercomm messages from master node before database has finished opening. This would make DB stall forever. Cannot rely on storage-event since it may not always work in some browsers of different processes.\n            readChanges(Observable.latestRevision[db.name]).then(cleanup).then(consumeIntercommMessages)\n            .catch('DatabaseClosedError', ()=>{\n                // Handle database closed error gracefully while reading changes.\n                // Don't trigger 'unhandledrejection'.\n                // Even though we intercept the close() method, it might be called when in the middle of\n                // reading changes and then that flow will cancel with DatabaseClosedError.\n            })\n            .finally(function() {\n                // Poll again in given interval:\n                if (mySyncNode.node && mySyncNode.node.id === currentInstance && db.isOpen()) {\n                    pollHandle = setTimeout(poll, LOCAL_POLL);\n                }\n            });\n        });\n    }\n\n    \n    function cleanup() {\n        var ourSyncNode = mySyncNode.node;\n        if (!ourSyncNode) return Promise.reject(new Dexie.DatabaseClosedError());\n        return db.transaction('rw', '_syncNodes', '_changes', '_intercomm', function() {\n            // Cleanup dead local nodes that has no heartbeat for over a minute\n            // Dont do the following:\n            //nodes.where(\"lastHeartBeat\").below(Date.now() - NODE_TIMEOUT).and(function (node) { return node.type == \"local\"; }).delete();\n            // Because client may have been in hybernate mode and recently woken up. That would lead to deletion of all nodes.\n            // Instead, we should mark any old nodes for deletion in a minute or so. If they still dont wakeup after that minute we could consider them dead.\n            var weBecameMaster = false;\n            db._syncNodes.where(\"lastHeartBeat\").below(Date.now() - NODE_TIMEOUT).filter(node => node.type === 'local').modify(function(node) {\n                if (node.deleteTimeStamp && node.deleteTimeStamp < Date.now()) {\n                    // Delete the node.\n                    delete this.value;\n                    // Cleanup localStorage \"deadnode:\" entry for this node (localStorage API was used to wakeup other windows (onstorage event) - an event type missing in indexedDB.)\n                    if (localStorage) {\n                        localStorage.removeItem('Dexie.Observable/deadnode:' + node.id + '/' + db.name);\n                    }\n                    // Check if we are deleting a master node\n                    if (node.isMaster) {\n                        // The node we are deleting is master. We must take over that role.\n                        // OK to call nodes.update(). No need to call Dexie.vip() because nodes is opened in existing transaction!\n                        db._syncNodes.update(ourSyncNode, { isMaster: 1 });\n                        weBecameMaster = true;\n                    }\n                    // Cleanup intercomm messages destinated to the node being deleted.\n                    // Those that waits for reply should be redirected to us.\n                    db._intercomm.where({destinationNode: node.id}).modify(function(msg) {\n                        if (msg.wantReply)\n                            msg.destinationNode = ourSyncNode.id;\n                        else\n                            // Delete the message from DB and if someone is waiting for reply, let ourselved answer the request.\n                            delete this.value;\n                    });\n                } else if (!node.deleteTimeStamp) {\n                    // Mark the node for deletion\n                    node.deleteTimeStamp = Date.now() + HIBERNATE_GRACE_PERIOD;\n                }\n            }).then(function() {\n                // Cleanup old revisions that no node is interested of.\n                Observable.deleteOldChanges(db);\n                return db.on(\"cleanup\").fire(weBecameMaster);\n            });\n        });\n    }\n\n    function onBeforeUnload() {\n        // Mark our own sync node for deletion.\n        if (!mySyncNode.node) return;\n        browserIsShuttingDown = true;\n        mySyncNode.node.deleteTimeStamp = 1; // One millisecond after 1970. Makes it occur in the past but still keeps it truthy.\n        mySyncNode.node.lastHeartBeat = 0;\n        db._syncNodes.put(mySyncNode.node); // This async operation may be cancelled since the browser is closing down now.\n        Observable.wereTheOneDying = true; // If other nodes in same window wakes up by this call, make sure they dont start taking over mastership and stuff...\n        // Inform other windows that we're gone, so that they may take over our role if needed. Setting localStorage item below will trigger Observable.onStorage, which will trigger onSuicie() below:\n        if (localStorage) {\n            localStorage.setItem('Dexie.Observable/deadnode:' + mySyncNode.node.id.toString() + '/' + db.name, \"dead\"); // In IE, this will also wakeup our own window. However, that is doublechecked in nursecall subscriber below.\n        }\n    }\n\n    function onSuicide(dbname, nodeID) {\n        if (dbname === db.name && !Observable.wereTheOneDying) {\n            // Make sure it's dead indeed. Second bullet. Why? Because it has marked itself for deletion in the onbeforeunload event, which is fired just before window dies.\n            // It's own call to put() may have been cancelled.\n            // Note also that in IE, this event may be called twice, but that doesnt harm!\n            Dexie.vip(function() {\n                db._syncNodes.update(nodeID, { deleteTimeStamp: 1, lastHeartBeat: 0 }).then(cleanup);\n            });\n        }\n    }\n\n}\n\n//\n// Static properties and methods\n// \n\nObservable.version = \"{version}\";\nObservable.latestRevision = {}; // Latest revision PER DATABASE. Example: Observable.latestRevision.FriendsDB = 37;\nObservable.on = Dexie.Events(null, \"latestRevisionIncremented\", \"suicideNurseCall\", \"intercomm\", \"beforeunload\"); // fire(dbname, value);\nObservable.createUUID = createUUID;\n\nObservable.deleteOldChanges = deleteOldChanges;\n\nObservable._onStorage = initOnStorage(Observable);\n\nObservable._onBeforeUnload = function() {\n    Observable.on.beforeunload.fire();\n};\n\ntry {\n    Observable.localStorageImpl = global.localStorage;\n} catch (ex){}\n\n//\n// Map window events to static events in Dexie.Observable:\n//\nif (global?.addEventListener) {\n    global.addEventListener(\"storage\", Observable._onStorage);\n    global.addEventListener(\"beforeunload\", Observable._onBeforeUnload);\n}\n\nif (Dexie.Observable) {\n    if (Dexie.Observable.version !== \"{version}\") {\n        throw new Error (`Mixed versions of dexie-observable`);\n    }\n} else {\n    // Register addon:\n    Dexie.Observable = Observable;\n    Dexie.addons.push(Observable);\n}\n\nexport default Dexie.Observable;\n"
  },
  {
    "path": "addons/Dexie.Observable/src/change_types.js",
    "content": "// Change Types\nexport const CREATE = 1;\nexport const UPDATE = 2;\nexport const DELETE = 3;\n"
  },
  {
    "path": "addons/Dexie.Observable/src/delete-old-changes.js",
    "content": "import Dexie from 'dexie';\n\nexport default function deleteOldChanges(db) {\n  // This is a background job and should never be done within\n  // a caller's transaction. Use Dexie.ignoreTransaction() to ensure that.\n  // We should not return the Promise but catch it ourselves instead.\n\n  // To prohibit starving the database we want to lock transactions as short as possible\n  // and since we're not in a hurry, we could do this job in chunks and reschedule a\n  // continuation every 500 ms.\n  const CHUNK_SIZE = 100;\n\n  Dexie.ignoreTransaction(()=>{\n    return db._syncNodes.orderBy(\"myRevision\").first(oldestNode => {\n      return db._changes\n          .where(\"rev\").below(oldestNode.myRevision)\n          .limit(CHUNK_SIZE)\n          .primaryKeys();\n    }).then(keysToDelete => {\n      if (keysToDelete.length === 0) return; // Done.\n      return db._changes.bulkDelete(keysToDelete).then(()=> {\n        // If not done garbage collecting, reschedule a continuation of it until done.\n        if (keysToDelete.length === CHUNK_SIZE) {\n          // Limit reached. Changes are there are more job to do. Schedule again:\n          setTimeout(() => db.isOpen() && deleteOldChanges(db), 500);\n        }\n      });\n    });\n  }).catch(()=>{\n    // The operation is not crucial. A failure could almost only be due to that database has been closed.\n    // No need to log this.\n  });\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/hooks/creating.js",
    "content": "import Dexie from 'dexie';\n\nimport {CREATE} from '../change_types';\n\nexport default function initCreatingHook(db, table) {\n  return function creatingHook(primKey, obj, trans) {\n    /// <param name=\"trans\" type=\"db.Transaction\"></param>\n    var rv = undefined;\n    if (primKey === undefined && table.schema.primKey.uuid) {\n      primKey = rv = Dexie.Observable.createUUID();\n      if (table.schema.primKey.keyPath) {\n        Dexie.setByKeyPath(obj, table.schema.primKey.keyPath, primKey);\n      }\n    }\n\n    var change = {\n      source: trans.source || null, // If a \"source\" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.\n      table: table.name,\n      key: primKey === undefined ? null : primKey,\n      type: CREATE,\n      obj: obj\n    };\n\n    var promise = db._changes.add(change).then(function (rev) {\n      trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);\n      return rev;\n    });\n\n    // Wait for onsuccess so that we have the primKey if it is auto-incremented and update the change item if so.\n    this.onsuccess = function (resultKey) {\n      if (primKey != resultKey)\n        promise._then(function () {\n          change.key = resultKey;\n          db._changes.put(change);\n        });\n    };\n\n    this.onerror = function () {\n      // If the main operation fails, make sure to regret the change\n      promise._then(function (rev) {\n        // Will only happen if app code catches the main operation error to prohibit transaction from aborting.\n        db._changes.delete(rev);\n      });\n    };\n\n    return rv;\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/hooks/crud-monitor.js",
    "content": "import initCreatingHook from './creating';\nimport initUpdatingHook from './updating';\nimport initDeletingHook from './deleting';\n\nexport default function initCrudMonitor(db) {\n//\n// The Creating/Updating/Deleting hook will make sure any change is stored to the changes table\n//\n  return function crudMonitor(table) {\n    /// <param name=\"table\" type=\"db.Table\"></param>\n    if (table.hook._observing) return;\n    table.hook._observing = true;\n\n    const tableName = table.name;\n    table.hook('creating').subscribe(initCreatingHook(db, table));\n\n    table.hook('updating').subscribe(initUpdatingHook(db, tableName));\n\n    table.hook('deleting').subscribe(initDeletingHook(db, tableName));\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/hooks/deleting.js",
    "content": "import {DELETE} from '../change_types';\n\nexport default function initDeletingHook(db, tableName) {\n  return function deletingHook(primKey, obj, trans) {\n    /// <param name=\"trans\" type=\"db.Transaction\"></param>\n    var promise = db._changes.add({\n      source: trans.source || null, // If a \"source\" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.\n      table: tableName,\n      key: primKey,\n      type: DELETE,\n      oldObj: obj\n    }).then(function (rev) {\n      trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);\n      return rev;\n    })\n        .catch((e) => {\n          console.log(obj)\n          console.log(e.stack)\n        })\n    this.onerror = function () {\n      // If the main operation fails, make sure to regret the change.\n      // Using _then because if promise is already fullfilled, the standard then() would\n      // do setTimeout() and we would loose the transaction.\n      promise._then(function (rev) {\n        // Will only happen if app code catches the main operation error to prohibit transaction from aborting.\n        db._changes.delete(rev);\n      });\n    };\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/hooks/updating.js",
    "content": "import Dexie from 'dexie';\n\nimport {UPDATE} from '../change_types';\n\nexport default function initUpdatingHook(db, tableName) {\n  return function updatingHook(mods, primKey, oldObj, trans) {\n    /// <param name=\"trans\" type=\"db.Transaction\"></param>\n    // mods may contain property paths with undefined as value if the property\n    // is being deleted. Since we cannot persist undefined we need to act\n    // like those changes is setting the value to null instead.\n    var modsWithoutUndefined = {};\n    // As of current Dexie version (1.0.3) hook may be called even if it wouldn't really change.\n    // Therefore we may do that kind of optimization here - to not add change entries if\n    // there's nothing to change.\n    var anythingChanged = false;\n    var newObj = Dexie.deepClone(oldObj);\n    for (var propPath in mods) {\n      var mod = mods[propPath];\n      if (typeof mod === 'undefined') {\n        Dexie.delByKeyPath(newObj, propPath);\n        modsWithoutUndefined[propPath] = null; // Null is as close we could come to deleting a property when not allowing undefined.\n        anythingChanged = true;\n      } else {\n        var currentValue = Dexie.getByKeyPath(oldObj, propPath);\n        if (mod !== currentValue && JSON.stringify(mod) !== JSON.stringify(currentValue)) {\n          Dexie.setByKeyPath(newObj, propPath, mod);\n          modsWithoutUndefined[propPath] = mod;\n          anythingChanged = true;\n        }\n      }\n    }\n    if (anythingChanged) {\n      var change = {\n        source: trans.source || null, // If a \"source\" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.\n        table: tableName,\n        key: primKey,\n        type: UPDATE,\n        mods: modsWithoutUndefined,\n        oldObj: oldObj,\n        obj: newObj\n      };\n      var promise = db._changes.add(change); // Just so we get the correct revision order of the update...\n      this.onsuccess = function () {\n        promise._then(function (rev) {\n          trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);\n        });\n      };\n      this.onerror = function () {\n        // If the main operation fails, make sure to regret the change.\n        promise._then(function (rev) {\n          // Will only happen if app code catches the main operation error to prohibit transaction from aborting.\n          db._changes.delete(rev);\n        });\n      };\n    }\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/intercomm.js",
    "content": "import Dexie from 'dexie';\n\nconst Promise = Dexie.Promise;\n\nexport default function initIntercomm(db, Observable, SyncNode, mySyncNode, localStorage) {\n//\n// Intercommunication between nodes\n//\n// Enable inter-process communication between browser windows using localStorage storage event (is registered in Dexie.Observable)\n\n  var requestsWaitingForReply = {};\n\n  /**\n   * @param {string} type Type of message\n   * @param message Message to send\n   * @param {number} destinationNode ID of destination node\n   * @param {{wantReply: boolean, isFailure: boolean, requestId: number}} options If {wantReply: true}, the returned promise will complete with the reply from remote. Otherwise it will complete when message has been successfully sent.</param>\n   */\n  db.observable.sendMessage = function (type, message, destinationNode, options) {\n    /// <param name=\"type\" type=\"String\">Type of message</param>\n    /// <param name=\"message\">Message to send</param>\n    /// <param name=\"destinationNode\" type=\"Number\">ID of destination node</param>\n    /// <param name=\"options\" type=\"Object\" optional=\"true\">{wantReply: Boolean, isFailure: Boolean, requestId: Number}. If wantReply, the returned promise will complete with the reply from remote. Otherwise it will complete when message has been successfully sent.</param>\n    options = options || {};\n    if (!mySyncNode.node)\n      return options.wantReply ?\n          Promise.reject(new Dexie.DatabaseClosedError()) :\n          Promise.resolve(); // If caller doesn't want a reply, it won't catch errors either.\n\n    var msg = {message: message, destinationNode: destinationNode, sender: mySyncNode.node.id, type: type};\n    Dexie.extend(msg, options); // wantReply: wantReply, success: !isFailure, requestId: ...\n    return Dexie.ignoreTransaction(()=> {\n      var tables = [\"_intercomm\"];\n      if (options.wantReply) tables.push(\"_syncNodes\"); // If caller wants a reply, include \"_syncNodes\" in transaction to check that there's a receiver there. Otherwise, new master will get it.\n      var promise = db.transaction('rw', tables, () => {\n        if (options.wantReply) {\n          // Check that there is a receiver there to take the request.\n          return db._syncNodes.where('id').equals(destinationNode).count(receiverAlive => {\n            if (receiverAlive)\n              return db._intercomm.add(msg);\n            else // If we couldn't find a node -> send to master\n              return db._syncNodes.where('isMaster').above(0).first(function (masterNode) {\n                msg.destinationNode = masterNode.id;\n                return db._intercomm.add(msg)\n              });\n          });\n        } else {\n          // If caller doesn't need a response, we don't have to make sure that it gets one.\n          return db._intercomm.add(msg);\n        }\n      }).then(messageId => {\n        var rv = null;\n        if (options.wantReply) {\n          rv = new Promise(function (resolve, reject) {\n            requestsWaitingForReply[messageId.toString()] = {resolve: resolve, reject: reject};\n          });\n        }\n        if (localStorage) {\n          localStorage.setItem(\"Dexie.Observable/intercomm/\" + db.name, messageId.toString());\n        }\n        Observable.on.intercomm.fire(db.name);\n        return rv;\n      });\n\n      if (!options.wantReply) {\n        promise.catch(()=> {\n        });\n        return;\n      } else {\n        // Forward rejection to caller if it waits for reply.\n        return promise;\n      }\n    });\n  };\n\n  // Send a message to all local _syncNodes\n  db.observable.broadcastMessage = function (type, message, bIncludeSelf) {\n    if (!mySyncNode.node) return;\n    var mySyncNodeId = mySyncNode.node.id;\n    Dexie.ignoreTransaction(()=> {\n      db._syncNodes.toArray(nodes => {\n        return Promise.all(nodes\n            .filter(node => node.type === 'local' && (bIncludeSelf || node.id !== mySyncNodeId))\n            .map(node => db.observable.sendMessage(type, message, node.id)));\n      }).catch(()=> {\n      });\n    });\n  };\n\n  function consumeIntercommMessages() {\n    // Check if we got messages:\n    if (!mySyncNode.node) return Promise.reject(new Dexie.DatabaseClosedError());\n\n    return Dexie.ignoreTransaction(()=> {\n      return db.transaction('rw', '_intercomm', function() {\n        return db._intercomm.where({destinationNode: mySyncNode.node.id}).toArray(messages => {\n          messages.forEach(msg => consumeMessage(msg));\n          return db._intercomm.where('id').anyOf(messages.map(msg => msg.id)).delete();\n        });\n      });\n    });\n  }\n\n  function consumeMessage(msg) {\n    if (msg.type === 'response') {\n      // This is a response. Lookup pending request and fulfill its promise.\n      var request = requestsWaitingForReply[msg.requestId.toString()];\n      if (request) {\n        if (msg.isFailure) {\n          request.reject(msg.message.error);\n        } else {\n          request.resolve(msg.message.result);\n        }\n        delete requestsWaitingForReply[msg.requestId.toString()];\n      }\n    } else {\n      // This is a message or request. Fire the event and add an API for the subscriber to use if reply is requested\n      msg.resolve = function (result) {\n        db.observable.sendMessage('response', {result: result}, msg.sender, {requestId: msg.id});\n      };\n      msg.reject = function (error) {\n        db.observable.sendMessage('response', {error: error.toString()}, msg.sender, {isFailure: true, requestId: msg.id});\n      };\n      db.on.message.fire(msg);\n    }\n  }\n\n  // Listener for 'intercomm' events\n  // Gets fired when we get a 'storage' event from local storage or when sendMessage is called\n  // 'storage' is used to communicate between tabs (sendMessage changes the localStorage to trigger the event)\n  // sendMessage is used to communicate in the same tab and to trigger a storage event\n  function onIntercomm(dbname) {\n    // When storage event trigger us to check\n    if (dbname === db.name) {\n      consumeIntercommMessages().catch('DatabaseClosedError', ()=> {});\n    }\n  }\n\n  return {\n    onIntercomm,\n    consumeIntercommMessages\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/on-storage.js",
    "content": "import Dexie from 'dexie';\n\nexport default function initOnStorage(Observable) {\n  return function onStorage(event) {\n    // We use the onstorage event to trigger onLatestRevisionIncremented since we will wake up when other windows modify the DB as well!\n    if (event.key && event.key.indexOf(\"Dexie.Observable/\") === 0) { // For example \"Dexie.Observable/latestRevision/FriendsDB\"\n      var parts = event.key.split('/');\n      var prop = parts[1];\n      var dbname = parts[2];\n      if (prop === 'latestRevision') {\n        var rev = parseInt(event.newValue, 10);\n        if (!isNaN(rev) && rev > Observable.latestRevision[dbname]) {\n          Observable.latestRevision[dbname] = rev;\n          Dexie.ignoreTransaction(function () {\n            Observable.on('latestRevisionIncremented').fire(dbname, rev);\n          });\n        }\n      } else if (prop.indexOf(\"deadnode:\") === 0) {\n        var nodeID = parseInt(prop.split(':')[1], 10);\n        if (event.newValue) {\n          Observable.on.suicideNurseCall.fire(dbname, nodeID);\n        }\n      } else if (prop === 'intercomm') {\n        if (event.newValue) {\n          Observable.on.intercomm.fire(dbname);\n        }\n      }\n    }\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/override-create-transaction.js",
    "content": "export default function initOverrideCreateTransaction(db, wakeupObservers) {\n  return function overrideCreateTransaction(origFunc) {\n    return function (mode, storenames, dbschema, parent) {\n      if (db.dynamicallyOpened()) return origFunc.apply(this, arguments); // Don't observe dynamically opened databases.\n      var addChanges = false;\n      if (mode === 'readwrite' && storenames.some(function (storeName) {\n            return dbschema[storeName] && dbschema[storeName].observable;\n          })) {\n        // At least one included store is a observable store. Make sure to also include the _changes store.\n        addChanges = true;\n        storenames = storenames.slice(0); // Clone\n        if (storenames.indexOf(\"_changes\") === -1)\n          storenames.push(\"_changes\"); // Otherwise, firefox will hang... (I've reported the bug to Mozilla@Bugzilla)\n      }\n      // Call original db._createTransaction()\n      var trans = origFunc.call(this, mode, storenames, dbschema, parent);\n      // If this transaction is bound to any observable table, make sure to add changes when transaction completes.\n      if (addChanges) {\n        trans._lastWrittenRevision = 0;\n        trans.on('complete', function () {\n          if (trans._lastWrittenRevision) {\n            // Changes were written in this transaction.\n            if (!parent) {\n              // This is root-level transaction, i.e. a physical commit has happened.\n              // Delay-trigger a wakeup call:\n              if (wakeupObservers.timeoutHandle) clearTimeout(wakeupObservers.timeoutHandle);\n              wakeupObservers.timeoutHandle = setTimeout(function () {\n                delete wakeupObservers.timeoutHandle;\n                wakeupObservers(trans._lastWrittenRevision);\n              }, 25);\n            } else {\n              // This is just a virtual commit of a sub transaction.\n              // Wait with waking up observers until root transaction has committed.\n              // Make sure to mark root transaction so that it will wakeup observers upon commit.\n              var rootTransaction = (function findRootTransaction(trans) {\n                return trans.parent ? findRootTransaction(trans.parent) : trans;\n              })(parent);\n              rootTransaction._lastWrittenRevision = Math.max(\n                  trans._lastWrittenRevision,\n                  rootTransaction.lastWrittenRevision || 0);\n            }\n          }\n        });\n        // Derive \"source\" property from parent transaction by default\n        if (trans.parent && trans.parent.source) trans.source = trans.parent.source;\n      }\n      return trans;\n    };\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/override-open.js",
    "content": "export default function initOverrideOpen(db, SyncNode, crudMonitor) {\n  return function overrideOpen(origOpen) {\n    return function () {\n      //\n      // Make sure to subscribe to \"creating\", \"updating\" and \"deleting\" hooks for all observable tables that were created in the stores() method.\n      //\n      Object.keys(db._allTables).forEach(tableName => {\n        let table = db._allTables[tableName];\n        if (table.schema.observable) {\n          crudMonitor(table);\n        }\n        if (table.name === \"_syncNodes\") {\n          table.mapToClass(SyncNode);\n        }\n      });\n      return origOpen.apply(this, arguments);\n    }\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/override-parse-stores-spec.js",
    "content": "export default function overrideParseStoresSpec(origFunc) {\n  return function(stores, dbSchema) {\n    // Create the _changes and _syncNodes tables\n    stores[\"_changes\"] = \"++rev\";\n    stores[\"_syncNodes\"] = \"++id,myRevision,lastHeartBeat,&url,isMaster,type,status\";\n    stores[\"_intercomm\"] = \"++id,destinationNode\";\n    stores[\"_uncommittedChanges\"] = \"++id,node\"; // For remote syncing when server returns a partial result.\n    // Call default implementation. Will populate the dbSchema structures.\n    origFunc.call(this, stores, dbSchema);\n    // Allow UUID primary keys using $$ prefix on primary key or indexes\n    Object.keys(dbSchema).forEach(function(tableName) {\n      var schema = dbSchema[tableName];\n      if (schema.primKey.name.indexOf('$$') === 0) {\n        schema.primKey.uuid = true;\n        schema.primKey.name = schema.primKey.name.substr(2);\n        schema.primKey.keyPath = schema.primKey.keyPath.substr(2);\n      }\n    });\n    // Now mark all observable tables\n    Object.keys(dbSchema).forEach(function(tableName) {\n      // Marked observable tables with \"observable\" in their TableSchema.\n      if (tableName.indexOf('_') !== 0 && tableName.indexOf('$') !== 0) {\n        dbSchema[tableName].observable = true;\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/utils.js",
    "content": "export function nop() {}\n\nexport function promisableChain(f1, f2) {\n  if (f1 === nop) return f2;\n  return function() {\n    var res = f1.apply(this, arguments);\n    if (res && typeof res.then === 'function') {\n      var thiz = this, args = arguments;\n      return res.then(function() {\n        return f2.apply(thiz, args);\n      });\n    }\n    return f2.apply(this, arguments);\n  };\n}\n\nexport function createUUID() {\n  // Decent solution from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript\n  var d = Date.now();\n  var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n    var r = (d + Math.random() * 16) % 16 | 0;\n    d = Math.floor(d / 16);\n    return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16);\n  });\n  return uuid;\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/src/wakeup-observers.js",
    "content": "import Dexie from 'dexie';\n\nexport default function initWakeupObservers(db, Observable, localStorage) {\n  return function wakeupObservers(lastWrittenRevision) {\n    // Make sure Observable.latestRevision[db.name] is still below our value, now when some time has elapsed and other db instances in same window possibly could have made changes too.\n    if (Observable.latestRevision[db.name] < lastWrittenRevision) {\n      // Set the static property lastRevision[db.name] to the revision of the last written change.\n      Observable.latestRevision[db.name] = lastWrittenRevision;\n      // Wakeup ourselves, and any other db instances on this window:\n      Dexie.ignoreTransaction(function () {\n        Observable.on('latestRevisionIncremented').fire(db.name, lastWrittenRevision);\n      });\n      // Observable.on.latestRevisionIncremented will only wakeup db's in current window.\n      // We need a storage event to wakeup other windwos.\n      // Since indexedDB lacks storage events, let's use the storage event from WebStorage just for\n      // the purpose to wakeup db instances in other windows.\n      if (localStorage) localStorage.setItem('Dexie.Observable/latestRevision/' + db.name, lastWrittenRevision); // In IE, this will also wakeup our own window. However, onLatestRevisionIncremented will work around this by only running once per revision id.\n    }\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/test/gh-actions.sh",
    "content": "#!/bin/bash -e\necho \"Installing dependencies for dexie-observable\"\npnpm install >/dev/null\npnpm run build\npnpm run test:typings\npnpm run test:ltcloud\npnpm run test:ltcloud:integration\n"
  },
  {
    "path": "addons/Dexie.Observable/test/integration/karma-env.js",
    "content": "// workerImports will be used by tests-open.js in the dexie test suite when\n// launching a Worker. This line will instruct the worker to import dexie-observable.\nwindow.workerImports.push(\"../addons/Dexie.Observable/dist/dexie-observable.js\");\n"
  },
  {
    "path": "addons/Dexie.Observable/test/integration/karma.conf.js",
    "content": "// Include common configuration\nconst {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../../../test/karma.common');\n\nmodule.exports = function (config) {\n  const browserMatrixOverrides = {\n    // Be fine with testing on local travis firefox + browserstack chrome, latest supported.\n    ci: [\"remote_chrome\"],\n    // Safari fails to reply on browserstack. Need to not have it here.\n    // Just complement with old chrome browser that is not part of CI test suite.\n    pre_npm_publish: [\n      \"Chrome\",\n    ]\n  };\n\n  const cfg = getKarmaConfig(browserMatrixOverrides, {\n    // Base path should point at the root \n    basePath: '../../../../',\n    // The files needed to apply dexie-observable to the standard dexie unit tests.\n    files: karmaCommon.files.concat([\n      'dist/dexie.js',\n      'addons/Dexie.Observable/test/integration/karma-env.js',\n      'addons/Dexie.Observable/dist/dexie-observable.js', // Apply observable addon\n      'test/bundle.js', // The dexie standard test suite\n      { pattern: 'addons/Dexie.Observable/dist/*.map', watched: false, included: false }\n    ])\n  });\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/test/integration/test-observable-dexie-tests.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Dexie Unit tests with Dexie.Observable applied</title>\n  <base href=\"../../../../test/\" />\n  <link rel=\"stylesheet\" href=\"../node_modules/qunitjs/qunit/qunit.css\">\n</head>\n<body>\n    <div id=\"qunit\"></div>\n    <div id=\"qunit-fixture\"></div>\n    <script src=\"babel-polyfill/polyfill.min.js\"></script>\n    <script>\n        // Hack for making the WebWorker test behave also here...\n        window.workerSource = \"worker.js\";\n        window.workerImports = [\"../dist/dexie.js\", \"../addons/Dexie.Observable/dist/dexie-observable.js\"];\n    </script>\n    <script src=\"../node_modules/qunitjs/qunit/qunit.js\"></script>\n    <script src=\"../dist/dexie.js\"></script>\n    <script src=\"../addons/Dexie.Observable/dist/dexie-observable.js\"></script>\n    <script src=\"bundle.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "addons/Dexie.Observable/test/typings/test-typings.ts",
    "content": "\nimport Dexie from 'dexie';\nimport '../../src/Dexie.Observable';\nimport dexieObservable from '../../src/Dexie.Observable';\nimport { IDatabaseChange, DatabaseChangeType } from '../../api';\n\ninterface Foo {\n    id: string;\n}\n\nclass MyDb extends Dexie {\n    foos: Dexie.Table<Foo, string>;\n\n    constructor() {\n        super('testdb', {addons: [dexieObservable, Dexie.Observable]});\n        this.version(1).stores({foos: '$$id'});\n    }\n}\n\nlet db = new MyDb();\n\nlet syncNode = new db.observable.SyncNode();\nsyncNode.isMaster;\nsyncNode.deleteTimeStamp.toExponential();\nsyncNode.lastHeartBeat.toExponential();\nsyncNode.myRevision.toFixed();\n\ndb.on('message', msg => {\n    msg.type;\n    msg.message;\n    msg.destinationNode * 1;\n    msg.wantReply;\n    msg.resolve('foo');\n    msg.reject(new Error('Foo'));\n});\ndb.observable.sendMessage('myMsgType', {foo: 'bar'}, 13, {wantReply: true});\ndb.observable.sendMessage('myMsgType', 'foobar', 13, {wantReply: false});\n\ndb.observable.broadcastMessage('myBroadcastMsgType', {foo2: 'bar2'}, false);\n\ndb.on('changes', changes => {\n    changes.forEach(change => {\n        switch (change.type) {\n            case DatabaseChangeType.Create:\n                change.table.toLowerCase();\n                change.key;\n                change.obj;                \n                break;\n            case DatabaseChangeType.Update:\n                change.table.toLowerCase();\n                change.key;\n                change.mods;\n                break;\n            case DatabaseChangeType.Delete:\n                change.table.toLowerCase();\n                change.key;\n                break;\n        }\n    })\n});\n\nDexie.Observable.createUUID().toLowerCase();\nDexie.Observable.on('latestRevisionIncremented', (dbName, rev) => {dbName.toLowerCase(); rev.toFixed()});\nDexie.Observable.on('latestRevisionIncremented').subscribe(()=>{});\nDexie.Observable.on('latestRevisionIncremented').fire(()=>{});\nDexie.Observable.on('latestRevisionIncremented').unsubscribe(()=>{});\n\nDexie.Observable.on('suicideNurseCall', (dbName, nodeId)=>{dbName.toLowerCase(); nodeId.toExponential()});\nDexie.Observable.on('intercomm', dbName=>{dbName.toLowerCase()});\nDexie.Observable.on('beforeunload', ()=>{});\n\nDexie.Observable.on('latestRevisionIncremented').unsubscribe(()=>{});\nvar x: IDatabaseChange = {key: 1, table: \"\", type: DatabaseChangeType.Delete, oldObj: {}};\nx.key;\nx.type;\nx.oldObj;\n"
  },
  {
    "path": "addons/Dexie.Observable/test/typings/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"es6\",\n        \"target\": \"es5\",\n        \"noImplicitAny\": true,\n        \"strictNullChecks\": true,\n        \"outDir\": \"../../tools/tmp/test-typings\",\n        \"moduleResolution\": \"node\",\n        \"lib\": [\"dom\", \"es2020\"]\n    },\n    \"files\": [\n        \"test-typings.ts\"\n    ]\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/.eslintrc.json",
    "content": "{\n  \"parserOptions\": {\n    \"ecmaVersion\": 6,\n    \"sourceType\": \"module\",\n    \"ecmaFeatures\": {\n    }\n  },\n  \"env\": {\n    \"browser\": true,\n    \"node\": true\n  },\n  \"rules\": {\n    \"no-undef\": [\"error\"]\n  },\n  \"globals\": {\n    \"Promise\": true\n  }\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/.gitignore",
    "content": "/bundle.js\n/bundle.js.map\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/deep-equal.js",
    "content": "import {equal} from 'QUnit';\n\n// Must use this rather than QUnit's deepEqual() because that one fails on Safari when run via karma-browserstack-launcher\nexport function deepEqual(a, b, description) {\n  if (typeof a === 'object' && typeof b === 'object' && a != null && b != null) {\n    equal(JSON.stringify(sortMembers(a), null, 2), JSON.stringify(sortMembers(b), null, 2), description);\n  } else {\n    equal(JSON.stringify(a, null, 2), JSON.stringify(b, null, 2), description);\n  }\n}\n\n/**\n * \n * @param {object} obj \n */\nfunction sortMembers (obj) {\n  return Object.keys(obj).sort().reduce((result, key) => {\n    result[key] = obj[key];\n    return result;\n  }, {});\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/hooks/tests-creating.js",
    "content": "import Dexie from 'dexie';\nimport {module, asyncTest, start, stop, strictEqual, ok} from 'QUnit';\nimport {deepEqual} from '../deep-equal';\nimport {resetDatabase} from '../../../../../test/dexie-unittest-utils';\nimport initCreatingHook from '../../../src/hooks/creating';\nimport initWakeupObservers from '../../../src/wakeup-observers';\nimport initOverrideCreateTransaction from '../../../src/override-create-transaction';\nimport {CREATE} from '../../../src/change_types';\n\nconst db = new Dexie('TestDBTable', {addons: []});\ndb.version(1).stores({\n  foo: \"id\",\n  bar: \"$$id\",\n  baz: \"++id\",\n  _changes: \"++rev\"\n});\n\nconst wakeupObservers = initWakeupObservers(db, {latestRevision: {}}, self.localStorage);\nconst overrideCreateTransaction = initOverrideCreateTransaction(db, wakeupObservers);\ndb._createTransaction = Dexie.override(db._createTransaction, overrideCreateTransaction);\n\nmodule('creating Hook', {\n  setup: () => {\n    stop();\n    // Do a full DB reset to clean _changes table\n    db._hasBeenCreated = false;\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should create a UUID key if $$ was given', () => {\n  db.bar.schema.primKey.uuid = true;\n  db.bar.schema.observable = true;\n  const creatingHook = initCreatingHook(db, db.bar);\n\n  db.bar.hook('creating', function(primKey, obj, trans) {\n    const ctx = {onsuccess: null, onerror: null};\n    const res = creatingHook.call(ctx, primKey, obj, trans);\n    // Note that this regex is not spec compliant but should be good enough for this test\n    const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n    ok(regex.test(res), 'We got a UUID');\n\n    this.onsuccess = function(resKey) {\n      try {\n        strictEqual(res, resKey, 'Key from success and hook should match');\n        db._changes.toArray((changes) => {\n          strictEqual(changes.length, 1, 'We have one change');\n          strictEqual(changes[0].key, resKey, 'Key should match');\n          strictEqual(changes[0].type, CREATE, 'CREATE type');\n          strictEqual(changes[0].table, 'bar', 'bar table');\n          // Normally $$ would be removed from the id in overrideParseStoresSpec\n          deepEqual(changes[0].obj, {foo: 'bar', $$id: resKey}, 'obj should match');\n          strictEqual(changes[0].source, null, 'We have no source');\n          ok(typeof trans._lastWrittenRevision !== 'undefined', '_lastWrittenRevision should be defined');\n        })\n        .catch((e) => {\n          ok(false, 'Error: ' + e);\n        })\n      } catch (e) {\n        ok(false, 'Error: ' + e);\n      }\n    };\n\n    this.onerror = function(e) {\n      ok(false, 'Error: ' + e);\n    }\n  });\n\n  db.bar.add({foo: 'bar'})\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n\nasyncTest('should not create a key if one was given', () => {\n  db.foo.schema.observable = true;\n  const creatingHook = initCreatingHook(db, db.foo);\n  const ID = 1;\n  const source = 10;\n\n  db.foo.hook('creating', function(primKey, obj, trans) {\n    trans.source = source;\n    const ctx = {onsuccess: null, onerror: null};\n    const res = creatingHook.call(ctx, primKey, obj, trans);\n    strictEqual(res, undefined, 'ID was given return undefined');\n\n    this.onsuccess = function(resKey) {\n      try {\n        strictEqual(resKey, ID, 'We got the given ID');\n        db._changes.toArray((changes) => {\n          strictEqual(changes.length, 1, 'We have one change');\n          strictEqual(changes[0].key, ID, 'Key should match');\n          strictEqual(changes[0].type, CREATE, 'CREATE type');\n          strictEqual(changes[0].table, 'foo', 'foo table');\n          deepEqual(changes[0].obj, {foo: 'bar', id: ID}, 'obj should match');\n          strictEqual(changes[0].source, source, 'We have source');\n          ok(typeof trans._lastWrittenRevision !== 'undefined', '_lastWrittenRevision should be defined');\n        })\n            .catch((e) => {\n              ok(false, 'Error: ' + e);\n            })\n      } catch (e) {\n        ok(false, 'Error: ' + e);\n      }\n    };\n\n    this.onerror = function(e) {\n      ok(false, 'Error: ' + e);\n    }\n  });\n\n  db.foo.add({id: ID, foo: 'bar'})\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n\nasyncTest('should use the auto-incremented key if ++ was given', () => {\n  db.baz.schema.observable = true;\n  const creatingHook = initCreatingHook(db, db.baz);\n  const ID = 1;\n\n  db.baz.hook('creating', function(primKey, obj, trans) {\n    const ctx = {onsuccess: null, onerror: null};\n    const res = creatingHook.call(ctx, primKey, obj, trans);\n    strictEqual(res, undefined, 'ID was auto-increment return undefined');\n\n    this.onsuccess = function(resKey) {\n      try {\n        strictEqual(resKey, ID, 'We got the given ID');\n        db._changes.toArray((changes) => {\n          strictEqual(changes.length, 1, 'We have one change');\n          strictEqual(changes[0].key, null, 'Key should be null');\n          strictEqual(changes[0].type, CREATE, 'CREATE type');\n          strictEqual(changes[0].table, 'baz', 'baz table');\n          deepEqual(changes[0].obj, {foo: 'bar'}, 'obj should match');\n          strictEqual(changes[0].source, null, 'We have no source');\n          ok(typeof trans._lastWrittenRevision !== 'undefined', '_lastWrittenRevision should be defined');\n        })\n            .catch((e) => {\n              ok(false, 'Error: ' + e);\n            })\n      } catch (e) {\n        ok(false, 'Error: ' + e);\n      }\n    };\n\n    this.onerror = function(e) {\n      ok(false, 'Error: ' + e);\n    }\n  });\n\n  db.baz.add({foo: 'bar'})\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/hooks/tests-deleting.js",
    "content": "import Dexie from 'dexie';\nimport {module, asyncTest, start, stop, strictEqual, ok} from 'QUnit';\nimport {deepEqual} from '../deep-equal';\nimport {resetDatabase} from '../../../../../test/dexie-unittest-utils';\nimport initDeletingHook from '../../../src/hooks/deleting';\nimport initWakeupObservers from '../../../src/wakeup-observers';\nimport initOverrideCreateTransaction from '../../../src/override-create-transaction';\nimport {DELETE} from '../../../src/change_types';\n\nconst db = new Dexie('TestDBTable', {addons: []});\ndb.version(1).stores({\n  foo: \"id\",\n  _changes: \"++rev\"\n});\n\nconst wakeupObservers = initWakeupObservers(db, {latestRevision: {}}, self.localStorage);\nconst overrideCreateTransaction = initOverrideCreateTransaction(db, wakeupObservers);\ndb._createTransaction = Dexie.override(db._createTransaction, overrideCreateTransaction);\n\nmodule('deleting Hook', {\n  setup: () => {\n    stop();\n    // Do a full DB reset to clean _changes table\n    db._hasBeenCreated = false;\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should create a DELETE change', () => {\n  db.foo.schema.observable = true;\n  const deletingHook = initDeletingHook(db, db.foo.name);\n  const ID = 10;\n  const source = 10;\n\n  db.foo.hook('deleting', function(primKey, obj, trans) {\n    trans.source = 10;\n    const ctx = {onsuccess: null, onerror: null};\n    deletingHook.call(ctx, primKey, obj, trans);\n\n    this.onsuccess = function() {\n      try {\n        db._changes.toArray((changes) => {\n          strictEqual(changes.length, 1, 'We have one change');\n          strictEqual(changes[0].key, ID, 'Key should match');\n          strictEqual(changes[0].type, DELETE, 'DELETE type');\n          strictEqual(changes[0].table, 'foo', 'foo table');\n          deepEqual(changes[0].oldObj, {foo: 'bar', id: ID}, 'oldObj should match');\n          strictEqual(changes[0].source, source, 'We have source');\n          ok(typeof trans._lastWrittenRevision !== 'undefined', '_lastWrittenRevision should be defined');\n        })\n            .catch((e) => {\n              ok(false, 'Error: ' + e);\n            })\n      } catch (e) {\n        ok(false, 'Error: ' + e);\n      }\n    };\n\n    this.onerror = function(e) {\n      ok(false, 'Error: ' + e);\n    }\n  });\n\n  db.foo.add({id: ID, foo: 'bar'})\n      .then(() => {\n        return db.foo.delete(ID)\n      })\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/hooks/tests-updating.js",
    "content": "import Dexie from 'dexie';\nimport {module, asyncTest, start, stop, strictEqual, ok} from 'QUnit';\nimport {deepEqual} from '../deep-equal';\nimport {resetDatabase} from '../../../../../test/dexie-unittest-utils';\nimport initUpdatingHook from '../../../src/hooks/updating';\nimport initWakeupObservers from '../../../src/wakeup-observers';\nimport initOverrideCreateTransaction from '../../../src/override-create-transaction';\nimport {UPDATE} from '../../../src/change_types';\n\nconst db = new Dexie('TestDBTable', {addons: []});\ndb.version(1).stores({\n  foo: \"id\",\n  _changes: \"++rev\"\n});\n\nconst wakeupObservers = initWakeupObservers(db, {latestRevision: {}}, self.localStorage);\nconst overrideCreateTransaction = initOverrideCreateTransaction(db, wakeupObservers);\ndb._createTransaction = Dexie.override(db._createTransaction, overrideCreateTransaction);\n\nmodule('updating Hook', {\n  setup: () => {\n    stop();\n    // Do a full DB reset to clean _changes table\n    db._hasBeenCreated = false;\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should create an UPDATE change', () => {\n  db.foo.schema.observable = true;\n  const updatingHook = initUpdatingHook(db, db.foo.name);\n  const ID = 1;\n  const source = 10;\n\n  db.foo.hook('updating', function hook(mods, primKey, obj, trans) {\n    // Remove this hook now otherwise other tests might call it\n    db.foo.hook('updating').unsubscribe(hook);\n\n    trans.source = source;\n    const ctx = {onsuccess: null, onerror: null};\n    updatingHook.call(ctx, mods, primKey, obj, trans);\n\n    this.onsuccess = function() {\n      try {\n        db._changes.toArray((changes) => {\n          strictEqual(changes.length, 1, 'We have one change');\n          strictEqual(changes[0].key, ID, 'Key should match');\n          strictEqual(changes[0].type, UPDATE, 'UPDATE type');\n          strictEqual(changes[0].table, 'foo', 'foo table');\n          deepEqual(changes[0].obj, {foo: 'baz', id: ID}, 'obj should match');\n          strictEqual(changes[0].source, source, 'We have source');\n          ok(typeof trans._lastWrittenRevision !== 'undefined', '_lastWrittenRevision should be defined');\n        })\n            .catch((e) => {\n              ok(false, 'Error: ' + e);\n            })\n      } catch (e) {\n        ok(false, 'Error: ' + e);\n      }\n    };\n\n    this.onerror = function(e) {\n      ok(false, 'Error: ' + e);\n    }\n  });\n\n  db.foo.add({id: ID, foo: 'bar'})\n      .then(() => {\n        return db.foo.update(ID, {foo: 'baz'});\n      })\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n\nasyncTest('should not create an UPDATE change if the mods are already in the old object', () => {\n  db.foo.schema.observable = true;\n  const updatingHook = initUpdatingHook(db, db.foo.name);\n  const ID = 2;\n  const source = 10;\n\n  db.foo.hook('updating', function hook(mods, primKey, obj, trans) {\n    // Remove this hook now otherwise other tests might call it\n    db.foo.hook('updating').unsubscribe(hook);\n\n    trans.source = source;\n    const ctx = {onsuccess: null, onerror: null};\n    updatingHook.call(ctx, mods, primKey, obj, trans);\n\n    this.onsuccess = function() {\n      try {\n        db._changes.toArray((changes) => {\n          strictEqual(changes.length, 0, 'We have no change');\n        })\n            .catch((e) => {\n              ok(false, 'Error: ' + e);\n            })\n      } catch (e) {\n        ok(false, 'Error: ' + e);\n      }\n    };\n\n    this.onerror = function(e) {\n      ok(false, 'Error: ' + e);\n    }\n  });\n\n  db.foo.add({id: ID, foo: 'bar'})\n      .then(() => {\n        return db.foo.update(ID, {foo: 'bar'});\n      }).then(()=>{\n        return db._changes.toArray((changes) => {\n          strictEqual(changes.length, 0, 'We have no change');\n        });\n      })\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/karma.conf.js",
    "content": "// Include common configuration\nconst {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../../../test/karma.common');\n\nmodule.exports = function (config) {\n  const browserMatrixOverrides = {\n    // Be fine with testing on local travis firefox + browserstack chrome, latest supported.\n    ci: [\"remote_chrome\"],\n    // Safari fails to reply on browserstack. Need to not have it here.\n    // Just complement with old chrome browser that is not part of CI test suite.\n    pre_npm_publish: [\n      \"Chrome\",\n    ]\n  };\n\n  const cfg = getKarmaConfig(browserMatrixOverrides, {\n    // Base path should point at the root \n    basePath: '../../../../',\n    files: karmaCommon.files.concat([\n      'dist/dexie.js',\n      'addons/Dexie.Observable/dist/dexie-observable.js',\n      'addons/Dexie.Observable/test/unit/bundle.js',\n      { pattern: 'addons/Dexie.Observable/test/unit/*.map', watched: false, included: false },\n      { pattern: 'addons/Dexie.Observable/dist/*.map', watched: false, included: false }\n    ])\n  });\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/run-unit-tests.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Dexie.Observable Unit tests</title>\n  <link rel=\"stylesheet\" href=\"../../../../node_modules/qunitjs/qunit/qunit.css\">\n</head>\n<body>\n    <div id=\"qunit\"></div>\n    <div id=\"qunit-fixture\"></div>\n    <script src=\"../../../../test/babel-polyfill/polyfill.min.js\"></script>\n    <script src=\"../../../../node_modules/qunitjs/qunit/qunit.js\"></script>\n    <script src=\"../../../../dist/dexie.js\"></script>\n    <script src=\"../../dist/dexie-observable.js\"></script>\n    <script src=\"bundle.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/tests-observable-misc.js",
    "content": "﻿import {module, asyncTest, equal, strictEqual, ok, start} from 'QUnit';\nimport {deepEqual} from './deep-equal';\nimport Dexie from 'dexie';\nimport 'dexie-observable';\n\nmodule(\"tests-observable-misc\", {\n  setup: function () {\n    stop();\n    Dexie.delete(\"ObservableTest\").then(function () {\n      start();\n    }).catch(function (e) {\n      ok(false, \"Could not delete database\");\n    });\n  },\n  teardown: function () {\n    stop();\n    Dexie.delete(\"ObservableTest\").then(start);\n  }\n});\n\nfunction createDB() {\n  var db = new Dexie(\"ObservableTest\");\n  db.version(1).stores({\n    friends: \"++id,name,shoeSize\",\n    CapitalIdTest: \"$$Id,name\"\n    //pets: \"++id,name,kind\",\n    //$emailWords: \"\",\n  });\n  return db;\n}\n\n// Basically check if Dexie.Observable adds a 'changes' event\n// Test works because of the second parameter to asyncTest which expects 4 assertions\nasyncTest(\"changes in on DB instance should trigger a change event in an other instance\", 4, function () {\n  var db1 = createDB();\n  var db2 = createDB();\n\n  db2.on('changes', function (changes, partitial) {\n    changes.forEach(function (change) {\n      switch (change.type) {\n        case 1:\n          ok(true, \"obj created: \" + JSON.stringify(change.obj));\n          break;\n        case 2:\n          ok(true, \"obj updated: \" + JSON.stringify(change.mods));\n          equal(JSON.stringify(change.mods), JSON.stringify({name: \"David\"}), \"Only modifying the name property\");\n          break;\n        case 3:\n          ok(true, \"obj deleted: \" + JSON.stringify(change.oldObj));\n          db1.close();\n          db2.close();\n          start();\n          break;\n      }\n    });\n  });\n\n  db1.open();\n  db2.open();\n\n  db1.friends.put({name: \"Dave\", shoeSize: 43}).then(function (id) {\n    // Update object:\n    return db1.friends.put({id: id, name: \"David\", shoeSize: 43});\n  }).then(function (id) {\n    // Delete object:\n    return db1.friends.delete(id);\n  }).catch(function (e) {\n    ok(false, \"Error: \" + e.stack || e);\n    start();\n  });\n});\n\n// Test UUID primary key\nasyncTest(\"Capital$$Id-test\", function () {\n  var db = createDB();\n  db.open();\n  db.CapitalIdTest.put({name: \"Hilda\"}).then(function () {\n    return db.CapitalIdTest.toCollection().first();\n  }).then(function (firstItem) {\n    ok(firstItem.name == \"Hilda\", \"Got first item\");\n    ok(firstItem.Id, \"First item has a primary key set: \" + firstItem.Id);\n    // Note that this regex is not spec compliant but should be good enough for this test\n    const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n    ok(regex.test(firstItem.Id), 'We got a UUID');\n  }).catch(function (e) {\n    ok(false, \"Error: \" + e);\n  }).finally(function () {\n    db.close();\n    start();\n  });\n});\n\n//\n// Test intercomm in same window\n//\nasyncTest('should receive a message from the first sync node', 4, () => {\n  const db1 = createDB();\n  const db2 = createDB();\n  let senderID;\n  let receiverID;\n  db2.on('message', (msg) => {\n    strictEqual(msg.message, 'foobar', 'We got the correct message');\n    strictEqual(msg.sender, senderID, 'We got the right sender ID');\n    strictEqual(msg.type, 'request', 'We got the correct type');\n    strictEqual(msg.destinationNode, receiverID, 'We got the correct destination node');\n    start();\n  });\n\n  db1.on('message', () => {\n    ok(false, 'We should not receive a message');\n  });\n\n  db1._syncNodes.toArray()\n      .then((arr) => {\n        senderID = arr[0].id;\n        return db2._syncNodes.toArray();\n      })\n      .then((arr) => {\n        receiverID = arr.filter((node) => node.id !== senderID)[0].id;\n        db1.observable.sendMessage('request', 'foobar', receiverID, {});\n      })\n      .catch((e) => {\n        ok(false, 'Error: ' + e);\n      });\n});\n\nasyncTest('master node should receive the message if the destination is not present and we want a reply', 5, () => {\n  const db1 = createDB();\n  let senderID;\n  db1.on('message', (msg) => {\n    deepEqual(msg.message, {foo: 'foobar'}, 'We got the correct message');\n    strictEqual(msg.sender, senderID, 'We got the right sender ID');\n    strictEqual(msg.type, 'request', 'We got the correct type');\n    strictEqual(msg.destinationNode, senderID, 'We got the correct destination node');\n    start();\n  });\n\n  db1._syncNodes.toArray()\n      .then((arr) => {\n        senderID = arr[0].id;\n        strictEqual(arr[0].isMaster, 1, 'We are master');\n        db1.observable.sendMessage('request', {foo: 'foobar'}, 10, {wantReply: true});\n      })\n      .catch((e) => {\n        ok(false, 'Error: ' + e);\n      });\n});\n\nasyncTest('sender should react on successful reply', 1, () => {\n  const db1 = createDB();\n  const db2 = createDB();\n  let senderID;\n  let receiverID;\n  db2.on('message', (msg) => {\n    msg.resolve('reply msg');\n  });\n\n  db1._syncNodes.toArray()\n      .then((arr) => {\n        senderID = arr[0].id;\n        return db2._syncNodes.toArray();\n      })\n      .then((arr) => {\n        receiverID = arr.filter((node) => node.id !== senderID)[0].id;\n        return db1.observable.sendMessage('request', {foo: 'foobar'}, receiverID, {wantReply: true});\n      })\n      .then((result) => {\n        strictEqual(result, 'reply msg', 'We got the correct result msg');\n      })\n      .catch((e) => {\n        ok(false, 'Error: ' + e);\n      })\n      .finally(start);\n});\n\nasyncTest('sender should react on failure reply', 1, () => {\n  const db1 = createDB();\n  const db2 = createDB();\n  let senderID;\n  let receiverID;\n  db2.on('message', (msg) => {\n    msg.reject('error msg');\n  });\n\n  db1._syncNodes.toArray()\n      .then((arr) => {\n        senderID = arr[0].id;\n        return db2._syncNodes.toArray();\n      })\n      .then((arr) => {\n        receiverID = arr.filter((node) => node.id !== senderID)[0].id;\n        return db1.observable.sendMessage('request', {foo: 'foobar'}, receiverID, {wantReply: true});\n      })\n      .catch((msg) => {\n        strictEqual(msg, 'error msg');\n      })\n      .finally(start);\n});\n\nasyncTest('sync nodes should react on broadcast', 4, () => {\n  const db1 = createDB();\n  const db2 = createDB();\n  let senderID;\n  let receiverID;\n  db2.on('message', (msg) => {\n    deepEqual(msg.message, {foo: 'foobar'}, 'We got the correct message');\n    strictEqual(msg.sender, senderID, 'We got the right sender ID');\n    strictEqual(msg.type, 'request', 'We got the correct type');\n    strictEqual(msg.destinationNode, receiverID, 'We got the correct destination node');\n    start();\n  });\n\n  db1.on('message', () => {\n    ok(false, 'We should not receive a message');\n  });\n\n  db1._syncNodes.toArray()\n      .then((arr) => {\n        senderID = arr[0].id;\n        return db2._syncNodes.toArray();\n      })\n      .then((arr) => {\n        receiverID = arr.filter((node) => node.id !== senderID)[0].id;\n        db1.observable.broadcastMessage('request', {foo: 'foobar'});\n      })\n      .catch((e) => {\n        ok(false, 'Error: ' + e);\n      });\n});\n\nasyncTest('sender should be able to react on broadcast if bIncludeSelf is true', 4, () => {\n  const db1 = createDB();\n  const db2 = createDB();\n  let senderID;\n  let receiverID;\n\n  db1.on('message', (msg) => {\n      deepEqual(msg.message, {foo: 'foobar'}, 'We got the correct message');\n      strictEqual(msg.sender, senderID, 'We got the right sender ID');\n      strictEqual(msg.type, 'broadcast', 'We got the correct type');\n      strictEqual(msg.destinationNode, senderID, 'We got the correct destination node');\n      start();\n  });\n\n  db1._syncNodes.toArray()\n      .then((arr) => {\n        senderID = arr[0].id;\n        return db2._syncNodes.toArray();\n      })\n      .then((arr) => {\n        receiverID = arr.filter((node) => node.id !== senderID)[0].id;\n        const bIncludeSelf = true;\n        db1.observable.broadcastMessage('broadcast', {foo: 'foobar'}, bIncludeSelf);\n      })\n      .catch((e) => {\n        ok(false, 'Error: ' + e);\n      });\n});\n\n// This tests relates to https://github.com/dexie/Dexie.js/issues/429#issuecomment-269793599\n// If db2 receives its message multiple times qunit will error as start() is called multiple times\nasyncTest('no matter how many times sendMessage is called a receiver should receive its message only once', 4, () => {\n  const db1 = createDB();\n  const db2 = createDB();\n  let senderID;\n  let receiverID;\n  db2.on('message', (msg) => {\n    deepEqual(msg.message, {foo: 'foobar'}, 'We got the correct message');\n    strictEqual(msg.sender, senderID, 'We got the right sender ID');\n    strictEqual(msg.type, 'request', 'We got the correct type');\n    strictEqual(msg.destinationNode, receiverID, 'We got the correct destination node');\n    start();\n  });\n\n  db1._syncNodes.toArray()\n      .then((arr) => {\n        senderID = arr[0].id;\n        return db2._syncNodes.toArray();\n      })\n      .then((arr) => {\n        receiverID = arr.filter((node) => node.id !== senderID)[0].id;\n        db1.observable.sendMessage('request', {foo: 'foobar'}, receiverID, {});\n        // Send messages for receivers that don't exist\n        db1.observable.sendMessage('request', {foo: 'foobar'}, 'foobar', {});\n        db1.observable.sendMessage('request', {foo: 'foobar'}, 'barbaz', {});\n      })\n      .catch((e) => {\n        ok(false, 'Error: ' + e);\n      });\n});\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/tests-override-open.js",
    "content": "import {module, test, strictEqual, deepEqual, ok} from 'QUnit';\nimport initOverrideOpen from '../../src/override-open';\n\nmodule('override-open', {\n  setup: () => {\n  },\n  teardown: () => {\n  }\n});\n\ntest('should call the given original function', () => {\n  let wasCalled = false;\n  function origFn() {\n    wasCalled = true;\n  }\n\n  const db = {\n    _allTables: {}\n  };\n\n  initOverrideOpen(db, function SyncNode() {}, function crudMonitor() {})(origFn)();\n  ok(wasCalled);\n});\n\ntest('should call the crudMonitor function for every observable table', () => {\n  const tables = [];\n  function crudMonitor(table) {\n    tables.push(table);\n  }\n\n  const db = {\n    _allTables: {\n      foo: {\n        // TableSchema: for more info see https://github.com/dfahlander/Dexie.js/wiki/TableSchema\n        schema: {\n          observable: true\n        }\n      },\n      _bar: {\n        schema: {}\n      }\n    }\n  };\n\n  initOverrideOpen(db, function SyncNode() {}, crudMonitor)(() => {})();\n  deepEqual(tables, [db._allTables.foo]);\n});\n\ntest('should call mapToClass for the _syncNodes table', () => {\n  function SyncNode() {}\n  let calledWithClass;\n  const db = {\n    _allTables: {\n      _syncNodes: {\n        name: '_syncNodes',\n        mapToClass(cls) {\n          calledWithClass = cls;\n        },\n        schema: {}\n      }\n    }\n  };\n\n  initOverrideOpen(db, SyncNode, function crudMonitor(){})(() => {})();\n  strictEqual(calledWithClass, SyncNode);\n});\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/tests-override-parse-stores-spec.js",
    "content": "import {module, test, strictEqual, deepEqual, ok} from 'QUnit';\nimport overrideParseStoresSpec from '../../src/override-parse-stores-spec';\n\nmodule('override-parse-stores', {\n  setup: () => {\n  },\n  teardown: () => {\n  }\n});\n\ntest('should create stores for Dexie.Observable and Dexie.Syncable', () => {\n function origFunc(stores/*, dbSchema*/) {\n   ok(typeof stores._changes === 'string', 'Should have _changes store');\n   ok(typeof stores._syncNodes === 'string', 'Should have _syncNodes store');\n   ok(typeof stores._intercomm === 'string', 'Should have _intercomm store');\n   ok(typeof stores._uncommittedChanges === 'string', 'Should have _uncommittedChanges store');\n }\n\n  const stores = {};\n  const dbSchema = {};\n  overrideParseStoresSpec(origFunc)(stores, dbSchema);\n});\n\ntest('should add UUID keys to the schema', () => {\n function origFunc(){}\n\n const stores = {\n   foo: '$$id',\n };\n const dbSchema = {\n   // TableSchema: for more info see https://github.com/dfahlander/Dexie.js/wiki/TableSchema\n   foo: {\n     name: 'foo',\n     // IndexSpec: for more info see https://github.com/dfahlander/Dexie.js/wiki/IndexSpec\n     primKey: {\n       name: '$$id',\n       keyPath: '$$id'\n     }\n   }\n };\n overrideParseStoresSpec(origFunc)(stores, dbSchema);\n\n  strictEqual(dbSchema.foo.primKey.name, 'id', 'Should remove $$ from the name');\n  strictEqual(dbSchema.foo.primKey.keyPath, 'id', 'Should remove $$ from keyPath');\n  strictEqual(dbSchema.foo.primKey.uuid, true, 'uuid should be set to true');\n});\n\ntest('should observe tables without _ and $', () => {\n  function origFunc(){}\n\n  const stores = {\n    foo: 'id',\n    _foo: 'id',\n    $bar: 'id'\n  };\n  const dbSchema = {\n    foo: {primKey: {name: 'id'}},\n    _foo: {primKey: {name: 'id'}},\n    $bar: {primKey: {name: 'id'}}\n  };\n  overrideParseStoresSpec(origFunc)(stores, dbSchema);\n\n  strictEqual(dbSchema.foo.observable, true, 'foo should be observed');\n  strictEqual(dbSchema._foo.observable, undefined, '_foo should not be observed');\n  strictEqual(dbSchema.$bar.observable, undefined, '$bar should not be observed');\n});\n"
  },
  {
    "path": "addons/Dexie.Observable/test/unit/unit-tests-all.js",
    "content": "import './hooks/tests-creating.js';\nimport './hooks/tests-deleting.js';\nimport './hooks/tests-updating.js';\nimport './tests-override-open.js';\nimport './tests-override-parse-stores-spec.js';\nimport './tests-observable-misc';\n"
  },
  {
    "path": "addons/Dexie.Observable/tools/build-configs/banner.txt",
    "content": "/* ========================================================================== \n *                           dexie-observable.js\n * ==========================================================================\n *\n * Dexie addon for observing database changes not just on local db instance\n * but also on other instances, tabs and windows.\n *\n * Comprises a base framework for dexie-syncable.js\n *\n * By David Fahlander, david.fahlander@gmail.com,\n *    Nikolas Poniros, https://github.com/nponiros\n *\n * ==========================================================================\n *\n * Version {version}, {date}\n *\n * https://dexie.org\n *\n * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/\n * \n */\n"
  },
  {
    "path": "addons/Dexie.Observable/tools/build-configs/rollup.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport {readFileSync} from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst packageJson = JSON.parse(readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));\nconst version = packageJson.version;\n\nexport default {\n  input: 'tools/tmp/es5/src/Dexie.Observable.js',\n  output: [{\n    file: 'dist/dexie-observable.js',\n    format: 'umd',\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n    globals: {dexie: \"Dexie\"},\n    name: 'Dexie.Observable',\n    sourcemap: true\n  },{\n    file: 'dist/dexie-observable.es.js',\n    format: 'es',\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n    globals: {dexie: \"Dexie\"},\n    name: 'Dexie.Observable',\n    sourcemap: true\n  }],\n  external: ['dexie'],\n  plugins: [ sourcemaps() ]\n};\n"
  },
  {
    "path": "addons/Dexie.Observable/tools/build-configs/rollup.tests.config.js",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\"\n];\n\nexport default {\n  input: 'tools/tmp/es5/test/addons/Dexie.Observable/test/unit/unit-tests-all.js',\n  output: {\n    file: 'test/unit/bundle.js',\n    format: 'umd',\n    globals: {dexie: \"Dexie\", \"dexie-observable\": \"Dexie.Observable\", QUnit: \"QUnit\"},\n    sourcemap: true,\n    name: 'dexieTests'\n  },\n  external: ['dexie', 'dexie-observable', 'QUnit'],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true}),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/Dexie.Observable/tools/build-configs/rollup.tests.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\"\n];\n\nexport default {\n  input: 'tools/tmp/es5/test/addons/Dexie.Observable/test/unit/unit-tests-all.js',\n  output: {\n    file: 'test/unit/bundle.js',\n    format: 'umd',\n    globals: {dexie: \"Dexie\", \"dexie-observable\": \"Dexie.Observable\", QUnit: \"QUnit\"},\n    sourcemap: true,\n    name: 'dexieTests'\n  },\n  external: ['dexie', 'dexie-observable', 'QUnit'],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true}),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/Dexie.Observable/tools/build-configs/rollup.tests.unit.config.js",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\"\n];\n\nexport default {\n  input: 'tools/tmp/es5/test/addons/Dexie.Observable/test/unit/unit-tests-all.js',\n  output: {\n    file: 'test/unit/bundle.js',\n    format: 'umd',\n    globals: {dexie: \"Dexie\", \"dexie-observable\": \"Dexie.Observable\", QUnit: \"QUnit\"},\n    sourcemap: true,\n    name: 'dexieTests'\n  },\n  external: ['dexie', 'dexie-observable', 'QUnit'],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true}),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/Dexie.Observable/tools/build-configs/rollup.tests.unit.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\"\n];\n\nexport default {\n  input: 'tools/tmp/es5/test/addons/Dexie.Observable/test/unit/unit-tests-all.js',\n  output: {\n    file: 'test/unit/bundle.js',\n    format: 'umd',\n    globals: {dexie: \"Dexie\", \"dexie-observable\": \"Dexie.Observable\", QUnit: \"QUnit\"},\n    sourcemap: true,\n    name: 'dexieTests'\n  },\n  external: ['dexie', 'dexie-observable', 'QUnit'],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true}),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/Dexie.Observable/tools/replaceVersionAndDate.js",
    "content": "const fs = require('fs');\nconst files = process.argv.slice(2);\nconst version = require('../package.json').version;\n\nfiles.forEach(file => {\n    let fileContent = fs.readFileSync(file, \"utf-8\");\n    fileContent = fileContent\n        .replace(/{version}/g, version)\n        .replace(/{date}/g, new Date().toDateString());\n    fs.writeFileSync(file, fileContent, \"utf-8\");\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/.gitignore",
    "content": "dist/*.js\ndist/*.map\ndist/*.ts\ndist/*.gz\n.eslintcache\n**/tmp/\n"
  },
  {
    "path": "addons/Dexie.Syncable/.npmignore",
    "content": "tools/\nsrc/\n.*\ntmp/\n**/tmp/\ntest\n*.log\n"
  },
  {
    "path": "addons/Dexie.Syncable/README.md",
    "content": "# Dexie.Syncable.js\n\nEnables two-way synchronization with remote database.\n\n*NOTE! This package has been unmaintained for a looong time and might be retired. Some people go under the impression that Dexie Cloud would be based on this package, but it's not - it has its own sync system based on middlewares - all source for it is in addons/dexie-cloud.*\n\n### Install\n```\nnpm install dexie --save\nnpm install dexie-observable --save\nnpm install dexie-syncable --save\n```\n\n### Use\n```js\nimport Dexie from 'dexie';\nimport 'dexie-syncable'; // will import dexie-observable as well.\n\n// Use Dexie as normally - but you can also register your sync protocols though\n// Dexie.Syncable.registerSyncProtocol() api as well as using the db.syncable api\n// as documented here.\n\n```\n\n### Dependency Tree\n\n * **Dexie.Syncable.js**\n   * [Dexie.Observable.js](https://dexie.org/docs/Observable/Dexie.Observable.js)\n     * [Dexie.js](https://dexie.org/docs/Dexie/Dexie.js)\n       * [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)\n   * _An implementation of [ISyncProtocol](https://dexie.org/docs/Syncable/Dexie.Syncable.ISyncProtocol)_\n\n### Tutorial\n\n#### 1. Include Required Sources\nIn your HTML, make sure to include Dexie.js, Dexie.Observable.js, Dexie.Syncable.js and an implementation of [ISyncProtocol](https://dexie.org/docs/Syncable/Dexie.Syncable.ISyncProtocol).\n\n    <html><head>\n        <script src=\"dexie.min.js\"></script>\n        <script src=\"dexie-observable.min.js\"></script>\n        <script src=\"dexie-syncable.min.js\"></script>\n        <script src=\"WebSocketSyncProtocol.js\"></script> <!-- Example implementation of ISyncProtocol -->\n        ...\n    </head><body>\n    </body></html>\n    \n##### Usage with existing DB\n\nIn case you want to use Dexie.Syncable with your existing database, but do not want to use UUID based Primary Keys as described below, you will have to do a schema upgrade. Without it Dexie.Syncable will not be able to properly work.\n\n```javascript\nimport Dexie from 'dexie';\nimport 'dexie-observable';\nimport 'dexie-syncable';\nimport 'your-sync-protocol-implementation';\n\nvar db = new Dexie('myExistingDb');\ndb.version(1).stores(... existing schema ...);\n\n// Now, add another version, just to trigger an upgrade for Dexie.Syncable\ndb.version(2).stores({}); // No need to add / remove tables. This is just to allow the addon to install its tables.\n```\n\n#### 2. Use UUID based Primary Keys ($$)\nTwo way replication cannot use auto-incremented keys if any sync node should be able to create objects no matter if it is offline or online. Dexie.Syncable comes with a new syntax when defining your store schemas: the double-dollar prefix ($$). Similarly to the ++ prefix in Dexie (meaning auto-incremented primary key), the double-dollar prefix means that the key will be given a universally unique identifier (UUID), in string format (For example \"9cc6768c-358b-4d21-ac4d-58cc0fddd2d6\").\n\n    var db = new Dexie(\"MySyncedDB\");\n    db.version(1).stores({\n        friends: \"$$oid,name,shoeSize\",\n        pets: \"$$oid,name,kind\"\n    });\n\n#### 3. Connect to Server\nYou must specify the URL of the server you want to keep in-sync with. This has to be done once in the entire database life-time, but doing it on every startup is ok as well, since it won't affect already connected URLs.\n\n    // This example uses the WebSocketSyncProtocol included in earlier steps.\n    db.syncable.connect (\"websocket\", \"https://syncserver.com/sync\");\n    db.syncable.on('statusChanged', function (newStatus, url) {\n        console.log (\"Sync Status changed: \" + Dexie.Syncable.StatusTexts[newStatus]);\n    });\n\n#### 4. Use Your Database\nQuery and modify your database as if it was a simple Dexie instance. Any changes will be replicated to the server and changes on the server or an other window will replicate back to you.\n\n    db.transaction('rw', db.friends, function (friends) {\n        friends.add({name: \"Arne\", shoeSize: 47});\n        friends.where('shoeSize').above(40).each(function (friend) {\n            console.log(\"Friend with shoeSize over 40: \" + friend.name);\n        });\n    });\n\n_NOTE: Transactions only provide the Atomicity part of the [ACID](http://en.wikipedia.org/wiki/ACID) properties when using 2-way synchronization. This is due to the fact that the syncronization phase may result in another change overwriting the changes. However, it's still meaningful to use the transaction() method for atomicity. Atomicity is guaranteed not only locally but also when synced to the server, meaning that a part of the changes will never commit on the server until all changes from the transaction have been synced. In practice, you cannot increment a counter in the database (for example) and expect it to be consistent, but you can have a guaranteed that if you add a sequence of objects, all or none of them will replicate._\n\n### API Reference\n\n#### Static Members\n\n[Dexie.Syncable.registerSyncProtocol (name, protocolInstance)](https://dexie.org/docs/Syncable/Dexie.Syncable.registerSyncProtocol())\nDefine how to replicate changes with your type of server.\n\n[Dexie.Syncable.Statuses](https://dexie.org/docs/Syncable/Dexie.Syncable.Statuses)\nEnum of possible sync statuses, such as OFFLINE, CONNECTING, ONLINE and ERROR.\n\n[Dexie.Syncable.StatusTexts](https://dexie.org/docs/Syncable/Dexie.Syncable.StatusTexts)\nText lookup for status numbers\n\n#### Non-Static Methods and Events\n\n[db.syncable.connect (protocol, url, options)](https://dexie.org/docs/Syncable/db.syncable.connect())\nCreate a persistend two-way sync connection with the given URL.\n\n[db.syncable.disconnect (url)](https://dexie.org/docs/Syncable/db.syncable.disconnect())\nStop syncing with the given URL but keep revision states until next connect.\n\n[db.syncable.delete(url)](https://dexie.org/docs/Syncable/db.syncable.delete())\nDelete all states and change queue for given URL.\n\n[db.syncable.list()](https://dexie.org/docs/Syncable/db.syncable.list())\nList the URLs of each remote node we have a state saved for.\n\n[db.syncable.on('statusChanged')](https://dexie.org/docs/Syncable/db.syncable.on('statusChanged'))\nEvent triggered when sync status changes.\n\n[db.syncable.setFilter ([criteria], filter)](https://dexie.org/docs/Syncable/db.syncable.setFilter())\nIgnore certain objects from being synced defined by the given filter.\n\n[db.syncable.getStatus (url)](https://dexie.org/docs/Syncable/db.syncable.getStatus())\nGet sync status for the given URL.\n\n[db.syncable.getOptions (url)](https://dexie.org/docs/Syncable/db.syncable.getOptions())\nGet the options object for the given URL.\n\n\n### Source\n\n[Dexie.Syncable.js](https://github.com/dexie/Dexie.js/blob/master/addons/Dexie.Syncable/src/Dexie.Syncable.js)\n\n### Description\n\nDexie.Syncable enables synchronization with a remote database (of almost any kind). It has its own API [ISyncProtocol](https://dexie.org/docs/Syncable/Dexie.Syncable.ISyncProtocol).\nThe [ISyncProtocol](https://dexie.org/docs/Syncable/Dexie.Syncable.ISyncProtocol) is pretty straight-forward to implement.\nThe implementation of that API defines how client- and server- changes are transported between local and remote nodes. The API support both poll-patterns\n(such as ajax calls) and direct reaction pattern (such as WebSocket or long-polling methods). See samples below for each pattern.\n\n### Sample [ISyncProtocol](https://dexie.org/docs/Syncable/Dexie.Syncable.ISyncProtocol) Implementations\n * [https://github.com/nponiros/sync_client](https://github.com/nponiros/sync_client)\n * [AjaxSyncProtocol.js](https://github.com/dfahlander/Dexie.js/blob/master/samples/remote-sync/ajax/AjaxSyncProtocol.js)\n * [WebSocketSyncProtocol.js](https://github.com/dfahlander/Dexie.js/blob/master/samples/remote-sync/websocket/WebSocketSyncProtocol.js)\n\n### Sample Sync Servers\n * [https://github.com/nponiros/sync_server](https://github.com/nponiros/sync_server)\n * [WebSocketSyncServer.js](https://github.com/dfahlander/Dexie.js/blob/master/samples/remote-sync/websocket/WebSocketSyncServer.js)\n"
  },
  {
    "path": "addons/Dexie.Syncable/api.d.ts",
    "content": "﻿/* dexie-syncable API - an independant syncronization API used by 'dexie-syncable'.\n * Version: 1.1\n * Date: December 2016\n *\n * Some assumptions are made upon how the database is structured though. We assume that:\n *  * Databases has 1..N tables. (For NOSQL databases without tables, this also works since it could be considered a db with a single table.)\n *  * Each table has a primary key.\n *\t* The primary key is a UUID of some kind since auto-incremented primary keys are not suitable for syncronization\n *    (auto-incremented key would work but changes of conflicts would increase on create).\n *  * A database record is a JSON compatible object.\n *  * Always assume that the client may send the same set of changes twice. For example if client sent changes that server stored, but network went down before\n *    client got the ack from server, the client may try resending same set of changes again. This means that the same Object Create change may be sent twice etc.\n *    The implementation must not fail if trying to create an object with the same key twice, or delete an object with a key that does not exist.\n *  * Client and server must resolve conflicts in such way that the result on both sides are equal.\n *  * Since a server is the point of the most up-to-date database, conflicts should be resolved by prefering server changes over client changes.\n *    This makes it predestinable for client that the more often the client syncs, the more chance to prohibit conflicts.\n *\n * By separating module 'dexie-syncable' from 'dexie-syncable/api' we\n * distinguish that:\n *\n *   import {...} from 'dexie-syncable/api' is only for getting access to its\n *                                          interfaces and has no side-effects.\n *                                          Typescript-only import.\n *\n *   import 'dexie-syncable' is only for side effects - to extend Dexie with\n *                           functionality of dexie-syncable.\n *                           Javascript / Typescript import.\n *\n */\n\nimport {IDatabaseChange} from 'dexie-observable/api';\nexport {DatabaseChangeType} from 'dexie-observable/api';\n\n/* ISyncProtocol\n\n   Interface to implement for enabling syncronization with a remote database server. The remote database server may be SQL- or NOSQL based\n   as long as it is capable of storing JSON compliant objects into some kind of object stores and reference them by a primary key.\n   The server must also be revision- and changes aware. This is something that for many databases needs to be implemented by a REST- or\n   WebSocket gateway between the client and the backend database. The gateway can act as a controller and make sure any changes\n   are registered in the 'changes' table and that the API provides a sync() method to interchange changes between client and server.\n\n   Two examples of a ISyncProtocol instances are found in:\n       https://github.com/dfahlander/Dexie.js/tree/master/samples/remote-sync/ajax/AjaxSyncProtocol.js\n       https://github.com/dfahlander/Dexie.js/tree/master/samples/remote-sync/websocket/WebSocketSyncProtocol.js\n\n*/\n\n/**\n * The interface to implement to provide sync towards a remote server.\n *\n * Documentation for this interface: https://github.com/dfahlander/Dexie.js/wiki/Dexie.Syncable.ISyncProtocol\n *\n */\nexport interface ISyncProtocol {\n    partialsThreshold?: number;\n    sync (\n        context: IPersistedContext,\n        url: string,\n        options: any,\n        baseRevision: any,\n        syncedRevision: any,\n        changes: IDatabaseChange[],\n        partial: boolean,\n        applyRemoteChanges: ApplyRemoteChangesFunction,\n        onChangesAccepted: ()=>void,\n        onSuccess: (continuation: PollContinuation | ReactiveContinuation)=>void,\n        onError: (error: any, again?: number) => void) : void;\n}\n\n/**\n * Documentation for this interface: https://github.com/dfahlander/Dexie.js/wiki/Dexie.Syncable.IPersistedContext\n */\nexport interface IPersistedContext {\n    save() : Promise<void>;\n    [customProp: string] : any;\n}\n\n/**\n * Documentation for this function: https://github.com/dfahlander/Dexie.js/wiki/Dexie.Syncable.ISyncProtocol\n */\nexport type ApplyRemoteChangesFunction = (\n    changes: IDatabaseChange[],\n    lastRevision: any,\n    partial?: boolean,\n    clear?: boolean)\n    => Promise<void>;\n\n/**\n * Provide a poll continuation if your backend is a reqest/response service, such as a REST API.\n */\nexport interface PollContinuation {\n    /** Implementation should return number of milliseconds until you want the framework to call sync() again. */\n    again: number\n}\n\n/**\n * Provide a reactive continuation if your backend is connected to over WebSocket, socket-io, signalR or such,\n * and may push changes back to the client as they occur.\n */\nexport interface ReactiveContinuation {\n    react (\n        /** List of local changes to send to server. */\n        changes: IDatabaseChange[],\n\n        /** Server revision that server needs to know in order to apply the changes correcly.  */\n        baseRevision: any,\n\n        /** If true, it means that reach() will be called upon again with additional changes once you'version\n         * called onChangesAccepted(). An implementation may handle this transactionally, i.e. wait with applying\n         * these changes and instead buffer them in a temporary table and the apply everything once reac() is called\n         * with partial=false.\n         */\n        partial: boolean,\n\n        /** Callback to call when the given changes has been acknowledged and persisted at the server side.\n         * This will mark the change-set as delivered and the framework wont try resending these changes anymore.\n         */\n        onChangesAccepted: ()=>void): void;\n\n    /** Implementation should disconned the underlying transport and stop calling applyRemoteChanges(). */\n    disconnect(): void;\n}\n\nexport enum SyncStatus {\n    /** An irrepairable error occurred and the sync provider is dead. */\n    ERROR = -1,\n\n    /** The sync provider hasnt yet become online, or it has been disconnected. */\n    OFFLINE = 0,\n\n    /** Trying to connect to server */\n    CONNECTING = 1,\n\n    /** Connected to server and currently in sync with server */\n    ONLINE = 2,\n\n    /** Syncing with server. For poll pattern, this is every poll call.\n     * For react pattern, this is when local changes are being sent to server. */\n    SYNCING = 3,\n\n    /** An error occured such as net down but the sync provider will retry to connect. */\n    ERROR_WILL_RETRY = 4\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/api.js",
    "content": "// This file is deliberatly left empty to allow the api.d.ts to contain the definitions for Dexie.Syncable without generating an error on webpack\n"
  },
  {
    "path": "addons/Dexie.Syncable/dist/README.md",
    "content": "## Can't find dexie-syncable.js?\nTranspiled code (dist version) IS ONLY checked in to\nthe [releases](https://github.com/dexie/Dexie.js/tree/releases/addons/Dexie.Syncable/dist).\nbranch.\n\n## Download\n[unpkg.com/dexie-syncable/dist/dexie-syncable.js](https://unpkg.com/dexie-syncable/dist/dexie-syncable.js)\n\n[unpkg.com/dexie-syncable/dist/dexie-syncable.min.js](https://unpkg.com/dexie-syncable/dist/dexie-syncable.min.js)\n\n[unpkg.com/dexie-syncable/dist/dexie-syncable.js.map](https://unpkg.com/dexie-syncable/dist/dexie-syncable.js.map)\n\n[unpkg.com/dexie-syncable/dist/dexie-syncable.min.js.map](https://unpkg.com/dexie-syncable/dist/dexie-syncable.min.js.map)\n\n## npm\n```\nnpm install dexie-syncable --save\n```\n## bower\nSince Dexie v1.3.4, addons are included in the dexie bower package. \n```\n$ bower install dexie --save\n$ ls bower_components/dexie/addons/Dexie.Syncable/dist\ndexie-syncable.js  dexie-syncable.js.map  dexie-syncable.min.js  dexie-syncable.min.js.map\n\n```\n## Or build them yourself...\nFork Dexie.js, then:\n```\ngit clone https://github.com/YOUR-USERNAME/Dexie.js.git\ncd Dexie.js\nnpm install\ncd addons/Dexie.Syncable\nnpm run build       # or npm run watch\n\n```\nIf you're on windows, you need to use an elevated command prompt of some reason to get `npm install` to work.\n"
  },
  {
    "path": "addons/Dexie.Syncable/package.json",
    "content": "{\n  \"name\": \"dexie-syncable\",\n  \"version\": \"4.0.1-beta.13\",\n  \"description\": \"Addon to Dexie that makes it possible to sync indexeDB with remote databases.\",\n  \"main\": \"dist/dexie-syncable.js\",\n  \"module\": \"dist/dexie-syncable.es.js\",\n  \"jsnext:main\": \"dist/dexie-syncable.es.js\",\n  \"typings\": \"dist/dexie-syncable.d.ts\",\n  \"jspm\": {\n    \"format\": \"cjs\",\n    \"ignore\": [\n      \"src/\"\n    ]\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dexie/Dexie.js.git\"\n  },\n  \"keywords\": [\n    \"indexeddb\",\n    \"dexie\",\n    \"addon\",\n    \"database\",\n    \"sync\"\n  ],\n  \"author\": \"David Fahlander <https://github.com/dfahlander>\",\n  \"contributors\": [\n    \"Nikolas Poniros <https://github.com/nponiros>\",\n    \"Martin Diphoorn <https://github.com/martindiphoorn>\"\n  ],\n  \"license\": \"Apache-2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/dexie/Dexie.js/issues\"\n  },\n  \"scripts\": {\n    \"build\": \"just-build\",\n    \"watch\": \"just-build --watch\",\n    \"test\": \"pnpm run build && pnpm run test:typings && pnpm run test:unit && pnpm run test:integration\",\n    \"test:unit\": \"karma start test/unit/karma.conf.js --single-run\",\n    \"test:integration\": \"karma start test/integration/karma.conf.js --single-run\",\n    \"test:typings\": \"tsc -p test/test-typings/\",\n    \"test:unit:debug\": \"karma start test/unit/karma.conf.js --log-level debug\",\n    \"test:integration:debug\": \"karma start test/integrations/karma.conf.js --log-level debug\",\n    \"test:ltcloud\": \"cross-env LAMBDATEST=true pnpm run test:ltTunnel & sleep 10 && pnpm run test:unit; UNIT_STATUS=$?; exit $UNIT_STATUS\",\n    \"test:ltTunnel\": \"node ../../test/lt-local\",\n    \"test:ltcloud:integration\": \"cross-env LAMBDATEST=true pnpm run test:integration; UNIT_STATUS=$?; kill $(cat tunnel.pid); exit $UNIT_STATUS\"\n  },\n  \"just-build\": {\n    \"default\": [\n      \"just-build release\"\n    ],\n    \"dev\": [\n      \"just-build dexie-syncable\"\n    ],\n    \"dexie-syncable\": [\n      \"# Build UMD module and the tests (two bundles)\",\n      \"tsc --allowJs --moduleResolution node --lib es2020,dom -t es5 -m es2015 --outDir tools/tmp/es5 --rootDir ../.. --sourceMap src/Dexie.Syncable.js test/unit/unit-tests-all.js [--watch 'Compilation complete.']\",\n      \"rollup -c tools/build-configs/rollup.config.mjs\",\n      \"rollup -c tools/build-configs/rollup.tests.config.mjs\",\n      \"node tools/replaceVersionAndDate.js dist/dexie-syncable.js\",\n      \"node tools/replaceVersionAndDate.js test/unit/bundle.js\",\n      \"# eslint \",\n      \"eslint src --cache\"\n    ],\n    \"release\": [\n      \"just-build dexie-syncable\",\n      \"# Copy Dexie.Syncable.d.ts to dist and replace version in it\",\n      \"node -e \\\"fs.writeFileSync('dist/dexie-syncable.d.ts', fs.readFileSync('src/Dexie.Syncable.d.ts'))\\\"\",\n      \"node tools/replaceVersionAndDate.js dist/dexie-syncable.d.ts\",\n      \"# Minify the default ES5 UMD module\",\n      \"cd dist\",\n      \"uglifyjs dexie-syncable.js -m -c negate_iife=0 -o dexie-syncable.min.js --source-map\"\n    ]\n  },\n  \"homepage\": \"https://dexie.org\",\n  \"peerDependencies\": {\n    \"dexie\": \"workspace:^\",\n    \"dexie-observable\": \"workspace:^\"\n  },\n  \"devDependencies\": {\n    \"dexie\": \"workspace:^\",\n    \"dexie-observable\": \"workspace:^\",\n    \"eslint\": \"^5.16.0\",\n    \"just-build\": \"^0.9.24\",\n    \"qunit\": \"2.10.0\",\n    \"qunitjs\": \"1.23.1\",\n    \"typescript\": \"^5.3.3\",\n    \"uglify-js\": \"^3.5.6\"\n  }\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/.eslintrc.json",
    "content": "{\n  \"extends\": \"eslint:recommended\",\n  \"parserOptions\": {\n    \"ecmaVersion\": 6,\n    \"sourceType\": \"module\",\n    \"ecmaFeatures\": {\n    }\n  },\n  \"rules\": {\n    \"no-undef\": [\"error\"],\n    \"no-unused-vars\": 1,\n    \"no-console\": 0,\n    \"no-empty\": 0\n  },\n  \"globals\": {\n    \"indexedDB\": false,\n    \"IDBKeyRange\": false,\n    \"setTimeout\": false,\n    \"clearTimeout\": false,\n    \"Symbol\": false,\n    \"setImmediate\": false,\n    \"console\": false,\n    \"self\": false,\n    \"window\": false,\n    \"global\": false,\n    \"navigator\": false,\n    \"location\": false,\n    \"chrome\": false,\n    \"document\": false,\n    \"MutationObserver\": false,\n    \"CustomEvent\": false,\n    \"dispatchEvent\": false,\n    \"localStorage\": false\n  }\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/Dexie.Syncable.d.ts",
    "content": "// Type definitions for dexie-syncable v{version}\n// Project: https://github.com/dfahlander/Dexie.js/tree/master/addons/Dexie.Syncable\n// Definitions by: David Fahlander <http://github.com/dfahlander>\n\nimport Dexie, { DexieEventSet } from 'dexie';\nimport 'dexie-observable';\nimport { ISyncProtocol, SyncStatus } from '../api';\nimport {IDatabaseChange} from 'dexie-observable/api';\n\n\nexport interface SyncableEventSet extends DexieEventSet {\n    (eventName: 'statusChanged', subscriber: (status: number, url: string) => void): void;\n}\n\n//\n// Extend Dexie interface\n//\ndeclare module 'dexie' {\n    interface Dexie {\n        syncable: {\n            version: string;\n            /**\n             * Connect to given URL using given protocol and options. See documentation at:\n             * https://github.com/dfahlander/Dexie.js/wiki/db.syncable.connect()\n             */\n            connect(protocol: string, url: string, options?: any): Dexie.Promise<void>;\n\n            /**\n             * Stop syncing with given url.. See docs at:\n             * https://github.com/dfahlander/Dexie.js/wiki/db.syncable.disconnect()\n             */\n            disconnect(url: string): Dexie.Promise<void>;\n\n            /**\n             * Stop syncing and delete all sync state for given URL. See docs at:\n             * https://github.com/dfahlander/Dexie.js/wiki/db.syncable.delete()\n             */\n            delete(url: string): Dexie.Promise<void>;\n\n            /**\n             * List remote URLs. See docs at:\n             * https://github.com/dfahlander/Dexie.js/wiki/db.syncable.list()\n             */\n            list (): Dexie.Promise<string[]>;\n\n            /**\n             * Get sync status for given URL. See docs at:\n             * https://github.com/dfahlander/Dexie.js/wiki/db.syncable.getStatus()\n             */\n            getStatus(url: string): Dexie.Promise<SyncStatus>;\n\n            /**\n             * Syncable events. See docs at:\n             * https://github.com/dfahlander/Dexie.js/wiki/db.syncable.on('statusChanged')\n             */\n            on: SyncableEventSet;\n        }\n\n        /**\n         * Table used for storing uncommitted changes when downloading partial change sets from\n         * a sync server.\n         *\n         * Each change is bound to a node id (represents the remote server that the change was\n         * downloaded from)\n         */\n        _uncommittedChanges: Dexie.Table<IDatabaseChange & {id: number, node: number}, number>;\n    }\n\n    interface DexieConstructor {\n        Syncable: {\n            (db: Dexie) : void;\n\n            version: string;\n\n            /**\n             * See documentation at:\n             * https://dexie.org/docs/Syncable/Dexie.Syncable.registerSyncProtocol()\n             */\n            registerSyncProtocol: (name: string, prototocolInstance: ISyncProtocol) => void;\n\n            /** Translates a sync status number into a string \"ERROR_WILL_RETRY\", \"ERROR\", etc */\n            StatusTexts: {[syncStatus:number]: string};\n        }\n    }\n}\n\n//\n// Extend dexie-observable interfaces\n//\ndeclare module \"dexie-observable\" {\n    // Extend SyncNode interface from Dexie.Observable to\n    // allow storing remote nodes in table _syncNodes.\n    interface SyncNode {\n        url: string, // Only applicable for \"remote\" nodes. Only used in Dexie.Syncable.\n        syncProtocol: string, // Tells which implementation of ISyncProtocol to use for remote syncing.\n        syncContext: any,\n        syncOptions: any,\n        status: number,\n        appliedRemoteRevision: any,\n        remoteBaseRevisions: { local: number, remote: any }[],\n        dbUploadState: {\n            tablesToUpload: string[],\n            currentTable: string,\n            currentKey: any,\n            localBaseRevision: number\n        }\n    }\n}\n\nexport default Dexie.Syncable;\n\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/Dexie.Syncable.js",
    "content": "/* ========================================================================== \n *                           dexie-syncable.js\n * ==========================================================================\n *\n * Dexie addon for syncing indexedDB with remote endpoints.\n *\n * By David Fahlander, david.fahlander@gmail.com,\n *    Nikolas Poniros, https://github.com/nponiros\n *\n * ==========================================================================\n *\n * Version {version}, {date}\n *\n * https://dexie.org\n *\n * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/\n * \n */\n\nimport Dexie from \"dexie\";\n// Depend on 'dexie-observable'\n// To support both ES6,AMD,CJS and UMD (plain script), we just import it and then access it as \"Dexie.Observable\".\n// That way, our plugin works in all UMD cases.\n// If target platform would only be module based (ES6/AMD/CJS), we could have done 'import Observable from \"dexie-observable\"'.\nimport \"dexie-observable\";\n\nimport initSyncableConnect from './syncable-connect';\nimport initConnectFn from './connect-fn';\nimport {Statuses, StatusTexts} from './statuses';\n\nvar override = Dexie.override,\n    Promise = Dexie.Promise,\n    Observable = Dexie.Observable;\n\n/** Dexie addon for remote database sync.\n * \n * @param {Dexie} db \n */\nfunction Syncable (db) {\n    if (!/^(3|4)\\./.test(Dexie.version))\n        throw new Error(`Missing dexie version 3.x or 4.x`);\n    if (!db.observable || (db.observable.version !== \"{version}\" && !/^(3|4)\\./.test(db.observable.version)))\n        throw new Error(`Missing dexie-observable version 3.x or 4.x`);\n    if (db.syncable) {\n        if (db.syncable.version !== \"{version}\") throw new Error(`Mixed versions of dexie-syncable: \"{version}\" vs \"${db.syncable.version}\"`);\n        return; // Addon already active.\n    }\n\n    var activePeers = [];\n\n    const connectFn = initConnectFn(db, activePeers);\n    const syncableConnect = initSyncableConnect(db, connectFn);\n\n    db.on('message', function(msg) {\n        // Message from other local node arrives...\n        Dexie.vip(function() {\n            if (msg.type === 'connect') {\n                // We are master node and another non-master node wants us to do the connect.\n                db.syncable.connect(msg.message.protocolName, msg.message.url, msg.message.options).then(msg.resolve, msg.reject);\n            } else if (msg.type === 'disconnect') {\n                db.syncable.disconnect(msg.message.url).then(msg.resolve, msg.reject);\n            } else if (msg.type === 'syncStatusChanged') {\n                // We are client and a master node informs us about syncStatus change.\n                // Lookup the connectedProvider and call its event\n                db.syncable.on.statusChanged.fire(msg.message.newStatus, msg.message.url);\n            }\n        });\n    });\n\n    db.on('cleanup', function(weBecameMaster) {\n        // A cleanup (done in Dexie.Observable) may result in that a master node is removed and we become master.\n        if (weBecameMaster) {\n            // We took over the master role in Observable's cleanup method.\n            // We should connect to remote servers now.\n            // At this point, also reconnect servers with status ERROR_WILL_RETRY as well as plain ERROR.\n            // Reason to reconnect to those with plain \"ERROR\" is that the ERROR state may occur when a database\n            // connection has been closed. The new master would then be expected to reconnect.\n            // Also, this is not an infinite poll(). This is rare event that a new browser tab takes over from\n            // an old closed one. \n            Dexie.ignoreTransaction(()=>Dexie.vip(()=>{\n                return db._syncNodes.where({type: 'remote'})\n                    .filter(node => node.status !== Statuses.OFFLINE)\n                    .toArray(connectedRemoteNodes => Promise.all(connectedRemoteNodes.map(node => \n                        db.syncable.connect(node.syncProtocol, node.url, node.syncOptions).catch(e => {\n                            console.warn(`Dexie.Syncable: Could not connect to ${node.url}. ${e.stack || e}`);\n                        })\n                    )));\n            })).catch('DatabaseClosedError', ()=>{});\n        }\n    });\n\n    // \"ready\" subscriber for the master node that makes sure it will always connect to sync server\n    // when the database opens. It will not wait for the connection to complete, just initiate the\n    // connection so that it will continue syncing as long as the database is open.\n\n    // Dexie.Observable's 'ready' subscriber will have been invoked prior to this, making sure\n    // that db._localSyncNode exists and persisted before this subscriber kicks in.\n    db.on('ready', function onReady() {\n        // Again, in onReady: If we ARE master, make sure to connect to remote servers that is in a connected state.\n        if (db._localSyncNode && db._localSyncNode.isMaster) {\n            // Make sure to connect to remote servers that is in a connected state (NOT OFFLINE or ERROR!)\n            // This \"ready\" subscriber will never be the one performing the initial sync request, because\n            // even after calling db.syncable.connect(), there won't exist any \"remote\" sync node yet.\n            // Instead, db.syncable.connect() will subscribe to \"ready\" also, and that subscriber will be\n            // called after this one. There, in that subscriber, the initial sync request will take place\n            // and the \"remote\" node will be created so that this \"ready\" subscriber can auto-connect the\n            // next time this database is opened.\n            // CONCLUSION: We can always assume that the local DB has been in sync with the server at least\n            // once in the past for each \"connectedRemoteNode\" we find in query below.\n\n            // Don't halt db.ready while connecting (i.e. we do not return a promise here!)\n            db._syncNodes\n                .where('type').equals('remote')\n                .and(node => node.status !== Statuses.OFFLINE)\n                .toArray(connectedRemoteNodes => {\n                    // There are connected remote nodes that we must manage (or take over to manage)\n                    connectedRemoteNodes.forEach(\n                            node => db.syncable.connect(\n                                node.syncProtocol,\n                                node.url,\n                                node.syncOptions)     \n                            .catch (()=>{}) // A failure will be triggered in on('statusChanged'). We can ignore.\n                    );\n                }).catch('DatabaseClosedError', ()=>{});\n        }\n    }, true); // True means the ready event will survive a db reopen - db.close()/db.open()\n\n    db.syncable = {\n        version: \"{version}\"\n    };\n\n    db.syncable.getStatus = function(url, cb) {\n        if (db.isOpen()) {\n            return Dexie.vip(function() {\n                return db._syncNodes.where('url').equals(url).first(function(node) {\n                    return node ? node.status : Statuses.OFFLINE;\n                });\n            }).then(cb);\n        } else {\n            return Promise.resolve(Syncable.Statuses.OFFLINE).then(cb);\n        }\n    };\n\n    db.syncable.getOptions = function(url, cb) {\n        return db.transaction('r?', db._syncNodes, () => {\n            return db._syncNodes.where('url').equals(url).first(function(node) {\n                return node.syncOptions;\n            }).then(cb);\n        });\n    };\n\n    db.syncable.list = function() {\n        return db.transaction('r?', db._syncNodes, ()=>{\n            return db._syncNodes.where('type').equals('remote').toArray(function(a) {\n                return a.map(function(node) { return node.url; });\n            });\n        });\n    };\n\n    db.syncable.on = Dexie.Events(db, { statusChanged: \"asap\" });\n\n    db.syncable.disconnect = function(url) {\n        return Dexie.ignoreTransaction(()=>{\n            return Promise.resolve().then(()=>{\n                if (db._localSyncNode && db._localSyncNode.isMaster) {\n                    return Promise.all(activePeers.filter(peer => peer.url === url).map(peer => {\n                        return peer.disconnect(Statuses.OFFLINE);\n                    }));\n                } else {\n                    return db._syncNodes.where('isMaster').above(0).first(masterNode => {\n                        return db.observable.sendMessage('disconnect', { url: url }, masterNode.id, {wantReply: true});\n                    });\n                }\n            }).then(()=>{\n                return db._syncNodes.where(\"url\").equals(url).modify(node => {\n                    node.status = Statuses.OFFLINE;\n                });\n            });\n        });\n    };\n\n    db.syncable.connect = function(protocolName, url, options) {\n        options = options || {}; // Make sure options is always an object because 1) Provider expects it to be. 2) We'll be persisting it and you cannot persist undefined.\n        var protocolInstance = Syncable.registeredProtocols[protocolName];\n        if (protocolInstance) {\n            return syncableConnect(protocolInstance, protocolName, url, options);\n        } else {\n            return Promise.reject(\n                new Error(\"ISyncProtocol '\" + protocolName + \"' is not registered in Dexie.Syncable.registerSyncProtocol()\")\n            );\n        }\n    };\n\n    db.syncable.delete = function(url) {\n        return db.syncable.disconnect(url).then(()=>{\n            return db.transaction('rw!', db._syncNodes, db._changes, db._uncommittedChanges, ()=>{\n                // Find the node(s)\n                // Several can be found, as detected by @martindiphoorn,\n                // let's delete them and cleanup _uncommittedChanges and _changes \n                // accordingly.\n                let nodeIDsToDelete;\n                return db._syncNodes\n                    .where(\"url\").equals(url)\n                    .toArray(nodes => nodes.map(node => node.id))\n                    .then(nodeIDs => {\n                        nodeIDsToDelete = nodeIDs;\n                        // Delete the syncNode that represents the remote endpoint.\n                        return db._syncNodes.where('id').anyOf(nodeIDs).delete()\n                    })\n                    .then (() =>\n                        // In case there were uncommittedChanges belonging to this, delete them as well\n                        db._uncommittedChanges.where('node').anyOf(nodeIDsToDelete).delete());\n            }).then(()=> {\n                // Spawn background job to delete old changes, now that a node has been deleted,\n                // there might be changes in _changes table that is not needed to keep anymore.\n                // This is done in its own transaction, or possible several transaction to prohibit\n                // starvation\n                Observable.deleteOldChanges(db);\n            });\n        });\n    };\n\n    db.syncable.unsyncedChanges = function(url) {\n        return db._syncNodes.where(\"url\").equals(url).first(function(node) {\n            return db._changes.where('rev').above(node.myRevision).toArray();\n        });\n    };\n\n    db.close = override(db.close, function(origClose) {\n        return function() {\n            activePeers.forEach(function(peer) {\n                peer.disconnect();\n            });\n            return origClose.apply(this, arguments);\n        };\n    });\n\n    Object.defineProperty(\n        db.observable.SyncNode.prototype,\n        'save', {\n            enumerable: false,\n            configurable: true,\n            writable: true,\n            value() {\n                return db.transaction('rw?', db._syncNodes, () => {\n                    return db._syncNodes.put(this);\n            });\n        }\n     });\n}\n\nSyncable.version = \"{version}\";\n\nSyncable.Statuses = Statuses;\n\nSyncable.StatusTexts = StatusTexts;\n\nSyncable.registeredProtocols = {}; // Map<String,ISyncProviderFactory> when key is the provider name.\n\nSyncable.registerSyncProtocol = function(name, protocolInstance) {\n    /// <summary>\n    ///    Register a synchronization protocol that can synchronize databases with remote servers.\n    /// </summary>\n    /// <param name=\"name\" type=\"String\">Provider name</param>\n    /// <param name=\"protocolInstance\" type=\"ISyncProtocol\">Implementation of ISyncProtocol</param>\n    const partialsThreshold = protocolInstance.partialsThreshold;\n    if (typeof partialsThreshold === 'number') {\n        // Don't allow NaN or negative threshold\n        if (isNaN(partialsThreshold) || partialsThreshold < 0) {\n            throw new Error('The given number for the threshold is not supported');\n        }\n        // If the threshold is 0 we will not send any client changes but will get server changes\n    } else {\n        // Use Infinity as the default so simple protocols don't have to care about partial synchronization\n        protocolInstance.partialsThreshold = Infinity;\n    }\n    Syncable.registeredProtocols[name] = protocolInstance;\n};\n\nif (Dexie.Syncable) {\n    if (Dexie.Syncable.version !== \"{version}\") {\n        throw new Error (`Mixed versions of dexie-syncable: \"{version}\" vs \"${Dexie.Syncable.version}\"`);\n    }\n} else {\n    // Register addon in Dexie:\n    Dexie.Syncable = Syncable;\n    Dexie.addons.push(Syncable);\n}\n\nexport default Dexie.Syncable;\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/PersistedContext.js",
    "content": "import Dexie from 'dexie';\n\nexport default function initPersistedContext(node) {\n  //\n  // PersistedContext : IPersistedContext\n  //\n  return class PersistedContext {\n    constructor(nodeID, otherProps) {\n      this.nodeID = nodeID;\n      if (otherProps) Dexie.extend(this, otherProps);\n    }\n\n    save() {\n      // Store this instance in the syncContext property of the node it belongs to.\n      return Dexie.vip(() => {\n        return node.save();\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/apply-changes.js",
    "content": "import { CREATE, DELETE, UPDATE } from './change_types';\nimport bulkUpdate from './bulk-update';\n\nexport default function initApplyChanges(db) {\n  return function applyChanges(changes) {\n    let collectedChanges = {};\n    changes.forEach((change) => {\n      if (!collectedChanges.hasOwnProperty(change.table)) {\n        collectedChanges[change.table] = { [CREATE]: [], [DELETE]: [], [UPDATE]: [] };\n      }\n      collectedChanges[change.table][change.type].push(change);\n    });\n    let table_names = Object.keys(collectedChanges);\n    let tables = table_names.map((table) => db.table(table));\n\n    return db.transaction(\"rw\", tables, () => {\n      table_names.forEach((table_name) => {\n        const table = db.table(table_name);\n        const specifyKeys = !table.schema.primKey.keyPath;\n        const createChangesToApply = collectedChanges[table_name][CREATE];\n        const deleteChangesToApply = collectedChanges[table_name][DELETE];\n        const updateChangesToApply = collectedChanges[table_name][UPDATE];\n        if (createChangesToApply.length > 0)\n          table.bulkPut(createChangesToApply.map(c => c.obj), specifyKeys ?\n            createChangesToApply.map(c => c.key) : undefined);\n        if (updateChangesToApply.length > 0)\n          bulkUpdate(table, updateChangesToApply);\n        if (deleteChangesToApply.length > 0)\n          table.bulkDelete(deleteChangesToApply.map(c => c.key));\n      });\n    });\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/bulk-update.js",
    "content": "import Dexie from 'dexie';\n\nexport default function bulkUpdate(table, changes) {\n  let keys = changes.map(c => c.key);\n  let map = {};\n  // Retrieve current object of each change to update and map each\n  // found object's primary key to the existing object:\n  return table.where(':id').anyOf(keys).raw().each((obj, cursor) => {\n    map[cursor.primaryKey+''] = obj;\n  }).then(()=>{\n    // Filter away changes whose key wasn't found in the local database\n    // (we can't update them if we do not know the existing values)\n    let updatesThatApply = changes.filter(c => map.hasOwnProperty(c.key+''));\n    // Apply modifications onto each existing object (in memory)\n    // and generate array of resulting objects to put using bulkPut():\n    let objsToPut = updatesThatApply.map (c => {\n      let curr = map[c.key+''];\n      Object.keys(c.mods).forEach(keyPath => {\n        Dexie.setByKeyPath(curr, keyPath, c.mods[keyPath]);\n      });\n      return curr;\n    });\n    return table.bulkPut(objsToPut);\n  });\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/change_types.js",
    "content": "// Change Types\nexport const CREATE = 1;\nexport const UPDATE = 2;\nexport const DELETE = 3;\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/combine-create-and-update.js",
    "content": "import Dexie from 'dexie';\n\nexport default function combineCreateAndUpdate(prevChange, nextChange) {\n  var clonedChange = Dexie.deepClone(prevChange); // Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.\n  Object.keys(nextChange.mods).forEach(function (keyPath) {\n    Dexie.setByKeyPath(clonedChange.obj, keyPath, nextChange.mods[keyPath]);\n  });\n  return clonedChange;\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/combine-update-and-update.js",
    "content": "import Dexie from 'dexie';\n\nexport default function combineUpdateAndUpdate(prevChange, nextChange) {\n  var clonedChange = Dexie.deepClone(prevChange); // Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.\n  Object.keys(nextChange.mods).forEach(function (keyPath) {\n    // If prev-change was changing a parent path of this keyPath, we must update the parent path rather than adding this keyPath\n    var hadParentPath = false;\n    Object.keys(prevChange.mods).filter(function (parentPath) { return keyPath.indexOf(parentPath + '.') === 0; }).forEach(function (parentPath) {\n      Dexie.setByKeyPath(clonedChange.mods[parentPath], keyPath.substr(parentPath.length + 1), nextChange.mods[keyPath]);\n      hadParentPath = true;\n    });\n    if (!hadParentPath) {\n      // Add or replace this keyPath and its new value\n      clonedChange.mods[keyPath] = nextChange.mods[keyPath];\n    }\n    // In case prevChange contained sub-paths to the new keyPath, we must make sure that those sub-paths are removed since\n    // we must mimic what would happen if applying the two changes after each other:\n    Object.keys(prevChange.mods).filter(function (subPath) { return subPath.indexOf(keyPath + '.') === 0; }).forEach(function (subPath) {\n      delete clonedChange.mods[subPath];\n    });\n  });\n  return clonedChange;\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/connect-fn.js",
    "content": "import Dexie from 'dexie';\n\nimport initGetOrCreateSyncNode from './get-or-create-sync-node';\nimport initConnectProtocol from './connect-protocol';\nimport {Statuses} from './statuses';\n\nexport default function initConnectFn(db, activePeers) {\n  return function connect(protocolInstance, protocolName, url, options, dbAliveID) {\n    /// <param name=\"protocolInstance\" type=\"ISyncProtocol\"></param>\n    var existingPeer = activePeers.filter(function (peer) {\n      return peer.url === url;\n    });\n    if (existingPeer.length > 0) {\n      const activePeer = existingPeer[0];\n      const diffObject = {};\n      Dexie.getObjectDiff(activePeer.syncOptions, options, diffObject);\n      // Options have been changed\n      // We need to disconnect and reconnect\n      if (Object.keys(diffObject).length !== 0) {\n        return db.syncable.disconnect(url)\n          .then(() => {\n            return execConnect();\n          })\n      } else {\n        // Never create multiple syncNodes with same protocolName and url. Instead, let the next call to connect() return the same promise that\n        // have already been started and eventually also resolved. If promise has already resolved (node connected), calling existing promise.then() will give a callback directly.\n        return existingPeer[0].connectPromise;\n      }\n    }\n\n    function execConnect() {\n      // Use an object otherwise we wouldn't be able to get the reject promise from\n      // connectProtocol\n      var rejectConnectPromise = {p: null};\n      const connectProtocol = initConnectProtocol(db, protocolInstance, dbAliveID, options, rejectConnectPromise);\n      const getOrCreateSyncNode = initGetOrCreateSyncNode(db, protocolName, url);\n      var connectPromise = getOrCreateSyncNode(options).then(function (node) {\n        return connectProtocol(node, activePeer);\n      });\n\n      var disconnected = false;\n      var activePeer = {\n        url: url,\n        status: Statuses.OFFLINE,\n        connectPromise: connectPromise,\n        syncOptions: options,\n        on: Dexie.Events(null, \"disconnect\"),\n        disconnect: function (newStatus, error) {\n          var pos = activePeers.indexOf(activePeer);\n          if (pos >= 0) activePeers.splice(pos, 1);\n          if (error && rejectConnectPromise.p) rejectConnectPromise.p(error);\n          if (!disconnected) {\n            activePeer.on.disconnect.fire(newStatus, error);\n          }\n          disconnected = true;\n        }\n      };\n      activePeers.push(activePeer);\n\n      return connectPromise;\n    }\n\n    return execConnect();\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/connect-protocol.js",
    "content": "import Dexie from 'dexie';\n\nimport initEnqueue from './enqueue';\nimport initSaveToUncommittedChanges from './save-to-uncommitted-changes';\nimport initFinallyCommitAllChanges from './finally-commit-all-changes';\nimport initGetLocalChangesForNode from './get-local-changes-for-node/get-local-changes-for-node';\nimport {Statuses} from './statuses';\n\nconst Promise = Dexie.Promise;\n\nexport default function initConnectProtocol(db, protocolInstance, dbAliveID, options, rejectConnectPromise) {\n  const enqueue = initEnqueue(db);\n  var hasMoreToGive = {hasMoreToGive: true};\n\n  function stillAlive() {\n    // A better method than doing db.isOpen() because the same db instance may have been reopened, but then this sync call should be dead\n    // because the new instance should be considered a fresh instance and will have another local node.\n    return db._localSyncNode && db._localSyncNode.id === dbAliveID;\n  }\n\n  return function connectProtocol(node, activePeer) {\n    /// <param name=\"node\" type=\"db.observable.SyncNode\"></param>\n    const getLocalChangesForNode = initGetLocalChangesForNode(db, hasMoreToGive, protocolInstance.partialsThreshold);\n\n    const url = activePeer.url;\n\n    function changeStatusTo(newStatus) {\n      if (node.status !== newStatus) {\n        node.status = newStatus;\n        node.save().then(()=> {\n          db.syncable.on.statusChanged.fire(newStatus, url);\n          // Also broadcast message to other nodes about the status\n          db.observable.broadcastMessage(\"syncStatusChanged\", {newStatus: newStatus, url: url}, false);\n        }).catch('DatabaseClosedError', ()=> {\n        });\n      }\n    }\n\n    activePeer.on('disconnect', function (newStatus) {\n      if (!isNaN(newStatus)) changeStatusTo(newStatus);\n    });\n\n    var connectedContinuation;\n    changeStatusTo(Statuses.CONNECTING);\n    return doSync();\n\n    function doSync() {\n      // Use enqueue() to ensure only a single promise execution at a time.\n      return enqueue(doSync, function () {\n        // By returning the Promise returned by getLocalChangesForNode() a final catch() on the sync() method will also catch error occurring in entire sequence.\n        return getLocalChangesForNode_autoAckIfEmpty(node, sendChangesToProvider);\n      }, dbAliveID);\n    }\n\n    function sendChangesToProvider(changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n      // Create a final Promise for the entire sync() operation that will resolve when provider calls onSuccess().\n      // By creating finalPromise before calling protocolInstance.sync() it is possible for provider to call onError() immediately if it wants.\n      var finalSyncPromise = new Promise(function (resolve, reject) {\n        rejectConnectPromise.p = function (err) {\n          reject(err);\n        };\n        Dexie.asap(function () {\n          try {\n            protocolInstance.sync(\n                node.syncContext,\n                url,\n                options,\n                remoteBaseRevision,\n                node.appliedRemoteRevision,\n                changes,\n                partial,\n                applyRemoteChanges,\n                onChangesAccepted,\n                function (continuation) {\n                  resolve(continuation);\n                },\n                onError);\n          } catch (ex) {\n            onError(ex, Infinity);\n          }\n\n          function onError(error, again) {\n            reject(error);\n            if (stillAlive()) {\n              if (!isNaN(again) && again < Infinity) {\n                setTimeout(function () {\n                  if (stillAlive()) {\n                    changeStatusTo(Statuses.SYNCING);\n                    doSync().catch('DatabaseClosedError', abortTheProvider);\n                  }\n                }, again);\n                changeStatusTo(Statuses.ERROR_WILL_RETRY, error);\n                if (connectedContinuation && connectedContinuation.disconnect) connectedContinuation.disconnect();\n                connectedContinuation = null;\n              } else {\n                abortTheProvider(error); // Will fire ERROR on statusChanged event.\n              }\n            }\n          }\n        });\n      });\n\n      return finalSyncPromise.then(function () {\n        // Resolve caller of db.syncable.connect() with undefined. Not with continuation!\n        return undefined;\n      }).finally(()=> {\n        // In case error happens after connect, don't try reject the connect promise anymore.\n        // This is important. A Dexie unit test that verifies unhandled rejections will fail when Dexie.Syncable addon\n        // is active and this happens. It would fire unhandledrejection but that we do not want.\n        rejectConnectPromise.p = null;\n      });\n\n      function onChangesAccepted() {\n        Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n          Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n        });\n        // We dont know if onSuccess() was called by provider yet. If it's already called, finalPromise.then() will execute immediately,\n        // otherwise it will execute when finalSyncPromise resolves.\n        finalSyncPromise.then(continueSendingChanges);\n        return node.save();\n      }\n    }\n\n    function abortTheProvider(error) {\n      activePeer.disconnect(Statuses.ERROR, error);\n    }\n\n    function getLocalChangesForNode_autoAckIfEmpty(node, cb) {\n      return getLocalChangesForNode(node, function autoAck(changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n        if (changes.length === 0 && 'myRevision' in nodeModificationsOnAck && nodeModificationsOnAck.myRevision !== node.myRevision) {\n          Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n            Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n          });\n          node.save().catch('DatabaseClosedError', ()=> {\n          });\n          return getLocalChangesForNode(node, autoAck);\n        } else {\n          return cb(changes, remoteBaseRevision, partial, nodeModificationsOnAck);\n        }\n      });\n    }\n\n    function applyRemoteChanges(remoteChanges, remoteRevision, partial/*, clear*/) {\n      const saveToUncommittedChanges = initSaveToUncommittedChanges(db, node);\n      const finallyCommitAllChanges = initFinallyCommitAllChanges(db, node);\n\n      return enqueue(applyRemoteChanges, function () {\n        if (!stillAlive()) return Promise.reject(new Dexie.DatabaseClosedError());\n        // FIXTHIS: Check what to do if clear() is true!\n        return (partial ? saveToUncommittedChanges(remoteChanges, remoteRevision) : finallyCommitAllChanges(remoteChanges, remoteRevision))\n            .catch(function (error) {\n              abortTheProvider(error);\n              return Promise.reject(error);\n            });\n      }, dbAliveID);\n    }\n\n    //\n    //\n    //  Continuation Patterns Follows\n    //\n    //\n\n    function continueSendingChanges(continuation) {\n      if (!stillAlive()) { // Database was closed.\n        if (continuation.disconnect)\n          continuation.disconnect();\n        return;\n      }\n\n      connectedContinuation = continuation;\n      activePeer.on('disconnect', function () {\n        if (connectedContinuation) {\n          if (connectedContinuation.react) {\n            try {\n              // react pattern must provide a disconnect function.\n              connectedContinuation.disconnect();\n            } catch (e) {\n            }\n          }\n          connectedContinuation = null; // Stop poll() pattern from polling again and abortTheProvider() from being called twice.\n        }\n      });\n\n      if (continuation.react) {\n        continueUsingReactPattern(continuation);\n      } else {\n        continueUsingPollPattern(continuation);\n      }\n    }\n\n    //  React Pattern (eager)\n    function continueUsingReactPattern(continuation) {\n      var changesWaiting, // Boolean\n          isWaitingForServer; // Boolean\n\n\n      function onChanges() {\n        if (connectedContinuation) {\n          changeStatusTo(Statuses.SYNCING);\n          if (isWaitingForServer)\n            changesWaiting = true;\n          else {\n            reactToChanges();\n          }\n        }\n      }\n\n      db.on('changes', onChanges);\n\n      activePeer.on('disconnect', function () {\n        db.on.changes.unsubscribe(onChanges);\n      });\n\n      function reactToChanges() {\n        if (!connectedContinuation) return;\n        changesWaiting = false;\n        isWaitingForServer = true;\n        getLocalChangesForNode_autoAckIfEmpty(node, function (changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n          if (!connectedContinuation) return;\n          if (changes.length > 0) {\n            continuation.react(changes, remoteBaseRevision, partial, function onChangesAccepted() {\n              Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n                Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n              });\n              node.save().catch('DatabaseClosedError', ()=> {\n              });\n              // More changes may be waiting:\n              reactToChanges();\n            });\n          } else {\n            isWaitingForServer = false;\n            if (changesWaiting) {\n              // A change jumped in between the time-spot of quering _changes and getting called back with zero changes.\n              // This is an expreemely rare scenario, and eventually impossible. But need to be here because it could happen in theory.\n              reactToChanges();\n            } else {\n              changeStatusTo(Statuses.ONLINE);\n            }\n          }\n        }).catch(ex => {\n          console.error(`Got ${ex.message} caught by reactToChanges`);\n          abortTheProvider(ex);\n        });\n      }\n\n      reactToChanges();\n    }\n\n    //  Poll Pattern\n    function continueUsingPollPattern() {\n\n      function syncAgain() {\n        getLocalChangesForNode_autoAckIfEmpty(node, function (changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n\n          protocolInstance.sync(node.syncContext, url, options, remoteBaseRevision, node.appliedRemoteRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError);\n\n          function onChangesAccepted() {\n            Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n              Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n            });\n            node.save().catch('DatabaseClosedError', ()=> {\n            });\n          }\n\n          function onSuccess(continuation) {\n            if (!connectedContinuation) {\n              // Got disconnected before succeeding. Quit.\n              return;\n            }\n            connectedContinuation = continuation;\n            if (partial) {\n              // We only sent partial changes. Need to do another round asap.\n              syncAgain();\n            } else {\n              // We've sent all changes now (in sync!)\n              if (!isNaN(continuation.again) && continuation.again < Infinity) {\n                // Provider wants to keep polling. Set Status to ONLINE.\n                changeStatusTo(Statuses.ONLINE);\n                setTimeout(function () {\n                  if (connectedContinuation) {\n                    changeStatusTo(Statuses.SYNCING);\n                    syncAgain();\n                  }\n                }, continuation.again);\n              } else {\n                // Provider seems finished polling. Since we are never going to poll again,\n                // disconnect provider and set status to OFFLINE until another call to db.syncable.connect().\n                activePeer.disconnect(Statuses.OFFLINE);\n              }\n            }\n          }\n\n          function onError(error, again) {\n            if (!isNaN(again) && again < Infinity) {\n              if (connectedContinuation) {\n                setTimeout(function () {\n                  if (connectedContinuation) {\n                    changeStatusTo(Statuses.SYNCING);\n                    syncAgain();\n                  }\n                }, again);\n                changeStatusTo(Statuses.ERROR_WILL_RETRY);\n              } // else status is already changed since we got disconnected.\n            } else {\n              abortTheProvider(error); // Will fire ERROR on onStatusChanged.\n            }\n          }\n        }).catch(abortTheProvider);\n      }\n\n      if (hasMoreToGive.hasMoreToGive) {\n        syncAgain();\n      } else if (connectedContinuation && !isNaN(connectedContinuation.again) && connectedContinuation.again < Infinity) {\n        changeStatusTo(Statuses.ONLINE);\n        setTimeout(function () {\n          if (connectedContinuation) {\n            changeStatusTo(Statuses.SYNCING);\n            syncAgain();\n          }\n        }, connectedContinuation.again);\n      } else {\n        // Provider seems finished polling. Since we are never going to poll again,\n        // disconnect provider and set status to OFFLINE until another call to db.syncable.connect().\n        activePeer.disconnect(Statuses.OFFLINE);\n      }\n    }\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/enqueue.js",
    "content": "import Dexie from 'dexie';\n\nexport default function initEnqueue(db) {\n  return function enqueue(context, fn, instanceID) {\n    function _enqueue() {\n      if (!context.ongoingOperation) {\n        context.ongoingOperation = Dexie.ignoreTransaction(function () {\n          return Dexie.vip(function () {\n            return fn();\n          });\n        }).finally(()=> {\n          delete context.ongoingOperation;\n        });\n      } else {\n        context.ongoingOperation = context.ongoingOperation.then(function () {\n          return enqueue(context, fn, instanceID);\n        });\n      }\n      return context.ongoingOperation;\n    }\n\n    if (!instanceID) {\n      // Caller wants to enqueue it until database becomes open.\n      if (db.isOpen()) {\n        return _enqueue();\n      } else {\n        return Dexie.Promise.reject(new Dexie.DatabaseClosedError());\n      }\n    } else if (db._localSyncNode && instanceID === db._localSyncNode.id) {\n      // DB is already open but queue doesn't want it to be queued if database has been closed (request bound to current instance of DB)\n      return _enqueue();\n    } else {\n      return Dexie.Promise.reject(new Dexie.DatabaseClosedError());\n    }\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/finally-commit-all-changes.js",
    "content": "import Dexie from 'dexie';\nimport initApplyChanges from './apply-changes';\n\nexport default function initFinallyCommitAllChanges(db, node) {\n  const applyChanges = initApplyChanges(db);\n\n  return function finallyCommitAllChanges(changes, remoteRevision) {\n    // 1. Open a write transaction on all tables in DB\n    const tablesToIncludeInTrans = db.tables.filter(table => table.name === '_changes' ||\n      table.name === '_uncommittedChanges' ||\n      table.schema.observable);\n\n    return db.transaction('rw!', tablesToIncludeInTrans, () => {\n      var trans = Dexie.currentTransaction;\n      var localRevisionBeforeChanges = 0;\n      return db._changes.orderBy('rev').last(function (lastChange) {\n        // Store what revision we were at before committing the changes\n        localRevisionBeforeChanges = (lastChange && lastChange.rev) || 0;\n      }).then(() => {\n        // Specify the source. Important for the change consumer to ignore changes originated from self!\n        trans.source = node.id;\n        // 2. Apply uncommitted changes and delete each uncommitted change\n        return db._uncommittedChanges.where('node').equals(node.id).toArray();\n      }).then(function (uncommittedChanges) {\n        return applyChanges(uncommittedChanges);\n      }).then(function () {\n        return db._uncommittedChanges.where('node').equals(node.id).delete();\n      }).then(function () {\n        // 3. Apply last chunk of changes\n        return applyChanges(changes);\n      }).then(function () {\n        // Get what revision we are at now:\n        return db._changes.orderBy('rev').last();\n      }).then(function (lastChange) {\n        var currentLocalRevision = (lastChange && lastChange.rev) || 0;\n        // 4. Update node states (appliedRemoteRevision, remoteBaseRevisions and eventually myRevision)\n        node.appliedRemoteRevision = remoteRevision;\n        node.remoteBaseRevisions.push({remote: remoteRevision, local: currentLocalRevision});\n        if (node.myRevision === localRevisionBeforeChanges) {\n          // If server was up-to-date before we added new changes from the server, update myRevision to last change\n          // because server is still up-to-date! This is also important in order to prohibit getLocalChangesForNode() from\n          // ever sending an empty change list to server, which would otherwise be done every second time it would send changes.\n          node.myRevision = currentLocalRevision;\n        }\n        // Garbage collect remoteBaseRevisions not in use anymore:\n        if (node.remoteBaseRevisions.length > 1) {\n          for (var i = node.remoteBaseRevisions.length - 1; i > 0; --i) {\n            if (node.myRevision >= node.remoteBaseRevisions[i].local) {\n              node.remoteBaseRevisions.splice(0, i);\n              break;\n            }\n          }\n        }\n        // We are not including _syncNodes in transaction, so this save() call will execute in its own transaction.\n        node.save().catch(err=> {\n          console.warn(\"Dexie.Syncable: Unable to save SyncNode after applying remote changes: \" + (err.stack || err));\n        });\n      });\n    });\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/get-local-changes-for-node/get-base-revision-and-max-client-revision.js",
    "content": "export default function getBaseRevisionAndMaxClientRevision(node) {\n  /// <param name=\"node\" type=\"db.observable.SyncNode\"></param>\n  if (node.remoteBaseRevisions.length === 0)\n    return {\n      // No remoteBaseRevisions have arrived yet. No limit on clientRevision and provide null as remoteBaseRevision:\n      maxClientRevision: Infinity,\n      remoteBaseRevision: null\n    };\n  for (var i = node.remoteBaseRevisions.length - 1; i >= 0; --i) {\n    if (node.myRevision >= node.remoteBaseRevisions[i].local) {\n      // Found a remoteBaseRevision that fits node.myRevision. Return remoteBaseRevision and eventually a roof maxClientRevision pointing out where next remoteBaseRevision bases its changes on.\n      return {\n        maxClientRevision: i === node.remoteBaseRevisions.length - 1 ? Infinity : node.remoteBaseRevisions[i + 1].local,\n        remoteBaseRevision: node.remoteBaseRevisions[i].remote\n      };\n    }\n  }\n  // There are at least one item in the list but the server hasn't yet become up-to-date with the 0 revision from client.\n  return {\n    maxClientRevision: node.remoteBaseRevisions[0].local,\n    remoteBaseRevision: null\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/get-local-changes-for-node/get-changes-since-revision.js",
    "content": "import {CREATE, UPDATE} from '../change_types';\nimport mergeChange from '../merge-change';\n\nexport default function initGetChangesSinceRevision(db, node, hasMoreToGive) {\n  return function getChangesSinceRevision(revision, maxChanges, maxRevision, cb) {\n    /// <param name=\"cb\" value=\"function(changes, partial, nodeModificationsOnAck) {}\">Callback that will retrieve next chunk of changes and a boolean telling if it's a partial result or not. If truthy, result is partial and there are more changes to come. If falsy, these changes are the final result.</param>\n    var changeSet = {};\n    var numChanges = 0;\n    var partial = false;\n    var ignoreSource = node.id;\n    var nextRevision = revision;\n    return db.transaction('r', db._changes, function () {\n      var query = db._changes.where('rev').between(revision, maxRevision, false, true);\n      return query.until(() => {\n        if (numChanges === maxChanges) {\n          partial = true;\n          return true;\n        }\n      }).each(function (change) {\n        // Note the revision in nextRevision:\n        nextRevision = change.rev;\n        // change.source is set based on currentTransaction.source\n        if (change.source === ignoreSource) return;\n        // Our _changes table contains more info than required (old objs, source etc). Just make sure to include the necessary info:\n        var changeToSend = {\n          type: change.type,\n          table: change.table,\n          key: change.key\n        };\n        if (change.type === CREATE)\n          changeToSend.obj = change.obj;\n        else if (change.type === UPDATE)\n          changeToSend.mods = change.mods;\n\n        var id = change.table + \":\" + change.key;\n        var prevChange = changeSet[id];\n        if (!prevChange) {\n          // This is the first change on this key. Add it unless it comes from the source that we are working against\n          changeSet[id] = changeToSend;\n          ++numChanges;\n        } else {\n          // Merge the oldchange with the new change\n          var nextChange = changeToSend;\n          var mergedChange = mergeChange(prevChange, nextChange);\n          changeSet[id] = mergedChange;\n        }\n      });\n    }).then(function () {\n      var changes = Object.keys(changeSet).map(function (key) {\n        return changeSet[key];\n      });\n      hasMoreToGive.hasMoreToGive = partial;\n      return cb(changes, partial, {myRevision: nextRevision});\n    });\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/get-local-changes-for-node/get-local-changes-for-node.js",
    "content": "import Dexie from 'dexie';\n\nimport getBaseRevisionAndMaxClientRevision from './get-base-revision-and-max-client-revision';\nimport initGetChangesSinceRevision from './get-changes-since-revision';\nimport initGetTableObjectsAsChanges from './get-table-objects-as-changes';\n\nexport default function initGetLocalChangesForNode(db, hasMoreToGive, partialsThreshold) {\n  var MAX_CHANGES_PER_CHUNK = partialsThreshold;\n\n  return function getLocalChangesForNode(node, cb) {\n    /// <summary>\n    ///     Based on given node's current revision and state, this function makes sure to retrieve next chunk of changes\n    ///     for that node.\n    /// </summary>\n    /// <param name=\"node\"></param>\n    /// <param name=\"cb\" value=\"function(changes, remoteBaseRevision, partial, nodeModificationsOnAck) {}\">Callback that will retrieve next chunk of changes and a boolean telling if it's a partial result or not. If truthy, result is partial and there are more changes to come. If falsy, these changes are the final result.</param>\n\n    const getChangesSinceRevision = initGetChangesSinceRevision(db, node, hasMoreToGive);\n    const getTableObjectsAsChanges = initGetTableObjectsAsChanges(db, node, MAX_CHANGES_PER_CHUNK, getChangesSinceRevision, hasMoreToGive, cb);\n\n    // Only a \"remote\" SyncNode created by Dexie.Syncable\n    // could not pass this test (remote nodes have myRevision: -1 on instantiation)\n    if (node.myRevision >= 0) {\n      // Node is based on a revision in our local database and will just need to get the changes that have occurred since that revision.\n      var brmcr = getBaseRevisionAndMaxClientRevision(node);\n      return getChangesSinceRevision(node.myRevision, MAX_CHANGES_PER_CHUNK, brmcr.maxClientRevision, function (changes, partial, nodeModificationsOnAck) {\n        return cb(changes, brmcr.remoteBaseRevision, partial, nodeModificationsOnAck);\n      });\n    } else {\n      // Node hasn't got anything from our local database yet. We will need to upload the entire DB to the node in the form of CREATE changes.\n      // Check if we're in the middle of already doing that:\n      if (node.dbUploadState === null) {\n        // Initialize dbUploadState\n        var tablesToUpload = db.tables.filter(function (table) {\n          return table.schema.observable;\n        }).map(function (table) {\n          return table.name;\n        });\n        if (tablesToUpload.length === 0) return Dexie.Promise.resolve(cb([], null, false, {})); // There are no synced tables at all.\n        var dbUploadState = {\n          tablesToUpload: tablesToUpload,\n          currentTable: tablesToUpload.shift(),\n          currentKey: null\n        };\n        return db._changes.orderBy('rev').last(function (lastChange) {\n          dbUploadState.localBaseRevision = (lastChange && lastChange.rev) || 0;\n          var collection = db.table(dbUploadState.currentTable).orderBy(':id');\n          return getTableObjectsAsChanges(dbUploadState, [], collection);\n        });\n      } else if (node.dbUploadState.currentKey) {\n        const collection = db.table(node.dbUploadState.currentTable).where(':id').above(node.dbUploadState.currentKey);\n        return getTableObjectsAsChanges(Dexie.deepClone(node.dbUploadState), [], collection);\n      } else {\n        const collection = db.table(dbUploadState.currentTable).orderBy(':id');\n        return getTableObjectsAsChanges(Dexie.deepClone(node.dbUploadState), [], collection);\n      }\n    }\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/get-local-changes-for-node/get-table-objects-as-changes.js",
    "content": "import {CREATE} from '../change_types';\nimport getBaseRevisionAndMaxClientRevision from './get-base-revision-and-max-client-revision';\n\nexport default function initGetTableObjectsAsChanges(db, node, MAX_CHANGES_PER_CHUNK, getChangesSinceRevision, hasMoreToGive, cb) {\n  return function getTableObjectsAsChanges(state, changes, collection) {\n    /// <param name=\"state\" value=\"{tablesToUpload:[''],currentTable:'_changes',currentKey:null,localBaseRevision:0}\"></param>\n    /// <param name=\"changes\" type=\"Array\" elementType=\"IDatabaseChange\"></param>\n    /// <param name=\"collection\" type=\"db.Collection\"></param>\n    var limitReached = false;\n    return collection.until(function () {\n      if (changes.length === MAX_CHANGES_PER_CHUNK) {\n        limitReached = true;\n        return true;\n      }\n    }).each(function (item, cursor) {\n      changes.push({\n        type: CREATE,\n        table: state.currentTable,\n        key: cursor.key,\n        obj: cursor.value\n      });\n      state.currentKey = cursor.key;\n    }).then(function () {\n      if (limitReached) {\n        // Limit reached. Send partial result.\n        hasMoreToGive.hasMoreToGive = true;\n        return cb(changes, null, true, {dbUploadState: state});\n      } else {\n        // Done iterating this table. Check if there are more tables to go through:\n        if (state.tablesToUpload.length === 0) {\n          // Done iterating all tables\n          // Now append changes occurred during our dbUpload:\n          var brmcr = getBaseRevisionAndMaxClientRevision(node);\n          return getChangesSinceRevision(state.localBaseRevision, MAX_CHANGES_PER_CHUNK - changes.length, brmcr.maxClientRevision, function (additionalChanges, partial, nodeModificationsOnAck) {\n            changes = changes.concat(additionalChanges);\n            nodeModificationsOnAck.dbUploadState = null;\n            return cb(changes, brmcr.remoteBaseRevision, partial, nodeModificationsOnAck);\n          });\n        } else {\n          // Not done iterating all tables. Continue on next table:\n          state.currentTable = state.tablesToUpload.shift();\n          return getTableObjectsAsChanges(state, changes, db.table(state.currentTable).orderBy(':id'));\n        }\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/get-or-create-sync-node.js",
    "content": "import Dexie from 'dexie';\n\nimport initPersistedContext from './PersistedContext';\n\nexport default function initGetOrCreateSyncNode(db, protocolName, url) {\n  return function getOrCreateSyncNode(options) {\n    return db.transaction('rw', db._syncNodes, db._changes, function () {\n      if (!url) throw new Error(\"Url cannot be empty\");\n\n      // Returning a promise from transaction scope will make the transaction promise resolve with the value of that promise.\n      return db._syncNodes.where(\"url\").equalsIgnoreCase(url).first(function (node) {\n        // If we found a node it will be instanceof SyncNode as Dexie.Observable\n        // maps to class\n        if (node) {\n          const PersistedContext = initPersistedContext(node);\n          // Node already there. Make syncContext become an instance of PersistedContext:\n          node.syncContext = new PersistedContext(node.id, node.syncContext);\n          node.syncProtocol = protocolName; // In case it was changed (would be very strange but...) could happen...\n          node.syncOptions = options; // Options could have been changed\n          db._syncNodes.put(node);\n        } else {\n          // Create new node and sync everything\n          node = new db.observable.SyncNode();\n          node.myRevision = -1;\n          node.appliedRemoteRevision = null;\n          node.remoteBaseRevisions = [];\n          node.type = \"remote\";\n          node.syncProtocol = protocolName;\n          node.url = url;\n          node.syncOptions = options;\n          node.lastHeartBeat = Date.now();\n          node.dbUploadState = null;\n          const PersistedContext = initPersistedContext(node);\n          Dexie.Promise.resolve(function () {\n            // If options.initialUpload is explicitely false, set myRevision to currentRevision.\n            if (options.initialUpload === false)\n              return db._changes.toCollection().lastKey(function (currentRevision) {\n                node.myRevision = currentRevision;\n              });\n          }()).then(function () {\n            db._syncNodes.add(node).then(function (nodeID) {\n              node.syncContext = new PersistedContext(nodeID); // Update syncContext in db with correct nodeId.\n              db._syncNodes.put(node);\n            });\n          });\n        }\n\n        return node; // returning node will make the db.transaction()-promise resolve with this value.\n      });\n    });\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/merge-change.js",
    "content": "import { CREATE, UPDATE, DELETE } from './change_types';\nimport combineCreateAndUpdate from './combine-create-and-update.js';\nimport combineUpdateAndUpdate from './combine-update-and-update.js';\n\nexport default function mergeChange(prevChange, nextChange) {\n  switch (prevChange.type) {\n    case CREATE:\n      switch (nextChange.type) {\n        case CREATE:\n          return nextChange; // Another CREATE replaces previous CREATE.\n        case UPDATE:\n          return combineCreateAndUpdate(prevChange, nextChange); // Apply nextChange.mods into prevChange.obj\n        case DELETE:\n          return nextChange; // Object created and then deleted. If it wasnt for that we MUST handle resent changes, we would skip entire change here. But what if the CREATE was sent earlier, and then CREATE/DELETE at later stage? It would become a ghost object in DB. Therefore, we MUST keep the delete change! If object doesnt exist, it wont harm!\n      }\n      break;\n    case UPDATE:\n      switch (nextChange.type) {\n        case CREATE:\n          return nextChange; // Another CREATE replaces previous update.\n        case UPDATE:\n          return combineUpdateAndUpdate(prevChange, nextChange); // Add the additional modifications to existing modification set.\n        case DELETE:\n          return nextChange; // Only send the delete change. What was updated earlier is no longer of interest.\n      }\n      break;\n    case DELETE:\n      switch (nextChange.type) {\n        case CREATE:\n          return nextChange; // A resurection occurred. Only create change is of interest.\n        case UPDATE:\n          return prevChange; // Nothing to do. We cannot update an object that doesnt exist. Leave the delete change there.\n        case DELETE:\n          return prevChange; // Still a delete change. Leave as is.\n      }\n      break;\n  }\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/save-to-uncommitted-changes.js",
    "content": "export default function initSaveToUncommittedChanges(db, node) {\n  return function saveToUncommittedChanges(changes, remoteRevision) {\n    return db.transaction('rw!', db._uncommittedChanges, () => {\n      return db._uncommittedChanges.bulkAdd(changes.map(change => {\n        let changeWithNodeId = {\n          node: node.id,\n          type: change.type,\n          table: change.table,\n          key: change.key\n        };\n        if (change.obj) changeWithNodeId.obj = change.obj;\n        if (change.mods) changeWithNodeId.mods = change.mods;\n        return changeWithNodeId;\n      }));\n    }).then(() => {\n      node.appliedRemoteRevision = remoteRevision;\n      return node.save();\n    });\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/statuses.js",
    "content": "export const Statuses = {\n  ERROR: -1, // An irreparable error occurred and the sync provider is dead.\n  OFFLINE: 0, // The sync provider hasn't yet become online, or it has been disconnected.\n  CONNECTING: 1, // Trying to connect to server\n  ONLINE: 2, // Connected to server and currently in sync with server\n  SYNCING: 3, // Syncing with server. For poll pattern, this is every poll call. For react pattern, this is when local changes are being sent to server.\n  ERROR_WILL_RETRY: 4 // An error occurred such as net down but the sync provider will retry to connect.\n};\n\nexport const StatusTexts = {\n  \"-1\": \"ERROR\",\n  \"0\": \"OFFLINE\",\n  \"1\": \"CONNECTING\",\n  \"2\": \"ONLINE\",\n  \"3\": \"SYNCING\",\n  \"4\": \"ERROR_WILL_RETRY\"\n};\n"
  },
  {
    "path": "addons/Dexie.Syncable/src/syncable-connect.js",
    "content": "import Dexie from 'dexie';\n\nconst Promise = Dexie.Promise;\n\nexport default function initSyncableConnect(db, connect) {\n  return function syncableConnect(protocolInstance, protocolName, url, options) {\n    if (db.isOpen()) {\n      // Database is open\n      if (!db._localSyncNode)\n        throw new Error(\"Precondition failed: local sync node is missing. Make sure Dexie.Observable is active!\");\n\n      if (db._localSyncNode.isMaster) {\n        // We are master node\n        return connect(protocolInstance, protocolName, url, options, db._localSyncNode.id);\n      } else {\n        // We are not master node\n        // Request master node to do the connect:\n        return db.table('_syncNodes').where('isMaster').above(0).first(function (masterNode) {\n          // There will always be a master node. In theory we may self have become master node when we come here. But that's ok. We'll request ourselves.\n          return db.observable.sendMessage('connect', {\n            protocolName: protocolName,\n            url: url,\n            options: options\n          }, masterNode.id, {wantReply: true});\n        });\n      }\n    } else if (db.hasBeenClosed()) {\n      // Database has been closed.\n      return Promise.reject(new Dexie.DatabaseClosedError());\n    } else if (db.hasFailed()) {\n      // Database has failed to open\n      return Promise.reject(new Dexie.InvalidStateError(\n          \"Dexie.Syncable: Cannot connect. Database has failed to open\"));\n    } else {\n      // Database not yet open. It may be on its way to open, or open() hasn't yet been called.\n      // Wait for it to open, then connect.\n      var promise = new Promise(function (resolve, reject) {\n        db.on(\"ready\", () => {\n          // First, check if this is the very first time we connect to given URL.\n          // Need to know, because if it is, we should stall the promise returned to\n          // db.on('ready') to not be fulfilled until the initial sync has succeeded.\n          return db._syncNodes.get({url}, node => {\n            // Ok, now we know whether we should await the connect promise or not.\n            // No matter, we should now connect (will maybe create the SyncNode instance\n            // representing the given URL)\n            let connectPromise = db.syncable.connect(protocolName, url, options);\n            connectPromise.then(resolve, reject);// Resolve the returned promise when connected.\n            // Ok, so let's see if we should suspend DB queries until connected or not:\n            if (node && node.appliedRemoteRevision) {\n              // The very first initial sync has been done so we need not wait\n              // for the connect promise to complete. It can continue in background.\n              // Returning here will resume db.on('ready') and resume all queries that\n              // the application has put to the database.\n              return;\n            }\n            // This was the very first time we connect to the remote server,\n            // we must make sure that the initial sync request succeeeds before resuming\n            // database queries that the application code puts onto the database.\n            // If OFFLINE or other error, don't allow the application to proceed.\n            // We are assuming that an initial sync is essential for the application to\n            // function correctly.\n            return connectPromise;\n          });\n        });\n        // Force open() to happen. Otherwise connect() may stall forever.\n        db.open().catch(ex => {\n          // If open fails, db.on('ready') may not have been called and we must\n          // reject promise with InvalidStateError\n          reject(new Dexie.InvalidStateError(\n              `Dexie.Syncable: Couldn't connect. Database failed to open`,\n              ex\n          ));\n        });\n      });\n      return promise;\n    }\n  };\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/gh-actions.sh",
    "content": "#!/bin/bash -e\ncd ../../Dexie.Observable\necho \"Installing dependencies for dexie-observable\"\npnpm install >/dev/null\necho \"Building dexie-observable\"\npnpm run build\ncd -\necho \"Installing dependencies for dexie-syncable\"\npnpm install >/dev/null\necho \"Building dexie-syncable\"\npnpm run build\n\npnpm run test:typings\npnpm run test:ltcloud\npnpm run test:ltcloud:integration\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/integration/dummy-sync-protocol.js",
    "content": "Dexie.Syncable.registerSyncProtocol(\"logger\", {\n    sync: function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n        /// <param name=\"changes\" type=\"Array\" elementType=\"IDatabaseChange\"></param>\n        /// <param name=\"applyRemoteChanges\" value=\"function (changes, lastRevision, partial, clear) {}\"></param>\n        /// <param name=\"onSuccess\" value=\"function (continuation) {}\"></param>\n        console.log(\"sync(changes.length: \"+ changes.length + \", baseRevision:\" + baseRevision + \", \" + (partial ? \"partial\" : \"full\") + \", syncedRevision:\" + syncedRevision + \")\");\n        changes.forEach(function (change) {\n            console.log(JSON.stringify(change, null, true));\n        });\n    \n        setTimeout(function () {\n            var dummyRev = 1,\n                CREATE = 1,\n                UPDATE = 2,\n                DELETE = 3;\n\n            onChangesAccepted().catch('DatabaseClosedError', function () {\n                console.log(\"Got DatabaseClosedError while calling onChangesAccepted()\");\n            });\n\n            applyRemoteChanges([], \"ServerRevision\" + dummyRev++, true, false).catch('DatabaseClosedError', function() {\n                console.log(\"Got DatabaseClosedError while calling applyRemoteChanges()\");\n            });\n            applyRemoteChanges([], \"ServerRevision\" + dummyRev++, false, false).catch('DatabaseClosedError', function(){\n                console.log(\"Got DatabaseClosedError while calling applyRemoteChanges()\");\n            })\n\n            /*var dummyPoller = setInterval(function(){\n            applyRemoteChanges([], \"ServerRevision\" + dummyRev++, true, false);\n            applyRemoteChanges([], \"ServerRevision\" + dummyRev++, false, false);\n        }, 10);*/\n        \n            onSuccess({\n                react: function (changes, baseRevision, partial, onChangesAccepted) {\n                    console.log(\"react(changes.length: \" + changes.length + \", baseRevision:\" + baseRevision + \", \" + (partial ? \"partial\" : \"full\") + \")\");\n                    changes.forEach(function (change) {\n                        console.log(JSON.stringify(change, null, true));\n                    });\n                    setTimeout(onChangesAccepted, 0);\n                },\n                disconnect: function () {\n                    console.log(\"disconned()\");\n                    //clearInterval(dummyPoller);\n                }\n            });\n        }, 0);\n    }\n});\n\n// Make sure to always call sync() before any call to open().\nDexie.addons.push(function (db) {\n    db.open = Dexie.override(db.open, function (origFunc) {\n        return function () {\n            if (!db.dynamicallyOpened() && !db.connectAlreadyCalled) {\n                db.connectAlreadyCalled = true;\n                console.log(\"Calling db.sync() on \" + db.name);\n                db.syncable.connect(\"logger\", \"logger\").then(function() {\n                    console.log(\"connect() promise was resolved (database=\" + db.name + \")\");\n                }).catch('DatabaseClosedError', function(){\n                }).catch(function(err) {\n                    console.error(\"Error from connect() (database=\" + db.name + \"): \" + err.stack || err);\n                });\n            }\n            return origFunc.apply(this, arguments);\n        }\n    });\n    db.close = Dexie.override(db.close, function (origFunc) {\n        return function() {\n            db.connectAlreadyCalled = false;\n            return origFunc.apply(this, arguments);\n        }\n    });\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/integration/karma-env.js",
    "content": "// workerImports will be used by tests-open.js in the dexie test suite when\n// launching a Worker. This line will instruct the worker to import dexie-observable\n// and dexie-syncable.\nwindow.workerImports.push(\"../addons/Dexie.Observable/dist/dexie-observable.js\");\nwindow.workerImports.push(\"../addons/Dexie.Syncable/dist/dexie-syncable.js\");\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/integration/karma.conf.js",
    "content": "// Include common configuration\nconst {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../../../test/karma.common');\n\nmodule.exports = function (config) {\n  const browserMatrixOverrides = {\n    // Be fine with testing on local travis firefox + browserstack chrome, latest supported.\n    ci: [\"remote_chrome\"],\n    // Safari fails to reply on browserstack. Need to not have it here.\n    // Just complement with old chrome browser that is not part of CI test suite.\n    pre_npm_publish: [\n      \"Chrome\",\n    ]\n  };\n\n  const cfg = getKarmaConfig(browserMatrixOverrides, {\n    // Base path should point at the root \n    basePath: '../../../../',\n    // The files needed to apply dexie-observable to the standard dexie unit tests.\n    files: karmaCommon.files.concat([\n      'dist/dexie.js',\n      'addons/Dexie.Syncable/test/integration/karma-env.js',\n      'addons/Dexie.Observable/dist/dexie-observable.js', // Apply observable addon\n      'addons/Dexie.Syncable/dist/dexie-syncable.js', // Apply syncable addon\n      'addons/Dexie.Syncable/test/integration/dummy-sync-protocol.js',\n      'test/bundle.js', // The dexie standard test suite\n      { pattern: 'addons/Dexie.Observable/dist/*.map', watched: false, included: false },\n      { pattern: 'addons/Dexie.Syncable/dist/*.map', watched: false, included: false }\n    ])\n  });\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/integration/test-syncable-dexie-tests.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Dexie Unit tests with Dexie.Syncable applied and dummy ISyncProtocol</title>\n  <base href=\"../../../../test/\" />\n  <link rel=\"stylesheet\" href=\"../node_modules/qunitjs/qunit/qunit.css\">\n</head>\n    <body>\n        <div id=\"qunit\"></div>\n        <div id=\"qunit-fixture\"></div>\n        <script src=\"babel-polyfill/polyfill.min.js\"></script>\n        <script>\n            // Hack for making the WebWorker test behave also here...\n            window.workerSource = \"worker.js\";\n            window.workerImports = [\n                \"../dist/dexie.js\",\n                \"../addons/Dexie.Observable/dist/dexie-observable.js\",\n                \"../addons/Dexie.Syncable/dist/dexie-syncable.js\"\n            ];\n        </script>\n        <script src=\"../node_modules/qunitjs/qunit/qunit.js\"></script>\n        <script src=\"../dist/dexie.js\"></script>\n        <script src=\"../addons/Dexie.Observable/dist/dexie-observable.js\"></script>\n        <script src=\"../addons/Dexie.Syncable/dist/dexie-syncable.js\"></script>\n        <script src=\"../addons/Dexie.Syncable/test/integration/dummy-sync-protocol.js\"></script>\n        <script src=\"bundle.js\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/test-typings/test-typings.ts",
    "content": "\nimport Dexie from 'dexie';\nimport 'dexie-observable';\nimport '../../src/Dexie.Syncable';\nimport dexieSyncable from '../../src/Dexie.Syncable';\nimport {DatabaseChangeType} from '../../api';\nimport {IDatabaseChange} from 'dexie-observable/api';\n\n//\n// Typings for registerSyncProtocol():\n//\nDexie.Syncable.registerSyncProtocol(\"myProtocol\", {\n    sync: (context,\n        url,\n        options,\n        baseRevision,\n        syncedRevision,\n        changes,\n        partial,\n        applyRemoteChanges,\n        onChangesAccepted,\n        onSuccess,\n        onError) => {\n            context[\"customProp\"] = 3;\n            context.save().catch(ex=>{});\n            url.toLowerCase();\n            changes.forEach(change => {\n                if (change.type === DatabaseChangeType.Create) {\n                    change.obj.hello; // Should be able to access custom props on change.obj.\n                    change.key;\n                    change.table.toLowerCase();\n                    change.type.toExponential();\n                } else if (change.type === DatabaseChangeType.Update) {\n                    Object.keys(change.mods).forEach(keyPath => {\n                        change.mods[keyPath];\n                    });\n                } else if (change.type === DatabaseChangeType.Delete) {\n                    change.key;\n                    change.table;\n                }\n            });\n            partial.valueOf(); // boolean\n            applyRemoteChanges(changes, {anyType:'anyValue'}, true);\n            applyRemoteChanges(changes, {anyType:'anyValue'});\n            onChangesAccepted();\n            onError(new Error(\"hoohoo\"), 5000);\n            // Poll pattern typings:\n            onSuccess({ again: 5000 }); \n            // React pattern typings:\n            onSuccess({\n                react (changes, baseRevision, partial, onChangesAccepted) {\n                    changes.forEach(change => change.key && change.table.toUpperCase() && change.type.toExponential());\n                    baseRevision;\n                    partial.valueOf();\n                    onChangesAccepted();\n                },\n                disconnect(){}\n            });\n        }\n});\n\n//\n// 2. Declare Your Database.\n//\nclass Foo {\n    id: Date;\n    bar() {};\n    age: number;\n    address: {\n        city: string;\n    }\n}\n\nclass MyDb extends Dexie {\n    foo: Dexie.Table<Foo, Date>;\n    constructor() {\n        super('mydb', {addons: [dexieSyncable, Dexie.Syncable]});\n        this.version(1).stores({foo: 'id'});\n        //\n        // Connect\n        //\n        this.syncable.connect(\n            \"myProtocol\",\n            \"https://remote-server/...\",\n            {anyOption: 'anyValue'})\n        .catch(err => {\n            console.error (`Failed to connect: ${err.stack || err}`);\n        });\n    }\n}\n\nvar db = new MyDb();\n// Start using the database as normal.\ndb.foo.where('x').notEqual(1).toArray(foos => {\n    foos.forEach(foo => foo.bar());\n}).catch(err => {\n});\ndb.foo.get(new Date()).then(foo => foo && foo.bar());\n\ndb.syncable.disconnect(\"myUrl\");\ndb.syncable.delete(\"myUrl\");\ndb.syncable.getStatus(\"myUrl\").finally(()=>{}).catch('DatabaseClosedError', ex => ex.name);\n/* BUGBUG: Fails! See issue #537. Need to fix db.syncable.list().then(urls => Promise.all(\n    urls.map(url => db.syncable.getStatus(url).then (status => ({url: url, status: status})))\n)).then(urlsAndStatuses => {\n    urlsAndStatuses.forEach(urlAndStatus => {\n        urlAndStatus.url.toLowerCase();\n        urlAndStatus.status.toExponential();\n    });\n});*/\n// With async/await\nasync function getUrlsAndStatuses() {\n    let urls = await db.syncable.list();\n    let statuses = await Dexie.Promise.all(urls.map(url => db.syncable.getStatus(url)));\n}\n\nfunction statusChanged(status: number, url: string) {\n    status.toExponential();\n    url.toLowerCase();\n}\ndb.syncable.on('statusChanged', statusChanged);\ndb.syncable.on('statusChanged').unsubscribe(statusChanged);\n\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/test-typings/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"es6\",\n        \"target\": \"es5\",\n        \"noImplicitAny\": true,\n        \"strictNullChecks\": true,\n        \"outDir\": \"../../tools/tmp/test-typings\",\n        \"moduleResolution\": \"node\",\n        \"lib\": [\"dom\", \"es2020\"]\n    },\n    \"files\": [\n        \"test-typings.ts\"\n    ]\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/.eslintrc.json",
    "content": "{\n  \"parserOptions\": {\n    \"ecmaVersion\": 6,\n    \"sourceType\": \"module\",\n    \"ecmaFeatures\": {\n    }\n  },\n  \"env\": {\n    \"browser\": true,\n    \"node\": true\n  },\n  \"rules\": {\n    \"no-undef\": [\"error\"]\n  },\n  \"globals\": {\n    \"Promise\": true\n  }\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/.gitignore",
    "content": "/bundle.js\n/bundle.js.map\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/get-local-changes-for-node/tests-get-base-revision-and-max-client-revision.js",
    "content": "import {module, test, deepEqual, equal, ok} from 'QUnit';\nimport getBaseRevisionAndMaxClientRevision from '../../../src/get-local-changes-for-node/get-base-revision-and-max-client-revision';\n\nmodule('getBaseRevisionAndMaxClientRevision', {\n  setup: () => {\n  },\n  teardown: () => {\n  }\n});\n\ntest('maxClientRevision should be Infinity and remoteBaseRevision null if we haven\\'t gotten any server changes yet', () => {\n  const syncNode = {\n    remoteBaseRevisions: []\n  };\n\n  const res = getBaseRevisionAndMaxClientRevision(syncNode);\n  deepEqual(res, { maxClientRevision: Infinity, remoteBaseRevision: null});\n});\n\ntest('remoteBaseRevision should be null and maxClientRevision should match the first server change if the sync node\\'s revision is not bigger that at least one remoteBaseRevisions', () => {\n  const syncNode = {\n    remoteBaseRevisions: [{ local: 2, remote: 1 }, { local: 3, remote: 1 }],\n    myRevision: 1\n  };\n\n  const res = getBaseRevisionAndMaxClientRevision(syncNode);\n  deepEqual(res, { maxClientRevision: 2, remoteBaseRevision: null});\n});\n\ntest('remoteBaseRevision should have the value of \"remote\" for a remoteBaseRevision\\'s \"local\" matching \"myRevision\"', () => {\n  const syncNode = {\n    remoteBaseRevisions: [{ local: 2, remote: 1 }, { local: 3, remote: 2 }],\n    myRevision: 2\n  };\n\n  const res = getBaseRevisionAndMaxClientRevision(syncNode);\n  equal(res.remoteBaseRevision, 1);\n});\n\ntest('maxClientRevision should have the value of the next \"local\" for a remoteBaseRevision\\'s \"local\" matching \"myRevision\"', () => {\n  const syncNode = {\n    remoteBaseRevisions: [{ local: 2, remote: 1 }, { local: 3, remote: 2 }],\n    myRevision: 2\n  };\n\n  const res = getBaseRevisionAndMaxClientRevision(syncNode);\n  equal(res.maxClientRevision, 3);\n});\n\ntest('maxClientRevision should be Infinity if the last remoteBaseRevision\\'s \"local\" matches \"myRevision\"', () => {\n  const syncNode = {\n    remoteBaseRevisions: [{ local: 2, remote: 1 }, { local: 3, remote: 2 }],\n    myRevision: 3\n  };\n\n  const res = getBaseRevisionAndMaxClientRevision(syncNode);\n  equal(res.maxClientRevision, Infinity);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/get-local-changes-for-node/tests-get-changes-since-revision.js",
    "content": "import Dexie from 'dexie';\nimport 'dexie-observable';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\nimport {resetDatabase} from '../../../../../test/dexie-unittest-utils';\nimport initGetChangesSinceRevision from '../../../src/get-local-changes-for-node/get-changes-since-revision';\nimport {CREATE, UPDATE} from '../../../src/change_types';\n\nconst db = new Dexie('TestDBTable');\ndb.version(1).stores({\n  foo: \"id\"\n});\n\nconst syncNode = new db.observable.SyncNode();\nconst nodeID = 1;\nsyncNode.id = nodeID;\nconst hasMoreToGive = {hasMoreToGive: false};\nconst getChangesSinceRevision = initGetChangesSinceRevision(db, syncNode, hasMoreToGive);\nmodule('getChangesSinceRevision', {\n  setup: () => {\n    stop();\n\n    // Do a full DB reset to clean _changes table\n    db._hasBeenCreated = false;\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should return relevant (between revision and maxRevision) changes', () => {\n  const createChange1 = {\n    rev: 1,\n    key: 1,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange2 = {\n    rev: 2,\n    key: 2,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange3 = {\n    rev: 3,\n    key: 3,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange4 = {\n    rev: 4,\n    key: 4,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const revision = 1;\n  const maxChanges = 10;\n  const maxRevision = 3;\n  function cb(changes, partial, revisionObject) {\n    strictEqual(revisionObject.myRevision, createChange3.rev, 'myRevision');\n    strictEqual(partial, false, 'is not a partial change');\n    strictEqual(changes.length, 2, 'get only 2 changes');\n    deepEqual(changes, [{\n      key: 2,\n      table: 'foo',\n      type: CREATE,\n      obj: {foo: 'bar'}\n    }, {\n      key: 3,\n      table: 'foo',\n      type: CREATE,\n      obj: {foo: 'bar'}\n    }], 'changes');\n    deepEqual(hasMoreToGive, {hasMoreToGive: false}, 'hasMoreToGive remains false');\n  }\n  const changesToAdd = [createChange1, createChange2, createChange3, createChange4];\n  db._changes.bulkAdd(changesToAdd)\n    .then(() => {\n      return getChangesSinceRevision(revision, maxChanges, maxRevision, cb)\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should ignore a change if it was set by this node', () => {\n  const createChange1 = {\n    rev: 1,\n    key: 1,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange2 = {\n    rev: 2,\n    key: 2,\n    type: CREATE,\n    source: 1,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange3 = {\n    rev: 3,\n    key: 3,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange4 = {\n    rev: 4,\n    key: 4,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const revision = 1;\n  const maxChanges = 10;\n  const maxRevision = 3;\n  function cb(changes/*, partial, revisionObject*/) {\n    strictEqual(changes.length, 1, 'get only 1 changes');\n    deepEqual(changes, [{\n      key: 3,\n      table: 'foo',\n      type: CREATE,\n      obj: {foo: 'bar'}\n    }], 'changes');\n  }\n  const changesToAdd = [createChange1, createChange2, createChange3, createChange4];\n  db._changes.bulkAdd(changesToAdd)\n    .then(() => {\n      return getChangesSinceRevision(revision, maxChanges, maxRevision, cb)\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should merge changes', () => {\n  const createChange1 = {\n    rev: 2,\n    key: 2,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange2 = {\n    rev: 3,\n    key: 2,\n    type: UPDATE,\n    source: 2,\n    table: 'foo',\n    mods: {foo: 'baz'}\n  };\n  const revision = 1;\n  const maxChanges = 10;\n  const maxRevision = 3;\n  function cb(changes/*, partial, revisionObject*/) {\n    strictEqual(changes.length, 1, 'get only 1 changes');\n    deepEqual(changes, [{\n      key: 2,\n      table: 'foo',\n      type: CREATE,\n      obj: {foo: 'baz'}\n    }], 'changes');\n  }\n  const changesToAdd = [createChange1, createChange2];\n  db._changes.bulkAdd(changesToAdd)\n    .then(() => {\n      return getChangesSinceRevision(revision, maxChanges, maxRevision, cb)\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should set hasMoreToGive to true if we have more changes than maxChanges', () => {\n  const createChange1 = {\n    rev: 1,\n    key: 1,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange2 = {\n    rev: 2,\n    key: 2,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange3 = {\n    rev: 3,\n    key: 3,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange4 = {\n    rev: 4,\n    key: 4,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const revision = 1;\n  const maxChanges = 2;\n  const maxRevision = 4;\n  hasMoreToGive.hasMoreToGive = false;\n  function cb(changes, partial, revisionObject) {\n    strictEqual(revisionObject.myRevision, createChange3.rev, 'myRevision');\n    strictEqual(partial, true, 'is a partial change');\n    strictEqual(changes.length, 2, 'get only 2 changes');\n    deepEqual(hasMoreToGive, {hasMoreToGive: true}, 'hasMoreToGive is true');\n  }\n  const changesToAdd = [createChange1, createChange2, createChange3, createChange4];\n  db._changes.bulkAdd(changesToAdd)\n      .then(() => {\n        return getChangesSinceRevision(revision, maxChanges, maxRevision, cb)\n      })\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n\n\nasyncTest('should set hasMoreToGive to true but give no changes if maxChanges is 0', () => {\n  const createChange1 = {\n    rev: 1,\n    key: 1,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange2 = {\n    rev: 2,\n    key: 2,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange3 = {\n    rev: 3,\n    key: 3,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const createChange4 = {\n    rev: 4,\n    key: 4,\n    type: CREATE,\n    source: 2,\n    table: 'foo',\n    obj: {foo: 'bar'}\n  };\n  const revision = 1;\n  const maxChanges = 0;\n  const maxRevision = 4;\n  hasMoreToGive.hasMoreToGive = false;\n  function cb(changes, partial, revisionObject) {\n    strictEqual(revisionObject.myRevision, revision, 'revision should not change');\n    strictEqual(partial, true, 'is a partial change');\n    strictEqual(changes.length, 0, 'get no changes');\n    deepEqual(hasMoreToGive, {hasMoreToGive: true}, 'hasMoreToGive is true');\n  }\n  const changesToAdd = [createChange1, createChange2, createChange3, createChange4];\n  db._changes.bulkAdd(changesToAdd)\n      .then(() => {\n        return getChangesSinceRevision(revision, maxChanges, maxRevision, cb)\n      })\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/get-local-changes-for-node/tests-get-local-changes-for-node.js",
    "content": "import Dexie from 'dexie';\nimport 'dexie-observable';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\nimport {resetDatabase} from '../../../../../test/dexie-unittest-utils';\nimport initGetLocalChangesForNode from '../../../src/get-local-changes-for-node/get-local-changes-for-node';\nimport {CREATE} from '../../../src/change_types';\n\nconst db = new Dexie('TestDBTable');\ndb.version(1).stores({\n  foo: \"id\",\n  bar: \"id\"\n});\n\nconst syncNode = new db.observable.SyncNode();\nconst nodeID = 1;\nsyncNode.id = nodeID;\nconst hasMoreToGive = {hasMoreToGive: false};\nmodule('getLocalChangesForNode', {\n  setup: () => {\n    stop();\n\n    // Do a full DB reset to clean _changes table\n    db._hasBeenCreated = false;\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should get the contents of our tables and create CREATE changes if node.myRevision is -1', () => {\n  syncNode.myRevision = -1;\n  syncNode.dbUploadState = null;\n  syncNode.remoteBaseRevisions = [];\n  const getLocalChangesForNode = initGetLocalChangesForNode(db, hasMoreToGive, 10);\n  const fooTableObject = {\n    id: 1,\n    foo: 'bar'\n  };\n  const barTableObject = {\n    id: 1,\n    bar: 'foo'\n  };\n  function cb(changes/*, remoteBaseRevision, partial, nodeModificationsOnAck*/) {\n    strictEqual(changes.length, 2, 'We have 2 changes');\n    deepEqual(changes, [{\n      key: 1,\n      obj: fooTableObject,\n      type: CREATE,\n      table: 'foo'\n    }, {\n      key: 1,\n      obj: barTableObject,\n      type: CREATE,\n      table: 'bar'\n    }], 'Changes match the objects in the tables');\n    deepEqual(hasMoreToGive, {hasMoreToGive: false}, 'it should\\'t change hasMoreToGive');\n  }\n  db.foo.add(fooTableObject)\n    .then(() => {\n      return db.bar.add(barTableObject);\n    })\n    .then(() => {\n      return getLocalChangesForNode(syncNode, cb);\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should return changes in _changes if myRevision >= 0', () => {\n  syncNode.myRevision = -1;\n  syncNode.dbUploadState = null;\n  syncNode.remoteBaseRevisions = [];\n  const getLocalChangesForNode = initGetLocalChangesForNode(db, hasMoreToGive, 10);\n  const fooTableObject1 = {\n    id: 1,\n    foo: 'bar'\n  };\n  const fooTableObject2 = {\n    id: 2,\n    foo: 'foobar'\n  };\n  function cb(changes/*, remoteBaseRevision, partial, nodeModificationsOnAck*/) {\n    strictEqual(changes.length, 2, 'We have 2 changes');\n    deepEqual(changes, [{\n      key: 1,\n      obj: fooTableObject1,\n      type: CREATE,\n      table: 'foo'\n    }, {\n      key: 2,\n      obj: fooTableObject2,\n      type: CREATE,\n      table: 'foo'\n    }], 'Changes match the objects in the tables');\n    deepEqual(hasMoreToGive, {hasMoreToGive: false}, 'it should\\'t change hasMoreToGive');\n  }\n  // This also adds changes to _changes\n  db.foo.bulkAdd([fooTableObject1, fooTableObject2])\n    .then(() => {\n      return getLocalChangesForNode(syncNode, cb);\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should return partial changes in _changes if myRevision >= 0 and threshold was reached', () => {\n  syncNode.myRevision = -1;\n  syncNode.dbUploadState = null;\n  syncNode.remoteBaseRevisions = [];\n  hasMoreToGive.hasMoreToGive = false;\n  const getLocalChangesForNode = initGetLocalChangesForNode(db, hasMoreToGive, 1);\n  const fooTableObject1 = {\n    id: 1,\n    foo: 'bar'\n  };\n  const fooTableObject2 = {\n    id: 2,\n    foo: 'foobar'\n  };\n  function cb(changes/*, remoteBaseRevision, partial, nodeModificationsOnAck*/) {\n    strictEqual(changes.length, 1, 'We have 1 change');\n    deepEqual(changes, [{\n      key: 1,\n      obj: fooTableObject1,\n      type: CREATE,\n      table: 'foo'\n    }], 'Changes match the objects in the tables');\n    deepEqual(hasMoreToGive, {hasMoreToGive: true}, 'it should change hasMoreToGive');\n  }\n  // This also adds changes to _changes\n  db.foo.bulkAdd([fooTableObject1, fooTableObject2])\n      .then(() => {\n        return getLocalChangesForNode(syncNode, cb);\n      })\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n\nasyncTest('should not return any changed if the threshold is 0 but should set hasMoreToGive to true', () => {\n  syncNode.myRevision = -1;\n  syncNode.dbUploadState = null;\n  syncNode.remoteBaseRevisions = [];\n  hasMoreToGive.hasMoreToGive = false;\n  const getLocalChangesForNode = initGetLocalChangesForNode(db, hasMoreToGive, 0);\n  const fooTableObject1 = {\n    id: 1,\n    foo: 'bar'\n  };\n  const fooTableObject2 = {\n    id: 2,\n    foo: 'foobar'\n  };\n  function cb(changes/*, remoteBaseRevision, partial, nodeModificationsOnAck*/) {\n    strictEqual(changes.length, 0, 'We have 0 changes');\n    deepEqual(hasMoreToGive, {hasMoreToGive: true}, 'it should change hasMoreToGive');\n  }\n  // This also adds changes to _changes\n  db.foo.bulkAdd([fooTableObject1, fooTableObject2])\n      .then(() => {\n        return getLocalChangesForNode(syncNode, cb);\n      })\n      .catch(function(err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/karma-env.js",
    "content": "QUnit.config.autostart = false;\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/karma.conf.js",
    "content": "// Include common configuration\nconst {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../../../test/karma.common');\n\nmodule.exports = function (config) {\n  const browserMatrixOverrides = {\n    // Be fine with testing on local travis firefox + browserstack chrome, latest supported.\n    ci: [\"remote_chrome\"],\n    // Safari fails to reply on browserstack. Need to not have it here.\n    // Just complement with old chrome browser that is not part of CI test suite.\n    pre_npm_publish: [\n      \"Chrome\",\n    ]\n  };\n\n  const cfg = getKarmaConfig(browserMatrixOverrides, {\n    // Base path should point at the root \n    basePath: '../../../../',\n    files: karmaCommon.files.concat([\n      'dist/dexie.js',\n      'addons/Dexie.Observable/dist/dexie-observable.js',\n\n      'samples/remote-sync/websocket/websocketserver-shim.js',\n      'samples/remote-sync/websocket/WebSocketSyncServer.js',// With shim applied, we can run the server in the browser\n\n      'addons/Dexie.Syncable/test/unit/bundle.js',\n      { pattern: 'addons/Dexie.Observable/dist/*.map', watched: false, included: false },\n      { pattern: 'addons/Dexie.Syncable/dist/*.map', watched: false, included: false },\n      { pattern: 'addons/Dexie.Syncable/test/unit/*.map', watched: false, included: false },\n    ])\n  });\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/run-unit-tests.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Dexie.Syncable Unit tests</title>\n  <link rel=\"stylesheet\" href=\"../../../../node_modules/qunitjs/qunit/qunit.css\">\n</head>\n<body>\n    <div id=\"qunit\"></div>\n    <div id=\"qunit-fixture\"></div>\n    <script src=\"../../../../test/babel-polyfill/polyfill.min.js\"></script>\n    <script src=\"../../../../node_modules/qunitjs/qunit/qunit.js\"></script>\n    <script src=\"../../../../dist/dexie.js\"></script>\n    <script src=\"../../../Dexie.Observable/dist/dexie-observable.js\"></script>\n    <script src=\"../../dist/dexie-syncable.js\"></script>\n    <script src=\"../../../../samples/remote-sync/websocket/websocketserver-shim.js\"></script>\n    <script src=\"../../../../samples/remote-sync/websocket/WebSocketSyncServer.js\"></script> <!-- With shim applied, we can run the server in the browser -->\n    <script src=\"bundle.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-PersistedContext.js",
    "content": "import Dexie from 'dexie';\nimport observable from 'dexie-observable';\n// Add this so we have the SyncNode.prototype.save method\nimport syncable from '../../src/Dexie.Syncable';\nimport {module, asyncTest, test, start, stop, propEqual, deepEqual, ok} from 'QUnit';\nimport {resetDatabase} from '../../../../test/dexie-unittest-utils';\nimport initPersistedContext from '../../src/PersistedContext';\n\nconst db = new Dexie('TestDBTable', {addons: [observable, syncable]});\ndb.version(1).stores({foo: '++id'});\n\nmodule('PersistedContext', {\n  setup: () => {\n    stop();\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should save any properties we add to the context into the DB', () => {\n  const syncNode = new db.observable.SyncNode();\n  const PersistedContext = initPersistedContext(syncNode);\n  let addedNodeID;\n  db._syncNodes.add(syncNode)\n  .then((nodeID) => {\n    addedNodeID = nodeID;\n    const persistedContext = new PersistedContext(syncNode.id);\n    syncNode.syncContext = persistedContext;\n    persistedContext.foobar = 'foobar';\n    return persistedContext.save()\n  })\n  .then(() => {\n    return db._syncNodes.get(addedNodeID);\n  })\n  .then((node) => {\n    deepEqual(node.syncContext, {foobar: 'foobar', nodeID: addedNodeID});\n  })\n  .catch(function(err) {\n    ok(false, \"Error: \" + err);\n  })\n  .finally(start);\n});\n\ntest('should extend the instance with the given options object', () => {\n  const syncNode = new db.observable.SyncNode();\n  const PersistedContext = initPersistedContext(syncNode);\n  const persistedContext = new PersistedContext(1, {foo: 'bar'});\n  propEqual(persistedContext, {nodeID: 1, foo: 'bar'});\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-WebSocketSyncServer.js",
    "content": "﻿import 'dexie-observable';\nimport '../../src/Dexie.Syncable';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\n\nmodule(\"tests-WebSocketSyncServer\");\n\nasyncTest(\"testWebSocketSyncServer\", function () {\n  var server = new SyncServer(1234);\n  server.start();\n\n  var ws = new WebSocket(\"http://dummy:1234\");\n  ws.onopen = function () {\n    ok(true, \"WebSocket opened\");\n    ws.send(JSON.stringify({\n      type: \"clientIdentity\",\n      clientIdentity: null\n    }));\n    ws.send(JSON.stringify({\n      type: \"subscribe\",\n      syncedRevision: null\n    }));\n  }\n  ws.onclose = function (reason) {\n    ok(true, \"WebSocket closed. Reason: \" + reason);\n    start();\n  }\n  ws.onerror = function (event) {\n    ok(false, \"Error: \" + event.reason);\n    start();\n  }\n\n  ws.onmessage = function (event) {\n    var requestFromServer = JSON.parse(event.data);\n    if (requestFromServer.type === \"clientIdentity\") {\n      ok(true, \"Got client identity: \" + requestFromServer.clientIdentity);\n      // Now send changes to server\n      ws.send(JSON.stringify({\n        type: \"changes\",\n        changes: [],\n        partial: false,\n        baseRevision: null,\n        requestId: 1\n      }));\n    } else if (requestFromServer.type == \"ack\") {\n      ok(true, \"Got ack from server: \" + requestFromServer.requestId);\n      equal(requestFromServer.requestId, 1, \"The request ID 1 was acked\");\n\n      // Now connect another WebSocket and send its changes to server so that server will react and send us the changes:\n      var ws2 = new WebSocket(\"http://dummy:1234\");\n      ws2.onopen = function () {\n        ws2.send(JSON.stringify({\n          type: \"clientIdentity\",\n          clientIdentity: null\n        }));\n        ws2.send(JSON.stringify({\n          type: \"changes\",\n          changes: [{type: 1, table: \"UllaBella\", key: \"apa\", obj: {name: \"Apansson\"}}],\n          partial: false,\n          baseRevision: null,\n          requestId: 1\n        }));\n      }\n    } else if (requestFromServer.type == \"changes\") {\n      if (requestFromServer.currentRevision == 0) {\n        ok(true, \"Got initial changes sent to us with current revision 0\");\n      } else {\n        ok(true, \"Got changes from server: \" + JSON.stringify(requestFromServer.changes));\n        equal(JSON.stringify(requestFromServer.changes), JSON.stringify([\n          {\n            rev: 1,\n            source: 2, // WebSocket2 was the source of the changes.\n            type: 1,\n            table: \"UllaBella\",\n            key: \"apa\",\n            obj: {name: \"Apansson\"}\n          }\n        ]), \"Changes where the same as the ones sent by WebSocket2\");\n        start();\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-apply-changes.js",
    "content": "import Dexie from 'dexie';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\nimport {resetDatabase} from '../../../../test/dexie-unittest-utils';\nimport initApplyChanges from '../../src/apply-changes';\nimport {CREATE, DELETE, UPDATE} from '../../src/change_types';\n\nconst db = new Dexie('TestDBTable', {addons: []});\ndb.version(1).stores({\n  foo: \"id\",\n  bar: \"++id\"\n});\n\nconst applyChanges = initApplyChanges(db);\nmodule('applyChanges', {\n  setup: () => {\n    stop();\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\n\nasyncTest('should be able to handle changes belonging to different tables', () => {\n  const fooCreateChange = {\n    key: 1,\n    table: 'foo',\n    obj: { foo: 'bar', id: 1 },\n    type: CREATE\n  };\n  const barCreateChange = {\n    table: 'bar',\n    obj: { foo: 'baz' },\n    type: CREATE\n  };\n  const changes = [fooCreateChange, barCreateChange];\n  applyChanges(changes, 0)\n    .then(() => {\n      return db.table('foo').get(fooCreateChange.key);\n    })\n    .then((obj) => {\n      // Works when key is given\n      deepEqual(obj, fooCreateChange.obj, 'fooCreateChange found in table');\n      return db.table('bar').toArray();\n    })\n    .then((objects) => {\n      // Works with auto-incremented key\n      strictEqual(objects[0].foo, barCreateChange.obj.foo, 'barCreateChange found in table');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should be able to handle large number of changes', () => {\n  let changes = [];\n  for( let i = 0; i < 10000; i++ ) {\n    changes.push({key : i, table: \"foo\",  obj: { id: i, foo: \"bar\" }, type: CREATE});\n  }\n  applyChanges(changes)\n    .then(() => {\n      ok(true, \"Tests passed!\");\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should be able to handle different types of changes', () => {\n  const tableData = [{ id: 2, foo: 'foobar' }, { id: 3, foo: 'bar' }];\n  const createChange = {\n    key: 1,\n    table: 'foo',\n    obj: { foo: 'bar', id: 1 },\n    type: CREATE\n  };\n  const updateChange = {\n    key: 2,\n    table: 'foo',\n    mods: { foo: 'baz' },\n    type: UPDATE\n  };\n  const deleteChange = {\n    key: 3,\n    table: 'foo',\n    type: DELETE\n  };\n  const changes = [createChange, updateChange, deleteChange];\n  db.table('foo').bulkPut(tableData)\n    .then(() => {\n      return applyChanges(changes, 0);\n    })\n    .then(() => {\n      return db.table('foo').get(createChange.key);\n    })\n    .then((obj) => {\n      deepEqual(obj, createChange.obj, 'createChange found in table');\n      return db.table('foo').get(updateChange.key);\n    })\n    .then((obj) => {\n      strictEqual(obj.foo, 'baz', 'updateChange found in table');\n      return db.table('foo').get(deleteChange.key);\n    })\n    .then((obj) => {\n      strictEqual(obj, undefined, 'deleteChange was executed on table');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-bulk-update.js",
    "content": "import Dexie from 'dexie';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\nimport {resetDatabase} from '../../../../test/dexie-unittest-utils';\nimport bulkUpdate from '../../src/bulk-update';\nimport {UPDATE} from '../../src/change_types';\n\nconst db = new Dexie('TestDBTable', {addons: []});\ndb.version(1).stores({\n  foo: \"id\"\n});\n\ndb.on(\"populate\", function () {\n  db.table('foo').add({foo: 'bar', id: 1});\n  db.table('foo').add({bar: 'baz', foo: { bar: 'foobar' }, id: 2});\n});\n\nmodule('bulkUpdate', {\n  setup: () => {\n    stop();\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should ignore any changes for which we didn\\'t find an object in the table', () => {\n  const changes = [{\n    key: 3,\n    mods: {id: 3, foo: 'bar'},\n    table: 'foo',\n    type: UPDATE\n  }];\n  bulkUpdate(db.table('foo'), changes)\n    .then(() => {\n      return db.table('foo').count();\n    })\n    .then((count) => {\n      strictEqual(count, 2, 'No changes made to the table');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should update the object in the table according to the changes', () => {\n  const updateKey1 = {\n    key: 1,\n    mods: {id: 1, foo: 'bar'},\n    table: 'foo',\n    type: UPDATE\n  };\n  const updateKey2 = {\n    key: 2,\n    mods: {id: 2, bar: 'bar', 'foo.bar': 'bazzz'},\n    table: 'foo',\n    type: UPDATE\n  };\n  const changes = [updateKey1, updateKey2];\n  bulkUpdate(db.table('foo'), changes)\n    .then(() => {\n      return db.table('foo').get(updateKey1.key);\n    })\n    .then((obj) => {\n      deepEqual(obj, updateKey1.mods, 'Key 1 updated');\n      return db.table('foo').get(updateKey2.key);\n    })\n    .then((obj) => {\n      deepEqual(obj, {id: 2, bar: 'bar', foo: {bar: 'bazzz'}}, 'Key 2 updated');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-changing-options.js",
    "content": "import Dexie from 'dexie';\nimport 'dexie-observable';\nimport '../../src/Dexie.Syncable';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\n\n/* The following is being tested:\n\n 1. getOptions method\n 2. changing options on an existing connected node by using connect() with a different options object than before\n 3. changing options on an existing disconnected node by using connect() with a different options object than before\n */\nconst db = new Dexie(\"optionsTestDB\");\nconst deletePromise = Dexie.delete(\"optionsTestDB\");\n\nmodule(\"tests-changing-options\", {\n  setup: function () {\n    db.close();\n    stop();\n    deletePromise.then(function () {\n      start()\n    });\n  },\n  teardown: function () {\n  }\n});\n\nasyncTest('Change options on an existing node', function () {\n  const protocolName = 'testProtocolChanges';\n  const serverUrl = 'http://dummy.local';\n  const syncProtocol = {\n    sync: undefined,\n    partialsThreshold: 1000\n  };\n\n  Dexie.Syncable.registerSyncProtocol(protocolName, syncProtocol);\n\n  db.version(1).stores({objects: \"$$\"});\n  db.open();\n\n  syncProtocol.sync = function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess) {\n    propEqual(options, {option1: 'option1'}, 'sync got the correct options');\n    onSuccess({again: 1000});\n  };\n\n  db.syncable.connect(protocolName, serverUrl, {option1: \"option1\"})\n      .then(() => {\n        return db.syncable.getOptions(serverUrl);\n      })\n      .then((options) => {\n        propEqual(options, {option1: 'option1'}, 'getOptions got the correct options');\n\n        syncProtocol.sync = function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess) {\n          propEqual(options, {newOptions: 'other options'}, 'sync got the new options');\n          onSuccess({again: 1000});\n        };\n\n        // Test changing options on an already connected node\n        // We are already connected but are changing options\n        // We expect that the next getOptions/sync call has the new options\n        return db.syncable.connect(protocolName, serverUrl, {newOptions: 'other options'});\n      })\n      .then(() => {\n        return db.syncable.getOptions(serverUrl);\n      })\n      .then((options) => {\n        propEqual(options, {newOptions: 'other options'}, 'getOptions got the new options');\n        // Test changing options on a disconnected existing node\n        return db.syncable.disconnect(serverUrl);\n      })\n      .then(() => {\n        syncProtocol.sync = function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess) {\n          propEqual(options, {evenNewerOptions: 'super new options'}, 'sync got the even newer options');\n          onSuccess({again: 1000});\n        };\n\n        return db.syncable.connect(protocolName, serverUrl, {evenNewerOptions: 'super new options'});\n      })\n      .then(() => {\n        return db.syncable.getOptions(serverUrl);\n      })\n      .then((options) => {\n        propEqual(options, {evenNewerOptions: 'super new options'}, 'getOptions got the even newer options');\n      })\n      .catch(function (err) {\n        ok(false, \"Error: \" + err);\n      })\n      .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-combine-create-and-update.js",
    "content": "import {module, test, deepEqual, ok} from 'QUnit';\nimport combineCreateAndUpdate from '../../src/combine-create-and-update';\n\nmodule('combineCreateAndUpdate', {\n  setup: () => {\n  },\n  teardown: () => {\n  }\n});\n\ntest('should get a create change and update change and return a combined object', () => {\n  const createChange = {\n    obj: {\n      foo: 'value',\n    },\n  };\n  const updateChange = {\n    mods: {\n      foo: 'value2',\n      bar: 'new Value',\n    },\n  };\n\n  const res = combineCreateAndUpdate(createChange, updateChange);\n  deepEqual(res.obj, { foo: 'value2', bar: 'new Value' });\n});\n\ntest('should not change the original createObject', () => {\n  const createChange = {\n    obj: {\n      foo: 'value',\n    },\n  };\n  const updateChange = {\n    mods: {\n      foo: 'value2',\n      bar: 'new Value',\n    },\n  };\n\n  combineCreateAndUpdate(createChange, updateChange);\n  deepEqual(createChange.obj, { foo: 'value' });\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-combine-update-and-update.js",
    "content": "import {module, test, deepEqual, ok} from 'QUnit';\nimport combineUpdateAndUpdate from '../../src/combine-update-and-update';\n\nmodule('combineUpdateAndUpdate', {\n  setup: () => {\n  },\n  teardown: () => {\n  }\n});\n\ntest('should combine the keys of nextChange.mods and prevChange.mods', () => {\n  const prevChange = {\n    mods: {\n      foo: 'bar',\n    },\n  };\n  const nextChange = {\n    mods: {\n      bar: 'baz',\n    },\n  };\n\n  const res = combineUpdateAndUpdate(prevChange, nextChange);\n  deepEqual(res.mods, {foo: 'bar', bar: 'baz'});\n});\n\ntest('should leave the original object untouched', () => {\n  const prevChange = {\n    mods: {\n      foo: 'bar',\n    },\n  };\n  const nextChange = {\n    mods: {\n      bar: 'baz',\n    },\n  };\n\n  combineUpdateAndUpdate(prevChange, nextChange);\n  deepEqual(prevChange, {mods: {foo: 'bar'}});\n});\n\ntest('should overwrite a previous change with the same key', () => {\n  const prevChange = {\n    mods: {\n      foo: 'bar',\n    },\n  };\n  const nextChange = {\n    mods: {\n      foo: 'baz',\n    },\n  };\n\n  const res = combineUpdateAndUpdate(prevChange, nextChange);\n  deepEqual(res.mods, {foo: 'baz'});\n});\n\ntest('should ignore a previous change which changed a parent object of the next change', () => {\n  const prevChange = {\n    mods: {\n      'foo': {bar: 'baz', baz: 'bar'},\n    },\n  };\n  const nextChange = {\n    mods: {\n      'foo.bar': 'foobar',\n    },\n  };\n\n  const res = combineUpdateAndUpdate(prevChange, nextChange);\n  deepEqual(res, {mods: {foo: {bar: 'foobar', baz: 'bar'}}});\n});\n\ntest('should ignore a previous change which changed a sub value of the nextChange', () => {\n  const prevChange = {\n    mods: {\n      'foo.bar': 'foobar',\n    },\n  };\n  const nextChange = {\n    mods: {\n      'foo': {bar: 'baz'},\n    },\n  };\n\n  const res = combineUpdateAndUpdate(prevChange, nextChange);\n  deepEqual(res, {mods: {'foo': {bar: 'baz'}}});\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-finally-commit-all-changes.js",
    "content": "import Dexie from 'dexie';\nimport observable from 'dexie-observable';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\nimport {resetDatabase} from '../../../../test/dexie-unittest-utils';\nimport initFinallyCommitAllChanges from '../../src/finally-commit-all-changes';\nimport {CREATE, DELETE, UPDATE} from '../../src/change_types';\n\nconst db = new Dexie('TestDBTable', {addons: [observable]});\ndb.version(1).stores({\n  foo: \"id\"\n});\n\nconst nodeID = 1;\n\nlet syncNode;\nlet finallyCommitAllChanges;\nmodule('finallyCommitAllChanges', {\n  setup: () => {\n    stop();\n\n    db.observable.SyncNode.prototype.save = function() { return { catch() {} }; };\n    syncNode = new db.observable.SyncNode();\n    syncNode.id = nodeID;\n    syncNode.remoteBaseRevisions = [];\n    finallyCommitAllChanges = initFinallyCommitAllChanges(db, syncNode);\n\n    // Do a full DB reset to clean _changes table\n    db._hasBeenCreated = false;\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should call node.save()', () => {\n  let wasCalled = false;\n  db.observable.SyncNode.prototype.save = function() {\n    wasCalled = true;\n    return { catch() {} };\n  };\n  finallyCommitAllChanges([], 1)\n    .then(() => {\n      ok(wasCalled);\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should apply _uncommittedChanges and remove them from that table', () => {\n  const createChange = {\n    key: 1,\n    node: nodeID,\n    type: CREATE,\n    obj: { id: 1, foo: 'bar' },\n    table: 'foo'\n  };\n  db._uncommittedChanges\n    .add(createChange)\n    .then(() => {\n      return finallyCommitAllChanges([], 1);\n    })\n    .then(() => {\n      return db.foo.get(createChange.key);\n    })\n    .then((obj) => {\n      deepEqual(obj, createChange.obj, 'Change was found in table \"foo\"');\n      return db._uncommittedChanges.where('node').equals(nodeID).count();\n    })\n    .then((count) => {\n      strictEqual(count, 0, 'No more entries in _uncommittedChanges for the given nodeID');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\n\nasyncTest('should put the changes into the given table', () => {\n  const createChange = {\n    key: 2,\n    node: nodeID,\n    type: CREATE,\n    obj: { id: 2, foo: 'barbaz' },\n    table: 'foo'\n  };\n  return finallyCommitAllChanges([createChange], 1)\n    .then(() => {\n      return db.foo.get(createChange.key);\n    })\n    .then((obj) => {\n      deepEqual(obj, createChange.obj, 'Change was found in table \"foo\"');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should set the nodeID as source for the currentTransaction', () => {\n  db.observable.SyncNode.prototype.save = function() {\n    strictEqual(Dexie.currentTransaction.source, nodeID);\n    return { catch() {} };\n  };\n  finallyCommitAllChanges([], 1)\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should update remoteBaseRevisions and remove any old revisions', () => {\n  const createChange = {\n    key: 1,\n    node: nodeID,\n    type: CREATE,\n    obj: { id: 1, foo: 'bar' },\n    table: 'foo'\n  };\n  syncNode.remoteBaseRevisions = [{local: 1, remote: 2}];\n  syncNode.myRevision = 2;\n\n  const remoteRevision = 3;\n  finallyCommitAllChanges([createChange], remoteRevision)\n    .then(() => {\n      strictEqual(syncNode.remoteBaseRevisions.length, 1, 'Only one remoteBaseRevision');\n      // We had no changes yet so the next local revision is 1\n      deepEqual(syncNode.remoteBaseRevisions, [{local: 1, remote: remoteRevision}], 'Make sure remoteBaseRevisions contains the correct object');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should update node.appliedRemoteRevision', () => {\n  const remoteRevision = 3;\n  finallyCommitAllChanges([], remoteRevision)\n    .then(() => {\n      strictEqual(syncNode.appliedRemoteRevision, remoteRevision);\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should update myRevision to the latest rev we got', () => {\n  const createChange = {\n    rev: 1,\n    key: 1,\n    node: nodeID,\n    type: CREATE,\n    obj: { id: 1, foo: 'bar' },\n    table: 'foo'\n  };\n  syncNode.remoteBaseRevisions = [{local: 1, remote: 2}];\n  // We had no revision before and no changes\n  syncNode.myRevision = 0;\n\n  const remoteRevision = 3;\n  finallyCommitAllChanges([createChange], remoteRevision)\n    .then(() => {\n      strictEqual(syncNode.myRevision, createChange.rev);\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-get-or-create-sync-node.js",
    "content": "import Dexie from 'dexie';\nimport observable from 'dexie-observable';\n// Add this so we have the SyncNode.prototype.save method\nimport syncable from '../../src/Dexie.Syncable';\nimport {module, asyncTest, test, start, stop, propEqual, deepEqual, strictEqual, ok} from 'QUnit';\nimport {resetDatabase} from '../../../../test/dexie-unittest-utils';\nimport initGetOrCreateSyncNode from '../../src/get-or-create-sync-node';\n\nconst db = new Dexie('TestDBTable', {addons: [observable, syncable]});\ndb.version(1).stores({foo: '++id'});\n\nconst protocolName = 'protocolName';\nconst url = 'http://foo.invalid';\nconst getOrCreateSyncNode = initGetOrCreateSyncNode(db, protocolName, url);\nmodule('getOrCreateSyncNode', {\n  setup: () => {\n    stop();\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should throw an error if no URL was passed', () => {\n  const getOrCreateSyncNode = initGetOrCreateSyncNode(db, protocolName);\n  getOrCreateSyncNode({})\n    .catch((e) => {\n      strictEqual(e.message, 'Url cannot be empty');\n    })\n    .finally(start);\n});\n\nasyncTest('should return a new node if none exists for the given URL', () => {\n  const nodeOpts = {foo: 'bar'};\n  let nodeID;\n  getOrCreateSyncNode(nodeOpts)\n    .then((node) => {\n      nodeID = node.id;\n      ok(node instanceof db.observable.SyncNode, 'returned node is instance of SyncNode');\n      strictEqual(node.syncProtocol, protocolName, 'syncProtocol is protocol name');\n      strictEqual(node.url, url, 'url is the url we passed to init');\n      deepEqual(node.syncOptions, nodeOpts, 'syncOptions are the same as the options we passed');\n      strictEqual(node.myRevision, -1, 'myRevision is -1');\n      propEqual(node.syncContext, {nodeID}, 'syncContext contains the correct nodeID');\n      return db._syncNodes.get(nodeID);\n    })\n    .then((node) => {\n      strictEqual(node.id, nodeID, 'Node was saved in the DB');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should return an existing node if one exists for the given URL', () => {\n  const nodeOpts = {foo: 'bar'};\n  let nodeID;\n  // Don't reuse the save URL, it would cause an error because the index is not unique\n  const otherUrl = 'http://bar.invalid';\n  const getOrCreateSyncNode = initGetOrCreateSyncNode(db, protocolName, otherUrl);\n  const syncNode = new db.observable.SyncNode();\n  syncNode.url = otherUrl;\n  let addedNodeID;\n  db._syncNodes.add(syncNode)\n    .then((nodeID) => {\n      addedNodeID = nodeID;\n      return getOrCreateSyncNode(nodeOpts)\n    })\n    .then((node) => {\n      ok(node instanceof db.observable.SyncNode, 'returned node is instance of SyncNode');\n      strictEqual(node.id, addedNodeID, 'We got the correct node back');\n      propEqual(node.syncContext, {nodeID: addedNodeID}, 'syncContext contains the correct nodeID');\n      propEqual(node.syncOptions, nodeOpts, 'node contains the given options');\n      return db._syncNodes.get(addedNodeID);\n    })\n    .then((node) => {\n      strictEqual(node.syncContext.nodeID, addedNodeID, 'Node was saved in the DB with the correct context');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should set myRevision to the last _changes if initialUpload is false', () => {\n  const nodeOpts = {initialUpload: false};\n  // Don't reuse the save URL, it would cause an error because the index is not unique\n  const otherUrl = 'http://baz.invalid';\n  const getOrCreateSyncNode = initGetOrCreateSyncNode(db, protocolName, otherUrl);\n  db._changes.add({key: 1, obj: {foo: 'bar'}, table: 'foo', type: 1})\n    .then(() => {\n      return getOrCreateSyncNode(nodeOpts);\n    })\n    .then((node) => {\n      strictEqual(node.myRevision, 1);\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-merge-change.js",
    "content": "import {module, test, deepEqual} from 'QUnit';\nimport mergeChange from '../../src/merge-change';\nimport {CREATE, UPDATE, DELETE} from '../../src/change_types';\n\n// Tests for if a key exists multiple times in a table\nmodule('mergeChange: prev change was CREATE', {\n  setup: () => {\n  },\n  teardown: () => {\n  }\n});\n\ntest('should just return the nextChange if it is CREATE', () => {\n  const prevChange = {\n    key: 1,\n    table: 'foo',\n    obj: {},\n    type: CREATE,\n  };\n  const nextChange = {\n    key: 1,\n    table: 'foo',\n    obj: {foo: 'bar'},\n    type: CREATE,\n  };\n  const res = mergeChange(prevChange, nextChange);\n  deepEqual(res, nextChange);\n});\n\ntest('should just return the nextChange if it is DELETE', () => {\n  const prevChange = {\n    key: 1,\n    table: 'foo',\n    obj: {},\n    type: CREATE,\n  };\n  const nextChange = {\n    rev: 1,\n    key: 1,\n    table: 'foo',\n    type: DELETE,\n  };\n  const res = mergeChange(prevChange, nextChange);\n  deepEqual(res, nextChange);\n});\n\ntest('should combine the CREATE and UPDATE change if nextChange is UPATE', () => {\n  const prevChange = {\n    key: 1,\n    table: 'foo',\n    obj: {\n      foo: 'baz',\n    },\n    type: CREATE,\n  };\n  const nextChange = {\n    key: 1,\n    table: 'foo',\n    mods: {\n      title: 'bar',\n    },\n    type: UPDATE,\n  };\n\n  const res = mergeChange(prevChange, nextChange);\n  const expectedResult = {\n    key: 1,\n    table: 'foo',\n    obj: {\n      title: 'bar',\n      foo: 'baz'\n    },\n    type: CREATE\n  };\n  deepEqual(res, expectedResult);\n});\n\nmodule('mergeChange: prev change was UPDATE', {\n  setup: () => {\n  },\n  teardown: () => {\n  }\n});\n\ntest('should return the nextChange if it is CREATE', () => {\n  const prevChange = {\n    key: 1,\n    table: 'foo',\n    mods: {foo: 'bar'},\n    type: UPDATE,\n  };\n  const nextChange = {\n    key: 1,\n    table: 'foo',\n    obj: {foo: 'bar baz'},\n    type: CREATE,\n  };\n  const res = mergeChange(prevChange, nextChange);\n  deepEqual(res, nextChange);\n});\n\ntest('should return the nextChange if it is DELETE', () => {\n  const prevChange = {\n    key: 1,\n    table: 'foo',\n    mods: {foo: 'bar'},\n    type: UPDATE,\n  };\n  const nextChange = {\n    rev: 1,\n    key: 1,\n    table: 'foo',\n    type: DELETE,\n  };\n  const res = mergeChange(prevChange, nextChange);\n  deepEqual(res, nextChange);\n});\n\ntest('should the changes if the nextChange is UPDATE', () => {\n  const prevChange = {\n    key: 1,\n    table: 'foo',\n    mods: {\n      foo: 'baz',\n    },\n    type: UPDATE,\n  };\n  const nextChange = {\n    key: 1,\n    table: 'foo',\n    mods: {\n      title: 'bar',\n    },\n    type: UPDATE,\n  };\n  const res = mergeChange(prevChange, nextChange);\n  const expectedResult = {\n    key: 1,\n    table: 'foo',\n    mods: {\n      title: 'bar',\n      foo: 'baz'\n    },\n    type: UPDATE\n  };\n  deepEqual(res, expectedResult);\n});\n\nmodule('mergeChange: prev change was DELETE', {\n  setup: () => {\n  },\n  teardown: () => {\n  }\n});\n\ntest('should return nextChange if it is CREATE', () => {\n  const prevChange = {\n    key: 1,\n    table: 'foo',\n    type: DELETE,\n  };\n  const nextChange = {\n    key: 1,\n    table: 'foo',\n    obj: {foo: 'bar'},\n    type: CREATE,\n  };\n  const res = mergeChange(prevChange, nextChange);\n  deepEqual(res, nextChange);\n});\n\ntest('should return the prevChange if nextChange is DELETE', () => {\n  const prevChange = {\n    key: 1,\n    table: 'foo',\n    type: DELETE,\n  };\n  const nextChange = {\n    key: 1,\n    table: 'foo',\n    type: DELETE,\n  };\n  const res = mergeChange(prevChange, nextChange);\n  deepEqual(res, prevChange);\n});\n\ntest('should return prevChange if nextChange is UPDATE', () => {\n  const prevChange = {\n    rev: 0,\n    key: 1,\n    table: 'foo',\n    type: DELETE,\n  };\n  const nextChange = {\n    rev: 1,\n    key: 1,\n    table: 'foo',\n    mods: {foo: 'bar'},\n    type: UPDATE,\n  };\n  const res = mergeChange(prevChange, nextChange);\n  deepEqual(res, prevChange);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-register-sync-protocol.js",
    "content": "import Dexie from 'dexie';\nimport '../../src/Dexie.Syncable';\nimport {module, test, strictEqual, raises} from 'QUnit';\n\nmodule('registerSyncProtocol', {\n  setup: () => {\n  },\n  teardown: () => {\n  }\n});\n\ntest('should set partialsThreshold to Infinity if no threshold was given', () => {\n  const protocolName = 'foo';\n  Dexie.Syncable.registerSyncProtocol(protocolName, {\n    sync() {},\n  });\n\n  strictEqual(Dexie.Syncable.registeredProtocols[protocolName].partialsThreshold, Infinity);\n});\n\ntest('should save the given partialsThreshold', () => {\n  const protocolName = 'foo';\n  Dexie.Syncable.registerSyncProtocol(protocolName, {\n    sync() {},\n    partialsThreshold: 1000\n  });\n\n  strictEqual(Dexie.Syncable.registeredProtocols[protocolName].partialsThreshold, 1000);\n});\n\ntest('should throw an error if the partialsThreshold is NaN or smaller 0', () => {\n  const protocolName = 'foo';\n\n  function fn1() {\n    Dexie.Syncable.registerSyncProtocol(protocolName, {\n      sync() {},\n      partialsThreshold: NaN\n    });\n  }\n\n  raises(fn1, Error, 'NaN test');\n\n  function fn2() {\n    Dexie.Syncable.registerSyncProtocol(protocolName, {\n      sync() {},\n      partialsThreshold: -10\n    });\n  }\n\n  raises(fn2, Error, 'Negative number test');\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-save-to-uncommitted-changes.js",
    "content": "import Dexie from 'dexie';\nimport observable from 'dexie-observable';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\nimport {resetDatabase} from '../../../../test/dexie-unittest-utils';\nimport initSaveToUncommittedChanges from '../../src/save-to-uncommitted-changes';\nimport {CREATE, DELETE, UPDATE} from '../../src/change_types';\n\nconst db = new Dexie('TestDBTable', {addons: [observable]});\ndb.version(1).stores({\n  foo: \"id\"\n});\n\nconst nodeID = 1;\ndb.observable.SyncNode.prototype.save = function() {\n  return {\n    then(cb){ cb(); }\n  };\n};\nlet syncNode;\nlet saveToUncommittedChanges;\nmodule('saveToUncommittedChanges', {\n  setup: () => {\n    stop();\n\n    syncNode = new db.observable.SyncNode();\n    syncNode.id = nodeID;\n    saveToUncommittedChanges = initSaveToUncommittedChanges(db, syncNode);\n\n    resetDatabase(db).catch(function (e) {\n      ok(false, \"Error resetting database: \" + e.stack);\n    }).finally(start);\n  },\n  teardown: () => {\n  }\n});\n\nasyncTest('should save the given changes in the _uncommittedChanges table', () => {\n  const create = {\n    key: 1,\n    table: 'foo',\n    type: CREATE,\n    obj: {foo: 'bar'}\n  };\n  const update = {\n    key: 2,\n    table: 'foo',\n    type: UPDATE,\n    mods: {bar: 'baz'}\n  };\n  const remove = {\n    key: 3,\n    table: 'foo',\n    type: DELETE\n  };\n  const changes = [create, update, remove];\n  saveToUncommittedChanges(changes, 10)\n    .then(() => {\n      return db._uncommittedChanges.toArray();\n    })\n    .then((changes) => {\n      strictEqual(changes.length, 3, 'Number of changes matches');\n      deepEqual(changes[0], {\n        id: 1,\n        key: 1,\n        type: CREATE,\n        node: nodeID,\n        table: 'foo',\n        obj: {foo: 'bar'}\n      }, 'Create change');\n      deepEqual(changes[1], {\n        id: 2,\n        key: 2,\n        type: UPDATE,\n        node: nodeID,\n        table: 'foo',\n        mods: {bar: 'baz'}\n      }, 'Update change');\n      deepEqual(changes[2], {\n        id: 3,\n        key: 3,\n        type: DELETE,\n        node: nodeID,\n        table: 'foo'\n      }, 'Delete change');\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n\nasyncTest('should add the remoteRevision to the given node', () => {\n  const remoteRevision = 20;\n  saveToUncommittedChanges([], remoteRevision)\n    .then(() => {\n      strictEqual(syncNode.appliedRemoteRevision, remoteRevision);\n    })\n    .catch(function(err) {\n      ok(false, \"Error: \" + err);\n    })\n    .finally(start);\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-syncable-partials.js",
    "content": "import Dexie from 'dexie';\nimport 'dexie-observable';\nimport '../../src/Dexie.Syncable';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\n\n/* The following is being tested:\n\n 1. Client partials\n 2. Receiving partials from the server\n 3. What happens when the partialsThreshold is 0\n */\nvar db1 = new Dexie(\"db1\");\nvar deletePromise = Dexie.delete(\"db1\");\n\nmodule(\"tests-syncable-partials\", {\n  setup: function () {\n    db1.close();\n    stop();\n    deletePromise.then(function () {\n      start()\n    });\n  },\n  teardown: function () {\n  }\n});\n\nconst partialsThreshold = 100;\nasyncTest(\"client/server partials\", function () {\n  var testNo = 0;\n  var callbacks = [];\n  Dexie.Syncable.registerSyncProtocol(\"testProtocol\", {\n    sync: function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n      var thiz = this, args = arguments;\n      Dexie.vip(function () {\n        try {\n          callbacks[testNo++].apply(thiz, args);\n        } catch (err) {\n          db1.close();\n          ok(false, err);\n          start();\n        }\n      });\n    },\n    partialsThreshold: partialsThreshold\n  });\n\n  db1.version(1).stores({objects: \"$$\"});\n\n  db1.on('populate', function () {\n    db1.objects.add({name: \"one\"});\n    db1.objects.add({name: \"two\"});\n    db1.objects.add({name: \"three\"});\n  });\n\n  db1.open();\n\n  db1.syncable.connect(\"testProtocol\", \"http://dummy.local\");\n\n  // Prepare tests\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    // applyRemoteChanges\n    applyRemoteChanges([], \"revision one\", false, false).then(function () {\n      // onChangesAccepted\n      onChangesAccepted();\n    }).then(function () {\n      // onSuccess\n      onSuccess({again: 1});\n    });\n  });\n\n  // Bulk add changes to pass the threshold\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    // baseRevision\n    equal(baseRevision, \"revision one\", \"Now we get changes based on revision two\");\n    // syncedRevision\n    equal(syncedRevision, \"revision one\", \"Sync revision is 'revision two' because client has got it\");\n    // partial\n    equal(partial, false, 'partial should be false');\n    onChangesAccepted();\n    db1.transaction('rw', db1.objects, function () {\n      for (var i = 0; i < partialsThreshold + 1; ++i) {\n        db1.objects.add({name: \"bulk\"});\n      }\n    }).then(function () {\n      onSuccess({again: 1});\n    });\n  });\n\n  // Make sure that we didn't receive more changes than the threshold\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    // baseRevision\n    equal(baseRevision, \"revision one\", \"Now we get changes based on revision two\");\n    // syncedRevision\n    equal(syncedRevision, \"revision one\", \"Sync revision is 'revision two' because client has got it\");\n    // changes\n    equal(changes.length, partialsThreshold, `Got ${partialsThreshold} changes`);\n    equal(changes[0].obj.name, \"bulk\", \"change is bulk\");\n    equal(partial, true, `More than ${partialsThreshold} changes gives partial=true`);\n    onChangesAccepted();\n    onSuccess({again: 1});\n  });\n\n  // Make sure we now get the rest of the changes\n  // Revisions shouldn't change\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    // baseRevision\n    equal(baseRevision, \"revision one\", \"Now we get changes based on revision two\");\n    // syncedRevision\n    equal(syncedRevision, \"revision one\", \"Sync revision is 'revision two' because client has got it\");\n    // changes\n    equal(changes.length, 1, \"Got 1 change\");\n    equal(changes[0].obj.name, \"bulk\", \"change is bulk\");\n    equal(partial, false, \"Last chunk with 1 change\");\n    onSuccess({again: 1});\n  });\n\n  // Test that a server partial is added to _uncommittedChanges and not to _changes\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    applyRemoteChanges([{\n      type: 1,\n      table: \"objects\",\n      key: \"apa\",\n      obj: {name: \"uncommittedChange\"}\n    }], \"revision with uncommitted\", true, false)\n        .then(() => {\n          return db1._uncommittedChanges.toArray().then((changes) => {\n            strictEqual(changes.length, 1, 'Should have one uncommitted change');\n            strictEqual(changes[0].key, 'apa', 'Key should match');\n            deepEqual(changes[0].obj, {name: 'uncommittedChange'}, 'Saved obj should match');\n            strictEqual(changes[0].table, 'objects', 'Table should match');\n            strictEqual(changes[0].type, 1, 'Type should match');\n\n            return db1._changes.toArray();\n          });\n        })\n        .then((changes) => {\n          ok(changes.every(function (change) {\n            return change.obj.name !== 'uncommittedChange'\n          }), 'The uncommitted change should not be in _changes');\n          return db1._syncNodes.where('url').equals('http://dummy.local').toArray();\n        })\n        .then((nodes) => {\n          strictEqual(nodes[0].appliedRemoteRevision, 'revision with uncommitted', \"The node's appliedRemoteRevision should be updated\");\n          onSuccess({again: 1});\n        });\n  });\n\n  // Test that if we don't have partial anymore -> move _uncommittedChanges to _changes\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    applyRemoteChanges([{\n      type: 1,\n      table: \"objects\",\n      key: \"abba\",\n      obj: {name: \"committedChange\"}\n    }], \"revision one\", false, false)\n        .then(() => {\n          return db1._uncommittedChanges.toArray().then((changes) => {\n            strictEqual(changes.length, 0, 'Should have no uncommitted change');\n            return db1._changes.toArray();\n          });\n        })\n        .then((changes) => {\n          ok(changes.some(function (change) {\n            return change.obj.name === 'uncommittedChange'\n          }), 'The uncommitted change should now be in _changes');\n          ok(changes.some(function (change) {\n            return change.obj.name === 'committedChange'\n          }), 'The committedChange should also be in _changes');\n        })\n        .then(function () {\n          return db1.delete();\n        }).catch(function (err) {\n      ok(false, \"Got error: \" + err);\n    }).finally(start);\n  });\n});\n\nasyncTest('partialsThreshold is zero', () => {\n  var testNo = 0;\n  var callbacks = [];\n  Dexie.Syncable.registerSyncProtocol(\"testProtocol\", {\n    sync: function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n      var thiz = this, args = arguments;\n      Dexie.vip(function () {\n        try {\n          callbacks[testNo++].apply(thiz, args);\n        } catch (err) {\n          db1.close();\n          ok(false, err);\n          start();\n        }\n      });\n    },\n    partialsThreshold: 0\n  });\n\n  db1.version(1).stores({objects: \"$$\"});\n\n  db1.on('populate', function () {\n    db1.objects.add({name: \"one\"});\n    db1.objects.add({name: \"two\"});\n  });\n\n  db1.open();\n\n  db1.syncable.connect(\"testProtocol\", \"http://dummy.local\", {option1: \"option1\"});\n\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial) {\n    // changes\n    equal(changes.length, 0, \"No changes\");\n    equal(partial, true, \"Partial since threshold is 0\");\n    start();\n  });\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-syncable.js",
    "content": "﻿import Dexie from 'dexie';\nimport 'dexie-observable';\nimport '../../src/Dexie.Syncable';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\n\n/* The following is being tested:\n\n 1. A dummy implementation of ISyncProtocol is registered so that the unit test can interact with the database correctly.\n 2. Test status changes\n 3. Test disconnect/reconnect and that we get changes which happened as we were disconnected\n */\nvar db1 = new Dexie(\"db1\");\nvar db2 = new Dexie(\"db1\");\nvar deletePromise = Dexie.delete(\"db1\");\n\nmodule(\"tests-syncable\", {\n  setup: function () {\n    db1.close();\n    db2.close();\n    stop();\n    deletePromise.then(function () {\n      start()\n    });\n  },\n  teardown: function () {\n  }\n});\n\nasyncTest(\"connect(), disconnect()\", function () {\n  var testNo = 0;\n  var callbacks = [];\n  Dexie.Syncable.registerSyncProtocol(\"testProtocol\", {\n    sync: function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n      var thiz = this, args = arguments;\n      Dexie.vip(function () {\n        try {\n          callbacks[testNo++].apply(thiz, args);\n        } catch (err) {\n          db1.close();\n          ok(false, err);\n          start();\n        }\n      });\n    },\n    partialsThreshold: 1000\n  });\n\n  db1.version(1).stores({objects: \"$$\"});\n  db2.version(1).stores({objects: \"$$\"});\n\n  db1.on('populate', function () {\n    db1.objects.add({name: \"one\"});\n    db1.objects.add({name: \"two\"});\n    db1.objects.add({name: \"three\"}).then(function (key) {\n      db1.objects.update(key, {name: \"four\"});\n    });\n  });\n\n  db1.syncable.on('statusChanged', function (newStatus) {\n    ok(true, \"Status changed to \" + Dexie.Syncable.StatusTexts[newStatus]);\n  });\n  db2.syncable.on('statusChanged', function (newStatus) {\n    ok(true, \"Status changed to \" + Dexie.Syncable.StatusTexts[newStatus]);\n  });\n\n  db1.open();\n\n  var connectPromise = db1.syncable.connect(\"testProtocol\", \"http://dummy.local\", {option1: \"option1\"});\n\n  // Test first sync call\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    // url\n    equal(url, \"http://dummy.local\", \"URL got through\");\n    // options\n    equal(options.option1, \"option1\", \"Options got through\");\n    // baseRevision\n    equal(baseRevision, null, \"Base revision is null\");\n    // syncedRevision\n    equal(syncedRevision, null, \"Sync revision is null\");\n    // changes\n    equal(changes.length, 3, \"Three changes (change number four should be reduced into change no 3\");\n    ok(changes.every(function (change) {\n      return change.type == 1\n    }), \"All three changes are create changes\");\n    ok(changes.some(function (change) {\n      return change.obj.name == \"one\"\n    }), \"'one' is among changes\");\n    ok(changes.some(function (change) {\n      return change.obj.name == \"two\"\n    }), \"'two' is among changes\");\n    ok(changes.some(function (change) {\n      return change.obj.name == \"four\"\n    }), \"'four' is among changes\");\n    // partial\n    equal(partial, false, \"Not partial since number of changes are below 1000\");\n    // applyRemoteChanges\n    applyRemoteChanges([{\n      type: 1,\n      table: \"objects\",\n      key: \"apa\",\n      obj: {name: \"five\"}\n    }], \"revision one\", false, false).then(function () {\n      // Create a local change between remoteChanges application\n      return db1.objects.add({name: \"six\"});\n    }).then(function () {\n      return applyRemoteChanges([{\n        type: 1,\n        table: \"objects\",\n        key: \"apa2\",\n        obj: {name: \"seven\"}\n      }], \"revision two\", false, false);\n    }).then(function () {\n      // onChangesAccepted\n      onChangesAccepted();\n      return db1.objects.add({name: \"eight\"});\n    }).then(function () {\n      // onSuccess\n      onSuccess({again: 1});\n    });\n  });\n\n  connectPromise.then(function () {\n    db1.objects.count(function (count) {\n      equal(count, 7, \"There should be seven objects in db after sync\");\n      // From populate:\n      // 1: one\n      // 2: two\n      // 3: four\n      // 4: applyRemoteChanges: \"five\" (\"apa\")\n      // 5: db.objects.add(\"six\")\n      // 6: applyRemoteChanges: \"seven\" (\"apa2\")\n      // 7: db.objects.add(\"eight\");\n    });\n    db1.objects.get(\"apa2\", function (seven) {\n      equal(seven.name, \"seven\", \"Have got the change from the server. If not, check that promise does not fire until all changes have committed.\");\n    });\n  }).catch('DatabaseClosedError', function () {\n    console.warn(\"DatabaseClosedError\");\n  }).catch(function (ex) {\n    ok(false, \"Could not connect. Error: \" + (ex.stack || ex));\n  });\n\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    // url\n    equal(url, \"http://dummy.local\", \"URL still there\");\n    // options\n    equal(options.option1, \"option1\", \"Options still there\");\n    // baseRevision\n    equal(baseRevision, \"revision one\", \"First chunk of changes is based on revision one because 'six' was created based on revision one\");\n    // syncedRevision\n    equal(syncedRevision, \"revision two\", \"Sync revision is 'revision two' because client has got it\");\n    // changes\n    equal(changes.length, 1, \"Even though there's two changes, we should only get the first one because they are based on different revisions\");\n    equal(changes[0].obj.name, \"six\", \"First change is six\");\n    equal(partial, false);\n    onChangesAccepted();\n    onSuccess({again: 1});\n  });\n\n  // Prepare disconnect test\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    // baseRevision\n    equal(baseRevision, \"revision two\", \"Now we get changes based on revision two\");\n    // syncedRevision\n    equal(syncedRevision, \"revision two\", \"Sync revision is 'revision two' because client has got it\");\n    // changes\n    equal(changes.length, 1, \"Got another change\");\n    equal(changes[0].obj.name, \"eight\", \"change is eight\");\n    equal(partial, false);\n    onChangesAccepted();\n\n    // Test disconnect()\n    db1.syncable.disconnect(\"http://dummy.local\");\n    onSuccess({again: 1}); // Framework should ignore again: 1 since it's disconnected.\n    setTimeout(reconnect, 500);\n    db1.objects.add({name: \"changeAfterDisconnect\"});\n  });\n\n  function reconnect() {\n    db1.close();\n    db1 = db2;\n    db1.open().then(function () {\n      return db1.objects.add({name: \"changeBeforeReconnect\"});\n    }).then(function () {\n      return db1.syncable.getStatus(\"http://dummy.local\", function (status) {\n        equal(status, Dexie.Syncable.Statuses.OFFLINE, \"Status is OFFLINE\");\n      });\n    }).then(function () {\n      db1.syncable.connect(\"testProtocol\", \"http://dummy.local\");\n    });\n  }\n\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    // baseRevision\n    equal(baseRevision, \"revision two\", \"baseRevision Still revision two\");\n    // syncedRevision\n    equal(syncedRevision, \"revision two\", \"syncedRevision Still revision two\");\n    // changes\n    equal(changes.length, 2, \"Got 2 changes after reconnect.\");\n    equal(changes[0].obj.name, \"changeAfterDisconnect\", \"change one is changeAfterDisconnect\");\n    equal(changes[1].obj.name, \"changeBeforeReconnect\", \"change two is changeBeforeReconnect\");\n    onChangesAccepted();\n\n    onSuccess({again: 10000}); // Wait a looong time for calling us again (so that we have the time to close and reopen and then force a sync sooner)\n\n    setTimeout(function () {\n      db1.syncable.getStatus(\"http://dummy.local\", function (status) {\n        // Close and open again and it will be status connected at once\n        equal(status, Dexie.Syncable.Statuses.ONLINE, \"Status is ONLINE\");\n      }).then(function () {\n        // Close and open again and it will be status connected at once\n        return db1.delete();\n      }).catch(function (err) {\n        ok(false, \"Got error: \" + err);\n      }).finally(start);\n    }, 100);\n  });\n});\n\nasyncTest('delete()', () => {\n  var testNo = 0;\n  var callbacks = [];\n  Dexie.Syncable.registerSyncProtocol(\"testProtocol\", {\n    sync: function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n      var thiz = this, args = arguments;\n      Dexie.vip(function () {\n        try {\n          callbacks[testNo++].apply(thiz, args);\n        } catch (err) {\n          db1.close();\n          ok(false, err);\n          start();\n        }\n      });\n    },\n    partialsThreshold: 10\n  });\n\n  db1.version(1).stores({objects: \"$$\"});\n\n  db1.on('populate', function () {\n    db1.objects.add({name: \"one\"});\n    db1.objects.add({name: \"two\"});\n  });\n\n  db1.open();\n\n  const url = \"http://urlToDelete.local\";\n  db1.syncable.connect(\"testProtocol\", url);\n\n  db1.syncable.on('statusChanged', function (newStatus) {\n    ok(true, \"Status changed to \" + Dexie.Syncable.StatusTexts[newStatus]);\n  });\n\n  const originalDisconnect = db1.syncable.disconnect;\n\n  let disconnectWasCalled = false;\n  db1.syncable.disconnect = function (url) {\n    disconnectWasCalled = true;\n    return originalDisconnect(url);\n  };\n\n  const originalDeleteOldChanges = Dexie.Observable.deleteOldChanges;\n\n  let deleteOldChangesWasCalled = false;\n  Dexie.Observable.deleteOldChanges = function (db) {\n    deleteOldChangesWasCalled = true;\n    return originalDeleteOldChanges(db);\n  };\n\n  callbacks.push(function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    // Add some uncommitted changes\n    applyRemoteChanges([{\n      type: 1,\n      table: \"objects\",\n      key: \"apa\",\n      obj: {name: \"uncommittedChangeBeforeDelete\"}\n    }], \"revision with uncommitted\", true, false)\n        .then(() => {\n          return db1.syncable.delete(url);\n        })\n        .then(() => {\n          ok(disconnectWasCalled, 'We got disconnected');\n          return db1._uncommittedChanges.toArray();\n        })\n        .then((uncommittedChanges) => {\n          ok(uncommittedChanges.every(function (change) {\n            return change.obj.name !== 'uncommittedChangeBeforeDelete'\n          }), 'The uncommitted change should not be in _uncommittedChanges anymore');\n          return db1._syncNodes.where('url').equals(url).toArray();\n        })\n        .then((nodes) => {\n          strictEqual(nodes.length, 0, 'All nodes with this url should be deleted');\n          ok(deleteOldChangesWasCalled, 'Observable.deleteOldChanges was called');\n        })\n        .catch(function (err) {\n          ok(false, \"Got error: \" + err);\n        }).finally(start);\n    onSuccess(); // Stop syncing\n  });\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/tests-syncprovider.js",
    "content": "﻿import Dexie from 'dexie';\nimport 'dexie-observable';\nimport '../../src/Dexie.Syncable';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\n\n/*  To Test:\n * db.sync() using WebSocketSyncProtocol and WebSocketSyncServer with two db's of different names in same window.\n * Add object to db1\n * Make sure it appears in db2\n * Add 1100 objects to db2\n * Make sure they all appear in db1  - ALSO DEBUG THAT:\n * db2 sends it's changes as two chunks where first one is partial and second final\n * SyncServer gets partial and applies to uncommittedChanges\n * SyncServer gets final and commits changes, as well as triggers db1 provider, who\n * calls applyRemoteChanges on all objects.\n * db1.on('changes') gets triggered.\n * Add 1000 objects the same way and debug that:\n * db2 sents it's changes as one or two chunks depending on how I implemented it.\n * Add another db with same name as one of the two dbs.\n * Make sure that the third DB is not master\n * Make sure that sync() on third db will call master and make it sync, or connect to existing syncing.\n * Make sure that the third DB gets changes added to other remote db.\n * Take down master and make sure third DB becomes master and can continue syncing.\n */\n\n/* WebSocketSyncProtocol\n * Was copied from /samples/remote-sync/websocket/WebSocketSyncProtocol.js\n * The tests would hang with the original file. Probably because of different instances of\n * Dexie and Dexie.Syncable (there were no registered protocols in the test when using directly the file from samples)\n */\n\n// Constants:\nvar RECONNECT_DELAY = 5000;\nDexie.Syncable.registerSyncProtocol(\"websocket\", {\n\n  sync: function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n    var requestId = 0;\n    var acceptCallbacks = {};\n    var ws = new WebSocket(url);\n    function sendChanges(changes, baseRevision, partial, onChangesAccepted) {\n      ++requestId;\n      acceptCallbacks[requestId.toString()] = onChangesAccepted;\n      ws.send(JSON.stringify({\n        type: 'changes',\n        changes: changes,\n        partial: partial,\n        baseRevision: baseRevision,\n        requestId: requestId\n      }));\n    }\n\n    ws.onopen = function (event) {\n      ws.send(JSON.stringify({\n        type: \"clientIdentity\",\n        clientIdentity: context.clientIdentity || null\n      }));\n      sendChanges(changes, baseRevision, partial, onChangesAccepted);\n      ws.send(JSON.stringify({\n        type: \"subscribe\",\n        syncedRevision: syncedRevision\n      }));\n    }\n    ws.onerror = function (event) {\n      ws.close();\n      onError(event.message, RECONNECT_DELAY);\n    }\n    ws.onclose = function (event) {\n      onError(\"Socket closed: \" + event.reason, RECONNECT_DELAY);\n    }\n    var isFirstRound = true;\n    ws.onmessage = function (event) {\n      try {\n        var requestFromServer = JSON.parse(event.data);\n        if (requestFromServer.type == \"changes\") {\n          applyRemoteChanges(requestFromServer.changes, requestFromServer.currentRevision, requestFromServer.partial);\n          if (isFirstRound && !requestFromServer.partial) {\n            onSuccess({\n              react: function (changes, baseRevision, partial, onChangesAccepted) {\n                sendChanges(changes, baseRevision, partial, onChangesAccepted);\n              },\n              disconnect: function () {\n                ws.close();\n              }\n            });\n            isFirstRound = false;\n          }\n        } else if (requestFromServer.type == \"ack\") {\n          var requestId = requestFromServer.requestId;\n          var acceptCallback = acceptCallbacks[requestId.toString()];\n          acceptCallback(); // Tell framework that server has acknowledged the changes sent.\n          delete acceptCallbacks[requestId.toString()];\n        } else if (requestFromServer.type == \"clientIdentity\") {\n          context.clientIdentity = requestFromServer.clientIdentity;\n          context.save();\n        } else if (requestFromServer.type == \"error\") {\n          var requestId = requestFromServer.requestId;\n          ws.close();\n          onError(requestFromServer.message, Infinity); // Don't reconnect - an error in application level means we have done something wrong.\n        }\n      } catch (e) {\n        ws.close();\n        onError(e, Infinity); // Something went crazy. Server sends invalid format or our code is buggy. Dont reconnect - it would continue failing.\n      }\n    }\n  }\n});\n\nmodule(\"tests-syncprovider\", {\n  setup: function () {\n    stop();\n    Dexie.Promise.all(Dexie.delete(\"SyncProviderTest\"), Dexie.delete(\"OtherClientDB\")).then(function () {\n      start();\n    }).catch(function (e) {\n      ok(false, \"Could not delete database\");\n    });\n  },\n  teardown: function () {\n    stop();\n    Dexie.Promise.all(Dexie.delete(\"SyncProviderTest\"), Dexie.delete(\"OtherClientDB\")\n    ).catch('DatabaseClosedError', function () {\n    }).then(function () {\n      start();\n    });\n  }\n});\n\nasyncTest(\"testSyncProvider\", function () {\n  var CREATE = 1,\n      UPDATE = 2,\n      DELETE = 3;\n\n  var db = new Dexie(\"SyncProviderTest\");\n  db.version(1).stores({\n    friends: \"$$id,name\",\n    pets: \"$$id,kind,name\"\n  });\n\n  // Setup the sync server\n  var server = new SyncServer(5000);\n  server.start();\n\n  // Connect our db client to it\n  db.syncable.connect(\"websocket\", \"http://dummy:5000\");\n  db.syncable.on('statusChanged', function (newStatus, url) {\n    console.log(\"Sync State Changed: \" + Dexie.Syncable.StatusTexts[newStatus]);\n  });\n\n  // Open database\n  db.open();\n\n  // Create another database to sync with:\n  var db2 = new Dexie(\"OtherClientDB\");\n  db2.version(1).stores({\n    friends: \"$$id\",\n    pets: \"$$id\"\n  });\n\n  db2.syncable.connect(\"websocket\", \"http://dummy:5000\");\n  db2.open().then(function () {\n    console.log(\"db2 opened\");\n  });\n\n  db2.on('changes', function (changes, partial) {\n    console.log(\"db2.on('changes'): changes.length: \" + changes.length + \"\\tpartial: \" + (partial ? \"true\" : \"false\"));\n    changes.forEach(function (change) {\n      //console.log(JSON.stringify(change));\n      db2.checkChange(change);\n    });\n  });\n\n  db.on('changes', function (changes, partial) {\n    console.log(\"db.on('changes'): changes.length: \" + changes.length + \"\\tpartial: \" + (partial ? \"true\" : \"false\"));\n    changes.forEach(function (change) {\n      if (db.checkChange) db.checkChange(change);\n    });\n  });\n\n  function waitFor(db, params) {\n    return new Dexie.Promise(function (resolve, reject) {\n      db.checkChange = function (change) {\n        var checker = {};\n        Dexie.extend(checker, change);\n        if (change.type == CREATE) Dexie.extend(checker, change.obj);\n        if (change.type == UPDATE) Dexie.extend(checker, change.mods);\n        var found = true;\n        Object.keys(params).forEach(function (param) {\n          if (!(param in checker)) found = false;\n          if (params[param] != checker[param]) found = false;\n        });\n        if (found) resolve();\n      }\n    });\n  }\n\n  db.friends.add({name: \"David\"});\n\n  waitFor(db2, {type: CREATE, name: \"David\"}).then(function () {\n    ok(true, \"The CREATE of friend 'David' was sent all the way to server and then back again to db2.\");\n    db.friends.where('name').equals('David').modify({name: \"Ylva\"});\n    return waitFor(db2, {type: UPDATE, name: \"Ylva\"});\n  }).then(function () {\n    ok(true, \"The UPDATE of friend 'David' to 'Ylva' was sent all the way around as well\");\n    return db.friends.where('name').equals('Ylva').first(function (friend) {\n      return friend.id;\n    })\n  }).then(function (id) {\n    db.friends.delete(id);\n    return waitFor(db2, {type: DELETE, key: id});\n  }).then(function () {\n    ok(true, \"The DELETE of friend 'Ylva' was sent all the way around as well\");\n    // Now send 1100 create requests\n    var petsToAdd = new Array(1100);\n    for (var i = 0; i < petsToAdd.length; ++i) {\n      petsToAdd[i] = {name: \"Josephina\" + (i + 1), kind: \"Dog\"};\n    }\n    return db2.pets.bulkAdd(petsToAdd);\n  }).then(function () {\n    ok(true, \"Successfully added 1100 pets into db2. Now wait for the last change to be synced into db1.\");\n    return waitFor(db, {type: CREATE, name: \"Josephina1100\"});\n  }).then(function () {\n    ok(true, \"All 1100 dogs where sent all the way around (db2-->db this time)\");\n    // Now check that db2 contains all dogs and that its _uncommittedChanges is emptied\n    return db.pets.count(function (count) {\n      equal(count, 1100, \"DB2 has 1100 pets now\");\n    });\n  }).then(function () {\n    return db._uncommittedChanges.count(function (count) {\n      equal(count, 0, \"DB2 has no uncommitted changes anymore\");\n    });\n  }).then(function () {\n    ok(true, \"Now send 1000 create this time (exact number of max changes per chunk)\");\n    var petsToAdd = new Array(1000);\n    for (var i = 0; i < petsToAdd.length; ++i) {\n      petsToAdd[i] = {name: \"Tito\" + (i + 1), kind: \"Cat\"};\n    }\n    return db.pets.bulkAdd(petsToAdd);\n  }).then(function () {\n    ok(true, \"Successfully added 1000 cats. Now wait for them to arrive in db2.\");\n    return waitFor(db2, {type: CREATE, name: \"Tito1000\"});\n  }).then(function () {\n    ok(true, \"All 1000 cats where sent all the way around (db-->db2 this time)\");\n  }).finally(function () {\n    console.log(\"Closing down\");\n    db.close();\n    db2.close();\n    start();\n  });\n});\n"
  },
  {
    "path": "addons/Dexie.Syncable/test/unit/unit-tests-all.js",
    "content": "import './get-local-changes-for-node/tests-get-base-revision-and-max-client-revision.js';\nimport './get-local-changes-for-node/tests-get-changes-since-revision.js';\nimport './get-local-changes-for-node/tests-get-local-changes-for-node.js';\nimport './tests-apply-changes.js';\nimport './tests-bulk-update.js';\nimport './tests-changing-options.js';\nimport './tests-combine-create-and-update.js';\nimport './tests-combine-update-and-update.js';\nimport './tests-finally-commit-all-changes.js';\nimport './tests-get-or-create-sync-node.js';\nimport './tests-merge-change.js';\nimport './tests-PersistedContext.js';\nimport './tests-register-sync-protocol.js';\nimport './tests-save-to-uncommitted-changes.js';\nimport './tests-syncable.js';\nimport './tests-syncable-partials.js';\nimport './tests-syncprovider.js';\nimport './tests-WebSocketSyncServer.js';\n"
  },
  {
    "path": "addons/Dexie.Syncable/tools/build-configs/banner.txt",
    "content": "/* ========================================================================== \n *                           dexie-syncable.js\n * ==========================================================================\n *\n * Dexie addon for syncing indexedDB with remote endpoints.\n *\n * By David Fahlander, david.fahlander@gmail.com,\n *    Nikolas Poniros, https://github.com/nponiros\n *\n * ==========================================================================\n *\n * Version {version}, {date}\n *\n * https://dexie.org\n *\n * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/\n * \n */\n"
  },
  {
    "path": "addons/Dexie.Syncable/tools/build-configs/rollup.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport {readFileSync} from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst packageJson = JSON.parse(readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));\nconst version = packageJson.version;\n\nexport default {\n  input: 'tools/tmp/es5/addons/Dexie.Syncable/src/Dexie.Syncable.js',\n  output: [{\n    file: 'dist/dexie-syncable.js',\n    format: 'umd',\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n    globals: {dexie: \"Dexie\", \"dexie-observable\": \"Dexie.Observable\"},\n    name: \"Dexie.Syncable\",\n  },{\n    file: 'dist/dexie-syncable.es.js',\n    format: 'es',\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n    globals: {dexie: \"Dexie\", \"dexie-observable\": \"Dexie.Observable\"},\n    name: \"Dexie.Syncable\",\n  }],\n  external: ['dexie', 'dexie-observable'],\n  plugins: [ sourcemaps() ]\n};\n"
  },
  {
    "path": "addons/Dexie.Syncable/tools/build-configs/rollup.tests.config.js",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\"\n];\n\nexport default {\n  input: 'tools/tmp/es5/addons/Dexie.Syncable/test/unit/unit-tests-all.js',\n  output: [{\n    file: 'test/unit/bundle.js',\n    format: 'umd',\n    name: 'dexieSyncableTests',\n    globals: {dexie: \"Dexie\", \"dexie-observable\": \"Dexie.Observable\", QUnit: \"QUnit\"},\n  }],\n  external: ['dexie', 'dexie-observable', 'QUnit'],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true}),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/Dexie.Syncable/tools/build-configs/rollup.tests.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\"\n];\n\nexport default {\n  input: 'tools/tmp/es5/addons/Dexie.Syncable/test/unit/unit-tests-all.js',\n  output: [{\n    file: 'test/unit/bundle.js',\n    format: 'umd',\n    name: 'dexieSyncableTests',\n    globals: {dexie: \"Dexie\", \"dexie-observable\": \"Dexie.Observable\", QUnit: \"QUnit\"},\n  }],\n  external: ['dexie', 'dexie-observable', 'QUnit'],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true}),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/Dexie.Syncable/tools/replaceVersionAndDate.js",
    "content": "const fs = require('fs');\nconst files = process.argv.slice(2);\nconst version = require('../package.json').version;\n\nfiles.forEach(file => {\n    let fileContent = fs.readFileSync(file, \"utf-8\");\n    fileContent = fileContent\n        .replace(/{version}/g, version)\n        .replace(/{date}/g, new Date().toDateString());\n    fs.writeFileSync(file, fileContent, \"utf-8\");\n});\n"
  },
  {
    "path": "addons/dexie-cloud/.gitignore",
    "content": "dist/\nesnext/\n.eslintcache\n**/tmp/\ntest/**/bundle.*\ndexie-cloud.key\ndexie-cloud.json\n"
  },
  {
    "path": "addons/dexie-cloud/.npmignore",
    "content": ".DS_Store\ntools/\nsrc/\nbin-src/\n.*\ntmp/\n**/tmp/\ntest\n*.log\ndexie-cloud.key\ndexie-cloud.json\n"
  },
  {
    "path": "addons/dexie-cloud/.vscode/settings.json",
    "content": "{\n  \"editor.formatOnSave\": true\n}\n"
  },
  {
    "path": "addons/dexie-cloud/README.md",
    "content": "The web client for [Dexie Cloud](https://dexie.org/cloud/).\n\n## Getting started\n\n```\nnpm install dexie@latest\nnpm install dexie-cloud-addon@latest\n```\n\n```ts\nimport { Dexie } from 'dexie';\nimport dexieCloud from 'dexie-cloud-addon';\n\nconst db = new Dexie('dbname', { addons: [dexieCloud]});\n\ndb.version(1).stores({\n    yourTable: '@primKeyProp, indexedProp1, indexedProp2, ...'\n});\n\ndb.cloud.configure({\n  databaseUrl: 'https://<yourdb>.dexie.cloud'\n})\n```\n\n## See also\n\nhttps://dexie.org/cloud/docs/dexie-cloud-addon#api\n\n## Obtaining a database URL\n\nRun the following command in a console / terminal:\n\n```\nnpx dexie-cloud create\n```\n\nSee also https://dexie.org/cloud/#getting-started\n\n*Having problems getting started, please [file an issue](https://github.com/dexie/Dexie.js/issues/new)*\n\n# The Cloud Service\n\n[![Better Stack Badge](https://uptime.betterstack.com/status-badges/v2/monitor/jist.svg)](https://www.dexie-cloud-status.com)\n\nThe official production service for Dexie Cloud Server is forever free of charge so you do not need to install any server to get started. The free service has some limits, see https://dexie.org/cloud/pricing\n\n# On-Prem version\n\nDexie Cloud Server is a closed source software that can be purchased and installed on own hardware, see [On-Prem Silver / On-Prem Gold](https://dexie.org/cloud/pricing)\n\n# CLI\n\nSee https://dexie.org/cloud/docs/cli\n\n# APIs\n\nSee https://dexie.org/cloud/docs/dexie-cloud-api\n"
  },
  {
    "path": "addons/dexie-cloud/TODO-SOCIALAUTH.md",
    "content": "# Social Authentication for Dexie Cloud\n\n## Overview\n\nThis feature adds support for OAuth 2.0 social login providers (Google, GitHub, Microsoft, Apple, and custom OAuth2) as an alternative to the existing OTP (One-Time Password) email authentication in Dexie Cloud.\n\n**Key Design Principle**: The Dexie Cloud server acts as an OAuth broker, handling all provider interactions including the OAuth callback. The client library (dexie-cloud-addon) never receives provider tokens - only Dexie Cloud authorization codes which are exchanged for Dexie Cloud tokens.\n\n### Related Files\n\n- **Detailed flow diagram**: [oauth_flow.md](oauth_flow.md) - Sequence diagrams and detailed protocol description\n- **Server implementation**: See `dexie-cloud-server` repository\n  - `src/api/oauth/registerOAuthEndpoints.ts` - OAuth endpoints\n  - `src/api/oauth/oauth-helpers.ts` - Provider exchange logic\n  - `src/api/registerTokenEndpoint.ts` - Token endpoint (authorization_code grant)\n\n### Flow Summary\n\n1. **Client** fetches available auth providers from `GET /auth-providers`\n2. **Client** redirects to `GET /oauth/login/:provider` (full page redirect)\n3. **Dexie Cloud Server** redirects to OAuth provider and handles callback at `/oauth/callback/:provider`\n4. **Server** exchanges provider code for tokens, verifies email, generates single-use Dexie auth code\n5. **Server** redirects back to client with `dxc-auth` query parameter (base64url-encoded JSON)\n6. **Client** detects `dxc-auth` in `db.cloud.configure()`, exchanges code for tokens via `POST /token`\n\n### Supported Providers\n- **Google** - OpenID Connect with PKCE\n- **GitHub** - OAuth 2.0 (client secret only)\n- **Microsoft** - OpenID Connect with PKCE\n- **Apple** - Sign in with Apple (form_post response mode)\n- **Custom OAuth2** - Configurable endpoints for self-hosted identity providers\n\n### Client Delivery Methods\n\n| Method | Use Case | Delivery Mechanism |\n|--------|----------|-------------------|\n| **Full Page Redirect** | Web SPAs (recommended) | HTTP redirect with `dxc-auth` query param |\n| **Custom URL Scheme** | Capacitor/Native apps | Deep link redirect (e.g., `myapp://`) |\n\n---\n\n## Implementation Status\n\n### ✅ Server-Side (dexie-cloud-server) - COMPLETE\n\n- [x] **OAuth provider configuration type** (`OAuthProviderConfig`)\n- [x] **`GET /auth-providers` endpoint** - Returns enabled providers and OTP status\n- [x] **`GET /oauth/login/:provider` endpoint** - Initiates OAuth flow with PKCE\n- [x] **`GET /oauth/callback/:provider` endpoint** - Handles provider callback, redirects with `dxc-auth`\n- [x] **`POST /token` with `grant_type: \"authorization_code\"`** - Exchanges Dexie auth code for tokens\n- [x] **OAuth helper functions** (`oauth-helpers.ts`)\n- [x] **Configuration GUI in dexie-cloud-manager**\n\n### ✅ Client-Side (dexie-cloud-addon) - COMPLETE\n\n#### Types in dexie-cloud-common\n\n- [x] **`OAuthProviderInfo` type** - Provider metadata\n- [x] **`AuthProvidersResponse` type** - Response from `/auth-providers`\n- [x] **`AuthorizationCodeTokenRequest` type** - Token request for OAuth codes\n\n#### Types and Interfaces in dexie-cloud-addon\n\n- [x] **Extended `LoginHints` interface** - Added `provider`, `oauthCode`\n- [x] **`DXCProviderSelection` interaction type** - For provider selection UI\n- [x] **`DexieCloudOptions` extension** - Added `socialAuth`, `oauthRedirectUri`\n\n#### Core Authentication Flow\n\n- [x] **`fetchAuthProviders()`** - Fetches available providers from server\n- [x] **`startOAuthRedirect()`** - Initiates OAuth via full page redirect\n- [x] **`parseOAuthCallback()`** - Parses `dxc-auth` query parameter\n- [x] **`cleanupOAuthUrl()`** - Removes `dxc-auth` from URL via `history.replaceState()`\n- [x] **`exchangeOAuthCode()`** - Exchanges Dexie auth code for tokens\n- [x] **OAuth detection in `configure()`** - Auto-detects `dxc-auth` on page load\n- [x] **OAuth processing in `onDbReady`** - Completes login when database is ready\n- [x] **Updated `login()` function** - Supports `provider` and `oauthCode` hints\n\n#### Default UI Components\n\n- [x] **`ProviderSelectionDialog`** - Renders provider selection screen\n- [x] **`AuthProviderButton`** - Renders individual provider buttons with icons\n- [x] **`OtpButton`** - \"Continue with email\" option\n- [x] **Updated `LoginDialog.tsx`** - Handles OAuth redirect flow\n- [x] **Updated `Styles.ts`** - Provider button styles\n\n#### Error Handling\n\n- [x] **`OAuthError` class** - Error codes: `access_denied`, `invalid_state`, `email_not_verified`, `expired_code`, `provider_error`, `network_error`\n\n---\n\n## 🔲 Remaining TODO\n\n### Testing\n\n- [ ] **Unit tests for OAuth flow**\n  - Test `parseOAuthCallback()` with various `dxc-auth` payloads\n  - Test error scenarios\n  - Test URL cleanup\n\n- [ ] **Integration tests**\n  - Test full redirect flow with mock server\n  - Test token exchange\n\n- [ ] **Manual testing**\n  - Test with `samples/dexie-cloud-todo-app`\n  - Test with Capacitor app (deep links)\n\n### Documentation\n\n- [ ] **Update README.md**\n  - Document OAuth login: `db.cloud.login({ provider: 'google' })`\n  - Show Capacitor integration pattern\n  - Explain redirect flow\n\n- [ ] **Update dexie.org docs**\n  - Add OAuth configuration guide\n  - Document `socialAuth` and `oauthRedirectUri` options\n\n---\n\n## Client Integration Patterns\n\n### Web SPA (Redirect Flow)\n\n```typescript\n// Configure database\ndb.cloud.configure({\n  databaseUrl: 'https://mydb.dexie.cloud'\n});\n\n// OAuth callback is handled automatically!\n// When page loads with ?dxc-auth=..., the addon:\n// 1. Detects the parameter in configure()\n// 2. Cleans up the URL immediately\n// 3. Completes login in db.on('ready')\n\n// To manually initiate OAuth (e.g., from custom UI):\nawait db.cloud.login({ provider: 'google' });\n// Page redirects to OAuth provider, then back with auth code\n```\n\n### Capacitor / Native App\n\n```typescript\n// Configure with custom URL scheme\ndb.cloud.configure({\n  databaseUrl: 'https://mydb.dexie.cloud',\n  oauthRedirectUri: 'myapp://'\n});\n\n// Handle deep link in app\nApp.addListener('appUrlOpen', async ({ url }) => {\n  const callback = handleOAuthCallback(url);\n  if (callback) {\n    await db.cloud.login({ \n      oauthCode: callback.code, \n      provider: callback.provider \n    });\n  }\n});\n\n// Initiate login (opens system browser)\nawait db.cloud.login({ provider: 'google' });\n```\n\n---\n\n## Architecture Diagram\n\n```\n┌──────────────────────────────────────────────────────────────────────────────┐\n│                              CLIENT (dexie-cloud-addon)                       │\n│                                                                              │\n│  ┌─────────────────┐    ┌───────────────────┐                               │\n│  │ LoginDialog     │───▶│ startOAuthRedirect│──▶ window.location.href =     │\n│  │ (default UI)    │    │ ()                │    /oauth/login/:provider     │\n│  └─────────────────┘    └───────────────────┘                               │\n│                                                                              │\n│            ... page navigates away, user authenticates ...                  │\n│                                                                              │\n│  ┌─────────────────┐    ┌───────────────────┐                               │\n│  │ Page loads with │───▶│ db.cloud.         │──▶ Detects dxc-auth param     │\n│  │ ?dxc-auth=...   │    │ configure()       │    Cleans URL immediately     │\n│  └─────────────────┘    └───────────────────┘    Stores pending code        │\n│                                   │                                          │\n│                                   ▼                                          │\n│                         ┌─────────────────┐    ┌─────────────────────────┐  │\n│                         │ db.on('ready')  │───▶│ POST /token             │  │\n│                         │                 │    │ grant_type:             │  │\n│                         │                 │◀───│ authorization_code      │  │\n│                         └─────────────────┘    └─────────────────────────┘  │\n│                                   │                                          │\n│                                   ▼                                          │\n│                         ┌─────────────────┐                                  │\n│                         │ User logged in! │                                  │\n│                         └─────────────────┘                                  │\n└──────────────────────────────────────────────────────────────────────────────┘\n\n┌──────────────────────────────────────────────────────────────────────────────┐\n│                         DEXIE CLOUD SERVER                                   │\n│                                                                              │\n│  /oauth/login/:provider                                                      │\n│  ├── Generate state, PKCE                                                   │\n│  ├── Store in challenges table                                              │\n│  └── Redirect to OAuth provider                                             │\n│                                                                              │\n│  /oauth/callback/:provider  ◀── OAuth provider redirects here               │\n│  ├── Verify state                                                           │\n│  ├── Exchange code for provider tokens (server-side!)                       │\n│  ├── Fetch user info, verify email                                          │\n│  ├── Generate Dexie auth code (single-use, 5 min TTL)                       │\n│  └── HTTP 302 redirect with ?dxc-auth=<base64url-json>                      │\n│                                                                              │\n│  POST /token (grant_type: authorization_code)                               │\n│  ├── Validate Dexie auth code                                               │\n│  ├── Extract stored user claims                                             │\n│  └── Return Dexie Cloud access + refresh tokens                             │\n└──────────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## The `dxc-auth` Query Parameter\n\nThe OAuth callback uses a single `dxc-auth` query parameter containing base64url-encoded JSON to avoid collisions with app query parameters:\n\n**Success:**\n```json\n{ \"code\": \"DEXIE_AUTH_CODE\", \"provider\": \"google\", \"state\": \"...\" }\n```\n\n**Error:**\n```json\n{ \"error\": \"Error message\", \"provider\": \"google\", \"state\": \"...\" }\n```\n\nExample URL:\n```\nhttps://myapp.com/?dxc-auth=eyJjb2RlIjoiLi4uIiwicHJvdmlkZXIiOiJnb29nbGUiLCJzdGF0ZSI6Ii4uLiJ9\n```\n\n---\n\n## Security Properties\n\n- 🛡 **No provider tokens reach client** - All provider exchange happens server-side\n- 🛡 **Single-use Dexie auth codes** - 5 minute TTL, deleted after use\n- 🛡 **PKCE protection** - Prevents code interception (where supported)\n- 🛡 **State parameter** - CSRF protection, stored server-side\n- 🛡 **Origin validation** - redirect_uri validated and whitelisted\n- 🛡 **Email verification enforced** - Server rejects unverified emails\n- 🛡 **No tokens in URL fragments** - Auth code in query param, not fragment\n\n---\n\n## Key Files\n\n**dexie-cloud-common:**\n- `src/OAuthProviderInfo.ts`\n- `src/AuthProvidersResponse.ts`\n- `src/AuthorizationCodeTokenRequest.ts`\n\n**dexie-cloud-addon:**\n- `src/authentication/oauthLogin.ts` - `startOAuthRedirect()`, `mapOAuthError()`\n- `src/authentication/handleOAuthCallback.ts` - `parseOAuthCallback()`, `cleanupOAuthUrl()`\n- `src/authentication/exchangeOAuthCode.ts` - Token exchange\n- `src/authentication/fetchAuthProviders.ts` - Fetch available providers\n- `src/errors/OAuthError.ts` - OAuth-specific errors\n- `src/default-ui/ProviderSelectionDialog.tsx` - Provider selection UI\n- `src/default-ui/AuthProviderButton.tsx` - Provider button component\n- `src/dexie-cloud-client.ts` - OAuth detection in `configure()`, processing in `onDbReady`\n- `src/DexieCloudOptions.ts` - `socialAuth`, `oauthRedirectUri` options\n"
  },
  {
    "path": "addons/dexie-cloud/dexie-cloud-import.json",
    "content": "{\n  \"demoUsers\": {\n    \"foo@demo.local\": {},\n    \"bar@demo.local\": {},\n    \"issue2228@demo.local\": {}\n  }\n}"
  },
  {
    "path": "addons/dexie-cloud/oauth_flow.md",
    "content": "# OAuth Authorization Code Flow for Dexie Cloud SPA Integration\n\n## Actors\n\n- **SPA** – Customer's frontend application\n- **Dexie Cloud** – Auth broker + database access control\n- **OAuth Provider** – Google, GitHub, Apple, Microsoft, etc.\n\n## Preconditions\n\nThe SPA:\n\n- Generates a persistent public/private keypair\n  - Private key stored securely in IndexedDB\n  - Public key sent later during token exchange\n- Needs two JWTs after login:\n  - Access Token (short-lived)\n  - Refresh Token (long-lived)\n\nDexie Cloud acts as OAuth broker and manages tenant + identity linkage.\n\n---\n\n## Flow Overview\n\n### 1. User Initiates Login\n\nUser clicks \"Login\", SPA displays list of providers:\n\n```\nGoogle | GitHub | Apple | Microsoft\n```\n\nNo nonce or PKCE is created yet.\n\n---\n\n### 2. User Selects Provider\n\nExample: User selects **Google**\n\nThe client initiates the OAuth flow. There are two ways to do this:\n\n#### 2a. Full Page Redirect (Recommended for Web SPAs)\n\n```js\nwindow.location.href = `https://<db>.dexie.cloud/oauth/login/google?redirect_uri=${encodeURIComponent(location.href)}`;\n```\n\nThe `redirect_uri` parameter specifies where Dexie Cloud should redirect after authentication.\nThis can be any page in your app - no dedicated callback route is needed.\n\n#### 2b. Custom URL Scheme (Capacitor / Native Apps)\n\n```js\n// Open in system browser or in-app browser\nBrowser.open({\n  url: `https://<db>.dexie.cloud/oauth/login/google?redirect_uri=${encodeURIComponent('myapp://')}`\n});\n```\n\nThe custom scheme `myapp://` tells Dexie Cloud to redirect back via deep link.\n\n---\n\n### 3. Dexie Cloud Prepares OAuth\n\nDexie Cloud receives `/oauth/login/google` and generates:\n\n- `state` (anti-CSRF)\n- `code_verifier` (PKCE)\n- `code_challenge` (PKCE)\n\nStores these in the challenges table, then redirects the browser to provider:\n\n```\nhttps://accounts.google.com/o/oauth2/v2/auth?\n  client_id=...\n  redirect_uri=https://<db>.dexie.cloud/oauth/callback/google\n  state=STATE\n  code_challenge=CHALLENGE\n  code_challenge_method=S256\n  response_type=code\n  scope=openid email profile\n```\n\nNote: `redirect_uri` points to the **Dexie Cloud server** callback endpoint.\n\n---\n\n### 4. Provider Authenticates User\n\nProvider authenticates the user and requests consent if needed.\n\n---\n\n### 5. Provider Callback to Dexie Cloud\n\nProvider redirects back to Dexie Cloud:\n\n```\nhttps://<db>.dexie.cloud/oauth/callback/google?code=CODE&state=STATE\n```\n\nDexie Cloud:\n\n1. Verifies `state`\n2. Performs token exchange with provider using PKCE\n3. Extracts identity claims (email/id/name/…)\n4. Verifies email is verified\n5. Links identity to tenant/database\n6. Generates a **single-use Dexie Cloud authorization code**\n7. Deletes the OAuth state (one-time use)\n\n---\n\n### 6. Dexie Cloud Delivers Auth Code to Client\n\nDexie Cloud issues an HTTP 302 redirect back to the client with the authorization code.\nThe auth data is encapsulated in a single `dxc-auth` query parameter containing base64url-encoded JSON.\nThis avoids collisions with the app's own query parameters.\n\n#### 6a. Full Page Redirect (Web SPAs)\n\nIf the client passed an http/https `redirect_uri`, Dexie Cloud redirects:\n\n```\nHTTP/1.1 302 Found\nLocation: https://myapp.com/?dxc-auth=eyJjb2RlIjoiLi4uIiwicHJvdmlkZXIiOiJnb29nbGUiLCJzdGF0ZSI6Ii4uLiJ9\n```\n\nThe `dxc-auth` parameter contains base64url-encoded JSON:\n\n```json\n{ \"code\": \"DEXIE_AUTH_CODE\", \"provider\": \"google\", \"state\": \"STATE\" }\n```\n\nOr in case of error:\n\n```json\n{ \"error\": \"Error message\", \"provider\": \"google\", \"state\": \"STATE\" }\n```\n\nThe app doesn't need a dedicated OAuth callback route - the dexie-cloud client library\ndetects and processes the `dxc-auth` parameter on any page load.\n\n#### 6b. Custom URL Scheme (Capacitor / Native Apps)\n\nIf the client passed a `redirect_uri` with a custom scheme (e.g., `myapp://`),\nDexie Cloud redirects to that URL with the same `dxc-auth` parameter:\n\n```\nHTTP/1.1 302 Found\nLocation: myapp://?dxc-auth=eyJjb2RlIjoiLi4uIiwicHJvdmlkZXIiOiJnb29nbGUiLCJzdGF0ZSI6Ii4uLiJ9\n```\n\nThe native app intercepts this deep link and decodes the parameter.\n\n#### 6c. Error Case\n\nIf no valid `redirect_uri` was provided, an error page is displayed\nexplaining that the auth flow cannot complete.\n\n---\n\n### 7. Client Receives Authorization Code\n\n**For Full Page Redirect (6a):**\n\nThe `dexie-cloud-addon` library handles OAuth callback detection automatically:\n\n1. When `db.cloud.configure()` is called, the addon checks for the `dxc-auth` query parameter\n2. This check only runs in DOM environments (not in Web Workers)\n3. If the parameter is present:\n   - The URL is immediately cleaned up using `history.replaceState()` to remove `dxc-auth`\n   - A `setTimeout(cb, 0)` is scheduled to initiate the token exchange\n   - The token exchange fetches from the configured `databaseUrl`\n   - The response is processed in the existing `db.on('ready')` callback when Dexie is ready\n\n```js\n// Pseudocode for dexie-cloud-addon implementation\nfunction configure(options) {\n  // Only check in DOM environment, not workers\n  if (typeof window !== 'undefined' && window.location) {\n    const encoded = new URLSearchParams(location.search).get('dxc-auth');\n    if (encoded) {\n      // Decode base64url (unpadded) to JSON\n      const padded = encoded + '='.repeat((4 - (encoded.length % 4)) % 4);\n      const base64 = padded.replace(/-/g, '+').replace(/_/g, '/');\n      const payload = JSON.parse(atob(base64));\n      const { code, provider, state, error } = payload;\n\n      // Clean up URL immediately (remove dxc-auth param)\n      const url = new URL(location.href);\n      url.searchParams.delete('dxc-auth');\n      history.replaceState(null, '', url.toString());\n\n      if (!error) {\n        // Schedule token exchange (processed in db.on('ready'))\n        setTimeout(() => {\n          // Perform token exchange with options.databaseUrl\n        }, 0);\n      }\n    }\n  }\n}\n```\n\n**For Capacitor/Native Apps (6b):**\n\nApp registers a deep link handler and decodes the same parameter:\n\n```js\n// Capacitor example\nApp.addListener('appUrlOpen', ({ url }) => {\n  const parsedUrl = new URL(url);\n  const encoded = parsedUrl.searchParams.get('dxc-auth');\n  if (encoded) {\n    const padded = encoded + '='.repeat((4 - (encoded.length % 4)) % 4);\n    const base64 = padded.replace(/-/g, '+').replace(/_/g, '/');\n    const payload = JSON.parse(atob(base64));\n    const { code, provider, state, error } = payload;\n    // Proceed to token exchange\n  }\n});\n```\n\nUpon success, client proceeds to token exchange.\n\n---\n\n### 8. Client Performs Token Exchange\n\nClient sends:\n\n```http\nPOST /token\nContent-Type: application/json\n```\n\nPayload:\n\n```json\n{\n  \"grant_type\": \"authorization_code\",\n  \"code\": \"<DEXIE_AUTH_CODE>\",\n  \"public_key\": \"<SPA_PUBLIC_KEY>\",\n  \"scopes\": [\"ACCESS_DB\"]\n}\n```\n\nDexie Cloud validates:\n\n- Dexie authorization code integrity\n- TTL (5 minutes)\n- Single-use constraint\n- Database context\n- User identity and claims from stored data\n- Subscription/license status\n\n---\n\n### 9. Dexie Cloud Issues Tokens\n\nDexie Cloud responds with:\n\n```json\n{\n  \"access_token\": \"...\",\n  \"refresh_token\": \"...\",\n  \"expires_in\": 3600,\n  \"token_type\": \"Bearer\"\n}\n```\n\nThis completes authentication.\n\n---\n\n## Security Properties Achieved\n\n- 🛑 No JWTs exposed via URL fragments\n- 🛑 Provider tokens never reach SPA (only Dexie tokens)\n- 🛡 Single-use Dexie authorization code (5 min TTL)\n- 🛡 PKCE prevents provider code interception\n- 🛡 State stored server-side with TTL (30 min)\n- 🛡 CSRF protection via `state` parameter\n- 🛡 OAuth state deleted after use\n- 🛡 Dexie auth code deleted after use\n- 🛡 Email verification enforced by server\n- 🛡 All provider exchanges happen server-side\n- 🛡 CORS + origin protections during `/token` exchange\n- 🛡 Future PoP (Proof-of-Possession) enabled via SPA public key\n- 🛡 Works with Apple, Google, Microsoft, GitHub\n\n---\n\n## Resulting Benefits\n\n- Works for SPA / PWA / Capacitor / WebViews\n- Supports multi-tenant architectures\n- Supports native account linking\n- Enables refresh token rotation\n- Supports offline-first/local-first model\n\nThis aligns with modern OIDC/OAuth best practices (2023+) and matches architectures used by:\nAuth0, Firebase, Supabase, Okta, MSAL, Google Identity Services, Clerk, etc.\n"
  },
  {
    "path": "addons/dexie-cloud/package.json",
    "content": "{\n  \"name\": \"dexie-cloud-addon\",\n  \"version\": \"4.4.1\",\n  \"description\": \"Dexie addon that syncs with to Dexie Cloud\",\n  \"type\": \"module\",\n  \"module\": \"dist/modern/dexie-cloud-addon.js\",\n  \"homepage\": \"https://dexie.org/cloud/docs/dexie-cloud-addon\",\n  \"exports\": {\n    \".\": {\n      \"development\": {\n        \"import\": \"./dist/modern/dexie-cloud-addon.js\",\n        \"require\": null,\n        \"types\": \"./dist/modern/dexie-cloud-addon.d.ts\"\n      },\n      \"production\": {\n        \"import\": \"./dist/modern/dexie-cloud-addon.min.js\",\n        \"require\": null,\n        \"types\": \"./dist/modern/dexie-cloud-addon.d.ts\"\n      },\n      \"default\": {\n        \"import\": \"./dist/modern/dexie-cloud-addon.min.js\",\n        \"require\": null,\n        \"types\": \"./dist/modern/dexie-cloud-addon.d.ts\"\n      }\n    },\n    \"./service-worker\": {\n      \"development\": {\n        \"import\": \"./dist/modern/service-worker.js\",\n        \"require\": \"./dist/umd/service-worker.js\",\n        \"default\": \"./dist/umd/service-worker.js\",\n         \"types\": \"./dist/modern/service-worker.d.ts\"\n      },\n      \"production\": {\n        \"import\": \"./dist/modern/service-worker.min.js\",\n        \"require\": \"./dist/umd/service-worker.min.js\",\n        \"default\": \"./dist/umd/service-worker.min.js\",\n        \"types\": \"./dist/modern/service-worker.d.ts\"\n      },\n      \"default\": {\n        \"import\": \"./dist/modern/service-worker.min.js\",\n        \"require\": \"./dist/umd/service-worker.min.js\",\n        \"default\": \"./dist/umd/service-worker.min.js\",\n        \"types\": \"./dist/modern/service-worker.d.ts\"\n      }\n    }\n  },\n  \"types\": \"dist/modern/dexie-cloud-addon.d.ts\",\n  \"engines\": {\n    \"node\": \">=14\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dexie/Dexie.js.git\"\n  },\n  \"scripts\": {\n    \"test\": \"just-build test && pnpm run test-unit\",\n    \"test-unit\": \"karma start test/unit/karma.conf.cjs --single-run\",\n    \"build\": \"rollup -c tools/build-configs/rollup.config.mjs\",\n    \"watch\": \"rollup -c tools/build-configs/rollup.config.mjs --watch\",\n    \"clean\": \"rm -rf tools/tmp dist test/unit/bundle.*\",\n    \"prepack\": \"pnpm run build\"\n  },\n  \"just-build\": {\n    \"default\": [\n      \"rollup -c tools/build-configs/rollup.config.mjs\"\n    ],\n    \"test\": [\n      \"just-build test-unit\"\n    ],\n    \"test-unit\": [\n      \"tsc -p test [--watch 'Watching for file changes.']\",\n      \"rollup -c tools/build-configs/rollup.test.unit.config.js\"\n    ]\n  },\n  \"author\": \"david.fahlander@gmail.com\",\n  \"license\": \"Apache-2.0\",\n  \"devDependencies\": {\n    \"@rollup/plugin-commonjs\": \"^29.0.0\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.3\",\n    \"@rollup/plugin-replace\": \"^5.0.4\",\n    \"@rollup/plugin-terser\": \"^0.4.4\",\n    \"@rollup/plugin-typescript\": \"^12.3.0\",\n    \"@types/node\": \"^18.11.18\",\n    \"just-build\": \"*\",\n    \"karma\": \"*\",\n    \"karma-chrome-launcher\": \"*\",\n    \"karma-firefox-launcher\": \"*\",\n    \"karma-qunit\": \"*\",\n    \"karma-webdriver-launcher\": \"*\",\n    \"qunit\": \"2.10.0\",\n    \"qunitjs\": \"1.23.1\",\n    \"lib0\": \"^0.2.97\",\n    \"preact\": \"*\",\n    \"rollup\": \"^4.53.3\",\n    \"terser\": \"^5.20.0\",\n    \"tslib\": \"*\",\n    \"typescript\": \"^5.6.3\",\n    \"y-dexie\": \"workspace:>=4.2.0-alpha.1 <5.0.0\"\n  },\n  \"dependencies\": {\n    \"dexie-cloud-common\": \"workspace:^\",\n    \"y-dexie\": \"workspace:^\",\n    \"rxjs\": \"^7.x\",\n    \"yjs\": \"^13.6.27\",\n    \"y-protocols\": \"^1.0.6\"\n  },\n  \"peerDependencies\": {\n    \"dexie\": \"workspace:^\"\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/DISABLE_SERVICEWORKER_STRATEGY.ts",
    "content": "import { isFirefox } from './isFirefox';\nimport { isSafari, safariVersion } from './isSafari';\n\n// What we know: Safari 14.1 (version 605) crashes when using dexie-cloud's service worker.\n// We don't know what exact call is causing this. Have tried safari-14-idb-fix with no luck.\n// Something we do in the service worker is triggering the crash.\n// When next Safari version (606) is out we will start enabling SW again, hoping that the bug is solved.\n// If not, we might increment 605 to 606.\nexport const DISABLE_SERVICEWORKER_STRATEGY =\n  (isSafari && safariVersion <= 605) || // Disable for Safari for now.\n  isFirefox; // Disable for Firefox for now. Seems to have a bug in reading CryptoKeys from IDB from service workers\n"
  },
  {
    "path": "addons/dexie-cloud/src/DXCWebSocketStatus.ts",
    "content": "export type DXCWebSocketStatus = \"not-started\" | \"connecting\" | \"connected\" | \"disconnected\" | \"error\";\n\n"
  },
  {
    "path": "addons/dexie-cloud/src/DexieCloudAPI.ts",
    "content": "import { DexieCloudOptions } from './DexieCloudOptions';\nimport { DBRealmRole, DexieCloudSchema, AuthProvidersResponse } from 'dexie-cloud-common';\nimport { UserLogin } from './db/entities/UserLogin';\nimport { PersistedSyncState } from './db/entities/PersistedSyncState';\nimport { SyncState } from './types/SyncState';\nimport { DXCUserInteraction } from './types/DXCUserInteraction';\nimport { DXCWebSocketStatus } from './DXCWebSocketStatus';\nimport { PermissionChecker } from './PermissionChecker';\nimport { DexieCloudSyncOptions } from \"./DexieCloudSyncOptions\";\nimport { Invite } from './Invite';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\n/** Progress state for blob downloads */\nexport interface BlobProgress {\n  /** Whether blob downloads are currently in progress */\n  isDownloading: boolean;\n  \n  /** Number of blobs remaining to download */\n  blobsRemaining: number;\n  \n  /** Total bytes remaining to download (estimated from BlobRef.$size) */\n  bytesRemaining: number;\n}\n\n/** The API of db.cloud, where `db` is an instance of Dexie with dexie-cloud-addon active.\n */\n\nexport interface LoginHints {\n  email?: string;\n  userId?: string;\n  grant_type?: 'demo' | 'otp';\n  otpId?: string;\n  otp?: string;\n  /** OAuth provider name to initiate OAuth flow (e.g., 'google', 'github') */\n  provider?: string;\n  /** Dexie Cloud authorization code received from OAuth callback */\n  oauthCode?: string;\n  /** Optional redirect path (relative or absolute) to use for OAuth redirect URI. */\n  redirectPath?: string;\n}\n\nexport interface DexieCloudAPI {\n  // Version of dexie-cloud-addon\n  version: string;\n\n  // Options configured using db.cloud.configure()\n  options: DexieCloudOptions | null;\n\n  // Dexie-Cloud specific schema (complementary to dexie schema)\n  schema: DexieCloudSchema | null;\n\n  // UserID of the currently logged in user. If not logged in,\n  // this string will be \"unauthorized\"\n  currentUserId: string;\n\n  // Observable of currently logged in user\n  currentUser: BehaviorSubject<UserLogin>;\n\n  // Observable of current WebSocket status\n  webSocketStatus: BehaviorSubject<DXCWebSocketStatus>;\n\n  // Observable of current Sync State\n  syncState: BehaviorSubject<SyncState>;\n\n  // Observable of persisted sync state\n  persistedSyncState: BehaviorSubject<PersistedSyncState | undefined>;\n\n  /** Observable of blob download progress.\n   * \n   * Shows the current state of background blob downloads (when blobMode='eager')\n   * or provides insight into unresolved blobs (when blobMode='lazy').\n   * \n   * Use this to show progress indicators or \"downloading for offline\" status.\n   */\n  blobProgress: Observable<BlobProgress>;\n\n  events: {\n    syncComplete: Observable<void>;\n  }\n\n  // Observable reflecting the GUI data that Dexie Cloud wants you to render if using\n  // db.cloud.configure({customLoginGui: true}).\n  // The information it wants you to render is login dialogs and error alerts.\n  // The information also contains action callbacks to call from Submit / Cancel buttons.\n  userInteraction: BehaviorSubject<DXCUserInteraction | undefined>;\n\n  // Observable of invites for the user to accept or reject\n  invites: Observable<Invite[]>;\n\n  // Observable of global application roles - a liveQuery() of the 'roles' table\n  roles: Observable<{[roleName: string]: DBRealmRole}>;\n\n  // Boolean whether service worker is used or not\n  usingServiceWorker?: boolean;\n  \n  // Boolean whether this Dexie instance is a private Dexie instance owned by\n  // the built-in Dexie Cloud service worker.\n  isServiceWorkerDB?: boolean;\n\n  /** Login using Dexie Cloud OTP or Demo user.\n   *\n   * @param email Email to authenticate\n   * @param userId Optional userId to authenticate\n   * @param grant_type requested grant type\n   */\n  login(hint?: LoginHints): Promise<void>;\n\n  logout(options?: {force?: boolean}): Promise<void>;\n\n  /**\n   * Connect to given URL\n   */\n  configure(options: DexieCloudOptions): void;\n\n  /** Trigger a sync\n   *\n   */\n  sync(options?: DexieCloudSyncOptions): Promise<void>;\n\n  /** Method that returns an observable of the available permissions of given\n   * entity.\n   * \n   * @param entity Entity to check permission for\n   */\n  permissions<T extends { owner: string; realmId: string; table: () => string; }>(entity: T): Observable<PermissionChecker<T>>;\n\n  /** Method that returns an observable of the available permissions of given\n   * object and table name.\n   * \n   * @param obj Object retrieved from a dexie query\n   * @param table Table name that the object was retrieved from\n   */\n   permissions<T>(obj: T, table: string): Observable<PermissionChecker<T, string>>;\n\n  /** Query available authentication providers from the server.\n   * \n   * Returns information about which OAuth providers are configured\n   * and whether OTP (email) authentication is enabled.\n   * \n   * Useful for apps that want to build their own login UI and show\n   * provider-specific buttons.\n   * \n   * @returns Promise resolving to available auth providers\n   */\n  getAuthProviders(): Promise<AuthProvidersResponse>;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/DexieCloudOptions.ts",
    "content": "import type { TokenFinalResponse } from 'dexie-cloud-common';\nimport type { LoginHints } from './DexieCloudAPI';\n\nexport interface PeriodicSyncOptions {\n  // The minimum interval time, in milliseconds, at which the service-worker's\n  // periodic sync should occur.\n  minInterval?: number;\n}\nexport interface DexieCloudOptions {\n  // URL to a database created with `npx dexie-cloud create`\n  databaseUrl: string;\n\n  // Whether to require authentication or opt-in to it using db.cloud.login()\n  requireAuth?: boolean | LoginHints\n\n  // Whether to use service worker. Combine with registering your own service\n  // worker and import \"dexie-cloud-addon/dist/modern/service-worker.min.js\" from it.\n  tryUseServiceWorker?: boolean;\n\n  // Optional customization of periodic sync.\n  // See https://developer.mozilla.org/en-US/docs/Web/API/PeriodicSyncManager/register\n  periodicSync?: PeriodicSyncOptions;\n\n  // Disable default login GUI and replace it with your own by\n  // subscribing to the `db.cloud.userInteraction` observable and render its emitted data.\n  customLoginGui?: boolean;\n\n  // Array of table names that should be considered local-only and\n  // not be synced with Dexie Cloud\n  unsyncedTables?: string[];\n\n  unsyncedProperties?: {\n    [tableName: string]: string[];\n  }\n\n  // By default Dexie Cloud will suffix the cloud DB ID to your IndexedDB database name\n  // in order to ensure that the local database is uniquely tied to the remote one and\n  // will use another local database if databaseURL is changed or if dexieCloud addon\n  // is not being used anymore.\n  //\n  // By setting this value to `false`, no suffix will be added to the database name and\n  // instead, it will use the exact name that is specified in the Dexie constructor, \n  // without a suffix.\n  nameSuffix?: boolean;\n\n  // Disable websocket connection\n  disableWebSocket?: boolean;\n\n  // Disable automatic sync on changes\n  disableEagerSync?: boolean;\n\n  // Provides a custom way of fetching the JWT tokens. This option\n  // can be used when integrating with custom authentication.\n  // See https://dexie.org/cloud/docs/db.cloud.configure()#fetchtoken\n  fetchTokens?: (tokenParams: {\n    public_key: string;\n    hints?: { userId?: string; email?: string };\n  }) => Promise<TokenFinalResponse>;\n\n  awarenessProtocol?: typeof import('y-protocols/awareness');\n\n  /** Enable social/OAuth authentication.\n   * - true (default): Fetch providers from server, show if available\n   * - false: Disable OAuth, always use OTP flow\n   * \n   * Use `false` for backward compatibility if your custom login UI\n   * doesn't handle the `DXCSelect` interaction type yet.\n   */\n  socialAuth?: boolean;\n\n  /** Redirect URI for OAuth callback.\n   * Defaults to window.location.href for web SPAs.\n   * \n   * For Capacitor/native apps, set this to a custom URL scheme:\n   * ```\n   * oauthRedirectUri: 'myapp://'\n   * ```\n   */\n  oauthRedirectUri?: string;\n\n  /** How to handle blob downloads from cloud storage.\n   * \n   * - 'eager' (default): Download blobs in background immediately after sync.\n   *   Best for offline-first apps that need all data available offline ASAP.\n   * \n   * - 'lazy': Download blobs on-demand when accessed.\n   *   Best for apps with large media that may not all be needed offline.\n   */\n  blobMode?: 'eager' | 'lazy';\n\n  /** Maximum string length (in characters) before offloading to blob storage during sync.\n   * \n   * Strings longer than this threshold are uploaded as blobs during sync,\n   * reducing sync payload size. The original string is kept intact in IndexedDB.\n   * \n   * Set to `Infinity` to disable string offloading.\n   * Minimum value is 100 to prevent accidental offloading of primary keys.\n   * Maximum value is 32768 (server limit).\n   * \n   * @default 32768\n   */\n  maxStringLength?: number;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/DexieCloudSyncOptions.ts",
    "content": "\nexport interface DexieCloudSyncOptions {\n  wait: boolean;\n  purpose: 'push' | 'pull';\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/DexieCloudTable.ts",
    "content": "import { EntityTable, InsertType } from 'dexie';\n\nexport interface DexieCloudEntity {\n  owner: string;\n  realmId: string;\n}\n\n/** Don't force the declaration of owner and realmId on every entity (some \n * types may not be interested of these props if they are never going to be shared)\n * Let the type system behave the same as the runtime and merge these props in automatically\n * when declaring the table where the props aren't explicitely declared.\n * User may also explicitely declare these props in order to manually set them when\n * they are interested of taking control over access.\n */\ntype WithDexieCloudProps<T> = T extends DexieCloudEntity ? T : T & DexieCloudEntity;\n\n/** Syntactic sugar for declaring a synced table of arbritary entity.\n * \n */\nexport type DexieCloudTable<T = any, TKeyPropName extends keyof T = never> =\n  EntityTable<\n  WithDexieCloudProps<T>,\n    TKeyPropName,\n    InsertType<WithDexieCloudProps<T>, TKeyPropName | 'owner' | 'realmId'>\n  >;\n\n"
  },
  {
    "path": "addons/dexie-cloud/src/InvalidLicenseError.ts",
    "content": "export class InvalidLicenseError extends Error {\n  name = 'InvalidLicenseError';\n  license?: 'expired' | 'deactivated';\n  constructor(license?: 'expired' | 'deactivated') {\n    super(\n      license === 'expired'\n        ? `License expired`\n        : license === 'deactivated'\n        ? `User deactivated`\n        : 'Invalid license'\n    );\n    if (license) {\n      this.license = license;\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/Invite.ts",
    "content": "import { DBPermissionSet, DBRealm, DBRealmMember } from 'dexie-cloud-common';\n\nexport interface Invite extends DBRealmMember {\n  realm?: DBRealm & { permissions: DBPermissionSet };\n  accept(): Promise<void>;\n  reject(): Promise<void>;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/PermissionChecker.ts",
    "content": "import { KeyPaths } from 'dexie';\nimport { DBPermissionSet } from 'dexie-cloud-common';\n\ntype TableName<T> = T extends {table: ()=>infer TABLE} ? TABLE extends string ? TABLE : string : string;\n\nexport class PermissionChecker<T, TableNames extends string = TableName<T>> {\n  private permissions: DBPermissionSet;\n  private tableName: TableNames;\n  private isOwner: boolean;\n\n  constructor(\n    permissions: DBPermissionSet,\n    tableName: TableNames,\n    isOwner: boolean\n  ) {\n    this.permissions = permissions || {};\n    this.tableName = tableName;\n    this.isOwner = isOwner;\n  }\n\n  add(...tableNames: TableNames[]): boolean {\n    // If user can manage the whole realm, return true.\n    if (this.permissions.manage === '*') return true;\n    // If user can manage given table in realm, return true\n    if (this.permissions.manage?.includes(this.tableName)) return true;\n    // If user can add any type, return true\n    if (this.permissions.add === '*') return true;\n    // If user can add objects into given table names in the realm, return true\n    if (\n      tableNames.every((tableName) => this.permissions.add?.includes(tableName))\n    ) {\n      return true;\n    }\n    return false;\n  }\n\n  update(...props: KeyPaths<T>[]): boolean {\n    // If user is owner of this object, or if user can manage the whole realm, return true.\n    if (this.isOwner || this.permissions.manage === '*') return true;\n    // If user can manage given table in realm, return true\n    if (this.permissions.manage?.includes(this.tableName)) return true;\n    // If user can update any prop in any table in this realm, return true unless\n    // it regards to ownership change:\n    if (this.permissions.update === '*') {\n      // @ts-ignore\n      return props.every((prop) => prop !== 'owner');\n    }\n    const tablePermissions = this.permissions.update?.[this.tableName];\n    // If user can update any prop in table and realm, return true unless\n    // accessing special props owner or realmId\n    if (tablePermissions === '*')\n      return props.every((prop) => prop !== 'owner');\n\n    // Explicitely listed properties to allow updates on:\n    return props.every((prop) =>\n      tablePermissions?.some(\n        (permittedProp) =>\n          permittedProp === prop || (permittedProp === '*' && prop !== 'owner')\n      )\n    );\n  }\n\n  delete(): boolean {\n    // If user is owner of this object, or if user can manage the whole realm, return true.\n    if (this.isOwner || this.permissions.manage === '*') return true;\n    // If user can manage given table in realm, return true\n    if (this.permissions.manage?.includes(this.tableName)) return true;\n    return false;\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/TSON.ts",
    "content": "import { \n  TypesonSimplified, \n  undefinedTypeDef,\n  blobTypeDef,\n  typedArrayTypeDefs,\n  arrayBufferTypeDef,\n  fileTypeDef,\n  dateTypeDef,\n  setTypeDef,\n  mapTypeDef,\n  numberTypeDef,\n} from 'dexie-cloud-common';\nimport { TypeDefSet } from 'dexie-cloud-common';\nimport { PropModSpec, PropModification } from 'dexie';\n\n// Since server revisions are stored in bigints, we need to handle clients without\n// bigint support to not fail when serverRevision is passed over to client.\n// We need to not fail when reviving it and we need to somehow store the information.\n// Since the revived version will later on be put into indexedDB we have another\n// issue: When reading it back from indexedDB we will get a poco object that we\n// cannot replace correctly when sending it to server. So we will also need\n// to do an explicit workaround in the protocol where a bigint is supported.\n// The workaround should be there regardless if browser supports BigInt or not, because\n// the serverRev might have been stored in IDB before the browser was upgraded to support bigint.\n//\n// if (typeof serverRev.rev !== \"bigint\")\n//   if (hasBigIntSupport)\n//     serverRev.rev = bigIntDef.bigint.revive(server.rev)\n//   else\n//     serverRev.rev = new FakeBigInt(server.rev)\nexport const hasBigIntSupport =\n  typeof BigInt === 'function' && typeof BigInt(0) === 'bigint';\n\nfunction getValueOfBigInt(x: bigint | FakeBigInt | string) {\n  if (typeof x === 'bigint') {\n    return x;\n  }\n  if (hasBigIntSupport) {\n    return typeof x === 'string' ? BigInt(x) : BigInt(x.v);\n  } else {\n    return typeof x === 'string' ? Number(x) : Number(x.v);\n  }\n}\n\nexport function compareBigInts(\n  a: bigint | FakeBigInt | string,\n  b: bigint | FakeBigInt | string\n) {\n  const valA = getValueOfBigInt(a);\n  const valB = getValueOfBigInt(b);\n  return valA < valB ? -1 : valA > valB ? 1 : 0;\n}\nexport class FakeBigInt {\n  v: string;\n  toString() {\n    return this.v;\n  }\n  constructor(value: string) {\n    this.v = value;\n  }\n}\n\nconst bigIntDef = hasBigIntSupport\n  ? {}\n  : {\n      bigint: {\n        test: (val: any) => val instanceof FakeBigInt,\n        replace: (fakeBigInt: any) => {\n          return {\n            $t: 'bigint',\n            ...fakeBigInt,\n          };\n        },\n        revive: ({ v }: { $t: 'bigint'; v: string }) =>\n          new FakeBigInt(v) as any as bigint,\n      },\n    };\n\nconst defs: TypeDefSet = {\n  ...undefinedTypeDef,\n  ...bigIntDef,\n  ...fileTypeDef,\n  PropModification: {\n    test: (val: any) => val instanceof PropModification,\n    replace: (propModification: any) => {\n      return {\n        $t: 'PropModification',\n        ...propModification['@@propmod'],\n      };\n    },\n    revive: ({\n      $t, // strip '$t'\n      ...propModSpec // keep the rest\n    }: {\n      $t: 'PropModification';\n    } & PropModSpec) => new PropModification(propModSpec),\n  },\n};\n\nexport const TSON = TypesonSimplified(\n  // Standard type definitions - TSON is transparent to BlobRefs\n  // BlobRefs use _bt convention and are handled by blobResolveMiddleware, not TSON\n  typedArrayTypeDefs,\n  arrayBufferTypeDef,\n  blobTypeDef,\n  // Non-binary built-in types\n  numberTypeDef,\n  dateTypeDef,\n  setTypeDef,\n  mapTypeDef,\n  // Custom type definitions\n  defs\n);\n"
  },
  {
    "path": "addons/dexie-cloud/src/WSObservable.ts",
    "content": "import { DBOperationsSet } from 'dexie-cloud-common';\nimport { BehaviorSubject, Observable, Subscriber, Subscription, tap } from 'rxjs';\nimport { TokenExpiredError } from './authentication/TokenExpiredError';\nimport { DXCWebSocketStatus } from './DXCWebSocketStatus';\nimport { TSON } from './TSON';\nimport type { YClientMessage, YServerMessage } from 'dexie-cloud-common';\nimport { DexieCloudDB } from './db/DexieCloudDB';\nimport { createYClientUpdateObservable } from './yjs/createYClientUpdateObservable';\nimport { applyYServerMessages } from './yjs/applyYMessages';\nimport { Table } from 'dexie';\nimport { getDocAwareness } from './yjs/awareness';\nimport * as awap from 'y-protocols/awareness';\nimport { encodeYMessage, decodeYMessage } from 'dexie-cloud-common';\nimport { UserLogin } from './dexie-cloud-client';\nimport { isEagerSyncDisabled } from './isEagerSyncDisabled';\nimport { getOpenDocSignal } from './yjs/reopenDocSignal';\nimport { getUpdatesTable } from './yjs/getUpdatesTable';\nimport { DEXIE_CLOUD_SYNCER_ID } from './sync/DEXIE_CLOUD_SYNCER_ID';\nimport { DexieYProvider, YSyncState } from 'y-dexie';\n\nconst SERVER_PING_TIMEOUT = 20000;\nconst CLIENT_PING_INTERVAL = 30000;\nconst FAIL_RETRY_WAIT_TIME = 60000;\n\nexport type WSClientToServerMsg = ReadyForChangesMessage | YClientMessage;\nexport interface ReadyForChangesMessage {\n  type: 'ready';\n  realmSetHash: string;\n  rev: string;\n}\n\nexport type WSConnectionMsg =\n  | RevisionChangedMessage\n  | RealmAddedMessage\n  | RealmAcceptedMessage\n  | RealmRemovedMessage\n  | RealmsChangedMessage\n  | ChangesFromServerMessage\n  | TokenExpiredMessage;\ninterface PingMessage {\n  type: 'ping';\n}\n\ninterface PongMessage {\n  type: 'pong';\n}\n\ninterface ErrorMessage {\n  type: 'error';\n  error: string;\n}\n\nexport interface ChangesFromServerMessage {\n  type: 'changes';\n  baseRev: string;\n  realmSetHash: string;\n  newRev: string;\n  changes: DBOperationsSet<string>;\n}\nexport interface RevisionChangedMessage {\n  type: 'rev';\n  rev: string;\n}\n\nexport interface RealmAddedMessage {\n  type: 'realm-added';\n  realm: string;\n}\n\nexport interface RealmAcceptedMessage {\n  type: 'realm-accepted';\n  realm: string;\n}\n\nexport interface RealmRemovedMessage {\n  type: 'realm-removed';\n  realm: string;\n}\n\nexport interface RealmsChangedMessage {\n  type: 'realms-changed';\n  realmsHash: string;\n}\nexport interface TokenExpiredMessage {\n  type: 'token-expired';\n}\n\nexport class WSObservable extends Observable<WSConnectionMsg> {\n  constructor(\n    db: DexieCloudDB,\n    rev: string | undefined,\n    yrev: string | undefined,\n    realmSetHash: string,\n    clientIdentity: string,\n    messageProducer: Observable<WSClientToServerMsg>,\n    webSocketStatus: BehaviorSubject<DXCWebSocketStatus>,\n    user: UserLogin\n  ) {\n    super(\n      (subscriber) =>\n        new WSConnection(\n          db,\n          rev,\n          yrev,\n          realmSetHash,\n          clientIdentity,\n          user,\n          subscriber,\n          messageProducer,\n          webSocketStatus\n        )\n    );\n  }\n}\n\nlet counter = 0;\n\nexport class WSConnection extends Subscription {\n  db: DexieCloudDB;\n  ws: WebSocket | null;\n  lastServerActivity: Date;\n  lastUserActivity: Date;\n  lastPing: Date;\n  databaseUrl: string;\n  rev: string | undefined;\n  yrev: string | undefined;\n  realmSetHash: string;\n  clientIdentity: string;\n  user: UserLogin;\n  subscriber: Subscriber<WSConnectionMsg>;\n  pauseUntil?: Date;\n  messageProducer: Observable<WSClientToServerMsg>;\n  webSocketStatus: BehaviorSubject<DXCWebSocketStatus>;\n  id = ++counter;\n\n  private pinger: any;\n  private subscriptions: Set<Subscription> = new Set();\n\n  constructor(\n    db: DexieCloudDB,\n    rev: string | undefined,\n    yrev: string | undefined,\n    realmSetHash: string,\n    clientIdentity: string,\n    user: UserLogin,\n    subscriber: Subscriber<WSConnectionMsg>,\n    messageProducer: Observable<WSClientToServerMsg>,\n    webSocketStatus: BehaviorSubject<DXCWebSocketStatus>\n  ) {\n    super(() => this.teardown());\n    console.debug(\n      'New WebSocket Connection',\n      this.id,\n      user.accessToken ? 'authorized' : 'unauthorized'\n    );\n    this.db = db;\n    this.databaseUrl = db.cloud.options!.databaseUrl;\n    this.rev = rev;\n    this.yrev = yrev;\n    this.realmSetHash = realmSetHash;\n    this.clientIdentity = clientIdentity;\n    this.user = user;\n    this.subscriber = subscriber;\n    this.lastUserActivity = new Date();\n    this.messageProducer = messageProducer;\n    this.webSocketStatus = webSocketStatus;\n    this.connect();\n  }\n\n  private teardown() {\n    console.debug('Teardown WebSocket Connection', this.id);\n    this.disconnect();\n  }\n\n  private disconnect() {\n    this.webSocketStatus.next('disconnected');\n    if (this.pinger) {\n      clearInterval(this.pinger);\n      this.pinger = null;\n    }\n    if (this.ws) {\n      try {\n        this.ws.close();\n      } catch {}\n    }\n    this.ws = null;\n    for (const sub of this.subscriptions) {\n      sub.unsubscribe();\n    }\n    this.subscriptions.clear();\n  }\n\n  reconnecting = false;\n  reconnect() {\n    if (this.reconnecting) return;\n    this.reconnecting = true;\n    try {\n      this.disconnect();\n    } catch {}\n    this.connect()\n      .catch(() => {})\n      .then(() => (this.reconnecting = false)); // finally()\n  }\n\n  async connect() {\n    this.lastServerActivity = new Date();\n    if (this.pauseUntil && this.pauseUntil > new Date()) {\n      console.debug('WS not reconnecting just yet', {\n        id: this.id,\n        pauseUntil: this.pauseUntil,\n      });\n      return;\n    }\n    if (this.ws) {\n      throw new Error(`Called connect() when a connection is already open`);\n    }\n    if (!this.databaseUrl)\n      throw new Error(`Cannot connect without a database URL`);\n    if (this.closed) {\n      //console.debug('SyncStatus: DUBB: Ooops it was closed!');\n      return;\n    }\n    const tokenExpiration = this.user.accessTokenExpiration;\n    if (tokenExpiration && tokenExpiration < new Date()) {\n      this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.\n      return;\n    }\n    this.webSocketStatus.next('connecting');\n    this.pinger = setInterval(async () => {\n      // setInterval here causes unnecessary pings when server is proved active anyway.\n      // TODO: Use setTimout() here instead. When triggered, check if we really need to ping.\n      // In case we've had server activity, we don't need to ping. Then schedule then next ping\n      // to the time when we should ping next time (based on lastServerActivity + CLIENT_PING_INTERVAL).\n      // Else, ping now and schedule next ping to CLIENT_PING_INTERVAL from now.\n      if (this.closed) {\n        console.debug('pinger check', this.id, 'CLOSED.');\n        this.teardown();\n        return;\n      }\n      if (this.ws) {\n        try {\n          this.ws.send(JSON.stringify({ type: 'ping' } as PingMessage));\n          setTimeout(() => {\n            console.debug(\n              'pinger setTimeout',\n              this.id,\n              this.pinger ? `alive` : 'dead'\n            );\n            if (!this.pinger) return;\n            if (this.closed) {\n              console.debug(\n                'pinger setTimeout',\n                this.id,\n                'subscription is closed'\n              );\n              this.teardown();\n              return;\n            }\n            if (\n              this.lastServerActivity <\n              new Date(Date.now() - SERVER_PING_TIMEOUT)\n            ) {\n              // Server inactive. Reconnect if user is active.\n              console.debug('pinger: server is inactive');\n              console.debug('pinger reconnecting');\n              this.reconnect();\n            } else {\n              console.debug('pinger: server still active');\n            }\n          }, SERVER_PING_TIMEOUT);\n        } catch {\n          console.debug('pinger catch error', this.id, 'reconnecting');\n          this.reconnect();\n        }\n      } else {\n        console.debug('pinger', this.id, 'reconnecting');\n        this.reconnect();\n      }\n    }, CLIENT_PING_INTERVAL);\n\n    // The following vars are needed because we must know which callback to ack when server sends it's ack to us.\n    const wsUrl = new URL(this.databaseUrl);\n    wsUrl.protocol = wsUrl.protocol === 'http:' ? 'ws' : 'wss';\n    const searchParams = new URLSearchParams();\n    if (this.subscriber.closed) return;\n    searchParams.set('v', '2');\n    if (this.rev) searchParams.set('rev', this.rev);\n    if (this.yrev) searchParams.set('yrev', this.yrev);\n    searchParams.set('realmsHash', this.realmSetHash);\n    searchParams.set('clientId', this.clientIdentity);\n    searchParams.set('dxcv', this.db.cloud.version);\n    if (this.user.accessToken) {\n      searchParams.set('token', this.user.accessToken);\n    }\n\n    // Connect the WebSocket to given url:\n    console.debug('dexie-cloud WebSocket create');\n    const ws = (this.ws = new WebSocket(`${wsUrl}/changes?${searchParams}`));\n    ws.binaryType = \"arraybuffer\";\n\n    ws.onclose = (event: Event) => {\n      if (!this.pinger) return;\n      console.debug('dexie-cloud WebSocket onclosed', this.id);\n      this.reconnect();\n    };\n\n    ws.onmessage = (event: MessageEvent) => {\n      if (!this.pinger) return;\n\n      this.lastServerActivity = new Date();\n      try {\n        const msg = typeof event.data === 'string'\n          ? TSON.parse(event.data) as\n            | WSConnectionMsg\n            | PongMessage\n            | ErrorMessage\n            | YServerMessage   \n          : decodeYMessage(new Uint8Array(event.data)) as\n            | YServerMessage;\n        console.debug('dexie-cloud WebSocket onmessage', msg.type, msg);\n        if (msg.type === 'error') {\n          throw new Error(`Error message from dexie-cloud: ${msg.error}`);\n        } else if (msg.type === 'aware') {\n          const docCache = DexieYProvider.getDocCache(this.db.dx);\n          const doc = docCache.find(msg.table, msg.k, msg.prop);\n          if (doc) {\n            const awareness = getDocAwareness(doc);\n            if (awareness) {\n              awap.applyAwarenessUpdate(\n                awareness,\n                msg.u,\n                'server',\n              );\n            }\n          }\n        } else if (msg.type === 'pong') {\n          // Do nothing\n        } else if (msg.type === 'doc-open') {\n          const docCache = DexieYProvider.getDocCache(this.db.dx);\n          const doc = docCache.find(msg.table, msg.k, msg.prop);\n          if (doc) {\n            getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.\n          }\n        } else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync' || msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {\n          applyYServerMessages([msg], this.db).then(async ({resyncNeeded, yServerRevision, receivedUntils}) => {\n            if (yServerRevision) {\n              await this.db.$syncState.update('syncState', { yServerRevision: yServerRevision });\n            }\n            if (msg.type === 'u-s' && receivedUntils) {\n              const utbl = getUpdatesTable(this.db, msg.table, msg.prop) as any as Table<YSyncState, string>;\n              if (utbl) {\n                const receivedUntil = receivedUntils[utbl.name];\n                if (receivedUntil) {\n                  await utbl.update(DEXIE_CLOUD_SYNCER_ID, { receivedUntil });\n                }\n              }\n            }\n            if (resyncNeeded) {\n              await this.db.cloud.sync({ purpose: 'pull', wait: true });\n            }\n          })\n        } else {\n          // Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)\n          this.subscriber.next(msg);\n        }\n      } catch (e) {\n        this.subscriber.error(e);\n      }\n    };\n\n    try {\n      let everConnected = false;\n      await new Promise((resolve, reject) => {\n        ws.onopen = (event) => {\n          console.debug('dexie-cloud WebSocket onopen');\n          everConnected = true;\n          resolve(null);\n        };\n        ws.onerror = (event: ErrorEvent) => {\n          if (!everConnected) {\n            const error = event.error || new Error('WebSocket Error');\n            this.subscriber.error(error);\n            this.webSocketStatus.next('error');\n            reject(error);\n          } else {\n            this.reconnect();\n          }\n        };\n      });\n      this.subscriptions.add(this.messageProducer.subscribe(\n        (msg) => {\n          if (!this.closed) {\n            if (\n              msg.type === 'ready' &&\n              this.webSocketStatus.value !== 'connected'\n            ) {\n              this.webSocketStatus.next('connected');\n            }\n            console.debug('dexie-cloud WebSocket send', msg.type, msg);\n            if (msg.type === 'ready') {\n              // Ok, we are certain to have stored everything up until revision msg.rev.\n              // Update this.rev in case of reconnect - remember where we were and don't just start over!\n              this.rev = msg.rev; \n              // ... and then send along the request to the server so it would also be updated!\n              this.ws?.send(TSON.stringify(msg));\n            } else {\n              // If it's not a \"ready\" message, it's an YMessage.\n              // YMessages can be sent binary encoded.\n              this.ws?.send(encodeYMessage(msg));\n            }\n          }\n        }\n      ));\n      if (this.user.isLoggedIn && !isEagerSyncDisabled(this.db)) {\n        this.subscriptions.add(\n          createYClientUpdateObservable(this.db).subscribe(\n            this.db.messageProducer\n          )\n        );\n      }\n    } catch (error) {\n      this.pauseUntil = new Date(Date.now() + FAIL_RETRY_WAIT_TIME);\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/associate.ts",
    "content": "export function associate<T extends object,M>(factory: (x: T)=>M): (x: T) => M {\n  const wm = new WeakMap<T, M>();\n  return (x: T) => {\n    let rv = wm.get(x);\n    if (!rv) {\n      rv = factory(x);\n      wm.set(x, rv);\n    }\n    return rv;\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/AuthPersistedContext.ts",
    "content": "import { DexieCloudDB } from \"../db/DexieCloudDB\";\nimport { UserLogin } from \"../db/entities/UserLogin\";\n\nexport interface AuthPersistedContext extends UserLogin {\n  save(): Promise<void>;\n}\n\n// Emulate true-private property db. Why? So it's not stored in DB.\nconst wm = new WeakMap<AuthPersistedContext, DexieCloudDB>();\n\nexport class AuthPersistedContext {\n  constructor(db: DexieCloudDB, userLogin: UserLogin) {\n    wm.set(this, db);\n    Object.assign(this, userLogin);\n  }\n\n  static load(db: DexieCloudDB, userId: string) {\n    return db\n      .table(\"$logins\")\n      .get(userId)\n      .then(\n        (userLogin) => new AuthPersistedContext(db, userLogin || {\n          userId,\n          claims: {\n            sub: userId\n          },\n          lastLogin: new Date(0)\n        })\n      );\n  }\n\n  async save() {\n    const db = wm.get(this)!;\n    db.table(\"$logins\").put(this);\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/TokenErrorResponseError.ts",
    "content": "import { TokenErrorResponse } from 'dexie-cloud-common';\n\nexport class TokenErrorResponseError extends Error {\n  title: string;\n  messageCode:\n    | 'INVALID_OTP'\n    | 'INVALID_EMAIL'\n    | 'LICENSE_LIMIT_REACHED'\n    | 'GENERIC_ERROR';\n  message: string;\n  messageParams?: { [param: string]: string };\n\n  constructor({\n    title,\n    message,\n    messageCode,\n    messageParams,\n  }: TokenErrorResponse) {\n    super(message);\n    this.name = 'TokenErrorResponseError';\n    this.title = title;\n    this.messageCode = messageCode;\n    this.messageParams = messageParams;\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/TokenExpiredError.ts",
    "content": "export class TokenExpiredError extends Error {\n  name = \"TokenExpiredError\";\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/UNAUTHORIZED_USER.ts",
    "content": "import { UserLogin } from '../db/entities/UserLogin';\n\nexport const UNAUTHORIZED_USER: UserLogin = {\n  userId: \"unauthorized\",\n  name: \"Unauthorized\",\n  claims: {\n    sub: \"unauthorized\",\n  },\n  lastLogin: new Date(0)\n}\n\ntry {\n  Object.freeze(UNAUTHORIZED_USER);\n  Object.freeze(UNAUTHORIZED_USER.claims);\n} catch {}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/authenticate.ts",
    "content": "import Dexie from 'dexie';\nimport type {\n  RefreshTokenRequest,\n  TokenErrorResponse,\n  TokenFinalResponse,\n} from 'dexie-cloud-common';\nimport { b64encode } from 'dexie-cloud-common';\nimport { BehaviorSubject } from 'rxjs';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { UserLogin } from '../db/entities/UserLogin';\nimport { DXCAlert } from '../types/DXCAlert';\nimport {\n  DXCMessageAlert,\n  DXCUserInteraction,\n} from '../types/DXCUserInteraction';\nimport { TokenErrorResponseError } from './TokenErrorResponseError';\nimport { alertUser, interactWithUser } from './interactWithUser';\nimport { InvalidLicenseError } from '../InvalidLicenseError';\nimport { LoginHints } from '../DexieCloudAPI';\nimport { OAuthRedirectError } from '../errors/OAuthRedirectError';\nimport { MINUTES } from '../helpers/date-constants';\n\nexport type FetchTokenCallback = (tokenParams: {\n  public_key: string;\n  hints?: LoginHints;\n}) => Promise<TokenFinalResponse | TokenErrorResponse>;\n\nexport async function loadAccessToken(\n  db: DexieCloudDB\n): Promise<UserLogin | null> {\n  const currentUser = await db.getCurrentUser();\n  const {\n    accessToken,\n    accessTokenExpiration,\n    refreshToken,\n    refreshTokenExpiration,\n    claims,\n  } = currentUser;\n  if (!accessToken) return null;\n  const expTime = accessTokenExpiration?.getTime() ?? Infinity;\n  if (expTime > (Date.now() + 5 * MINUTES) && (currentUser.license?.status || 'ok') === 'ok') {\n    return currentUser;\n  }\n  if (!refreshToken) {\n    throw new Error(`Refresh token missing`);\n  }\n  const refreshExpTime = refreshTokenExpiration?.getTime() ?? Infinity;\n  if (refreshExpTime <= Date.now()) {\n    throw new Error(`Refresh token has expired`);\n  }\n  const refreshedLogin = await refreshAccessToken(\n    db.cloud.options!.databaseUrl,\n    currentUser\n  );\n  await db.table('$logins').update(claims.sub, {\n    accessToken: refreshedLogin.accessToken,\n    accessTokenExpiration: refreshedLogin.accessTokenExpiration,\n    claims: refreshedLogin.claims,\n    license: refreshedLogin.license,\n    data: refreshedLogin.data,\n  });\n  return refreshedLogin;\n}\n\nexport async function authenticate(\n  url: string,\n  context: UserLogin,\n  fetchToken: FetchTokenCallback,\n  userInteraction: BehaviorSubject<DXCUserInteraction | undefined>,\n  hints?: LoginHints\n): Promise<UserLogin> {\n  if (\n    context.accessToken &&\n    context.accessTokenExpiration!.getTime() > Date.now()\n  ) {\n    return context;\n  } else if (\n    context.refreshToken &&\n    (!context.refreshTokenExpiration ||\n      context.refreshTokenExpiration.getTime() > Date.now())\n  ) {\n    return await refreshAccessToken(url, context);\n  } else {\n    return await userAuthenticate(context, fetchToken, userInteraction, hints);\n  }\n}\n\nexport async function refreshAccessToken(\n  url: string,\n  login: UserLogin\n): Promise<UserLogin> {\n  if (!login.refreshToken)\n    throw new Error(`Cannot refresh token - refresh token is missing.`);\n  if (!login.nonExportablePrivateKey)\n    throw new Error(\n      `login.nonExportablePrivateKey is missing - cannot sign refresh token without a private key.`\n    );\n\n  const time_stamp = Date.now();\n  const signing_algorithm = 'RSASSA-PKCS1-v1_5';\n  const textEncoder = new TextEncoder();\n  const data = textEncoder.encode(login.refreshToken + time_stamp);\n  const binarySignature = await crypto.subtle.sign(\n    signing_algorithm,\n    login.nonExportablePrivateKey,\n    data\n  );\n  const signature = b64encode(binarySignature);\n\n  const tokenRequest: RefreshTokenRequest = {\n    grant_type: 'refresh_token',\n    refresh_token: login.refreshToken,\n    scopes: ['ACCESS_DB'],\n    signature,\n    signing_algorithm,\n    time_stamp,\n  };\n  const res = await fetch(`${url}/token`, {\n    body: JSON.stringify(tokenRequest),\n    method: 'post',\n    headers: { 'Content-Type': 'application/json' },\n    mode: 'cors',\n  });\n  if (res.status !== 200)\n    throw new Error(`RefreshToken: Status ${res.status} from ${url}/token`);\n  const response: TokenFinalResponse | TokenErrorResponse = await res.json();\n  if (response.type === 'error') {\n    throw new TokenErrorResponseError(response);\n  }\n  login.accessToken = response.accessToken;\n  login.accessTokenExpiration = response.accessTokenExpiration\n    ? new Date(response.accessTokenExpiration)\n    : undefined;\n  login.claims = response.claims;\n  login.license = {\n    type: response.userType,\n    status: response.claims.license || 'ok',\n  }\n  if (response.evalDaysLeft != null) {\n    login.license.evalDaysLeft = response.evalDaysLeft;\n  }\n  if (response.userValidUntil != null) {\n    login.license.validUntil = new Date(response.userValidUntil);\n  }\n  if (response.data) {\n    login.data = response.data;\n  }\n  return login;\n}\n\nasync function userAuthenticate(\n  context: UserLogin,\n  fetchToken: FetchTokenCallback,\n  userInteraction: BehaviorSubject<DXCUserInteraction | undefined>,\n  hints?: LoginHints\n) {\n  if (!crypto.subtle) {\n    if (typeof location !== 'undefined' && location.protocol === 'http:') {\n      throw new Error(`Dexie Cloud Addon needs to use WebCrypto, but your browser has disabled it due to being served from an insecure location. Please serve it from https or http://localhost:<port> (See https://stackoverflow.com/questions/46670556/how-to-enable-crypto-subtle-for-unsecure-origins-in-chrome/46671627#46671627)`);\n    } else {\n      throw new Error(`This browser does not support WebCrypto.`);\n    }\n  }\n  const { privateKey, publicKey } = await crypto.subtle.generateKey(\n    {\n      name: 'RSASSA-PKCS1-v1_5',\n      modulusLength: 2048,\n      publicExponent: new Uint8Array([0x01, 0x00, 0x01]),\n      hash: { name: 'SHA-256' },\n    },\n    false, // Non-exportable...\n    ['sign', 'verify']\n  );\n  if (!privateKey || !publicKey)\n    throw new Error(`Could not generate RSA keypair`); // Typings suggest these can be undefined...\n  context.nonExportablePrivateKey = privateKey; //...but storable!\n  const publicKeySPKI = await crypto.subtle.exportKey('spki', publicKey);\n  const publicKeyPEM = spkiToPEM(publicKeySPKI);\n  context.publicKey = publicKey;\n\n  try {\n    const response2 = await fetchToken({\n      public_key: publicKeyPEM,\n      hints,\n    });\n\n    if (response2.type === 'error') {\n      throw new TokenErrorResponseError(response2);\n    }\n\n    if (response2.type !== 'tokens')\n      throw new Error(\n        `Unexpected response type from token endpoint: ${(response2 as any).type}`\n      );\n\n    /*const licenseStatus = response2.claims.license || 'ok';\n    if (licenseStatus !== 'ok') {\n      throw new InvalidLicenseError(licenseStatus);\n    }*/\n\n    context.accessToken = response2.accessToken;\n    context.accessTokenExpiration = new Date(response2.accessTokenExpiration);\n    context.refreshToken = response2.refreshToken;\n    if (response2.refreshTokenExpiration) {\n      context.refreshTokenExpiration = new Date(\n        response2.refreshTokenExpiration\n      );\n    }\n    context.userId = response2.claims.sub;\n    context.email = response2.claims.email;\n    context.name = response2.claims.name;\n    context.claims = response2.claims;\n    context.license = {\n      type: response2.userType,\n      status: response2.claims.license || 'ok',\n    }\n    context.data = response2.data;\n    if (response2.evalDaysLeft != null) {\n      context.license.evalDaysLeft = response2.evalDaysLeft;\n    }\n    if (response2.userValidUntil != null) {\n      context.license.validUntil = new Date(response2.userValidUntil);\n    }\n\n    if (response2.alerts && response2.alerts.length > 0) {\n      await interactWithUser(userInteraction, {\n        type: 'message-alert',\n        title: 'Authentication Alert',\n        fields: {},\n        alerts: response2.alerts as DXCAlert[],\n      });\n    }\n    return context;\n  } catch (error: any) {\n    // OAuth redirect is not an error - page is navigating away\n    if (error instanceof OAuthRedirectError || error?.name === 'OAuthRedirectError') {\n      throw error; // Re-throw without logging\n    }\n    if (error instanceof TokenErrorResponseError) {\n      await alertUser(userInteraction, error.title, {\n        type: 'error',\n        messageCode: error.messageCode,\n        message: error.message,\n        messageParams: {},\n      });\n      throw error;\n    }\n    let message = `We're having a problem authenticating right now.`;\n    console.error (`Error authenticating`, error);\n    if (error instanceof TypeError) {\n      const isOffline = typeof navigator !== 'undefined' && !navigator.onLine;\n      if (isOffline) {\n        message = `You seem to be offline. Please connect to the internet and try again.`;\n      } else if (typeof location !== 'undefined' && (Dexie.debug || location.hostname === 'localhost' || location.hostname === '127.0.0.1')) {\n        // The audience is most likely the developer. Suggest to whitelist the localhost origin:\n        const whitelistCommand = `npx dexie-cloud whitelist ${location.origin}`;\n        message = `Could not connect to server. Please verify that your origin '${location.origin}' is whitelisted using \\`npx dexie-cloud whitelist\\``;\n        await alertUser(userInteraction, 'Authentication Failed', {\n          type: 'error',\n          messageCode: 'GENERIC_ERROR',\n          message,\n          messageParams: {},\n          copyText: whitelistCommand,\n        }).catch(() => {});\n      } else {\n        message = `Could not connect to server. Please verify the connection.`;\n        await alertUser(userInteraction, 'Authentication Failed', {\n          type: 'error',\n          messageCode: 'GENERIC_ERROR',\n          message,\n          messageParams: {},\n        }).catch(() => {});\n      }  \n    }\n\n    throw error;\n  }\n}\n\nfunction spkiToPEM(keydata: ArrayBuffer) {\n  const keydataB64 = b64encode(keydata);\n  const keydataB64Pem = formatAsPem(keydataB64);\n  return keydataB64Pem;\n}\n\nfunction formatAsPem(str: string) {\n  let finalString = '-----BEGIN PUBLIC KEY-----\\n';\n\n  while (str.length > 0) {\n    finalString += str.substring(0, 64) + '\\n';\n    str = str.substring(64);\n  }\n\n  finalString = finalString + '-----END PUBLIC KEY-----';\n\n  return finalString;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/currentUserObservable.ts",
    "content": "import { Dexie } from \"dexie\";\nimport { BehaviorSubject } from 'rxjs';\nimport { UserLogin } from '../db/entities/UserLogin';\n\n//export const currentUserWeakMap = new WeakMap<Dexie, BehaviorSubject<UserLogin>>();\n\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/exchangeOAuthCode.ts",
    "content": "import type {\n  AuthorizationCodeTokenRequest,\n  TokenFinalResponse,\n  TokenErrorResponse,\n} from 'dexie-cloud-common';\nimport { OAuthError } from '../errors/OAuthError';\nimport { TokenErrorResponseError } from './TokenErrorResponseError';\n\n/** Options for exchanging an OAuth code */\nexport interface ExchangeOAuthCodeOptions {\n  /** The Dexie Cloud database URL */\n  databaseUrl: string;\n  /** The Dexie Cloud authorization code from OAuth callback */\n  code: string;\n  /** The client's public key in PEM format */\n  publicKey: string;\n  /** Requested scopes (defaults to ['ACCESS_DB']) */\n  scopes?: string[];\n}\n\n/**\n * Exchanges a Dexie Cloud authorization code for access and refresh tokens.\n * \n * This is called after the OAuth callback delivers the authorization code\n * via postMessage (popup flow) or redirect.\n * \n * @param options - Exchange options\n * @returns Promise resolving to TokenFinalResponse\n * @throws OAuthError or TokenErrorResponseError on failure\n */\nexport async function exchangeOAuthCode(\n  options: ExchangeOAuthCodeOptions\n): Promise<TokenFinalResponse> {\n  const { databaseUrl, code, publicKey, scopes = ['ACCESS_DB'] } = options;\n\n  const tokenRequest: AuthorizationCodeTokenRequest = {\n    grant_type: 'authorization_code',\n    code,\n    public_key: publicKey,\n    scopes,\n  };\n\n  try {\n    const res = await fetch(`${databaseUrl}/token`, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(tokenRequest),\n      mode: 'cors',\n    });\n\n    if (!res.ok) {\n      // Read body once as text to avoid stream consumption issues\n      const bodyText = await res.text().catch(() => res.statusText);\n      \n      if (res.status === 400 || res.status === 401) {\n        // Try to parse error response as JSON\n        try {\n          const errorResponse: TokenErrorResponse = JSON.parse(bodyText);\n          if (errorResponse.type === 'error') {\n            // Check for specific error codes\n            if (errorResponse.messageCode === 'INVALID_OTP') {\n              // In the context of OAuth, this likely means expired code\n              throw new OAuthError('expired_code', undefined, errorResponse.message);\n            }\n            throw new TokenErrorResponseError(errorResponse);\n          }\n        } catch (e) {\n          if (e instanceof OAuthError || e instanceof TokenErrorResponseError) {\n            throw e;\n          }\n          // Fall through to generic error\n        }\n      }\n      \n      throw new OAuthError('provider_error', undefined, `Token exchange failed: ${res.status} ${bodyText}`);\n    }\n\n    const response: TokenFinalResponse | TokenErrorResponse = await res.json();\n\n    if (response.type === 'error') {\n      throw new TokenErrorResponseError(response);\n    }\n\n    if (response.type !== 'tokens') {\n      throw new OAuthError('provider_error', undefined, `Unexpected response type: ${(response as any).type}`);\n    }\n\n    return response;\n  } catch (error) {\n    if (error instanceof OAuthError || error instanceof TokenErrorResponseError) {\n      throw error;\n    }\n    \n    if (error instanceof TypeError) {\n      // Network error\n      throw new OAuthError('network_error');\n    }\n    \n    throw error;\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/fetchAuthProviders.ts",
    "content": "import type { AuthProvidersResponse } from 'dexie-cloud-common';\n\n/** Default response when OAuth is disabled or unavailable */\nconst OTP_ONLY_RESPONSE: AuthProvidersResponse = {\n  providers: [],\n  otpEnabled: true,\n};\n\n/**\n * Fetches available authentication providers from the Dexie Cloud server.\n * \n * @param databaseUrl - The Dexie Cloud database URL\n * @param socialAuthEnabled - Whether social auth is enabled in client config (default: true)\n * @returns Promise resolving to AuthProvidersResponse\n * \n * Handles failures gracefully:\n * - 404 → Returns OTP-only (old server version)\n * - Network error → Returns OTP-only\n * - socialAuthEnabled: false → Returns OTP-only without fetching\n */\nexport async function fetchAuthProviders(\n  databaseUrl: string,\n  socialAuthEnabled: boolean = true\n): Promise<AuthProvidersResponse> {\n  // If social auth is disabled, return OTP-only without fetching\n  if (!socialAuthEnabled) {\n    return OTP_ONLY_RESPONSE;\n  }\n\n  try {\n    const res = await fetch(`${databaseUrl}/auth-providers`, {\n      method: 'GET',\n      headers: { 'Accept': 'application/json' },\n      mode: 'cors',\n    });\n\n    if (res.status === 404) {\n      // Old server version without OAuth support\n      console.debug('[dexie-cloud] Server does not support /auth-providers endpoint. Using OTP-only authentication.');\n      return OTP_ONLY_RESPONSE;\n    }\n\n    if (!res.ok) {\n      console.warn(`[dexie-cloud] Failed to fetch auth providers: ${res.status} ${res.statusText}`);\n      return OTP_ONLY_RESPONSE;\n    }\n\n    return await res.json();\n  } catch (error) {\n    // Network error or other failure - fall back to OTP\n    console.debug('[dexie-cloud] Could not fetch auth providers, falling back to OTP:', error);\n    return OTP_ONLY_RESPONSE;\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/handleOAuthCallback.ts",
    "content": "import { OAuthError } from '../errors/OAuthError';\n\n/** Parsed OAuth callback parameters from dxc-auth query parameter */\nexport interface OAuthCallbackParams {\n  /** The Dexie Cloud authorization code */\n  code: string;\n  /** The OAuth provider that was used */\n  provider: string;\n  /** The state parameter */\n  state: string;\n}\n\n/** Decoded dxc-auth payload structure */\ninterface DxcAuthPayload {\n  code?: string;\n  provider: string;\n  state: string;\n  error?: string;\n}\n\n/**\n * Decodes a base64url-encoded string to a regular string.\n * Base64url uses - instead of + and _ instead of /, and may omit padding.\n */\nfunction decodeBase64Url(encoded: string): string {\n  // Add padding if needed\n  const padded = encoded + '='.repeat((4 - (encoded.length % 4)) % 4);\n  // Convert base64url to base64\n  const base64 = padded.replace(/-/g, '+').replace(/_/g, '/');\n  return atob(base64);\n}\n\n/**\n * Parses OAuth callback parameters from the dxc-auth query parameter.\n * \n * The dxc-auth parameter contains base64url-encoded JSON with the following structure:\n * - On success: { \"code\": \"...\", \"provider\": \"...\", \"state\": \"...\" }\n * - On error: { \"error\": \"...\", \"provider\": \"...\", \"state\": \"...\" }\n * \n * @param url - The URL to parse (defaults to window.location.href)\n * @returns OAuthCallbackParams if valid callback, null otherwise\n * @throws OAuthError if there's an error in the callback\n */\nexport function parseOAuthCallback(url?: string): OAuthCallbackParams | null {\n  const targetUrl = url || (typeof window !== 'undefined' ? window.location.href : '');\n  \n  if (!targetUrl) {\n    return null;\n  }\n\n  const parsed = new URL(targetUrl);\n  const encoded = parsed.searchParams.get('dxc-auth');\n\n  if (!encoded) {\n    return null; // Not an OAuth callback URL\n  }\n\n  let payload: DxcAuthPayload;\n  try {\n    const json = decodeBase64Url(encoded);\n    payload = JSON.parse(json);\n  } catch (e) {\n    console.warn('[dexie-cloud] Failed to parse dxc-auth parameter:', e);\n    return null;\n  }\n\n  const { code, provider, state, error } = payload;\n\n  // Check for error first\n  if (error) {\n    if (error.toLowerCase().includes('access_denied') || error.toLowerCase().includes('access denied')) {\n      throw new OAuthError('access_denied', provider, error);\n    }\n    if (error.toLowerCase().includes('email') && error.toLowerCase().includes('verif')) {\n      throw new OAuthError('email_not_verified', provider, error);\n    }\n    \n    throw new OAuthError('provider_error', provider, error);\n  }\n\n  // Validate required fields for success case\n  if (!code || !provider || !state) {\n    console.warn('[dexie-cloud] Invalid dxc-auth payload: missing required fields');\n    return null;\n  }\n\n  return { code, provider, state };\n}\n\n/**\n * Validates the OAuth state parameter against the stored state.\n * \n * @param receivedState - The state from the callback URL\n * @returns true if valid, false otherwise\n */\nexport function validateOAuthState(receivedState: string): boolean {\n  if (typeof sessionStorage === 'undefined') {\n    console.warn('[dexie-cloud] sessionStorage not available, cannot validate OAuth state');\n    return false; // Fail closed - reject if we cannot validate CSRF protection\n  }\n\n  const storedState = sessionStorage.getItem('dexie-cloud-oauth-state');\n  \n  if (!storedState) {\n    console.warn('[dexie-cloud] No stored OAuth state found');\n    return false;\n  }\n\n  // Clear the stored state after validation attempt\n  sessionStorage.removeItem('dexie-cloud-oauth-state');\n  \n  return storedState === receivedState;\n}\n\n/**\n * Cleans up the dxc-auth query parameter from the URL.\n * Call this after successfully handling the callback to clean up the browser URL.\n */\nexport function cleanupOAuthUrl(): void {\n  if (typeof window === 'undefined' || !window.history?.replaceState) {\n    return;\n  }\n\n  const url = new URL(window.location.href);\n  \n  if (!url.searchParams.has('dxc-auth')) {\n    return;\n  }\n\n  url.searchParams.delete('dxc-auth');\n  const cleanUrl = url.pathname + (url.searchParams.toString() ? `?${url.searchParams.toString()}` : '') + url.hash;\n  window.history.replaceState(null, '', cleanUrl);\n}\n\n/**\n * Complete handler for OAuth callback.\n * \n * Parses the dxc-auth query parameter, validates state, and returns the parameters\n * needed to complete the login flow.\n * \n * Note: For web SPAs using full page redirect, the dexie-cloud-addon automatically\n * detects and processes the dxc-auth parameter when db.cloud.configure() is called.\n * This function is primarily useful for Capacitor/native apps handling deep links.\n * \n * @param url - The callback URL (defaults to window.location.href)\n * @returns OAuthCallbackParams if valid callback, null otherwise\n * @throws OAuthError on validation failure or if callback contains an error\n * \n * @example\n * ```typescript\n * // Capacitor deep link handler:\n * App.addListener('appUrlOpen', async ({ url }) => {\n *   const callback = handleOAuthCallback(url);\n *   if (callback) {\n *     await db.cloud.login({ oauthCode: callback.code, provider: callback.provider });\n *   }\n * });\n * ```\n */\nexport function handleOAuthCallback(url?: string): OAuthCallbackParams | null {\n  const params = parseOAuthCallback(url);\n  \n  if (!params) {\n    return null;\n  }\n\n  // Validate state for CSRF protection\n  if (!validateOAuthState(params.state)) {\n    throw new OAuthError('invalid_state', params.provider);\n  }\n\n  return params;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/interactWithUser.ts",
    "content": "import Dexie from 'dexie';\nimport type { OAuthProviderInfo } from 'dexie-cloud-common';\nimport { BehaviorSubject } from 'rxjs';\nimport { take } from 'rxjs/operators';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { DXCAlert } from '../types/DXCAlert';\nimport { DXCInputField } from '../types/DXCInputField';\nimport { DXCUserInteraction, DXCGenericUserInteraction, DXCOption } from '../types/DXCUserInteraction';\n\n/** Email/envelope icon data URL for OTP option */\nconst EmailIcon = `data:image/svg+xml;base64,${btoa('<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"#666666\" stroke-width=\"2\"><rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\"/><path d=\"M22 6L12 13 2 6\"/></svg>')}`;\n\n/**\n * Converts an OAuthProviderInfo to a generic DXCOption.\n */\nfunction providerToOption(provider: OAuthProviderInfo): DXCOption {\n  return {\n    name: 'provider',\n    value: provider.name,\n    displayName: `Continue with ${provider.displayName}`,\n    iconUrl: provider.iconUrl,\n    styleHint: provider.type,\n  };\n}\n\nexport interface DXCUserInteractionRequest {\n  type: DXCUserInteraction['type'];\n  title: string;\n  alerts: DXCAlert[];\n  submitLabel?: string;\n  cancelLabel?: string | null;\n  fields: { [name: string]: DXCInputField };\n}\n\nexport function interactWithUser<T extends DXCUserInteractionRequest>(\n  userInteraction: BehaviorSubject<DXCUserInteraction | undefined>,\n  req: T\n): Promise<{\n  [P in keyof T['fields']]: string;\n}> {\n  let done = false;\n  return new Promise<{\n    [P in keyof T['fields']]: string;\n  }>((resolve, reject) => {\n    const interactionProps = {\n      submitLabel: 'Submit',\n      cancelLabel: 'Cancel',\n      ...req,\n      onSubmit: (res: {\n        [P in keyof T['fields']]: string;\n      }) => {\n        userInteraction.next(undefined);\n        done = true;\n        resolve(res);\n      },\n      onCancel: () => {\n        userInteraction.next(undefined);\n        done = true;\n        reject(new Dexie.AbortError('User cancelled'));\n      },\n    } as DXCUserInteraction;\n    userInteraction.next(interactionProps);\n    // Start subscribing for external updates to db.cloud.userInteraction, and if so, cancel this request.\n    /*const subscription = userInteraction.subscribe((currentInteractionProps) => {\n      if (currentInteractionProps !== interactionProps) {\n        if (subscription) subscription.unsubscribe();\n        if (!done) {\n          reject(new Dexie.AbortError(\"User cancelled\"));\n        }\n      }\n    });*/\n  });\n}\n\nexport function alertUser(\n  userInteraction: BehaviorSubject<DXCUserInteraction | undefined>,\n  title: string,\n  ...alerts: DXCAlert[]\n) {\n  return interactWithUser(userInteraction, {\n    type: 'message-alert',\n    title,\n    alerts,\n    fields: {},\n    submitLabel: 'OK',\n    cancelLabel: null,\n  });\n}\n\nexport async function promptForEmail(\n  userInteraction: BehaviorSubject<DXCUserInteraction | undefined>,\n  title: string,\n  emailHint?: string\n) {\n  let email = emailHint || '';\n  // Regular expression for email validation\n  // ^[\\w-+.]+@([\\w-]+\\.)+[\\w-]{2,10}(\\sas\\s[\\w-+.]+@([\\w-]+\\.)+[\\w-]{2,10})?$\n  //\n  // ^[\\w-+.]+ : Matches the start of the string. Allows one or more word characters\n  // (a-z, A-Z, 0-9, and underscore), hyphen, plus, or dot.\n  //\n  // @ : Matches the @ symbol.\n  // ([\\w-]+\\.)+ : Matches one or more word characters or hyphens followed by a dot.\n  //   The plus sign outside the parentheses means this pattern can repeat one or more times,\n  //   allowing for subdomains.\n  // [\\w-]{2,10} : Matches between 2 and 10 word characters or hyphens. This is typically for\n  //   the domain extension like .com, .net, etc.\n  // (\\sas\\s[\\w-+.]+@([\\w-]+\\.)+[\\w-]{2,10})?$ : This part is optional (due to the ? at the end).\n  //   If present, it matches \" as \" followed by another valid email address. This allows for the\n  //   input to be either a single email address or two email addresses separated by \" as \". \n  //\n  // The use case for \"<email1> as <email2>\"\" is for when a database owner with full access to the\n  // database needs to impersonate another user in the database in order to troubleshoot. This\n  // format will only be possible to use when email1 is the owner of an API client with GLOBAL_READ\n  // and GLOBAL_WRITE permissions on the database. The email will be checked on the server before\n  // allowing it and giving out a token for email2, using the OTP sent to email1.\n  while (!email || !/^[\\w-+.]+@([\\w-]+\\.)+[\\w-]{2,10}(\\sas\\s[\\w-+.]+@([\\w-]+\\.)+[\\w-]{2,10})?$/.test(email)) {\n    email = (\n      await interactWithUser(userInteraction, {\n        type: 'email',\n        title,\n        alerts: email\n          ? [\n              {\n                type: 'error',\n                messageCode: 'INVALID_EMAIL',\n                message: 'Please enter a valid email address',\n                messageParams: {},\n              },\n            ]\n          : [],\n        fields: {\n          email: {\n            type: 'email',\n            placeholder: 'you@somedomain.com',\n          },\n        },\n      })\n    ).email;\n  }\n  return email;\n}\n\nexport async function promptForOTP(\n  userInteraction: BehaviorSubject<DXCUserInteraction | undefined>,\n  email: string,\n  alert?: DXCAlert\n) {\n  const alerts: DXCAlert[] = [\n    {\n      type: 'info',\n      messageCode: 'OTP_SENT',\n      message: `A One-Time password has been sent to {email}`,\n      messageParams: { email },\n    },\n  ];\n  if (alert) {\n    alerts.push(alert);\n  }\n  const { otp } = await interactWithUser(userInteraction, {\n    type: 'otp',\n    title: 'Enter OTP',\n    alerts,\n    fields: {\n      otp: {\n        type: 'otp',\n        label: 'OTP',\n        placeholder: 'Paste OTP here',\n      },\n    },\n  });\n  return otp;\n}\n\nexport async function confirmLogout(\n  userInteraction: BehaviorSubject<DXCUserInteraction | undefined>,\n  currentUserId: string,\n  numUnsyncedChanges: number\n) {\n  const alerts: DXCAlert[] = [\n    {\n      type: 'warning',\n      messageCode: 'LOGOUT_CONFIRMATION',\n      message: `{numUnsyncedChanges} unsynced changes will get lost!\n                Logout anyway?`,\n      messageParams: {\n        currentUserId,\n        numUnsyncedChanges: numUnsyncedChanges.toString(),\n      }\n    },\n  ];\n  return await interactWithUser(userInteraction, {\n    type: 'logout-confirmation',\n    title: 'Confirm Logout',\n    alerts,\n    fields: {},\n    submitLabel: 'Confirm logout',\n    cancelLabel: 'Cancel'\n  })\n    .then(() => true)\n    .catch(() => false);\n}\n\n/** Result from provider selection prompt */\nexport type ProviderSelectionResult =\n  | { type: 'provider'; provider: string }\n  | { type: 'otp' };\n\n/**\n * Prompts the user to select an authentication method (OAuth provider or OTP).\n * \n * This function converts OAuth providers and OTP option into generic DXCOption[]\n * for the DXCSelect interaction, handling icon fetching and style hints.\n * \n * @param userInteraction - The user interaction BehaviorSubject\n * @param providers - Available OAuth providers\n * @param otpEnabled - Whether OTP is available\n * @param title - Dialog title\n * @param alerts - Optional alerts to display\n * @returns Promise resolving to the user's selection\n */\nexport async function promptForProvider(\n  userInteraction: BehaviorSubject<DXCUserInteraction | undefined>,\n  providers: OAuthProviderInfo[],\n  otpEnabled: boolean,\n  title: string = 'Choose login method',\n  alerts: DXCAlert[] = []\n): Promise<ProviderSelectionResult> {\n  // Convert providers to generic options\n  const providerOptions = providers.map(providerToOption);\n  \n  // Build the options array\n  const options: DXCOption[] = [...providerOptions];\n  \n  // Add OTP option if enabled\n  if (otpEnabled) {\n    options.push({\n      name: 'otp',\n      value: 'email',\n      displayName: 'Continue with email',\n      iconUrl: EmailIcon,\n      styleHint: 'otp',\n    });\n  }\n  \n  return new Promise<ProviderSelectionResult>((resolve, reject) => {\n    const interactionProps: DXCGenericUserInteraction = {\n      type: 'generic',\n      title,\n      alerts,\n      options,\n      fields: {},\n      submitLabel: '', // No submit button - just options\n      cancelLabel: 'Cancel',\n      onSubmit: (params: { [key: string]: string }) => {\n        userInteraction.next(undefined);\n        // Check which option was selected\n        if ('otp' in params) {\n          resolve({ type: 'otp' });\n        } else if ('provider' in params) {\n          resolve({ type: 'provider', provider: params.provider });\n        } else {\n          // Unknown - default to OTP\n          resolve({ type: 'otp' });\n        }\n      },\n      onCancel: () => {\n        userInteraction.next(undefined);\n        reject(new Dexie.AbortError('User cancelled'));\n      },\n    };\n    \n    userInteraction.next(interactionProps);\n  });\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/login.ts",
    "content": "import { DexieCloudDB } from '../db/DexieCloudDB';\nimport { LoginHints } from '../DexieCloudAPI';\nimport { triggerSync } from '../sync/triggerSync';\nimport { authenticate, loadAccessToken } from './authenticate';\nimport { AuthPersistedContext } from './AuthPersistedContext';\nimport { logout } from './logout';\nimport { otpFetchTokenCallback } from './otpFetchTokenCallback';\nimport { setCurrentUser } from './setCurrentUser';\nimport { UNAUTHORIZED_USER } from './UNAUTHORIZED_USER';\n\nexport async function login(\n  db: DexieCloudDB,\n  hints?: LoginHints\n) {\n  const currentUser = await db.getCurrentUser();\n  const origUserId = currentUser.userId;\n  if (currentUser.isLoggedIn && (!hints || (!hints.email && !hints.userId))) {\n    const licenseStatus = currentUser.license?.status || 'ok';\n    if (\n      licenseStatus === 'ok' &&\n      currentUser.accessToken &&\n      (!currentUser.accessTokenExpiration ||\n        currentUser.accessTokenExpiration.getTime() > Date.now())\n    ) {\n      // Already authenticated according to given hints. And license is valid.\n      return false;\n    }\n    if (\n      currentUser.refreshToken &&\n      (!currentUser.refreshTokenExpiration ||\n        currentUser.refreshTokenExpiration.getTime() > Date.now())\n    ) {\n      // Refresh the token\n      await loadAccessToken(db);\n      return false;\n    }\n    // No refresh token - must re-authenticate:\n  }\n  const context = new AuthPersistedContext(db, {\n    claims: {},\n    lastLogin: new Date(0),\n  });\n  try {\n    await authenticate(\n      db.cloud.options!.databaseUrl,\n      context,\n      db.cloud.options!.fetchTokens || otpFetchTokenCallback(db),\n      db.cloud.userInteraction,\n      hints\n    );\n  } catch (err) {\n    if (err.name === 'OAuthRedirectError') {\n      return false; // Page is redirecting for OAuth login\n    }\n    throw err;\n  }\n  if (\n    origUserId !== UNAUTHORIZED_USER.userId &&\n    context.userId !== origUserId\n  ) {\n    // User was logged in before, but now logged in as another user.\n    await logout(db);\n  }\n\n  /*try {\n    await context.save();\n  } catch (e) {\n    try {\n      if (e.name === 'DataCloneError') {\n        console.debug(`Login context property names:`, Object.keys(context));\n        console.debug(`Login context:`, context);\n        console.debug(`Login context JSON:`, JSON.stringify(context));\n      }\n    } catch {}\n    throw e;\n  }*/\n  await setCurrentUser(db, context);\n  // Make sure to resync as the new login will be authorized\n  // for new realms.\n  triggerSync(db, 'pull');\n  return context.userId !== origUserId;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/logout.ts",
    "content": "import { DexieCloudDB } from '../db/DexieCloudDB';\nimport { TXExpandos } from '../types/TXExpandos';\nimport { confirmLogout } from './interactWithUser';\nimport { UNAUTHORIZED_USER } from './UNAUTHORIZED_USER';\nimport { waitUntil } from './waitUntil';\n\nexport async function logout(db: DexieCloudDB) {\n  const numUnsyncedChanges = await _logout(db);\n  if (numUnsyncedChanges) {\n    if (\n      await confirmLogout(\n        db.cloud.userInteraction,\n        db.cloud.currentUserId,\n        numUnsyncedChanges\n      )\n    ) {\n      await _logout(db, { deleteUnsyncedData: true });\n    } else {\n      throw new Error(`User cancelled logout due to unsynced changes`);\n    }\n  }\n}\n\nexport async function _logout(db: DexieCloudDB, { deleteUnsyncedData = false } = {}) {\n  // Clear the database without emptying configuration options.\n  const [numUnsynced, loggedOut] = await db.dx.transaction('rw', db.dx.tables, async (tx) => {\n    // @ts-ignore\n    const idbtrans: IDBTransaction & TXExpandos = tx.idbtrans;\n    idbtrans.disableChangeTracking = true;\n    idbtrans.disableAccessControl = true;\n    const mutationTables = tx.storeNames.filter((tableName) =>\n      tableName.endsWith('_mutations')\n    );\n\n    // Count unsynced changes\n    const unsyncCounts = await Promise.all(\n      mutationTables.map((mutationTable) => tx.table(mutationTable).count())\n    );\n    const sumUnSynced = unsyncCounts.reduce((a, b) => a + b, 0);\n\n    if (sumUnSynced > 0 && !deleteUnsyncedData) {\n      // Let caller ask user if they want to delete unsynced data.\n      return [sumUnSynced, false];\n    }\n    \n    // Either there are no unsynched changes, or caller provided flag deleteUnsynchedData = true.\n    // Clear all tables except $jobs and $syncState (except the persisted sync state which is\n    // also cleared because we're going to rebuild it using a fresh sync).\n    db.$syncState.delete('syncState');\n    for (const table of db.dx.tables) {\n      if (table.name !== '$jobs' && table.name !== '$syncState') {\n        table.clear();\n      }\n    }\n    return [sumUnSynced, true];\n  });\n\n  if (loggedOut) {\n    // Wait for currentUser observable to emit UNAUTHORIZED_USER\n    await waitUntil(db.cloud.currentUser, (user) => user.userId === UNAUTHORIZED_USER.userId);\n    // Then perform an initial sync\n    await db.cloud.sync({purpose: 'pull', wait: true});\n  }\n  return numUnsynced;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/oauthLogin.ts",
    "content": "import { OAuthError } from '../errors/OAuthError';\n\n/** Options for initiating OAuth redirect */\nexport interface OAuthRedirectOptions {\n  /** The Dexie Cloud database URL */\n  databaseUrl: string;\n  /** The OAuth provider name */\n  provider: string;\n  /** Optional redirect URI override.\n   * Defaults to window.location.href for web apps.\n   * For Capacitor/native apps, use a custom URL scheme like 'myapp://'\n   */\n  redirectUri?: string;\n}\n\n/** Build the OAuth login URL */\nfunction buildOAuthLoginUrl(options: OAuthRedirectOptions): string {\n  const url = new URL(`${options.databaseUrl}/oauth/login/${options.provider}`);\n  \n  // Set the redirect URI - defaults to current page URL for web SPAs\n  const redirectUri = options.redirectUri || \n    (typeof window !== 'undefined' ? window.location.href : '');\n  if (redirectUri) {\n    url.searchParams.set('redirect_uri', redirectUri);\n  }\n  \n  return url.toString();\n}\n\n/**\n * Initiates OAuth login via full page redirect.\n * \n * The page will navigate to the OAuth provider. After authentication,\n * the user is redirected back to the app with a `dxc-auth` query parameter\n * containing base64url-encoded JSON with the authorization code.\n * \n * The dexie-cloud-addon automatically detects and processes this parameter\n * when db.cloud.configure() is called on page load.\n * \n * @param options - OAuth redirect options\n * \n * @example\n * ```typescript\n * // Initiate OAuth login\n * startOAuthRedirect({\n *   databaseUrl: 'https://mydb.dexie.cloud',\n *   provider: 'google'\n * });\n * // Page navigates away, user authenticates, then returns with auth code\n * ```\n */\nexport function startOAuthRedirect(options: OAuthRedirectOptions): void {\n  if (typeof window === 'undefined') {\n    throw new Error('OAuth redirect requires a browser environment');\n  }\n  \n  const loginUrl = buildOAuthLoginUrl(options);\n  window.location.href = loginUrl;\n}\n\n/** Map OAuth error strings to error codes */\nexport function mapOAuthError(error: string): OAuthError['code'] {\n  const lowerError = error.toLowerCase();\n  \n  if (lowerError.includes('access_denied') || lowerError.includes('access denied')) {\n    return 'access_denied';\n  }\n  if (lowerError.includes('email') && lowerError.includes('verif')) {\n    return 'email_not_verified';\n  }\n  if (lowerError.includes('expired')) {\n    return 'expired_code';\n  }\n  if (lowerError.includes('state')) {\n    return 'invalid_state';\n  }\n  \n  return 'provider_error';\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/otpFetchTokenCallback.ts",
    "content": "import {\n  AuthorizationCodeTokenRequest,\n  DemoTokenRequest,\n  OTPTokenRequest1,\n  OTPTokenRequest2,\n  TokenErrorResponse,\n  TokenFinalResponse,\n  TokenRequest,\n  TokenResponse,\n} from 'dexie-cloud-common';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { HttpError } from '../errors/HttpError';\nimport { FetchTokenCallback } from './authenticate';\nimport { exchangeOAuthCode } from './exchangeOAuthCode';\nimport { fetchAuthProviders } from './fetchAuthProviders';\nimport { alertUser, promptForEmail, promptForOTP, promptForProvider } from './interactWithUser';\nimport { startOAuthRedirect } from './oauthLogin';\nimport { OAuthRedirectError } from '../errors/OAuthRedirectError';\n\nexport function otpFetchTokenCallback(db: DexieCloudDB): FetchTokenCallback {\n  const { userInteraction } = db.cloud;\n  return async function otpAuthenticate({ public_key, hints }) {\n    let tokenRequest: TokenRequest;\n    const url = db.cloud.options?.databaseUrl;\n    if (!url) throw new Error(`No database URL given.`);\n    \n    // Handle OAuth code exchange (from redirect/deep link flows)\n    if (hints?.oauthCode && hints.provider) {\n      return await exchangeOAuthCode({\n        databaseUrl: url,\n        code: hints.oauthCode,\n        publicKey: public_key,\n        scopes: ['ACCESS_DB'],\n      });\n    }\n    \n    // Handle OAuth provider login via redirect\n    if (hints?.provider) {\n      let resolvedRedirectUri: string | undefined = undefined;\n      if (hints.redirectPath) {\n        // If redirectPath is absolute, use as is. If relative, resolve against current location\n        if (/^https?:\\/\\//i.test(hints.redirectPath)) {\n          resolvedRedirectUri = hints.redirectPath;\n        } else if (typeof window !== 'undefined' && window.location) {\n          // Use URL constructor to resolve relative path\n          resolvedRedirectUri = new URL(hints.redirectPath, window.location.href).toString();\n        } else if (typeof location !== 'undefined' && location.href) {\n          resolvedRedirectUri = new URL(hints.redirectPath, location.href).toString();\n        }\n      }\n      initiateOAuthRedirect(db, hints.provider, resolvedRedirectUri);\n      // This function never returns - page navigates away\n      throw new OAuthRedirectError(hints.provider);\n    }\n    \n    if (hints?.grant_type === 'demo') {\n      const demo_user = await promptForEmail(\n        userInteraction,\n        'Enter a demo user email',\n        hints?.email || hints?.userId\n      );\n      tokenRequest = {\n        demo_user,\n        grant_type: 'demo',\n        scopes: ['ACCESS_DB'],\n        public_key\n      } satisfies DemoTokenRequest;\n    } else if (hints?.otpId && hints.otp) {\n      // User provided OTP ID and OTP code. This means that the OTP email\n      // has already gone out and the user may have clicked a magic link\n      // in the email with otp and otpId in query and the app has picked\n      // up those values and passed them to db.cloud.login().\n      tokenRequest = {\n        grant_type: 'otp',\n        otp_id: hints.otpId,\n        otp: hints.otp,\n        scopes: ['ACCESS_DB'],\n        public_key,\n      } satisfies OTPTokenRequest2;\n    } else if (hints?.grant_type === 'otp' || hints?.email) {\n      // User explicitly requested OTP flow - skip provider selection\n      const email = hints?.email || await promptForEmail(\n        userInteraction,\n        'Enter email address'\n      );\n      if (/@demo.local$/.test(email)) {\n        tokenRequest = {\n          demo_user: email,\n          grant_type: 'demo',\n          scopes: ['ACCESS_DB'],\n          public_key\n        } satisfies DemoTokenRequest;\n      } else {\n        tokenRequest = {\n          email,\n          grant_type: 'otp',\n          scopes: ['ACCESS_DB'],\n        } satisfies OTPTokenRequest1;\n      }\n    } else {\n      // Check for available auth providers (OAuth + OTP)\n      const socialAuthEnabled = db.cloud.options?.socialAuth !== false;\n      const authProviders = await fetchAuthProviders(url, socialAuthEnabled);\n      \n      // If we have OAuth providers available, prompt for selection\n      if (authProviders.providers.length > 0) {\n        const selection = await promptForProvider(\n          userInteraction,\n          authProviders.providers,\n          authProviders.otpEnabled,\n          'Sign in',\n        );\n        \n        if (selection.type === 'provider') {\n          // User selected an OAuth provider - initiate redirect\n          initiateOAuthRedirect(db, selection.provider);\n          // This function never returns - page navigates away\n          throw new OAuthRedirectError(selection.provider);\n        }\n        // User chose OTP - continue with email prompt below\n      }\n      \n      const email = await promptForEmail(\n        userInteraction,\n        'Enter email address',\n        hints?.email\n      );\n      if (/@demo.local$/.test(email)) {\n        tokenRequest = {\n          demo_user: email,\n          grant_type: 'demo',\n          scopes: ['ACCESS_DB'],\n          public_key\n        } satisfies DemoTokenRequest;\n      } else {\n        tokenRequest = {\n          email,\n          grant_type: 'otp',\n          scopes: ['ACCESS_DB'],\n        } satisfies OTPTokenRequest1;\n      }\n    }\n    const res1 = await fetch(`${url}/token`, {\n      body: JSON.stringify(tokenRequest),\n      method: 'post',\n      headers: { 'Content-Type': 'application/json' },\n      mode: 'cors',\n    });\n    if (res1.status !== 200) {\n      const errMsg = await res1.text();\n      await alertUser(userInteraction, \"Token request failed\", {\n        type: 'error',\n        messageCode: 'GENERIC_ERROR',\n        message: errMsg,\n        messageParams: {}\n      }).catch(()=>{});\n      throw new HttpError(res1, errMsg);\n    }\n    const response: TokenResponse = await res1.json();\n    if (response.type === 'tokens' || response.type === 'error') {\n      // Demo user request can get a \"tokens\" response right away\n      // Error can also be returned right away.\n      return response;\n    } else if (tokenRequest.grant_type === 'otp' && 'email' in tokenRequest) {\n      if (response.type !== 'otp-sent')\n        throw new Error(`Unexpected response from ${url}/token`);\n      const otp = await promptForOTP(userInteraction, tokenRequest.email);\n      const tokenRequest2 = {\n        ...tokenRequest,\n        otp: otp || '',\n        otp_id: response.otp_id,\n        public_key\n      } satisfies OTPTokenRequest2;\n\n      let res2 = await fetch(`${url}/token`, {\n        body: JSON.stringify(tokenRequest2),\n        method: 'post',\n        headers: { 'Content-Type': 'application/json' },\n        mode: 'cors',\n      });\n      while (res2.status === 401) {\n        const errorText = await res2.text();\n        tokenRequest2.otp = await promptForOTP(userInteraction, tokenRequest.email, {\n          type: 'error',\n          messageCode: 'INVALID_OTP',\n          message: errorText,\n          messageParams: {}\n        });\n        res2 = await fetch(`${url}/token`, {\n          body: JSON.stringify(tokenRequest2),\n          method: 'post',\n          headers: { 'Content-Type': 'application/json' },\n          mode: 'cors',\n        });\n      }\n      if (res2.status !== 200) {\n        const errMsg = await res2.text();\n        throw new HttpError(res2, errMsg);\n      }\n      const response2: TokenFinalResponse | TokenErrorResponse = await res2.json();\n      return response2;\n    } else {\n      throw new Error(`Unexpected response from ${url}/token`);\n    }\n  };\n}\n\n/**\n * Initiates OAuth login via full page redirect.\n * \n * The page will navigate away to the OAuth provider. After authentication,\n * the user is redirected back with a dxc-auth query parameter that is\n * automatically detected by db.cloud.configure().\n */\nfunction initiateOAuthRedirect(\n  db: DexieCloudDB,\n  provider: string,\n  redirectUriOverride?: string\n): void {\n  const url = db.cloud.options?.databaseUrl;\n  if (!url) throw new Error(`No database URL given.`);\n  \n  const redirectUri =\n    redirectUriOverride ||\n    db.cloud.options?.oauthRedirectUri ||\n    (typeof location !== 'undefined' ? location.href : undefined);\n  \n  // CodeRabbit suggested to fail fast here, but the only situation where\n  // redirectUri would be undefined is in non-browser environments, and\n  // in those environments OAuth redirect does not make sense anyway\n  // and will fail fast in startOAuthRedirect().\n  \n  // Start OAuth redirect flow - page navigates away\n  startOAuthRedirect({\n    databaseUrl: url,\n    provider,\n    redirectUri,\n  });\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/setCurrentUser.ts",
    "content": "import { DexieCloudDB } from '../db/DexieCloudDB';\nimport { prodLog } from '../prodLog';\nimport { AuthPersistedContext } from './AuthPersistedContext';\nimport { waitUntil } from './waitUntil';\n\n/** This function changes or sets the current user as requested.\n *\n * Use cases:\n * * Initially on db.ready after reading the current user from db.$logins.\n *   This will make sure that any unsynced operations from the previous user is synced before\n *   changing the user.\n * * Upon user request\n *\n * @param db\n * @param newUser\n */\nexport async function setCurrentUser(\n  db: DexieCloudDB,\n  user: AuthPersistedContext\n) {\n  const $logins = db.table('$logins');\n  await db.transaction('rw', $logins, async (tx) => {\n    const existingLogins = await $logins.toArray();\n    await Promise.all(\n      existingLogins\n        .filter((login) => login.userId !== user.userId && login.isLoggedIn)\n        .map((login) => {\n          login.isLoggedIn = false;\n          return $logins.put(login);\n        })\n    );\n    user.isLoggedIn = true;\n    user.lastLogin = new Date();\n    try {\n      await user.save();\n    } catch (e) {\n      try {\n        if (e.name === 'DataCloneError') {\n          // We've seen this buggy behavior in some browsers and in case it happens\n          // again we really need to collect the details to understand what's going on.\n          prodLog('debug', `Login context property names:`, Object.keys(user));\n          prodLog('debug', `Login context property names:`, Object.keys(user));\n          prodLog('debug', `Login context:`, user);\n          prodLog('debug', `Login context JSON:`, JSON.stringify(user));\n        }\n      } catch {}\n      throw e;\n    }\n    console.debug('Saved new user', user.email);\n  });\n  await waitUntil(\n    db.cloud.currentUser,\n    (currentUser) => currentUser.userId === user.userId\n  );\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/authentication/waitUntil.ts",
    "content": "import { filter, firstValueFrom, from, InteropObservable, Observable } from 'rxjs';\n\nexport function waitUntil<T>(\n  o: Observable<T> | InteropObservable<T>, // Works with Dexie's liveQuery observables if we'd need that\n  predicate: (value: T) => boolean\n) {\n  return firstValueFrom(from(o).pipe(\n    filter(predicate),\n  ));\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/computeSyncState.ts",
    "content": "import { combineLatest, Observable, of } from 'rxjs';\nimport { debounceTime, map, startWith, switchMap } from 'rxjs/operators';\nimport { getCurrentUserEmitter } from './currentUserEmitter';\nimport { DexieCloudDB, SyncStateChangedEventData } from './db/DexieCloudDB';\nimport { isOnline } from './sync/isOnline';\nimport { SyncState } from './types/SyncState';\nimport { userIsActive, userIsReallyActive } from './userIsActive';\n\nexport function computeSyncState(db: DexieCloudDB): Observable<SyncState> {\n  let _prevStatus = db.cloud.webSocketStatus.value;\n  const lazyWebSocketStatus = db.cloud.webSocketStatus.pipe(\n    switchMap((status) => {\n      const prevStatus = _prevStatus;\n      _prevStatus = status;\n      const rv = of(status);\n      switch (status) {\n        // A normal scenario is that the WS reconnects and falls shortly in disconnected-->connection-->connected.\n        // Don't distract user with this unless these things take more time than normal:\n\n        // Only show disconnected if disconnected more than 500ms, or if we can\n        // see that the user is indeed not active.\n        case 'disconnected':\n          return userIsActive.value ? rv.pipe(debounceTime(500)) : rv;\n\n        // Only show connecting if previous state was 'not-started' or 'error', or if\n        // the time it takes to connect goes beyond 4 seconds.\n        case 'connecting':\n          return prevStatus === 'not-started' || prevStatus === 'error'\n            ? rv\n            : rv.pipe(debounceTime(4000));\n        default:\n          return rv;\n      }\n    })\n  );\n  return combineLatest([\n    lazyWebSocketStatus,\n    db.syncStateChangedEvent.pipe(startWith({ phase: 'initial' } as SyncStateChangedEventData)),\n    getCurrentUserEmitter(db.dx._novip),\n    userIsReallyActive\n  ]).pipe(\n    map(([status, syncState, user, userIsActive]) => {\n      if (user.license?.status && user.license.status !== 'ok') {\n        return {\n          phase: 'offline',\n          status: 'offline',\n          license: user.license.status\n        } satisfies SyncState;\n      }\n      let { phase, error, progress } = syncState;\n      let adjustedStatus = status;\n      if (phase === 'error') {\n        // Let users only rely on the status property to display an icon.\n        // If there's an error in the sync phase, let it show on that\n        // status icon also.\n        adjustedStatus = 'error';\n      }\n      if (status === 'not-started') {\n        // If websocket isn't yet connected becase we're doing\n        // the startup sync, let the icon show the symbol for connecting.\n        if (phase === 'pushing' || phase === 'pulling') {\n          adjustedStatus = 'connecting';\n        }\n      }      \n      const previousPhase = db.cloud.syncState.value.phase;\n      //const previousStatus = db.cloud.syncState.value.status;\n      if (previousPhase === 'error' && (syncState.phase === 'pushing' || syncState.phase === 'pulling')) {\n        // We were in an errored state but is now doing sync. Show \"connecting\" icon.\n        adjustedStatus = 'connecting';\n      }\n      /*if (syncState.phase === 'in-sync' && adjustedStatus === 'connecting') {\n        adjustedStatus = 'connected';\n      }*/\n        \n      if (!userIsActive) {\n        adjustedStatus = 'disconnected';\n      }\n\n      const retState: SyncState = {\n        phase,\n        error,\n        progress,\n        status: isOnline ? adjustedStatus : 'offline',\n        license: 'ok'\n      };\n\n      return retState;\n    })\n  );\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/createSharedValueObservable.ts",
    "content": "import {\n  concat,\n  from,\n  InteropObservable,\n  map,\n  Observable,\n  ObservableInput,\n  share,\n  timer,\n} from 'rxjs';\nimport { ObservableWithCurrentValue } from './mapValueObservable';\n\nexport function createSharedValueObservable<T>(\n  o: ObservableInput<T>,\n  defaultValue: T\n): ObservableWithCurrentValue<T> {\n  let currentValue = defaultValue;\n  let shared = from(o).pipe(\n    map((x) => (currentValue = x)),\n    share({ resetOnRefCountZero: () => timer(1000) })\n  ) as ObservableWithCurrentValue<T>;\n\n  const rv = new Observable((observer) => {\n    let didEmit = false;\n    const subscription = shared.subscribe({\n      next(value) {\n        didEmit = true;\n        observer.next(value);\n      },\n      error(error) {\n        observer.error(error);\n      },\n      complete() {\n        observer.complete();\n      }\n    });\n    if (!didEmit && !subscription.closed) {\n      observer.next(currentValue);\n    }\n    return subscription;\n  }) as ObservableWithCurrentValue<T>;\n\n  rv.getValue = () => currentValue;\n  return rv;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/currentUserEmitter.ts",
    "content": "import Dexie from \"dexie\";\nimport { BehaviorSubject } from \"rxjs\";\nimport { associate } from \"./associate\";\nimport { UNAUTHORIZED_USER } from \"./authentication/UNAUTHORIZED_USER\";\nimport { UserLogin } from \"./dexie-cloud-client\";\n\nexport const getCurrentUserEmitter = associate((db: Dexie) => new BehaviorSubject<UserLogin>(\n  {...UNAUTHORIZED_USER, isLoading: true}\n));\n"
  },
  {
    "path": "addons/dexie-cloud/src/db/DexieCloudDB.ts",
    "content": "import Dexie, { Table } from 'dexie';\nimport { GuardedJob } from './entities/GuardedJob';\nimport { UserLogin } from './entities/UserLogin';\nimport { PersistedSyncState } from './entities/PersistedSyncState';\nimport { UNAUTHORIZED_USER } from '../authentication/UNAUTHORIZED_USER';\nimport { DexieCloudOptions } from '../DexieCloudOptions';\nimport { BehaviorSubject, Subject } from 'rxjs';\nimport { BaseRevisionMapEntry } from './entities/BaseRevisionMapEntry';\nimport {\n  DBRealm,\n  DBRealmMember,\n  DBRealmRole,\n  DexieCloudSchema,\n} from 'dexie-cloud-common';\nimport { BroadcastedAndLocalEvent } from '../helpers/BroadcastedAndLocalEvent';\nimport { SyncState, SyncStatePhase } from '../types/SyncState';\nimport { MessagesFromServerConsumer } from '../sync/messagesFromServerQueue';\nimport { YClientMessage } from 'dexie-cloud-common';\nimport { BlobDownloadTracker } from '../sync/BlobDownloadTracker';\n\n/*export interface DexieCloudDB extends Dexie {\n  table(name: string): Table<any, any>;\n  table(name: \"$jobs\"): Table<GuardedJob, string>;\n  table(name: \"$logins\"): Table<UserLogin, string>;\n  table(name: \"$syncState\"): Table<SyncState, \"syncState\">;\n  //table(name: \"$pendingChangesFromServer\"): Table<DBOperationsSet, number>;\n}\n*/\n\nexport interface SyncStateChangedEventData {\n  phase: SyncStatePhase;\n  error?: Error;\n  progress?: number;\n}\n\ntype SyncStateTable = Table<\n  PersistedSyncState | DexieCloudSchema | DexieCloudOptions,\n  'syncState' | 'options' | 'schema'\n>;\nexport interface DexieCloudDBBase {\n  readonly name: Dexie['name'];\n  readonly close: Dexie['close'];\n  transaction: Dexie['transaction'];\n  table: Dexie['table'];\n  readonly tables: Dexie['tables'];\n  readonly cloud: Dexie['cloud'];\n  readonly $jobs: Table<GuardedJob, string>;\n  readonly $logins: Table<UserLogin, string>;\n  readonly $syncState: SyncStateTable;\n  readonly $baseRevs: Table<BaseRevisionMapEntry, [string, number]>;\n\n  readonly realms: Table<DBRealm, string>;\n  readonly members: Table<DBRealmMember, string>;\n  readonly roles: Table<DBRealmRole, [string, string]>;\n\n  readonly localSyncEvent: Subject<{ purpose?: 'pull' | 'push' }>;\n  readonly syncStateChangedEvent: BroadcastedAndLocalEvent<SyncStateChangedEventData>;\n  readonly syncCompleteEvent: BroadcastedAndLocalEvent<void>;\n  readonly dx: Dexie;\n  readonly initiallySynced: boolean;\n}\n\nexport interface DexieCloudDB extends DexieCloudDBBase {\n  getCurrentUser(): Promise<UserLogin>;\n  getSchema(): Promise<DexieCloudSchema | undefined>;\n  getOptions(): Promise<DexieCloudOptions | undefined>;\n  getPersistedSyncState(): Promise<PersistedSyncState | undefined>;\n  setInitiallySynced(initiallySynced: boolean): void;\n  reconfigure(): void;\n  messageConsumer: MessagesFromServerConsumer;\n  messageProducer: Subject<YClientMessage>;\n  blobDownloadTracker: BlobDownloadTracker;\n}\n\nconst wm = new WeakMap<object, DexieCloudDB>();\n\nexport const DEXIE_CLOUD_SCHEMA = {\n  members: '@id, [userId+realmId], [email+realmId], realmId',\n  roles: '[realmId+name]',\n  realms: '@realmId',\n  $jobs: '',\n  $syncState: '',\n  $baseRevs: '[tableName+clientRev]',\n  $logins: 'claims.sub, lastLogin',\n};\n\nlet static_counter = 0;\nexport function DexieCloudDB(dx: Dexie): DexieCloudDB {\n  if ('vip' in dx) dx = dx['vip']; // Avoid race condition. Always map to a vipped dexie that don't block during db.on.ready().\n  let db = wm.get(dx);\n  if (!db) {\n    const localSyncEvent = new Subject<{ purpose: 'push' | 'pull' }>();\n    let syncStateChangedEvent =\n      new BroadcastedAndLocalEvent<SyncStateChangedEventData>(\n        `syncstatechanged-${dx.name}`\n      );\n    let syncCompleteEvent = new BroadcastedAndLocalEvent<void>(\n        `synccomplete-${dx.name}`\n      );\n    localSyncEvent['id'] = ++static_counter;\n    let initiallySynced = false;\n    db = {\n      get name() {\n        return dx.name;\n      },\n      close() {\n        return dx.close();\n      },\n      transaction: dx.transaction.bind(dx),\n      table: dx.table.bind(dx),\n      get tables() {\n        return dx.tables;\n      },\n      get cloud() {\n        return dx.cloud;\n      },\n      get $jobs() {\n        return dx.table('$jobs') as Table<GuardedJob, string>;\n      },\n      get $syncState() {\n        return dx.table('$syncState') as SyncStateTable;\n      },\n      get $baseRevs() {\n        return dx.table('$baseRevs') as Table<\n          BaseRevisionMapEntry,\n          [string, number]\n        >;\n      },\n      get $logins() {\n        return dx.table('$logins') as Table<UserLogin, string>;\n      },\n\n      get realms() {\n        return dx.realms;\n      },\n      get members() {\n        return dx.members;\n      },\n      get roles() {\n        return dx.roles;\n      },\n      get initiallySynced() {\n        return initiallySynced;\n      },\n      localSyncEvent,\n      get syncStateChangedEvent() {\n        return syncStateChangedEvent;\n      },\n      get syncCompleteEvent() {\n        return syncCompleteEvent;\n      },\n      dx,\n    } as DexieCloudDB;\n\n    const helperMethods: Partial<DexieCloudDB> = {\n      getCurrentUser() {\n        return db!.$logins\n          .toArray()\n          .then(\n            (logins) => logins.find((l) => l.isLoggedIn) || UNAUTHORIZED_USER\n          );\n      },\n      getPersistedSyncState() {\n        return db!.$syncState.get('syncState') as Promise<\n          PersistedSyncState | undefined\n        >;\n      },\n      getSchema() {\n        return db!.$syncState.get('schema').then((schema: DexieCloudSchema) => {\n          if (schema) {\n            for (const table of db!.tables) {\n              if (table.schema.primKey && table.schema.primKey.keyPath && schema[table.name]) {\n                schema[table.name].primaryKey = nameFromKeyPath(\n                  table.schema.primKey.keyPath\n                );\n              }\n            }\n          }\n          return schema;\n        }) as Promise<DexieCloudSchema | undefined>;\n      },\n      getOptions() {\n        return db!.$syncState.get('options') as Promise<\n          DexieCloudOptions | undefined\n        >;\n      },\n      setInitiallySynced(value) {\n        initiallySynced = value;\n      },\n      reconfigure() {\n        syncStateChangedEvent = new BroadcastedAndLocalEvent<SyncState>(\n          `syncstatechanged-${dx.name}`\n        );\n        syncCompleteEvent = new BroadcastedAndLocalEvent<void>(\n          `synccomplete-${dx.name}`\n        );\n      },\n    };\n\n    Object.assign(db, helperMethods);\n    db.messageConsumer = MessagesFromServerConsumer(db);\n    db.messageProducer = new Subject<YClientMessage>();\n    db.blobDownloadTracker = new BlobDownloadTracker(db);\n    wm.set(dx, db);\n  }\n  return db;\n}\n\nfunction nameFromKeyPath (keyPath?: string | string[]): string {\n  return typeof keyPath === 'string' ?\n    keyPath :\n    keyPath ? ('[' + [].join.call(keyPath, '+') + ']') : \"\";\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/db/entities/BaseRevisionMapEntry.ts",
    "content": "// This interface has been moved to dexie-cloud-common. TODO: Remove file and update imports.\nexport interface BaseRevisionMapEntry {\n  tableName: string;\n  clientRev: number;\n  serverRev: any;\n}"
  },
  {
    "path": "addons/dexie-cloud/src/db/entities/EntityCommon.ts",
    "content": "export interface EntityCommon {\n  realmId?: string;\n  owner?: string;\n  $ts?: string;\n  _hasBlobRefs?: 1; // Indicates that the entity has unresolved BlobRefs\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/db/entities/GuardedJob.ts",
    "content": "\nexport interface GuardedJob {\n  nodeId: string;\n  started: Date;\n  heartbeat: Date;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/db/entities/Member.ts",
    "content": "export interface Member {\n  id?: string; // Auto-generated universal primary key\n  realmId: string;\n  userId?: string; // User identity. Set by the system when user accepts invite.\n  email?: string; // The email of the requested user (for invites).\n  name?: string; // The name of the requested user (for invites).\n  invite?: boolean;\n  invited?: Date;\n  accepted?: Date;\n  rejected?: Date;\n  roles?: string[]; // Array of role names for this user.\n  permissions?: {\n    add?: string[] | \"*\"; // array of tables or \"*\" (all).\n    update?: {\n      [tableName: string]: string[] | \"*\"; // array of properties or \"*\" (all).\n    };\n    manage?: string[] | \"*\"; // array of tables or \"*\" (all).\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/db/entities/PersistedSyncState.ts",
    "content": "export interface PersistedSyncState {\n  serverRevision?: any;\n  yServerRevision?: string;\n  latestRevisions: {\n    [tableName: string]: number\n  };\n  realms: string[];\n  inviteRealms: string[];\n  clientIdentity: string;\n  initiallySynced?: boolean;\n  remoteDbId?: string;\n  syncedTables: string[];\n  timestamp?: Date;\n  error?: string;\n  yDownloadedRealms?: {\n    [realmId: string]: \"*\" | {\n      tbl: string;\n      prop: string;\n      key: any;\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/db/entities/Realm.ts",
    "content": "export interface Realm {\n  /** Primary key of the realm.\n   */\n  realmId: string;\n\n  /** The name of the realm.\n   *\n   * This property is optional but it can be a good practice to name a realm for what it represents.\n   */\n  name?: string;\n\n  /** Contains the user-ID of the owner. An owner has implicit full write-access to the realm\n   * and all obejcts connected to it.\n   */\n  owner?: string;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/db/entities/Role.ts",
    "content": "export interface Role {\n  realmId: string;\n  name: string;\n  permissions: {\n    add?: string[] | \"*\"; // array of tables or \"*\" (all).\n    update?: {\n      [tableName: string]: string[] | \"*\"; // array of properties or \"*\" (all).\n    };\n    manage?: string[] | \"*\"; // array of tables or \"*\" (all).\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/db/entities/UserLogin.ts",
    "content": "import { DXCUserInteraction } from \"../../types/DXCUserInteraction\";\n\nexport interface UserLogin {\n  userId?: string;\n  name?: string;\n  email?: string;\n  claims: {\n    [claimName: string]: any;\n  }\n  license?: {\n    type: 'demo' | 'eval' | 'prod' | 'client';\n    status: 'ok' | 'expired' | 'deactivated';\n    validUntil?: Date;\n    evalDaysLeft?: number;\n  }\n  lastLogin: Date;\n  accessToken?: string;\n  accessTokenExpiration?: Date;\n  refreshToken?: string;\n  refreshTokenExpiration?: Date;\n  nonExportablePrivateKey?: CryptoKey;\n  publicKey?: CryptoKey;\n  isLoggedIn?: boolean;\n  data?: any; // From user data\n  isLoading?: boolean; // true while we still are loading user info\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/default-ui/Dialog.tsx",
    "content": "import { Styles } from './Styles';\nimport { ComponentChildren, h } from 'preact';\n\nexport function Dialog({ children, className }: { children?: ComponentChildren, className?: string }) {\n  return (\n    <div className={`dexie-dialog ${className || ''}`}>\n      <div style={Styles.Darken} />\n      <div style={Styles.DialogOuter}>\n        <div style={Styles.DialogInner}>{children}</div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/default-ui/LoginDialog.tsx",
    "content": "import { Dialog } from './Dialog';\nimport { Styles } from './Styles';\nimport { h, Fragment } from 'preact';\nimport { useLayoutEffect, useRef, useState } from 'preact/hooks';\nimport { DXCUserInteraction, DXCOption } from '../types/DXCUserInteraction';\nimport { resolveText } from '../helpers/resolveText';\nimport { DXCInputField } from '../types/DXCInputField';\nimport { OptionButton, Divider } from './OptionButton';\nimport { DXCAlert } from '../types/DXCAlert';\n\nconst OTP_LENGTH = 8;\n\n/** Props for LoginDialog - accepts any user interaction */\ninterface LoginDialogProps {\n  title: string;\n  alerts: DXCAlert[];\n  fields: { [name: string]: DXCInputField };\n  options?: DXCOption[];\n  submitLabel?: string;\n  cancelLabel?: string | null;\n  onSubmit: (params: { [paramName: string]: string }) => void;\n  onCancel: () => void;\n}\n\n/**\n * Generic dialog that can render:\n * - Form fields (text inputs)\n * - Selectable options (buttons)\n * - Or both together\n * \n * When an option is clicked, calls onSubmit({ [option.name]: option.value }).\n * This unified approach means the same callback handles both form submission\n * and option selection.\n */\nexport function LoginDialog({\n  title,\n  alerts,\n  fields,\n  options,\n  submitLabel,\n  cancelLabel,\n  onCancel,\n  onSubmit,\n}: LoginDialogProps) {\n  const [params, setParams] = useState<{ [param: string]: string }>({});\n\n  const firstFieldRef = useRef<HTMLInputElement>(null);\n  useLayoutEffect(() => firstFieldRef.current?.focus(), []);\n\n  const fieldEntries = Object.entries(fields || {}) as [string, DXCInputField][];\n  const hasFields = fieldEntries.length > 0;\n  const hasOptions = options && options.length > 0;\n\n  // Group options by name to detect if we have multiple groups\n  const optionGroups = new Map<string, typeof options>();\n  if (options) {\n    for (const option of options) {\n      const group = optionGroups.get(option.name) || [];\n      group.push(option);\n      optionGroups.set(option.name, group);\n    }\n  }\n  const hasMultipleGroups = optionGroups.size > 1;\n\n  // Handler for option clicks - calls onSubmit with { [option.name]: option.value }\n  const handleOptionClick = (option: DXCOption) => {\n    onSubmit({ [option.name]: option.value });\n  };\n\n  return (\n    <Dialog className=\"dxc-login-dlg\">\n      <>\n        <h3 style={Styles.WindowHeader}>{title}</h3>\n        {alerts.map((alert, idx) => (\n          <div key={idx}>\n            <p style={Styles.Alert[alert.type]}>{resolveText(alert)}</p>\n            {alert.copyText && <CopyButton text={alert.copyText} />}\n          </div>\n        ))}\n\n        {/* Render options if present */}\n        {hasOptions && (\n          <div class=\"dxc-options\">\n            {hasMultipleGroups ? (\n              // Render with dividers between groups\n              Array.from(optionGroups.entries()).map(([groupName, groupOptions], groupIdx) => (\n                <Fragment key={groupName}>\n                  {groupIdx > 0 && <Divider />}\n                  {groupOptions!.map((option) => (\n                    <OptionButton\n                      key={`${option.name}-${option.value}`}\n                      option={option}\n                      onClick={() => handleOptionClick(option)}\n                    />\n                  ))}\n                </Fragment>\n              ))\n            ) : (\n              // Simple case: all options in one group\n              options!.map((option) => (\n                <OptionButton\n                  key={`${option.name}-${option.value}`}\n                  option={option}\n                  onClick={() => handleOptionClick(option)}\n                />\n              ))\n            )}\n          </div>\n        )}\n\n        {/* Divider between options and fields if both are present */}\n        {hasOptions && hasFields && <Divider />}\n\n        {/* Render form fields if present */}\n        {hasFields && (\n          <form\n            onSubmit={(ev) => {\n              ev.preventDefault();\n              onSubmit(params);\n            }}\n          >\n            {fieldEntries.map(\n              ([fieldName, { type, label, placeholder }], idx) => (\n                <label style={Styles.Label} key={idx}>\n                  {label ? `${label}: ` : ''}\n                  <input\n                    ref={idx === 0 ? firstFieldRef : undefined}\n                    type={type}\n                    name={fieldName}\n                    autoComplete=\"on\"\n                    style={Styles.Input}\n                    autoFocus\n                    placeholder={placeholder}\n                    value={params[fieldName] || ''}\n                    onInput={(ev) => {\n                      const value = valueTransformer(type, ev.target?.['value']);\n                      let updatedParams = {\n                        ...params,\n                        [fieldName]: value,\n                      };\n                      setParams(updatedParams);\n                      if (type === 'otp' && value?.trim().length === OTP_LENGTH) {\n                        // Auto-submit when OTP is filled in.\n                        onSubmit(updatedParams);\n                      }\n                    }}\n                  />\n                </label>\n              )\n            )}\n          </form>\n        )}\n      </>\n      <div style={Styles.ButtonsDiv}>\n        <>\n          {/* Show submit button if there are fields, OR if there are no options and no fields (e.g., message alert) */}\n          {submitLabel && (hasFields || (!hasOptions && !hasFields)) && (\n            <button\n              type=\"submit\"\n              style={Styles.PrimaryButton}\n              onClick={() => onSubmit(params)}\n            >\n              {submitLabel}\n            </button>\n          )}\n          {cancelLabel && (\n            <button style={Styles.Button} onClick={onCancel}>\n              {cancelLabel}\n            </button>\n          )}\n        </>\n      </div>\n    </Dialog>\n  );\n}\n\nfunction valueTransformer(type: string, value: string) {\n  switch (type) {\n    case 'email':\n      return value.toLowerCase();\n    case 'otp':\n      return value.toUpperCase();\n    default:\n      return value;\n  }\n}\n\nfunction CopyButton({ text }: { text: string }) {\n  const [copied, setCopied] = useState(false);\n  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  // Cleanup timeout on unmount\n  useLayoutEffect(() => {\n    return () => {\n      if (timeoutRef.current !== null) clearTimeout(timeoutRef.current);\n    };\n  }, []);\n\n  const scheduleCopiedReset = () => {\n    if (timeoutRef.current !== null) clearTimeout(timeoutRef.current);\n    setCopied(true);\n    timeoutRef.current = setTimeout(() => {\n      timeoutRef.current = null;\n      setCopied(false);\n    }, 2000);\n  };\n\n  const handleClick = () => {\n    if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {\n      navigator.clipboard.writeText(text).then(scheduleCopiedReset).catch(() => {\n        fallbackCopy(text, scheduleCopiedReset);\n      });\n    } else {\n      fallbackCopy(text, scheduleCopiedReset);\n    }\n  };\n\n  return (\n    <button\n      type=\"button\"\n      style={copied ? Styles.CopyButtonCopied : Styles.CopyButton}\n      onClick={handleClick}\n      title=\"Copy to clipboard\"\n    >\n      {copied ? '✓ Copied!' : `📋 ${text}`}\n    </button>\n  );\n}\n\nfunction fallbackCopy(text: string, onSuccess: () => void) {\n  const textarea = document.createElement('textarea');\n  textarea.value = text;\n  textarea.style.position = 'fixed';\n  textarea.style.opacity = '0';\n  document.body.appendChild(textarea);\n  textarea.select();\n  const success = document.execCommand('copy');\n  document.body.removeChild(textarea);\n  if (success) onSuccess();\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/default-ui/OptionButton.tsx",
    "content": "import { h } from 'preact';\nimport { DXCOption } from '../types/DXCUserInteraction';\nimport { Styles } from './Styles';\n\nexport interface OptionButtonProps {\n  option: DXCOption;\n  onClick: () => void;\n}\n\n/** Get style based on styleHint (for provider branding, etc.) */\nfunction getOptionStyle(styleHint?: string): Record<string, string> {\n  const baseStyle = { ...Styles.ProviderButton };\n  \n  if (!styleHint) {\n    return baseStyle;\n  }\n  \n  switch (styleHint) {\n    case 'google':\n      return { ...baseStyle, ...Styles.ProviderGoogle };\n    case 'github':\n      return { ...baseStyle, ...Styles.ProviderGitHub };\n    case 'microsoft':\n      return { ...baseStyle, ...Styles.ProviderMicrosoft };\n    case 'apple':\n      return { ...baseStyle, ...Styles.ProviderApple };\n    case 'otp':\n      return { ...Styles.OtpButton };\n    case 'custom-oauth2':\n      return { ...baseStyle, ...Styles.ProviderCustom };\n    default:\n      return baseStyle;\n  }\n}\n\n/**\n * Generic button component for selectable options.\n * Displays the option's icon and display name.\n * \n * Style is determined by the styleHint property for branding purposes.\n */\nexport function OptionButton({ option, onClick }: OptionButtonProps) {\n  const { displayName, iconUrl, styleHint } = option;\n  const style = getOptionStyle(styleHint);\n  \n  return (\n    <button\n      type=\"button\"\n      style={style}\n      onClick={onClick}\n      class={`dxc-option-btn${styleHint ? ` dxc-option-${styleHint}` : ''}`}\n      aria-label={displayName}\n    >\n      {iconUrl && (\n        <img\n          src={iconUrl}\n          alt=\"\"\n          style={Styles.ProviderButtonIcon}\n          aria-hidden=\"true\"\n        />\n      )}\n      <span style={Styles.ProviderButtonText}>{displayName}</span>\n    </button>\n  );\n}\n\n/**\n * Visual divider with \"or\" text.\n */\nexport function Divider() {\n  return (\n    <div style={Styles.Divider}>\n      <div style={Styles.DividerLine} />\n      <span style={Styles.DividerText}>or</span>\n      <div style={Styles.DividerLine} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/default-ui/Styles.ts",
    "content": "export const Styles: { [styleAlias: string]: Partial<CSSStyleDeclaration> | any} = {\n  Error: {\n    color: \"red\",\n  },\n  Alert: {\n    error: {\n      color: \"red\",\n      fontWeight: \"bold\"\n    },\n    warning: {\n      color: \"#f80\",\n      fontWeight: \"bold\"\n    },\n    info: {\n      color: \"black\"\n    }\n  },\n  Darken: {\n    position: \"fixed\",\n    top: 0,\n    left: 0,\n    opacity: 0.5,\n    backgroundColor: \"#000\",\n    width: \"100vw\",\n    height: \"100vh\",\n    zIndex: 150,\n    webkitBackdropFilter: \"blur(2px)\",\n    backdropFilter: \"blur(2px)\",\n  },\n  DialogOuter: {\n    position: \"fixed\",\n    top: 0,\n    left: 0,\n    width: \"100vw\",\n    height: \"100vh\",\n    zIndex: 150,\n    alignItems: \"center\",\n    display: \"flex\",\n    justifyContent: \"center\",\n    padding: \"16px\",\n    boxSizing: \"border-box\"\n  },\n  DialogInner: {\n    position: \"relative\",\n    color: \"#222\",\n    backgroundColor: \"#fff\",\n    padding: \"24px\",\n    marginBottom: \"2em\",\n    maxWidth: \"400px\",\n    width: \"100%\",\n    maxHeight: \"90%\",\n    overflowY: \"auto\",\n    border: \"3px solid #3d3d5d\",\n    borderRadius: \"8px\",\n    boxShadow: \"0 0 80px 10px #666\",\n    fontFamily: \"sans-serif\",\n    boxSizing: \"border-box\"\n  },\n  Input: {\n    height: \"35px\",\n    width: \"100%\",\n    maxWidth: \"100%\",\n    borderColor: \"#ccf4\",\n    outline: \"none\",\n    fontSize: \"16px\",\n    padding: \"8px\",\n    boxSizing: \"border-box\",\n    backgroundColor: \"#f9f9f9\",\n    borderRadius: \"4px\",\n    border: \"1px solid #ccc\",\n    marginTop: \"6px\",\n    fontFamily: \"inherit\"\n  },\n  Button: {\n    padding: \"10px 20px\",\n    margin: \"0 4px\",\n    border: \"1px solid #d1d5db\",\n    borderRadius: \"6px\",\n    backgroundColor: \"#ffffff\",\n    cursor: \"pointer\",\n    fontSize: \"14px\",\n    fontWeight: \"500\",\n    color: \"#374151\",\n    transition: \"all 0.2s ease\"\n  },\n  PrimaryButton: {\n    padding: \"10px 20px\",\n    margin: \"0 4px\",\n    border: \"1px solid #3b82f6\",\n    borderRadius: \"6px\",\n    backgroundColor: \"#3b82f6\",\n    color: \"white\",\n    cursor: \"pointer\",\n    fontSize: \"14px\",\n    fontWeight: \"500\",\n    transition: \"all 0.2s ease\"\n  },\n  ButtonsDiv: {\n    display: \"flex\",\n    justifyContent: \"flex-end\",\n    gap: \"12px\",\n    marginTop: \"24px\",\n    paddingTop: \"20px\"\n  },\n  Label: {\n    display: \"block\",\n    marginBottom: \"12px\",\n    fontSize: \"14px\",\n    fontWeight: \"500\",\n    color: \"#333\"\n  },\n  WindowHeader: {\n    margin: \"0 0 20px 0\",\n    fontSize: \"18px\",\n    fontWeight: \"600\",\n    color: \"#333\",\n    borderBottom: \"1px solid #eee\",\n    paddingBottom: \"10px\"\n  },\n  // OAuth Provider Button Styles\n  ProviderButton: {\n    display: \"flex\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    width: \"100%\",\n    padding: \"12px 16px\",\n    marginBottom: \"10px\",\n    border: \"1px solid #d1d5db\",\n    borderRadius: \"6px\",\n    backgroundColor: \"#ffffff\",\n    cursor: \"pointer\",\n    fontSize: \"14px\",\n    fontWeight: \"500\",\n    color: \"#374151\",\n    transition: \"all 0.2s ease\",\n    gap: \"12px\"\n  },\n  ProviderButtonIcon: {\n    width: \"20px\",\n    height: \"20px\",\n    flexShrink: 0,\n    display: \"flex\",\n    alignItems: \"center\",\n    justifyContent: \"center\"\n  },\n  ProviderButtonText: {\n    flex: 1,\n    textAlign: \"left\" as const\n  },\n  // Provider-specific colors\n  ProviderGoogle: {\n    backgroundColor: \"#ffffff\",\n    border: \"1px solid #dadce0\",\n    color: \"#3c4043\"\n  },\n  ProviderGitHub: {\n    backgroundColor: \"#ffffff\",\n    border: \"1px solid #dadce0\",\n    color: \"#181717\"\n  },\n  ProviderMicrosoft: {\n    backgroundColor: \"#ffffff\",\n    border: \"1px solid #dadce0\",\n    color: \"#5e5e5e\"\n  },\n  ProviderApple: {\n    backgroundColor: \"#000000\",\n    border: \"1px solid #000000\",\n    color: \"#ffffff\"\n  },\n  ProviderCustom: {\n    backgroundColor: \"#ffffff\",\n    border: \"1px solid #dadce0\",\n    color: \"#181717\"\n  },\n  // Divider styles\n  Divider: {\n    display: \"flex\",\n    alignItems: \"center\",\n    margin: \"20px 0\",\n    color: \"#6b7280\",\n    fontSize: \"13px\"\n  },\n  DividerLine: {\n    flex: 1,\n    height: \"1px\",\n    backgroundColor: \"#e5e7eb\"\n  },\n  DividerText: {\n    padding: \"0 12px\",\n    color: \"#9ca3af\"\n  },\n  // OTP Button (Continue with email)\n  OtpButton: {\n    display: \"flex\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    width: \"100%\",\n    padding: \"12px 16px\",\n    border: \"1px solid #d1d5db\",\n    borderRadius: \"6px\",\n    backgroundColor: \"#f9fafb\",\n    cursor: \"pointer\",\n    fontSize: \"14px\",\n    fontWeight: \"500\",\n    color: \"#374151\",\n    transition: \"all 0.2s ease\",\n    gap: \"12px\"\n  },\n  // Copy button for alerts with copyText\n  CopyButton: {\n    display: \"inline-flex\",\n    alignItems: \"center\",\n    gap: \"4px\",\n    padding: \"4px 10px\",\n    marginTop: \"8px\",\n    border: \"1px solid #d1d5db\",\n    borderRadius: \"4px\",\n    backgroundColor: \"#f9fafb\",\n    cursor: \"pointer\",\n    fontSize: \"12px\",\n    fontWeight: \"500\",\n    color: \"#374151\",\n    transition: \"all 0.15s ease\",\n    fontFamily: \"monospace\"\n  },\n  CopyButtonCopied: {\n    display: \"inline-flex\",\n    alignItems: \"center\",\n    gap: \"4px\",\n    padding: \"4px 10px\",\n    marginTop: \"8px\",\n    border: \"1px solid #22c55e\",\n    borderRadius: \"4px\",\n    backgroundColor: \"#f0fdf4\",\n    cursor: \"default\",\n    fontSize: \"12px\",\n    fontWeight: \"500\",\n    color: \"#16a34a\",\n    fontFamily: \"monospace\"\n  },\n  // Cancel button for provider selection\n  CancelButtonRow: {\n    display: \"flex\",\n    justifyContent: \"center\",\n    marginTop: \"16px\"\n  }\n};\n"
  },
  {
    "path": "addons/dexie-cloud/src/default-ui/index.tsx",
    "content": "import Dexie from \"dexie\";\nimport \"../extend-dexie-interface\";\nimport { h, Component } from \"preact\";\nimport { from, Subscription } from \"rxjs\";\nimport { LoginDialog } from './LoginDialog';\nimport { DXCUserInteraction } from \"../types/DXCUserInteraction\";\nimport * as preact from \"preact\";\n\nexport interface Props {\n  db: Dexie;\n}\n\ninterface State {\n  userInteraction: DXCUserInteraction | undefined;\n}\n\nexport default class LoginGui extends Component<Props, State> {\n  subscription?: Subscription;\n  observer = (userInteraction: DXCUserInteraction | undefined) => this.setState({userInteraction});\n\n  constructor(props: Props) {\n    super(props);\n    this.state = { userInteraction: undefined };\n  }\n\n  componentDidMount() {\n    this.subscription = from(this.props.db.cloud.userInteraction).subscribe(this.observer);\n  }\n\n  componentWillUnmount() {\n    if (this.subscription) {\n      this.subscription.unsubscribe();\n      delete this.subscription;\n    }\n  }\n\n  render(props: Props, {userInteraction}: State) {\n    if (!userInteraction) return null;\n    \n    // LoginDialog handles all interaction types uniformly\n    // (forms with fields, options, or both)\n    return <LoginDialog {...userInteraction as any} />;\n  }\n}\n\nexport function setupDefaultGUI(db: Dexie) {\n  let closed = false;\n\n  const el = document.createElement('div');\n  if (document.body) {\n    document.body.appendChild(el);\n    preact.render(<LoginGui db={db.vip} />, el);\n  } else {\n    addEventListener('DOMContentLoaded', ()=>{\n      if (!closed) {\n        document.body.appendChild(el);\n        preact.render(<LoginGui db={db.vip} />, el);\n      }\n    });\n  }\n\n  return {\n    unsubscribe() {\n      try { el.remove(); } catch {}\n      closed = true;\n    },\n    get closed() {\n      return closed;\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/define-ydoc-trigger.ts",
    "content": "import Dexie, {\n  RangeSet,\n  type DBCore,\n  type Middleware,\n  type Table,\n} from 'dexie';\nimport { DexieYProvider, YUpdateRow } from 'y-dexie';\nimport type { Doc as YjsDoc } from 'yjs';\n\nconst ydocTriggers: {\n  [ydocTable: string]: {\n    trigger: (ydoc: YjsDoc, parentId: any) => any;\n    parentTable: string;\n    prop: string;\n  };\n} = {};\n\nconst middlewares = new WeakMap<Dexie, Middleware<DBCore>>();\nlet subscribedToProviderBeforeUnload = false;\nconst txRunner = TriggerRunner(\"tx\"); // Trigger registry for transaction completion. Avoids open docs.\nconst unloadRunner = TriggerRunner(\"unload\"); // Trigger registry for unload. Runs when a document is closed.\n\ntype TriggerRegistry = Map<\n  string,\n  {\n    db: Dexie;\n    parentTable: string;\n    parentId: any;\n    prop: string;\n    triggers: Set<(ydoc: YjsDoc, parentId: any) => any>;\n  }\n>;\n\nfunction TriggerRunner(name: string) {\n  let triggerExecPromise: Promise<any> | null = null;\n  let triggerScheduled = false;\n  let registry: TriggerRegistry = new Map<\n    string,\n    {\n      db: Dexie;\n      parentTable: string;\n      parentId: any;\n      prop: string;\n      triggers: Set<(ydoc: YjsDoc, parentId: any) => any>;\n    }\n  >();\n\n  async function execute(registryCopy: TriggerRegistry) {\n    for (const {\n      db,\n      parentId,\n      triggers,\n      parentTable,\n      prop,\n    } of registryCopy.values()) {\n      const yDoc = DexieYProvider.getOrCreateDocument(\n        db,\n        parentTable,\n        prop,\n        parentId\n      );\n      try {\n        const provider = DexieYProvider.load(yDoc); // If doc is open, this would just be a ++refount\n        await provider.whenLoaded; // If doc is loaded, this would resolve immediately\n        for (const trigger of triggers) {\n          await trigger(yDoc, parentId);\n        }\n      } catch (error) {\n        if (error?.name === 'AbortError') {\n          // Ignore abort errors. They are expected when the document is closed.\n        } else {\n          console.error(`Error in YDocTrigger ${error}`);\n        }\n      } finally {\n        DexieYProvider.release(yDoc);\n      }\n    }\n  }\n\n  return {\n    name,\n    async run() {\n      console.log(`Running trigger (${name})?`, triggerScheduled, registry.size, !!triggerExecPromise);\n      if (!triggerScheduled && registry.size > 0) {\n        triggerScheduled = true;\n        if (triggerExecPromise) await triggerExecPromise.catch(() => {});\n        setTimeout(() => {\n          // setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes\n          console.log(\"Running trigger really!\", name);\n          triggerScheduled = false;\n          const registryCopy = registry;\n          registry = new Map();\n          triggerExecPromise = execute(registryCopy).finally(\n            () => {\n              triggerExecPromise = null;\n            }\n          );\n        }, 0);\n      }\n    },\n    enqueue(\n      db: Dexie,\n      parentTable: string,\n      parentId: any,\n      prop: string,\n      trigger: (ydoc: YjsDoc, parentId: any) => any\n    ) {\n      const key = `${db.name}:${parentTable}:${parentId}:${prop}`;\n      let entry = registry.get(key);\n      if (!entry) {\n        entry = {\n          db,\n          parentTable,\n          parentId,\n          prop,\n          triggers: new Set(),\n        };\n        console.log(`Adding trigger ${key}`);\n        registry.set(key, entry);\n      }\n      entry.triggers.add(trigger);\n    },\n  };\n}\n\nconst createMiddleware: (db: Dexie) => Middleware<DBCore> = (db) => ({\n  stack: 'dbcore',\n  level: 10,\n  name: 'yTriggerMiddleware',\n  create: (down) => {\n    return {\n      ...down,\n      transaction: (stores, mode, options) => {\n        const idbtrans = down.transaction(stores, mode, options);\n        if (mode === 'readonly') return idbtrans;\n        if (!stores.some((store) => ydocTriggers[store])) return idbtrans;\n        (idbtrans as IDBTransaction).addEventListener(\n          'complete',\n          onTransactionCommitted\n        );\n        return idbtrans;\n      },\n      table: (updatesTable) => {\n        const coreTable = down.table(updatesTable);\n        const triggerSpec = ydocTriggers[updatesTable];\n        if (!triggerSpec) return coreTable;\n        const { trigger, parentTable, prop } = triggerSpec;\n        return {\n          ...coreTable,\n          mutate(req) {\n            switch (req.type) {\n              case 'add': {\n                for (const yUpdateRow of req.values) {\n                  if (yUpdateRow.k == undefined) continue; // A syncer or garbage collection state does not point to a key\n                  const primaryKey = (yUpdateRow as YUpdateRow).k;\n                  const doc = DexieYProvider.getDocCache(db).find(\n                    parentTable,\n                    primaryKey,\n                    prop\n                  );\n                  const runner =\n                    doc && DexieYProvider.for(doc)?.refCount\n                      ? unloadRunner // Document is open. Wait with trigger until it's closed.\n                      : txRunner; // Document is closed. Run trigger immediately after transaction commits.\n                  runner.enqueue(db, parentTable, primaryKey, prop, trigger);\n                }\n                break;\n              }\n              case 'delete':\n                // @ts-ignore\n                if (req.trans._rejecting_y_ypdate) {\n                  // The deletion came from a rejection, not garbage collection.\n                  // When that happens, let the triggers run to compute new values\n                  // based on the deleted updates.\n                  coreTable\n                    .getMany({\n                      keys: req.keys,\n                      trans: req.trans,\n                      cache: 'immutable',\n                    })\n                    .then((updates) => {\n                      const keySet = new RangeSet();\n                      for (const { k } of updates as YUpdateRow[]) {\n                        if (k != undefined) keySet.addKey(k);\n                      }\n                      for (const interval of keySet) {\n                        txRunner.enqueue(\n                          db,\n                          parentTable,\n                          interval.from,\n                          prop,\n                          trigger\n                        );\n                      }\n                    });\n                }\n                break;\n            }\n            return coreTable.mutate(req);\n          },\n        };\n      },\n    };\n  },\n});\n\nfunction onTransactionCommitted() {\n  txRunner.run();\n}\n\nfunction beforeProviderUnload() {\n  unloadRunner.run();\n}\n\nexport function defineYDocTrigger<T, TKey>(\n  table: Table<T, TKey>,\n  prop: keyof T & string,\n  trigger: (ydoc: YjsDoc, parentId: TKey) => any\n) {\n  const updatesTable = table.schema.yProps?.find(\n    (p) => p.prop === prop\n  )?.updatesTable;\n  if (!updatesTable)\n    throw new Error(\n      `Table ${table.name} does not have a Yjs property named ${prop}`\n    );\n  ydocTriggers[updatesTable] = {\n    trigger,\n    parentTable: table.name,\n    prop,\n  };\n  const db = table.db._novip;\n  let mw = middlewares.get(db);\n  if (!mw) {\n    mw = createMiddleware(db);\n    middlewares.set(db, mw);\n  }\n  db.use(mw);\n\n  if (!subscribedToProviderBeforeUnload) {\n    DexieYProvider.on('beforeunload', beforeProviderUnload);\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/dexie-cloud-addon.ts",
    "content": "import dexieCloudAddon from './dexie-cloud-client';\nexport * from './dexie-cloud-client';\nexport default dexieCloudAddon;\n"
  },
  {
    "path": "addons/dexie-cloud/src/dexie-cloud-client.ts",
    "content": "import Dexie, { liveQuery, Subscription, Table } from 'dexie';\nimport {\n  DBPermissionSet,\n  DBRealmMember,\n  getDbNameFromDbUrl,\n} from 'dexie-cloud-common';\nimport { BehaviorSubject, combineLatest, firstValueFrom, from, fromEvent, Subject } from 'rxjs';\nimport { createDownloadingState, observeBlobProgress } from './sync/blobProgress';\nimport { downloadUnresolvedBlobs } from './sync/eagerBlobDownloader';\nimport { filter, map, skip, startWith, switchMap, take } from 'rxjs/operators';\nimport { login } from './authentication/login';\nimport { UNAUTHORIZED_USER } from './authentication/UNAUTHORIZED_USER';\nimport { DexieCloudDB } from './db/DexieCloudDB';\nimport { PersistedSyncState } from './db/entities/PersistedSyncState';\nimport { DexieCloudOptions } from './DexieCloudOptions';\nimport { DISABLE_SERVICEWORKER_STRATEGY } from './DISABLE_SERVICEWORKER_STRATEGY';\nimport './extend-dexie-interface';\nimport { DexieCloudSyncOptions } from './DexieCloudSyncOptions';\nimport { IS_SERVICE_WORKER } from './helpers/IS_SERVICE_WORKER';\nimport { throwVersionIncrementNeeded } from './helpers/throwVersionIncrementNeeded';\nimport { createIdGenerationMiddleware } from './middlewares/createIdGenerationMiddleware';\nimport { createImplicitPropSetterMiddleware } from './middlewares/createImplicitPropSetterMiddleware';\nimport { createMutationTrackingMiddleware } from './middlewares/createMutationTrackingMiddleware';\nimport { createBlobResolveMiddleware } from './middlewares/blobResolveMiddleware';\n//import { dexieCloudSyncProtocol } from \"./dexieCloudSyncProtocol\";\nimport { overrideParseStoresSpec } from './overrideParseStoresSpec';\nimport { performInitialSync } from './performInitialSync';\nimport { connectWebSocket } from './sync/connectWebSocket';\nimport { isSyncNeeded } from './sync/isSyncNeeded';\nimport { LocalSyncWorker } from './sync/LocalSyncWorker';\nimport {\n  registerPeriodicSyncEvent,\n  registerSyncEvent,\n} from './sync/registerSyncEvent';\nimport { triggerSync } from './sync/triggerSync';\nimport { DXCUserInteraction } from './types/DXCUserInteraction';\nimport { SyncState } from './types/SyncState';\nimport { updateSchemaFromOptions } from './updateSchemaFromOptions';\nimport { verifySchema } from './verifySchema';\nimport { setupDefaultGUI } from './default-ui';\nimport { DXCWebSocketStatus } from './DXCWebSocketStatus';\nimport { computeSyncState } from './computeSyncState';\nimport { generateKey } from './middleware-helpers/idGenerationHelpers';\nimport { permissions } from './permissions';\nimport { getCurrentUserEmitter } from './currentUserEmitter';\nimport { NewIdOptions } from './types/NewIdOptions';\nimport { getInvitesObservable } from './getInvitesObservable';\nimport { getGlobalRolesObservable } from './getGlobalRolesObservable';\nimport { UserLogin } from './db/entities/UserLogin';\nimport { InvalidLicenseError } from './InvalidLicenseError';\nimport { logout, _logout } from './authentication/logout';\nimport { loadAccessToken } from './authentication/authenticate';\nimport { isEagerSyncDisabled } from './isEagerSyncDisabled';\nimport { createYHandler } from \"./yjs/createYHandler\";\nimport { DexieYProvider } from 'y-dexie';\nimport { parseOAuthCallback, cleanupOAuthUrl } from './authentication/handleOAuthCallback';\nimport { OAuthError } from './errors/OAuthError';\nimport { alertUser } from './authentication/interactWithUser';\nimport { fetchAuthProviders } from './authentication/fetchAuthProviders';\nexport { DexieCloudTable } from './DexieCloudTable';\nexport * from './getTiedRealmId';\nexport {\n  DBRealm,\n  DBRealmMember,\n  DBRealmRole,\n  DBSyncedObject,\n  DBPermissionSet,\n  AuthProvidersResponse,\n  OAuthProviderInfo,\n} from 'dexie-cloud-common';\nexport { resolveText } from './helpers/resolveText';\nexport { Invite } from './Invite';\nexport type { UserLogin, DXCWebSocketStatus, SyncState };\nexport type { DexieCloudSyncOptions };\nexport type { DexieCloudOptions, PeriodicSyncOptions } from './DexieCloudOptions';\nexport * from './types/DXCAlert';\nexport * from './types/DXCInputField';\nexport * from './types/DXCUserInteraction';\nexport { defineYDocTrigger } from './define-ydoc-trigger';\n\nconst DEFAULT_OPTIONS: Partial<DexieCloudOptions> = {\n  nameSuffix: true,\n};\n\nexport function dexieCloud(dexie: Dexie) {\n  const origIdbName = dexie.name;\n  //\n  //\n  //\n  const currentUserEmitter = getCurrentUserEmitter(dexie);\n  const subscriptions: Subscription[] = [];\n  let configuredProgramatically = false;\n  \n  // Pending OAuth auth code from dxc-auth redirect (detected in configure())\n  let pendingOAuthCode: { code: string; provider: string } | null = null;\n  \n  // Pending OAuth error from dxc-auth redirect (detected in configure())\n  let pendingOAuthError: OAuthError | null = null;\n\n  // local sync worker - used when there's no service worker.\n  let localSyncWorker: { start: () => void; stop: () => void } | null = null;\n  dexie.on(\n    'ready',\n    async (dexie: Dexie) => {\n      try {\n        await onDbReady(dexie);\n      } catch (error) {\n        console.error(error);\n        // Make sure to succeed with database open even if network is down.\n      }\n    },\n    true // true = sticky\n  );\n\n  /** Void starting subscribers after a close has happened. */\n  let closed = false;\n  function throwIfClosed() {\n    if (closed) throw new Dexie.DatabaseClosedError();\n  }\n\n  dexie.once('close', () => {\n    subscriptions.forEach((subscription) => subscription.unsubscribe());\n    subscriptions.splice(0, subscriptions.length);\n    closed = true;\n    localSyncWorker && localSyncWorker.stop();\n    localSyncWorker = null;\n    currentUserEmitter.next(UNAUTHORIZED_USER);\n  });\n\n  const syncComplete = new Subject<void>();\n  const downloading$ = createDownloadingState();\n\n  dexie.cloud = {\n    // @ts-ignore\n    version: __VERSION__,\n    options: { ...DEFAULT_OPTIONS } as DexieCloudOptions,\n    schema: null,\n    get currentUserId() {\n      return currentUserEmitter.value.userId || UNAUTHORIZED_USER.userId!;\n    },\n    currentUser: currentUserEmitter,\n    syncState: new BehaviorSubject<SyncState>({\n      phase: 'initial',\n      status: 'not-started',\n    }),\n\n    events: {\n      syncComplete,\n    },\n\n    persistedSyncState: new BehaviorSubject<PersistedSyncState | undefined>(\n      undefined\n    ),\n    blobProgress: observeBlobProgress(DexieCloudDB(dexie), downloading$),\n    userInteraction: new BehaviorSubject<DXCUserInteraction | undefined>(\n      undefined\n    ),\n    webSocketStatus: new BehaviorSubject<DXCWebSocketStatus>('not-started'),\n    async login(hint) {\n      const db = DexieCloudDB(dexie);\n      await db.cloud.sync();\n      await login(db, hint);\n    },\n    invites: getInvitesObservable(dexie),\n    roles: getGlobalRolesObservable(dexie),\n    configure(options: DexieCloudOptions) {\n      // Validate maxStringLength — Infinity disables offloading, otherwise must be\n      // a finite number between 100 and the server limit (32768).\n      // Minimum 100 prevents accidental offloading of primary keys and short strings\n      // that would break sync.\n      const MIN_STRING_LENGTH = 100;\n      const MAX_SERVER_STRING_LENGTH = 32768;\n      if (\n        options.maxStringLength !== undefined &&\n        options.maxStringLength !== Infinity &&\n        (!Number.isFinite(options.maxStringLength) ||\n          options.maxStringLength < MIN_STRING_LENGTH ||\n          options.maxStringLength > MAX_SERVER_STRING_LENGTH)\n      ) {\n        throw new Error(\n          `maxStringLength must be Infinity or a finite number in [${MIN_STRING_LENGTH}, ${MAX_SERVER_STRING_LENGTH}]. Got: ${options.maxStringLength}`\n        );\n      }\n      options = dexie.cloud.options = { ...dexie.cloud.options, ...options };\n      configuredProgramatically = true;\n      if (options.databaseUrl && options.nameSuffix) {\n        // @ts-ignore\n        dexie.name = `${origIdbName}-${getDbNameFromDbUrl(\n          options.databaseUrl\n        )}`;\n        DexieCloudDB(dexie).reconfigure(); // Update observable from new dexie.name\n      }\n      updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);\n      \n      // Check for OAuth callback (dxc-auth query parameter)\n      // Only check in DOM environment, not workers\n      if (typeof window !== 'undefined' && window.location) {\n        try {\n          const callback = parseOAuthCallback();\n          if (callback) {\n            // Store the pending auth code for processing when db is ready\n            pendingOAuthCode = { code: callback.code, provider: callback.provider };\n            console.debug('[dexie-cloud] OAuth callback detected, auth code stored for processing');\n          }\n        } catch (error) {\n          // parseOAuthCallback throws OAuthError on error callbacks\n          if (error instanceof OAuthError) {\n            pendingOAuthError = error;\n            console.error('[dexie-cloud] OAuth callback error:', error.message);\n          } else {\n            console.error('[dexie-cloud] OAuth callback error:', error);\n          }\n        }\n      }\n    },\n    async logout({ force } = {}) {\n      force\n        ? await _logout(DexieCloudDB(dexie), { deleteUnsyncedData: true })\n        : await logout(DexieCloudDB(dexie));\n    },\n    async getAuthProviders() {\n      const options = dexie.cloud.options;\n      if (!options?.databaseUrl) {\n        throw new Error('Dexie Cloud not configured. Call db.cloud.configure() first.');\n      }\n      const socialAuthEnabled = options.socialAuth !== false;\n      return fetchAuthProviders(options.databaseUrl, socialAuthEnabled);\n    },\n    async sync(\n      { wait, purpose }: DexieCloudSyncOptions = { wait: true, purpose: 'push' }\n    ) {\n      if (wait === undefined) wait = true;\n      const db = DexieCloudDB(dexie);\n      const licenseStatus = db.cloud.currentUser.value.license?.status || 'ok';\n      if (licenseStatus !== 'ok') {\n        // Refresh access token to check for updated license\n        await loadAccessToken(db);\n      }\n      if (purpose === 'pull') {\n        const syncState = db.cloud.persistedSyncState.value;\n        triggerSync(db, purpose);\n        if (wait) {\n          const newSyncState = await firstValueFrom(\n            db.cloud.persistedSyncState.pipe(\n              filter(\n                (newSyncState) =>\n                  newSyncState?.timestamp != null &&\n                  (!syncState || newSyncState.timestamp > syncState.timestamp!)\n              )\n            )\n          );\n          if (newSyncState?.error) {\n            throw new Error(`Sync error: ` + newSyncState.error);\n          }\n        }\n      } else if (await isSyncNeeded(db)) {\n        const syncState = db.cloud.persistedSyncState.value;\n        triggerSync(db, purpose);\n        if (wait) {\n          console.debug('db.cloud.login() is waiting for sync completion...');\n          await firstValueFrom(\n            from(\n              liveQuery(async () => {\n                const syncNeeded = await isSyncNeeded(db);\n                const newSyncState = await db.getPersistedSyncState();\n                if (\n                  newSyncState?.timestamp !== syncState?.timestamp &&\n                  newSyncState?.error\n                )\n                  throw new Error(`Sync error: ` + newSyncState.error);\n                return syncNeeded;\n              })\n            ).pipe(filter((isNeeded) => !isNeeded))\n          );\n          console.debug(\n            'Done waiting for sync completion because we have nothing to push anymore'\n          );\n        }\n      }\n    },\n    permissions(\n      obj: { owner: string; realmId: string; table?: () => string },\n      tableName?: string\n    ) {\n      return permissions(dexie._novip, obj, tableName);\n    },\n  };\n\n  dexie.Version.prototype['_parseStoresSpec'] = Dexie.override(\n    dexie.Version.prototype['_parseStoresSpec'],\n    (origFunc) => overrideParseStoresSpec(origFunc, dexie)\n  );\n\n  dexie.Table.prototype.newId = function (\n    this: Table<any>,\n    { colocateWith }: NewIdOptions = {}\n  ) {\n    const shardKey =\n      colocateWith && colocateWith.substr(colocateWith.length - 3);\n    return generateKey(dexie.cloud.schema![this.name].idPrefix || '', shardKey);\n  };\n\n  dexie.Table.prototype.idPrefix = function (this: Table<any>) {\n    return this.db.cloud.schema?.[this.name]?.idPrefix || '';\n  };\n\n  dexie.use(createBlobResolveMiddleware(DexieCloudDB(dexie)));\n  dexie.use(\n    createMutationTrackingMiddleware({\n      currentUserObservable: dexie.cloud.currentUser,\n      db: DexieCloudDB(dexie),\n    })\n  );\n  dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));\n  dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));\n\n  async function onDbReady(dexie: Dexie) {\n    closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!\n    const db = DexieCloudDB(dexie);\n    // Setup default GUI:\n    if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n      if (!db.cloud.options?.customLoginGui) {\n        subscriptions.push(setupDefaultGUI(dexie));\n      }\n    }\n    if (!db.cloud.isServiceWorkerDB) {\n      subscriptions.push(computeSyncState(db).subscribe(dexie.cloud.syncState));\n    }\n\n    // Forward db.syncCompleteEvent to be publicly consumable via db.cloud.events.syncComplete:\n    subscriptions.push(db.syncCompleteEvent.subscribe(syncComplete));\n\n    // Eager blob download: When blobMode='eager' (default), download unresolved blobs after sync\n    const blobMode = db.cloud.options?.blobMode ?? 'eager';\n    if (blobMode === 'eager') {\n      let eagerBlobDownloadInFlight: Promise<void> | null = null;\n      const downloadBlobs = () => {\n        if (eagerBlobDownloadInFlight) return;\n        eagerBlobDownloadInFlight = Dexie.ignoreTransaction(\n          () => downloadUnresolvedBlobs(db, downloading$)\n        )\n          .catch(err => {\n            console.error('[dexie-cloud] Eager blob download failed:', err);\n          })\n          .finally(() => {\n            eagerBlobDownloadInFlight = null;\n          });\n      };\n      setTimeout(downloadBlobs, 0); // Don't block ready event. Start downloading blobs in the background right after.\n      // And also after every sync completes:\n      subscriptions.push(db.syncCompleteEvent.subscribe(downloadBlobs));\n    }\n\n\n    //verifyConfig(db.cloud.options); Not needed (yet at least!)\n    // Verify the user has allowed version increment.\n    if (!db.tables.every((table) => table.core)) {\n      throwVersionIncrementNeeded();\n    }\n    const swRegistrations =\n      'serviceWorker' in navigator\n        ? await navigator.serviceWorker.getRegistrations()\n        : [];\n\n    const [initiallySynced, lastSyncedRealms] = await db.transaction(\n      'rw',\n      db.$syncState,\n      async () => {\n        const { options, schema } = db.cloud;\n        const [persistedOptions, persistedSchema, persistedSyncState] =\n          await Promise.all([\n            db.getOptions(),\n            db.getSchema(),\n            db.getPersistedSyncState(),\n          ]);\n        if (!configuredProgramatically) {\n          // Options not specified programatically (use case for SW!)\n          // Take persisted options:\n          db.cloud.options = persistedOptions || null;\n        } else if (\n          !persistedOptions ||\n          JSON.stringify(persistedOptions) !== JSON.stringify(options)\n        ) {\n          // Update persisted options:\n          if (!options) throw new Error(`Internal error`); // options cannot be null if configuredProgramatically is set.\n          const newPersistedOptions: DexieCloudOptions = {\n            ...options,\n          };\n          delete newPersistedOptions.fetchTokens;\n          delete newPersistedOptions.awarenessProtocol;\n          await db.$syncState.put(newPersistedOptions, 'options');\n        }\n        if (\n          db.cloud.options?.tryUseServiceWorker &&\n          'serviceWorker' in navigator &&\n          swRegistrations.length > 0 &&\n          !DISABLE_SERVICEWORKER_STRATEGY\n        ) {\n          // * Configured for using service worker if available.\n          // * Browser supports service workers\n          // * There are at least one service worker registration\n          console.debug('Dexie Cloud Addon: Using service worker');\n          db.cloud.usingServiceWorker = true;\n        } else {\n          // Not configured for using service worker or no service worker\n          // registration exists. Don't rely on service worker to do any job.\n          // Use LocalSyncWorker instead.\n          if (\n            db.cloud.options?.tryUseServiceWorker &&\n            !db.cloud.isServiceWorkerDB\n          ) {\n            console.debug(\n              'dexie-cloud-addon: Not using service worker.',\n              swRegistrations.length === 0\n                ? 'No SW registrations found.'\n                : 'serviceWorker' in navigator && DISABLE_SERVICEWORKER_STRATEGY\n                ? 'Avoiding SW background sync and SW periodic bg sync for this browser due to browser bugs.'\n                : 'navigator.serviceWorker not present'\n            );\n          }\n          db.cloud.usingServiceWorker = false;\n        }\n        updateSchemaFromOptions(schema, db.cloud.options);\n        updateSchemaFromOptions(persistedSchema, db.cloud.options);\n        if (!schema) {\n          // Database opened dynamically (use case for SW!)\n          // Take persisted schema:\n          db.cloud.schema = persistedSchema || null;\n        } else if (\n          !persistedSchema ||\n          JSON.stringify(persistedSchema) !== JSON.stringify(schema)\n        ) {\n          // Update persisted schema (but don't overwrite table prefixes)\n          const newPersistedSchema = persistedSchema || {};\n          for (const [table, tblSchema] of Object.entries(schema)) {\n            const newTblSchema = newPersistedSchema[table];\n            if (!newTblSchema) {\n              newPersistedSchema[table] = { ...tblSchema };\n            } else {\n              newTblSchema.markedForSync = tblSchema.markedForSync;\n              tblSchema.deleted = newTblSchema.deleted;\n              newTblSchema.generatedGlobalId = tblSchema.generatedGlobalId;\n            }\n          }\n          await db.$syncState.put(newPersistedSchema, 'schema');\n\n          // Make sure persisted table prefixes are being used instead of computed ones:\n          // Let's assign all props as the newPersistedSchems should be what we should be working with.\n          Object.assign(schema, newPersistedSchema);\n        }\n        return [persistedSyncState?.initiallySynced, persistedSyncState?.realms];\n      }\n    );\n\n    if (initiallySynced) {\n      db.setInitiallySynced(true);\n    }\n\n    verifySchema(db);\n\n    // Manage CurrentUser observable:\n    throwIfClosed();\n    if (!db.cloud.isServiceWorkerDB) {\n      subscriptions.push(\n        liveQuery(() => db.getCurrentUser().then(user => {\n          if (!user.isLoggedIn && typeof location !== 'undefined' && /dxc-auth\\=/.test(location.search)) {\n            // Still loading user because OAuth redirect just happened.\n            // Keep isLoading true.\n            return { ...user, isLoading: true };\n          }\n          return user;\n        })).subscribe(currentUserEmitter)\n      );\n      // Manage PersistendSyncState observable:\n      subscriptions.push(\n        liveQuery(() => db.getPersistedSyncState()).subscribe(\n          db.cloud.persistedSyncState\n        )\n      );\n      // Wait till currentUser and persistedSyncState gets populated\n      // with things from the database and not just the default values.\n      // This is so that when db.open() completes, user should be safe\n      // to subscribe to these observables and get actual data.\n      await firstValueFrom(combineLatest([\n        currentUserEmitter.pipe(skip(1), take(1)),\n        db.cloud.persistedSyncState.pipe(skip(1), take(1)),\n      ]));\n\n      const yHandler = createYHandler(db);\n      DexieYProvider.on.new.subscribe(yHandler);\n      db.dx.once('close', () => {\n        DexieYProvider.on.new.unsubscribe(yHandler);\n      });\n    }\n\n    // HERE: If requireAuth, do athentication now.\n    let changedUser = false;\n    let user = await db.getCurrentUser();\n    \n    // Show pending OAuth error if present (from dxc-auth redirect)\n    if (pendingOAuthError && !db.cloud.isServiceWorkerDB) {\n      const error = pendingOAuthError;\n      pendingOAuthError = null; // Clear pending error\n      console.debug('[dexie-cloud] Showing OAuth error:', error.message);\n      // Show alert to user about the OAuth error\n      // Guard so UI errors don't abort initialization\n      try {\n        await alertUser(db.cloud.userInteraction, 'Authentication Error', {\n          type: 'error',\n          messageCode: 'GENERIC_ERROR',\n          message: error.message,\n          messageParams: { provider: error.provider || 'unknown' }\n        });\n        // Clean up URL (remove dxc-auth param)\n        cleanupOAuthUrl();\n      } catch (uiError) {\n        console.error('[dexie-cloud] Failed to show OAuth error alert:', uiError);\n      }\n    }\n    \n    // Process pending OAuth callback if present (from dxc-auth redirect)\n    if (pendingOAuthCode && !db.cloud.isServiceWorkerDB) {\n      const { code, provider } = pendingOAuthCode;\n      pendingOAuthCode = null; // Clear pending code\n      console.debug('[dexie-cloud] Processing OAuth callback, provider:', provider);\n      try {\n        changedUser = await login(db, { oauthCode: code, provider });\n        user = await db.getCurrentUser();\n        // Clean up URL (remove dxc-auth param)\n        cleanupOAuthUrl();\n      } catch (error) {\n        console.error('[dexie-cloud] OAuth login failed:', error);\n        // Continue with normal flow - user can try again\n      }\n    }\n    \n    const requireAuth = db.cloud.options?.requireAuth;\n    if (requireAuth) {\n      if (db.cloud.isServiceWorkerDB) {\n        // If this is a service worker DB, we can't do authentication here,\n        // we just wait until the application has done it.\n        console.debug('Dexie Cloud Service worker. Waiting for application to authenticate.');\n        await firstValueFrom(currentUserEmitter.pipe(filter((user) => !!user.isLoggedIn), take(1)));\n        console.debug('Dexie Cloud Service worker. Application has authenticated.');\n      } else {\n        if (typeof requireAuth === 'object') {\n          // requireAuth contains login hints. Check if we already fulfil it:\n          if (\n            !user.isLoggedIn ||\n            (requireAuth.userId && user.userId !== requireAuth.userId) ||\n            (requireAuth.email && user.email !== requireAuth.email)\n          ) {\n            // If not, login the configured user:\n            changedUser = await login(db, requireAuth);\n          }\n        } else if (!user.isLoggedIn) {\n          // requireAuth is true and user is not logged in\n          changedUser = await login(db);\n        }\n      }\n    }\n    if (user.isLoggedIn && (!lastSyncedRealms || !lastSyncedRealms.includes(user.userId!))) {\n      // User has been logged in but this is not reflected in the sync state.\n      // This can happen if page is reloaded after login but before the sync call following\n      // the login was complete.\n      // The user is to be viewed as changed becuase current syncState does not reflect the presence\n      // of the logged-in user.\n      changedUser = true; // Set changedUser to true to trigger a pull-sync later down.\n    }\n\n    if (localSyncWorker) localSyncWorker.stop();\n    localSyncWorker = null;\n    throwIfClosed();\n\n    const doInitialSync = db.cloud.options?.databaseUrl && (!initiallySynced || changedUser);\n    if (doInitialSync) {\n      // Do the initial sync directly in the browser thread no matter if we are using service worker or not.\n      await performInitialSync(db, db.cloud.options!, db.cloud.schema!);\n      db.setInitiallySynced(true);\n    }\n\n    throwIfClosed();\n    if (db.cloud.usingServiceWorker && db.cloud.options?.databaseUrl) {\n      if (!doInitialSync) {\n        registerSyncEvent(db, 'push').catch(() => {});\n      }\n      registerPeriodicSyncEvent(db).catch(() => {});\n    } else if (\n      db.cloud.options?.databaseUrl &&\n      db.cloud.schema &&\n      !db.cloud.isServiceWorkerDB\n    ) {\n      // There's no SW. Start SyncWorker instead.\n      localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema!);\n      localSyncWorker.start();\n      if (!doInitialSync) {\n        triggerSync(db, 'push');\n      }\n    }\n\n    // Listen to online event and do sync.\n    throwIfClosed();\n    if (!db.cloud.isServiceWorkerDB) {\n      subscriptions.push(\n        fromEvent(self, 'online').subscribe(() => {\n          console.debug('online!');\n          db.syncStateChangedEvent.next({\n            phase: 'not-in-sync',\n          });\n          if (!isEagerSyncDisabled(db)) {\n            triggerSync(db, 'push');\n          }\n        }),\n        fromEvent(self, 'offline').subscribe(() => {\n          console.debug('offline!');\n          db.syncStateChangedEvent.next({\n            phase: 'offline',\n          });\n        })\n      );\n    }\n\n    // Connect WebSocket unless we are in a service worker or websocket is disabled.\n    if (\n      db.cloud.options?.databaseUrl &&\n      !db.cloud.options?.disableWebSocket &&\n      !IS_SERVICE_WORKER\n    ) {\n      subscriptions.push(connectWebSocket(db));\n    }\n  }\n}\n\n// @ts-ignore\ndexieCloud.version = __VERSION__;\n\nDexie.Cloud = dexieCloud;\n\nexport default dexieCloud;\n"
  },
  {
    "path": "addons/dexie-cloud/src/errors/HttpError.ts",
    "content": "export class HttpError extends Error {\n  httpStatus: number;\n  constructor(\n    res: Response,\n    message?: string)\n  {\n    super(message || `${res.status} ${res.statusText}`);\n    this.httpStatus = res.status;\n  }\n\n  get name() {\n    return \"HttpError\";\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/errors/OAuthError.ts",
    "content": "/** OAuth-specific error codes */\nexport type OAuthErrorCode =\n  | 'access_denied'\n  | 'invalid_state'\n  | 'email_not_verified'\n  | 'expired_code'\n  | 'provider_error'\n  | 'network_error';\n\n/** User-friendly messages for OAuth error codes */\nconst ERROR_MESSAGES: Record<OAuthErrorCode, string> = {\n  access_denied: 'Access was denied by the authentication provider.',\n  invalid_state: 'The authentication response could not be verified. Please try again.',\n  email_not_verified: 'Your email address must be verified before you can log in.',\n  expired_code: 'The authentication code has expired. Please try again.',\n  provider_error: 'An error occurred with the authentication provider.',\n  network_error: 'A network error occurred during authentication. Please check your connection and try again.',\n};\n\n/** Error class for OAuth-specific errors */\nexport class OAuthError extends Error {\n  readonly code: OAuthErrorCode;\n  readonly provider?: string;\n\n  constructor(code: OAuthErrorCode, provider?: string, customMessage?: string) {\n    super(customMessage || ERROR_MESSAGES[code]);\n    this.name = 'OAuthError';\n    this.code = code;\n    this.provider = provider;\n  }\n\n  /** Get user-friendly message for this error */\n  get userMessage(): string {\n    return ERROR_MESSAGES[this.code] || this.message;\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/errors/OAuthRedirectError.ts",
    "content": "/**\n * Error thrown when initiating an OAuth redirect.\n * \n * This is not a real error - it's used to signal that the page is\n * navigating away to an OAuth provider. It should be caught and\n * silently ignored at the appropriate level.\n */\nexport class OAuthRedirectError extends Error {\n  readonly provider: string;\n  \n  constructor(provider: string) {\n    super(`OAuth redirect initiated for provider: ${provider}`);\n    this.name = 'OAuthRedirectError';\n    this.provider = provider;\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/extend-dexie-interface.ts",
    "content": "import { IndexableType, TableProp } from 'dexie';\nimport {\n  DBRealm,\n  DBRealmMember,\n  DBRealmRole,\n} from 'dexie-cloud-common';\nimport { Member } from './db/entities/Member';\nimport { Role } from './db/entities/Role';\nimport { EntityCommon } from './db/entities/EntityCommon';\nimport { DexieCloudAPI } from './DexieCloudAPI';\nimport { DexieCloudTable } from './DexieCloudTable';\nimport { NewIdOptions } from './types/NewIdOptions';\n\ntype Optional<T, Props extends keyof T> = Omit<T, Props> & Partial<T>;\n\n//\n// Extend Dexie interface\n//\ndeclare module 'dexie' {\n  interface Dexie {\n    cloud: DexieCloudAPI;\n    realms: Table<DBRealm, string, Optional<DBRealm, 'realmId' | 'owner'>>;\n    members: Table<DBRealmMember, string, Optional<DBRealmMember, 'id' | 'owner'>>;\n    roles: Table<DBRealmRole, [string, string], Optional<DBRealmRole, 'owner'>>;\n  }\n\n  interface Table {\n    newId(options: NewIdOptions): string;\n    idPrefix(): string;\n  }\n\n  interface DexieConstructor {\n    Cloud: {\n      (db: Dexie): void;\n\n      version: string;\n    };\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/getGlobalRolesObservable.ts",
    "content": "import Dexie, { liveQuery } from 'dexie';\nimport { DBRealmRole } from 'dexie-cloud-common';\nimport { associate } from './associate';\nimport { createSharedValueObservable } from './createSharedValueObservable';\n\nexport const getGlobalRolesObservable = associate((db: Dexie) => {\n  return createSharedValueObservable(\n    liveQuery(() =>\n      db.roles\n        .where({ realmId: 'rlm-public' })\n        .toArray()\n        .then((roles) => {\n          const rv: { [roleName: string]: DBRealmRole } = {};\n          for (const role of roles\n            .slice()\n            .sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))) {\n            rv[role.name] = role;\n          }\n          return rv;\n        })\n    ),\n    {}\n  );\n});\n"
  },
  {
    "path": "addons/dexie-cloud/src/getInternalAccessControlObservable.ts",
    "content": "import Dexie, { liveQuery } from 'dexie';\nimport { DBRealm, DBRealmMember } from 'dexie-cloud-common';\nimport { concat, Observable, timer } from 'rxjs';\nimport { share, switchMap } from 'rxjs/operators';\nimport { associate } from './associate';\nimport { createSharedValueObservable } from './createSharedValueObservable';\nimport { getCurrentUserEmitter } from './currentUserEmitter';\n\nexport type InternalAccessControlData = {\n  readonly selfMembers: DBRealmMember[];\n  readonly realms: DBRealm[];\n  readonly userId: string;\n};\n\nexport const getInternalAccessControlObservable = associate((db: Dexie) => {\n  return createSharedValueObservable(\n    getCurrentUserEmitter(db._novip).pipe(\n      switchMap((currentUser) =>\n        liveQuery(() =>\n          db.transaction('r', 'realms', 'members', () =>\n            Promise.all([\n              db.members.where({ userId: currentUser.userId }).toArray(),\n              db.realms.toArray(),\n              currentUser.userId!,\n            ] as const).then(([selfMembers, realms, userId]) => {\n              //console.debug(`PERMS: Result from liveQUery():`, JSON.stringify({selfMembers, realms, userId}, null, 2))\n              return { selfMembers, realms, userId };\n            })\n          )\n        )\n      )\n    ), {\n      selfMembers: [],\n      realms: [],\n      get userId() {\n        return db.cloud.currentUserId;\n      },\n    }\n  );\n  /* let refCount = 0;\n  return new Observable(observer => {\n    const subscription = o.subscribe(observer);\n    console.debug ('PERMS subscribe', ++refCount);\n    return {\n      unsubscribe() {\n        console.debug ('PERMS unsubscribe', --refCount);\n        subscription.unsubscribe();\n      }\n    }\n  })*/\n});\n"
  },
  {
    "path": "addons/dexie-cloud/src/getInvitesObservable.ts",
    "content": "import { Dexie, liveQuery } from 'dexie';\nimport { DBRealmMember } from 'dexie-cloud-common';\nimport { combineLatest } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { associate } from './associate';\nimport { createSharedValueObservable } from './createSharedValueObservable';\nimport { getCurrentUserEmitter } from './currentUserEmitter';\nimport { getInternalAccessControlObservable } from './getInternalAccessControlObservable';\nimport { getPermissionsLookupObservable } from './getPermissionsLookupObservable';\nimport { Invite } from './Invite';\nimport { mapValueObservable } from './mapValueObservable';\n\nexport const getInvitesObservable = associate((db: Dexie) => {\n  const membersByEmail = getCurrentUserEmitter(db._novip).pipe(\n    switchMap((currentUser) =>\n      liveQuery(() =>\n        db.members.where({ email: currentUser.email || '' }).toArray()\n      )\n    )\n  );\n  const permissions = getPermissionsLookupObservable(db._novip);\n  const accessControl = getInternalAccessControlObservable(db._novip);\n  return createSharedValueObservable(\n    combineLatest([membersByEmail, accessControl, permissions]).pipe(\n      map(([membersByEmail, accessControl, realmLookup]) => {\n        const reducer = (\n          result: { [id: string]: Invite },\n          m: DBRealmMember\n        ) => ({ ...result, [m.id!]: { ...m, realm: realmLookup[m.realmId] } });\n        const emailMembersById = membersByEmail.reduce(reducer, {});\n        const membersById = accessControl.selfMembers.reduce(\n          reducer,\n          emailMembersById\n        );\n        return Object.values(membersById)\n          .filter((invite: DBRealmMember) => !invite.accepted)\n          .map(\n            (invite: DBRealmMember) =>\n              ({\n                ...invite,\n                async accept() {\n                  await db.members.update(invite.id!, { accepted: new Date() });\n                },\n                async reject() {\n                  await db.members.update(invite.id!, { rejected: new Date() });\n                },\n              } satisfies Invite)\n          );\n      })\n    ),\n    []\n  );\n});\n"
  },
  {
    "path": "addons/dexie-cloud/src/getPermissionsLookupObservable.ts",
    "content": "import Dexie from 'dexie';\nimport { DBPermissionSet, DBRealm, DBRealmMember } from 'dexie-cloud-common';\nimport { combineLatest, Observable } from 'rxjs';\nimport { map, startWith, tap } from 'rxjs/operators';\nimport { associate } from './associate';\nimport { UNAUTHORIZED_USER } from './authentication/UNAUTHORIZED_USER';\nimport { createSharedValueObservable } from './createSharedValueObservable';\nimport { getGlobalRolesObservable } from './getGlobalRolesObservable';\nimport { getInternalAccessControlObservable } from './getInternalAccessControlObservable';\nimport { flatten } from './helpers/flatten';\nimport { mapValueObservable } from './mapValueObservable';\nimport { mergePermissions } from './mergePermissions';\n\nexport type PermissionsLookup = {\n  [realmId: string]: DBRealm & { permissions: DBPermissionSet };\n};\n\nexport type PermissionsLookupObservable = Observable<PermissionsLookup> & {\n  getValue(): PermissionsLookup;\n};\n\nexport const getPermissionsLookupObservable = associate((db: Dexie) => {\n  const o = createSharedValueObservable(\n    combineLatest([\n      getInternalAccessControlObservable(db._novip),\n      getGlobalRolesObservable(db._novip),\n    ]).pipe(\n      map(([{ selfMembers, realms, userId }, globalRoles]) => ({\n        selfMembers,\n        realms,\n        userId,\n        globalRoles,\n      }))\n    ),\n    {\n      selfMembers: [],\n      realms: [],\n      userId: UNAUTHORIZED_USER.userId!,\n      globalRoles: {},\n    }\n  );\n\n  return mapValueObservable(\n    o,\n    ({ selfMembers, realms, userId, globalRoles }) => {\n      const rv = realms\n        .map((realm) => {\n          const selfRealmMembers = selfMembers.filter(\n            (m) => m.realmId === realm.realmId\n          );\n          const directPermissionSets = selfRealmMembers\n            .map((m) => m.permissions!)\n            .filter((p) => p);\n          const rolePermissionSets = flatten(\n            selfRealmMembers.map((m) => m.roles!).filter((roleName) => roleName)\n          )\n            .map((role) => globalRoles[role]!)\n            .filter((role) => role)\n            .map((role) => role.permissions);\n\n          return {\n            ...realm,\n            permissions:\n              realm.owner === userId\n                ? ({ manage: '*' } as DBPermissionSet)\n                : mergePermissions(\n                    ...directPermissionSets,\n                    ...rolePermissionSets\n                  ),\n          };\n        })\n        .reduce((p, c) => ({ ...p, [c.realmId]: c }), {\n          [userId!]: {\n            realmId: userId,\n            owner: userId,\n            name: userId,\n            permissions: { manage: '*' },\n          } as DBRealm & { permissions: DBPermissionSet },\n        });\n      return rv;\n    }\n  );\n});\n"
  },
  {
    "path": "addons/dexie-cloud/src/getTiedRealmId.ts",
    "content": "\nexport function getTiedRealmId(objectId: string) {\n  return 'rlm~' + objectId;\n}\n\nexport function getTiedObjectId(realmId: string) {\n  return realmId.startsWith('rlm~') ? realmId.substr(4) : null;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/BroadcastedAndLocalEvent.ts",
    "content": "import { Observable } from \"rxjs\";\nimport { SWBroadcastChannel } from \"./SWBroadcastChannel\";\n\nconst events: Map<string, Array<(ev: CustomEvent)=>void>> =\n  globalThis['lbc-events'] || (globalThis['lbc-events'] = new Map<string, Array<(ev: CustomEvent)=>void>>());\n\nfunction addListener(name: string, listener: (ev: CustomEvent)=>void) {\n  if (events.has(name)) {\n    events.get(name)!.push(listener);\n  } else {\n    events.set(name, [listener]);\n  }\n}\nfunction removeListener(name: string, listener: (ev: CustomEvent)=>void) {\n  const listeners = events.get(name);\n  if (listeners) {\n    const idx = listeners.indexOf(listener);\n    if (idx !== -1) {\n      listeners.splice(idx, 1);\n    }\n  }\n}\nfunction dispatch(ev: CustomEvent) {\n  const listeners = events.get(ev.type);\n  if (listeners) {\n    listeners.forEach(listener => {\n      try {\n        listener(ev);\n      } catch {\n      }\n    });\n  }\n}\n\nexport class BroadcastedAndLocalEvent<T> extends Observable<T>{\n  name: string;\n  bc: BroadcastChannel | SWBroadcastChannel\n\n  constructor(name: string) {\n    const bc = typeof BroadcastChannel === \"undefined\"\n      ? new SWBroadcastChannel(name) : new BroadcastChannel(name);\n    super(subscriber => {\n      function onCustomEvent(ev: CustomEvent) {\n        subscriber.next(ev.detail);\n      }\n      function onMessageEvent(ev: MessageEvent) {\n        console.debug(\"BroadcastedAndLocalEvent: onMessageEvent\", ev);\n        subscriber.next(ev.data);\n      }\n      let unsubscribe: ()=>void;\n      //self.addEventListener(`lbc-${name}`, onCustomEvent); // Fails in service workers\n      addListener(`lbc-${name}`, onCustomEvent); // Works better in service worker\n\n      try {  \n        if (bc instanceof SWBroadcastChannel) {\n          unsubscribe = bc.subscribe(message => subscriber.next(message));\n        } else {\n          console.debug(\"BroadcastedAndLocalEvent: bc.addEventListener()\", name, \"bc is a\", bc);\n          bc.addEventListener(\"message\", onMessageEvent);\n        }\n      } catch (err) {\n        // Service workers might fail to subscribe outside its initial script.\n        console.warn('Failed to subscribe to broadcast channel', err);\n      }\n      return () => {\n        //self.removeEventListener(`lbc-${name}`, onCustomEvent);\n        removeListener(`lbc-${name}`, onCustomEvent);\n        if (bc instanceof SWBroadcastChannel) {\n          unsubscribe!();\n        } else {\n          bc.removeEventListener(\"message\", onMessageEvent);\n        }\n      }\n    });\n    this.name = name;\n    this.bc = bc;\n  }\n\n  next(message: T) {\n    console.debug(\"BroadcastedAndLocalEvent: bc.postMessage()\", {...message}, \"bc is a\", this.bc);\n    this.bc.postMessage(message);\n    const ev = new CustomEvent(`lbc-${this.name}`, { detail: message });\n    //self.dispatchEvent(ev);\n    dispatch(ev);\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/CancelToken.ts",
    "content": "import Dexie from \"dexie\";\n\nexport interface CancelToken {\n  cancelled: boolean;\n}\n\nexport function throwIfCancelled(cancelToken?: CancelToken) {\n  if (cancelToken?.cancelled) throw new Dexie.AbortError(`Operation was cancelled`);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/IS_SERVICE_WORKER.ts",
    "content": "export const IS_SERVICE_WORKER =\n  typeof self !== \"undefined\" && \"clients\" in self && !self.document;\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/SWBroadcastChannel.ts",
    "content": "const swHolder: { registration?: ServiceWorkerRegistration } = {};\nconst swContainer = typeof self !== 'undefined' && self.document && // self.document is to verify we're not the SW ourself\n                    typeof navigator !== 'undefined' && navigator.serviceWorker; \nif (swContainer)\n  swContainer.ready.then(\n    (registration) => (swHolder.registration = registration)\n  );\n\nif (typeof self !== 'undefined' && 'clients' in self && !self.document) {\n  // We are the service worker. Propagate messages to all our clients.\n  addEventListener('message', (ev: any) => {\n    if (ev.data?.type?.startsWith('sw-broadcast-')) {\n      [...self['clients'].matchAll({ includeUncontrolled: true })].forEach(\n        (client) => client.id !== ev.source?.id && client.postMessage(ev.data)\n      );\n    }\n  });\n}\n\n/** This class is a fallback for browsers that lacks BroadcastChannel but have\n * service workers (which is Safari versions 11.1 through 15.3).\n * Safari 15.4 with BroadcastChannel was released on 2022-03-14.\n * We might be able to remove this class in a near future as Safari < 15.4 is\n * already very low in market share as of 2023-03-10.\n */\nexport class SWBroadcastChannel {\n  name: string;\n  constructor(name: string) {\n    this.name = name;\n  }\n  subscribe(listener: (message: any) => void) {\n    if (!swContainer) return () => {};\n    const forwarder = (ev: MessageEvent) => {\n      if (ev.data?.type === `sw-broadcast-${this.name}`) {\n        listener(ev.data.message);\n      }\n    };\n    swContainer.addEventListener('message', forwarder);\n    return () => swContainer.removeEventListener('message', forwarder);\n  }\n  postMessage(message: any) {\n    if (typeof self['clients'] === 'object') {\n      // We're a service worker. Propagate to our browser clients.\n      [...self['clients'].matchAll({ includeUncontrolled: true })].forEach(\n        (client) =>\n          client.postMessage({\n            type: `sw-broadcast-${this.name}`,\n            message,\n          })\n      );\n    } else if (swHolder.registration) {\n      // We're a client (browser window or other worker)\n      // Post to SW so it can repost to all its clients and to itself\n      swHolder.registration.active?.postMessage({\n        type: `sw-broadcast-${this.name}`,\n        message,\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/allSettled.ts",
    "content": "\nexport function allSettled(possiblePromises: any[]) {\n  return new Promise(resolve => {\n      if (possiblePromises.length === 0) resolve([]);\n      let remaining = possiblePromises.length;\n      const results = new Array(remaining);\n      possiblePromises.forEach((p, i) => Promise.resolve(p).then(\n          value => results[i] = {status: \"fulfilled\", value},\n          reason => results[i] = {status: \"rejected\", reason})\n          .then(()=>--remaining || resolve(results)));\n  });\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/bulkUpdate.ts",
    "content": "import Dexie, { Table, cmp } from 'dexie';\n\nexport async function bulkUpdate(\n  table: Table,\n  keys: any[],\n  changeSpecs: { [keyPath: string]: any }[]\n) {\n  const objs = await table.bulkGet(keys);\n  const resultKeys: any[] = [];\n  const resultObjs: any[] = [];\n  keys.forEach((key, idx) => {\n    const obj = objs[idx];\n    if (obj) {\n      for (const [keyPath, value] of Object.entries(changeSpecs[idx])) {\n        if (keyPath === table.schema.primKey.keyPath) {\n          if (cmp(value, key) !== 0) {\n            throw new Error(`Cannot change primary key`);\n          }\n        } else {\n          Dexie.setByKeyPath(obj, keyPath, value);\n        }\n      }\n      resultKeys.push(key);\n      resultObjs.push(obj);\n    }\n  });\n  await (table.schema.primKey.keyPath == null\n    ? table.bulkPut(resultObjs, resultKeys)\n    : table.bulkPut(resultObjs));\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/computeRealmSetHash.ts",
    "content": "import { PersistedSyncState } from '../db/entities/PersistedSyncState';\nimport { b64encode } from 'dexie-cloud-common';\n\nexport async function computeRealmSetHash({\n  realms,\n  inviteRealms,\n}: PersistedSyncState) {\n  const data = JSON.stringify(\n    [\n      ...realms.map((realmId) => ({ realmId, accepted: true })),\n      ...inviteRealms.map((realmId) => ({ realmId, accepted: false })),\n    ].sort((a, b) =>\n      a.realmId < b.realmId ? -1 : a.realmId > b.realmId ? 1 : 0\n    )\n  );\n  const byteArray = new TextEncoder().encode(data);\n  const digestBytes = await crypto.subtle.digest('SHA-1', byteArray);\n  const base64 = b64encode(digestBytes);\n  return base64;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/date-constants.ts",
    "content": "export const SECONDS = 1000;\nexport const MINUTES = 60 * SECONDS;\nexport const HOURS = 60 * MINUTES;\nexport const DAYS = 24 * HOURS;\nexport const WEEKS = 7 * DAYS;\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/flatten.ts",
    "content": "const concat = [].concat;\nexport function flatten<T>(a: (T | T[])[]): T[] {\n  return concat.apply([], a);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/getMutationTable.ts",
    "content": "\n\nexport function getMutationTable(tableName: string) {\n  return `$${tableName}_mutations`;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/getSyncableTables.ts",
    "content": "import { IndexableType, Table } from \"dexie\";\nimport { DexieCloudDB } from \"../db/DexieCloudDB\";\nimport { EntityCommon } from \"../db/entities/EntityCommon\";\n\nexport function getSyncableTables(db: DexieCloudDB): Table<EntityCommon>[] {\n  return Object.entries(db.cloud.schema || {})\n    .filter(([, { markedForSync }]) => markedForSync)\n    .map(([tbl]) => db.tables.find(({name}) => name === tbl))\n    .filter((syncableTable): syncableTable is Table<EntityCommon> => !!syncableTable);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/getTableFromMutationTable.ts",
    "content": "\n\nexport function getTableFromMutationTable(mutationTable: string) {\n  const tableName = /^\\$(.*)_mutations$/.exec(mutationTable)?.[1];\n  if (!tableName) throw new Error(`Given mutationTable ${mutationTable} is not correct`);\n  return tableName;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/makeArray.ts",
    "content": "\nexport function makeArray<T>(iterable: Iterable<T>): T[] {\n  return [].slice.call(iterable);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/randomString.ts",
    "content": "export function randomString(bytes: number) {\n  const buf = new Uint8Array(bytes);\n  if (typeof crypto !== 'undefined') {\n    crypto.getRandomValues(buf);\n  } else {\n    for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);\n  }\n  if (typeof Buffer !== 'undefined' && Buffer.from) {\n    return Buffer.from(buf).toString('base64');\n  } else if (typeof btoa !== 'undefined') {\n    return btoa(String.fromCharCode.apply(null, buf));\n  } else {\n    throw new Error('No btoa or Buffer available');\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/resolveText.ts",
    "content": "import { DXCAlert } from \"../types/DXCAlert\";\n\n/** Resolve a message template with parameters.\n * \n * Example:\n *  resolveText({\n *    message: \"Hello {name}!\",\n *    messageCode: \"HELLO\",\n *    messageParams: {name: \"David\"}\n *  }) => \"Hello David!\"\n *  \n * @param message Template message with {vars} in it.\n * @param messageCode Unique code for the message. Can be used for translation.\n * @param messageParams Parameters to be used in the message.\n * @returns A final message where parameters have been replaced with values.\n */\nexport function resolveText({message, messageCode, messageParams}: DXCAlert) {\n  return message.replace(/\\{\\w+\\}/ig, n => messageParams[n.substring(1, n.length-1)]);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/throwVersionIncrementNeeded.ts",
    "content": "import Dexie from \"dexie\";\n\nexport function throwVersionIncrementNeeded() {\n  throw new Dexie.SchemaError(\n    `Version increment needed to allow dexie-cloud change tracking`\n  );\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/helpers/visibilityState.ts",
    "content": "import { BehaviorSubject, from, fromEvent } from 'rxjs';\nimport { map, startWith } from 'rxjs/operators';\n\nexport function createVisibilityStateObservable() {\n  return fromEvent(document, 'visibilitychange').pipe(\n    map(() => document.visibilityState),\n    startWith(document.visibilityState)\n  );\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/isEagerSyncDisabled.ts",
    "content": "import { DexieCloudDB } from './db/DexieCloudDB';\n\nexport function isEagerSyncDisabled(db: DexieCloudDB) {\n  return (\n    db.cloud.options?.disableEagerSync ||\n    db.cloud.currentUser.value?.license?.status !== 'ok' ||\n    !db.cloud.options?.databaseUrl\n  );\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/isFirefox.ts",
    "content": "// @ts-ignore\nexport const isFirefox = typeof InstallTrigger !== 'undefined';\n"
  },
  {
    "path": "addons/dexie-cloud/src/isSafari.ts",
    "content": "export const isSafari =\n  typeof navigator !== 'undefined' &&\n  /Safari\\//.test(navigator.userAgent) &&\n  !/Chrom(e|ium)\\/|Edge\\//.test(navigator.userAgent);\n\nexport const safariVersion = isSafari\n  ? // @ts-ignore\n    [].concat(navigator.userAgent.match(/Safari\\/(\\d*)/))[1]\n  : NaN;\n"
  },
  {
    "path": "addons/dexie-cloud/src/mapValueObservable.ts",
    "content": "import { map, Observable } from 'rxjs';\n\nexport interface ObservableWithCurrentValue<T> extends Observable<T> {\n  getValue(): T;\n}\n\nexport function mapValueObservable<T, R>(\n  o: ObservableWithCurrentValue<T>,\n  mapper: (x: T) => R\n): ObservableWithCurrentValue<R> {\n  let currentValue: R | undefined;\n  const rv = o.pipe(\n    map((x) => (currentValue = mapper(x)))\n  ) as ObservableWithCurrentValue<R>;\n  rv.getValue = () =>\n    currentValue !== undefined\n      ? currentValue\n      : (currentValue = mapper(o.getValue()));\n  return rv;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/mergePermissions.ts",
    "content": "// TODO: Move to dexie-cloud-common\n\nimport { DBPermissionSet } from 'dexie-cloud-common';\n\nexport function mergePermissions(\n  ...permissions: DBPermissionSet[]\n): DBPermissionSet {\n  if (permissions.length === 0) return {};\n  const reduced = permissions.reduce((result, next) => {\n    const ret = { ...result } as DBPermissionSet;\n    for (const [verb, rights] of Object.entries(next) as [\n      keyof DBPermissionSet,\n      DBPermissionSet[keyof DBPermissionSet]\n    ][]) {\n      if (verb in ret && ret[verb]) {\n        if (ret[verb] === '*') continue;\n        if (rights === '*') {\n          ret[verb] = '*';\n        } else if (Array.isArray(rights) && Array.isArray(ret[verb])) {\n          // Both are arrays (verb is 'add' or 'manage')\n          const r = ret as { [v in typeof verb]?: string[] };\n          const retVerb = r[verb]!; // \"!\" because Array.isArray(ret[verb])\n          r[verb] = [...new Set([...retVerb, ...rights])];\n        } else if (\n          typeof rights === 'object' &&\n          rights &&\n          typeof ret[verb] === 'object'\n        ) {\n          // Both are objects (verb is 'update')\n          const mergedRights = ret[verb] as {\n            [tableName: string]: '*' | string[];\n          }; // because we've checked that typeof ret[verb] === 'object' and earlier that not ret[verb] === '*'.\n          for (const [tableName, tableRights] of Object.entries(rights) as [\n            string,\n            string[] | '*'\n          ][]) {\n            if (mergedRights[tableName] === '*') continue;\n            if (tableRights === '*') {\n              mergedRights[tableName] = '*';\n            } else if (\n              Array.isArray(mergedRights[tableName]) &&\n              Array.isArray(tableRights)\n            ) {\n              mergedRights[tableName] = [\n                ...new Set([...mergedRights[tableName], ...tableRights]),\n              ];\n            }\n          }\n        }\n      } else {\n        /* This compiles without type assertions. Keeping the comment to\n           explain why we do tsignore on the next statement.\n        if (verb === \"add\") {\n          ret[verb] = next[verb];\n        } else if (verb === \"update\") {\n          ret[verb] = next[verb];\n        } else if (verb === \"manage\") {\n          ret[verb] = next[verb];\n        } else {\n          ret[verb] = next[verb];\n        }\n        */\n        //@ts-ignore\n        ret[verb] = next[verb];\n      }\n    }\n    return ret;\n  });\n  return reduced;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/middleware-helpers/guardedTable.ts",
    "content": "import { DBCoreTable, DBCoreTransaction } from \"dexie\";\nimport { allSettled } from \"../helpers/allSettled\";\n\nlet counter = 0;\n\nexport function guardedTable(table: DBCoreTable) {\n  const prop = \"$lock\"+ (++counter);\n  return {\n    ...table,\n    count: readLock(table.count, prop),\n    get: readLock(table.get, prop),\n    getMany: readLock(table.getMany, prop),\n    openCursor: readLock(table.openCursor, prop),\n    query: readLock(table.query, prop),\n    mutate: writeLock(table.mutate, prop),\n  };\n}\n\nfunction readLock<TReq extends { trans: DBCoreTransaction }, TRes>(\n  fn: (req: TReq) => Promise<TRes>,\n  prop: string\n): (req: TReq) => Promise<TRes> {\n  return function readLocker(req): Promise<TRes> {\n    const {\n      readers,\n      writers,\n    }: { writers: Promise<any>[]; readers: Promise<any>[] } =\n      req.trans[prop] || (req.trans[prop] = { writers: [], readers: [] });\n    const numWriters = writers.length;\n    const promise = (numWriters > 0\n      ? writers[numWriters - 1].then(() => fn(req), () => fn(req))\n      : fn(req)\n    ).finally(() => {readers.splice(readers.indexOf(promise))});\n    readers.push(promise);\n    return promise;\n  };\n}\n\nfunction writeLock<TReq extends { trans: DBCoreTransaction }, TRes>(\n  fn: (req: TReq) => Promise<TRes>,\n  prop: string\n): (req: TReq) => Promise<TRes> {\n  return function writeLocker(req): Promise<TRes> {\n    const {\n      readers,\n      writers,\n    }: { writers: Promise<any>[]; readers: Promise<any>[] } =\n      req.trans[prop] || (req.trans[prop] = { writers: [], readers: [] });\n    let promise = (writers.length > 0\n      ? writers[writers.length - 1].then(() => fn(req), () => fn(req))\n      : readers.length > 0\n      ? allSettled(readers).then(() => fn(req))\n      : fn(req)\n    ).finally(() => {writers.shift();});\n    writers.push(promise);\n    return promise;\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/middleware-helpers/idGenerationHelpers.ts",
    "content": "import {\n  DBCoreAddRequest,\n  DBCoreDeleteRequest,\n  DBCoreIndex, DBCorePutRequest\n} from 'dexie';\nimport { b64LexEncode } from 'dexie-cloud-common';\n\nconst { toString } = {};\nexport function toStringTag(o: Object) {\n  return toString.call(o).slice(8, -1);\n}\n\nexport function getEffectiveKeys(\n  primaryKey: DBCoreIndex,\n  req: (Pick<DBCoreAddRequest | DBCorePutRequest, 'type' | 'values'> & {\n    keys?: any[];\n  }) |\n    Pick<DBCoreDeleteRequest, 'keys' | 'type'>\n) {\n  if (req.type === 'delete')\n    return req.keys;\n  return req.keys?.slice() || req.values.map(primaryKey.extractKey!);\n}\nfunction applyToUpperBitFix(orig: string, bits: number) {\n  return (\n    (bits & 1 ? orig[0].toUpperCase() : orig[0].toLowerCase()) +\n    (bits & 2 ? orig[1].toUpperCase() : orig[1].toLowerCase()) +\n    (bits & 4 ? orig[2].toUpperCase() : orig[2].toLowerCase())\n  );\n}\nconst consonants = /b|c|d|f|g|h|j|k|l|m|n|p|q|r|s|t|v|x|y|z/i;\nfunction isUpperCase(ch: string) {\n  return ch >= 'A' && ch <= 'Z';\n}\n\nexport function generateTablePrefix(\n  tableName: string,\n  allPrefixes: Set<string>\n) {\n  let rv = tableName[0].toLocaleLowerCase(); // \"users\" = \"usr\", \"friends\" = \"frn\", \"realms\" = \"rlm\", etc.\n  for (let i = 1, l = tableName.length; i < l && rv.length < 3; ++i) {\n    if (consonants.test(tableName[i]) || isUpperCase(tableName[i]))\n      rv += tableName[i].toLowerCase();\n  }\n  while (allPrefixes.has(rv)) {\n    if (/\\d/g.test(rv)) {\n      rv = rv.substr(0, rv.length - 1) + (rv[rv.length - 1] + 1);\n      if (rv.length > 3)\n        rv = rv.substr(0, 3);\n      else\n        continue;\n    } else if (rv.length < 3) {\n      rv = rv + '2';\n      continue;\n    }\n    let bitFix = 1;\n    let upperFixed = rv;\n    while (allPrefixes.has(upperFixed) && bitFix < 8) {\n      upperFixed = applyToUpperBitFix(rv, bitFix);\n      ++bitFix;\n    }\n    if (bitFix < 8)\n      rv = upperFixed;\n    else {\n      let nextChar = (rv.charCodeAt(2) + 1) & 127;\n      rv = rv.substr(0, 2) + String.fromCharCode(nextChar);\n      // Here, in theory we could get an infinite loop if having 127*8 table names with identical 3 first consonants.\n    }\n  }\n  return rv;\n}\nlet time = 0;\n/**\n *\n * @param prefix A unique 3-letter short-name of the table.\n * @param shardKey 3 last letters from another ID if colocation is requested. Verified on server on inserts - guarantees unique IDs across shards.\n *  The shardKey part of the key represent the shardId where it was first created. An object with this\n *  primary key can later on be moved to another shard without being altered. The reason for having\n *  the origin shardKey as part of the key, is that the server will not need to check uniqueness constraint\n *  across all shards on every insert. Updates / moves across shards are already controlled by the server\n *  in the sense that the objects needs to be there already - we only need this part for inserts.\n * @returns\n */\nexport function generateKey(prefix: string, shardKey?: string) {\n  const a = new Uint8Array(18);\n  const timePart = new Uint8Array(a.buffer, 0, 6);\n  const now = Date.now(); // Will fit into 6 bytes until year 10 895.\n  if (time >= now) {\n    // User is bulk-creating objects the same millisecond.\n    // Increment the time part by one millisecond for each item.\n    // If bulk-creating 1,000,000 rows client-side in 10 seconds,\n    // the last time-stamp will be 990 seconds in future, which is no biggie at all.\n    // The point is to create a nice order of the generated IDs instead of\n    // using random ids.\n    ++time;\n  } else {\n    time = now;\n  }\n  timePart[0] = time / 1099511627776; // Normal division (no bitwise operator) --> works with >= 32 bits.\n  timePart[1] = time / 4294967296;\n  timePart[2] = time / 16777216;\n  timePart[3] = time / 65536;\n  timePart[4] = time / 256;\n  timePart[5] = time;\n  const randomPart = new Uint8Array(a.buffer, 6);\n  crypto.getRandomValues(randomPart);\n  const id = new Uint8Array(a.buffer);\n  return prefix + b64LexEncode(id) + (shardKey || '');\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/middlewares/blobResolveMiddleware.ts",
    "content": "/**\n * DBCore Middleware for resolving BlobRefs on read\n * \n * This middleware intercepts read operations and resolves any BlobRefs\n * found in objects marked with _hasBlobRefs.\n * \n * Important: Avoids async/await to preserve Dexie's Promise.PSD context.\n * Uses Dexie.waitFor() only for explicit rw transactions to keep them alive.\n * For readonly or implicit transactions, resolves directly (no waitFor needed).\n * \n * Resolved blobs are queued for saving via BlobSavingQueue, which uses\n * setTimeout(fn, 0) to completely isolate from Dexie's transaction context.\n * Each blob is saved atomically using Table.update() with its keyPath to\n * avoid race conditions with other property changes.\n * \n * Blob downloads use Authorization header (same as sync) via the server\n * proxy endpoint: GET /blob/{ref}\n */\n\nimport Dexie, { \n  DBCore, \n  DBCoreGetManyRequest, \n  DBCoreGetRequest, \n  DBCoreQueryRequest, \n  DBCoreOpenCursorRequest,\n  DBCoreCursor,\n  DBCoreTable, \n  DBCoreTransaction, \n  Middleware\n} from 'dexie';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { hasUnresolvedBlobRefs, resolveAllBlobRefs, ResolvedBlob } from '../sync/blobResolve';\nimport { BlobSavingQueue } from '../sync/BlobSavingQueue';\nimport { TXExpandos } from '../types/TXExpandos';\nimport { UserLogin } from '../dexie-cloud-client';\n\nexport function createBlobResolveMiddleware(db: DexieCloudDB): Middleware<DBCore> {\n  return {\n    stack: 'dbcore' as const,\n    name: 'blobResolve',\n    level: 2, // Run above cache (0) and other middlewares (1) to resolve BlobRefs from cached data\n    create(downlevelDatabase: DBCore): DBCore {\n      // Create a single queue instance for this database\n      const blobSavingQueue = new BlobSavingQueue(db);\n\n      return {\n        ...downlevelDatabase,\n        table(tableName: string): DBCoreTable {\n          if (!db.cloud) {\n            // db.cloud not yet initialized - skip blob resolution\n            // Fall through to downlevel table to avoid crash\n            return downlevelDatabase.table(tableName);\n          }\n          const dbUrl = db.cloud.options?.databaseUrl;\n          const downlevelTable = downlevelDatabase.table(tableName);\n          \n          // Skip internal tables\n          if (tableName.startsWith('$')) {\n            return downlevelTable;\n          }\n\n          return {\n            ...downlevelTable,\n\n            get(req: DBCoreGetRequest) {\n              if ((req.trans as DBCoreTransaction & TXExpandos)?.disableBlobResolve) {\n                return downlevelTable.get(req);\n              }\n              return downlevelTable.get(req).then(result => {\n                if (result && hasUnresolvedBlobRefs(result)) {\n                  return resolveAndSave(downlevelTable, req.trans, req.key, result, blobSavingQueue, db);\n                }\n                return result;\n              });\n            },\n\n            getMany(req: DBCoreGetManyRequest) {\n              if ((req.trans as DBCoreTransaction & TXExpandos)?.disableBlobResolve) {\n                return downlevelTable.getMany(req);\n              }\n              return downlevelTable.getMany(req).then(results => {\n                // Check if any results need resolution\n                const needsResolution = results.some(r => r && hasUnresolvedBlobRefs(r));\n                if (!needsResolution) return results;\n                \n                return Dexie.Promise.all(\n                  results.map((result, index) => {\n                    if (result && hasUnresolvedBlobRefs(result)) {\n                      return resolveAndSave(downlevelTable, req.trans, req.keys[index], result, blobSavingQueue, db);\n                    }\n                    return result;\n                  })\n                );\n              });\n            },\n\n            query(req: DBCoreQueryRequest) {\n              if ((req.trans as DBCoreTransaction & TXExpandos)?.disableBlobResolve) {\n                return downlevelTable.query(req);\n              }\n              return downlevelTable.query(req).then(result => {\n                if (!result.result || !Array.isArray(result.result)) return result;\n                \n                // Check if any results need resolution\n                const needsResolution = result.result.some(r => r && hasUnresolvedBlobRefs(r));\n                if (!needsResolution) return result;\n                                \n                return Dexie.Promise.all(\n                  result.result.map(item => {\n                    if (item && hasUnresolvedBlobRefs(item)) {\n                      return resolveAndSave(downlevelTable, req.trans, undefined, item, blobSavingQueue, db);\n                    }\n                    return item;\n                  })\n                ).then(resolved => ({ ...result, result: resolved }));\n              });\n            },\n\n            openCursor(req: DBCoreOpenCursorRequest) {\n              if ((req.trans as DBCoreTransaction & TXExpandos)?.disableBlobResolve) {\n                return downlevelTable.openCursor(req);\n              }\n              return downlevelTable.openCursor(req).then(cursor => {\n                if (!cursor) return cursor; // No results, so no resolution needed\n                if (!req.values) return cursor; // No values requested, so no resolution needed\n                if (!dbUrl) return cursor; // No database URL configured, can't resolve blobs\n                return createBlobResolvingCursor(cursor, downlevelTable, blobSavingQueue, db);\n              });\n            },\n          };\n        },\n      };\n    },\n  };\n}\n\n/**\n * Create a cursor wrapper that resolves BlobRefs in values synchronously.\n * \n * Uses Object.create() to inherit all cursor methods, only overriding:\n * - start(): Resolves BlobRefs before calling the callback\n * - value: Getter that returns the resolved value\n * \n * Returns the cursor synchronously. Resolution happens in start() before\n * each onNext callback, ensuring cursor.value is always available.\n */\nfunction createBlobResolvingCursor(\n  cursor: DBCoreCursor,\n  table: DBCoreTable,\n  blobSavingQueue: BlobSavingQueue,\n  db: DexieCloudDB\n): DBCoreCursor {\n  // Create wrapped cursor using Object.create() - inherits everything\n  const wrappedCursor = Object.create(cursor, {\n    value: {\n      value: cursor.value,\n      enumerable: true,\n      writable: true\n    },\n    start: {\n      value(onNext: () => void): Promise<any> {\n        // Override start to resolve BlobRefs before each callback\n        return cursor.start(() => {\n          const rawValue = cursor.value;\n          if (!rawValue || !hasUnresolvedBlobRefs(rawValue)) {\n            onNext();\n            return;\n          }\n          resolveAndSave(table, cursor.trans, cursor.primaryKey, rawValue, blobSavingQueue, db, true).then(resolved => {\n            wrappedCursor.value = resolved;\n            onNext();\n          }, err => {\n            console.error('Failed to resolve BlobRefs for cursor value:', err);\n            onNext();\n          });\n        });\n      }\n    }\n  });\n\n  return wrappedCursor;\n}\n\n\n/**\n * Resolve BlobRefs in an object and queue each blob for atomic saving.\n * \n * Uses Dexie.waitFor() only when needed:\n * - Skip waitFor for readonly ('r') transactions\n * - Skip waitFor for implicit transactions (most common in liveQuery)\n * - Use waitFor only for explicit rw transactions that need to stay alive\n * \n * Each resolved blob is queued individually with its keyPath for atomic\n * update using downCore transaction with the specific keyPath - this avoids race conditions.\n * \n * Returns Dexie.Promise to preserve PSD context.\n */\nfunction resolveAndSave(\n  table: DBCoreTable,\n  trans: DBCoreTransaction,\n  pKey: any | undefined, // optional. If missing, tries to extract from object using primary key path\n  obj: any,\n  blobSavingQueue: BlobSavingQueue,\n  db: DexieCloudDB,\n  isCursorValue: boolean = false // Flag to indicate if we're resolving a cursor value (which may not have a primary key)\n): Promise<any> {\n  try {\n    // Determine if we need waitFor:\n    // Skip waitFor ONLY if BOTH conditions are met:\n    //   1. readonly transaction\n    //   2. implicit (non-explicit) transaction\n    //\n    // Transaction.explicit is true when user called db.transaction() explicitly.\n    // For implicit transactions (auto-created for single operations),\n    // Dexie handles async automatically so no waitFor needed.\n    const currentTx = Dexie.currentTransaction;\n    const isReadonly = currentTx?.mode === 'readonly';\n    const isExplicit = currentTx?.explicit === true;\n\n    // Skip waitFor only for implicit readonly (most common case: liveQuery)\n    const skipWaitFor = isReadonly && !isExplicit && !isCursorValue;\n    const needsWaitFor = currentTx && !skipWaitFor;\n    const dbUrl = db.cloud.options?.databaseUrl || '';\n\n    // Collect resolved blobs with their keyPaths\n    const resolvedBlobs: ResolvedBlob[] = [];\n\n    // Create the resolution promise with auth info\n    const resolutionPromise = resolveAllBlobRefs(obj, dbUrl, resolvedBlobs, '', new WeakMap(), db.blobDownloadTracker)\n\n    // Wrap with waitFor to keep transaction alive during fetch\n    const resolvePromise = needsWaitFor\n      ? Dexie.waitFor(resolutionPromise)\n      : Dexie.Promise.resolve(resolutionPromise);\n\n    return resolvePromise.then(resolved => {\n      // Get primary key from the object\n      const primaryKey = table.schema.primaryKey;\n      const key = pKey !== undefined ? pKey : primaryKey.keyPath\n        ? Dexie.getByKeyPath(obj, primaryKey.keyPath as string)\n        : undefined;\n\n      if (key !== undefined) {\n        // Queue each resolved blob individually for atomic update\n        // This uses setTimeout(fn, 0) to completely isolate from\n        // Dexie's transaction context (avoids inheriting PSD)\n        if (isReadonly) {\n          blobSavingQueue.saveBlobs(table.name, key, resolvedBlobs);\n        } else {\n          // For rw transactions, we can save directly without queueing\n          // since we're still in the same transaction context\n          table.mutate({ type: 'put', keys: [key], values: [resolved], trans }).catch(err => {\n            console.error(`Failed to save resolved blob on ${table.name}:${key}:`, err);\n          });\n        }\n      }\n\n      return resolved;\n    }).catch(err => {\n      console.error(`[dexie-cloud:blobResolve] Failed to resolve BlobRefs on ${table.name}:`, err);\n      return obj; // Return original object on error - never block the read pipeline\n    });\n  } catch (err) {\n    console.error(`[dexie-cloud:blobResolve] Sync error in resolveAndSave on ${table.name}:`, err);\n    return Dexie.Promise.resolve(obj); // Never block reads\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/middlewares/createIdGenerationMiddleware.ts",
    "content": "import Dexie, {\n  DBCore,\n  DBCoreAddRequest,\n  DBCoreMutateRequest,\n  DBCorePutRequest,\n  DBCoreTransaction,\n  Middleware,\n} from 'dexie';\nimport { isValidSyncableID } from 'dexie-cloud-common';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport {\n  getEffectiveKeys,\n  generateKey,\n  toStringTag,\n} from '../middleware-helpers/idGenerationHelpers';\nimport { TXExpandos } from '../types/TXExpandos';\n\nexport function createIdGenerationMiddleware(\n  db: DexieCloudDB\n): Middleware<DBCore> {\n  return {\n    stack: 'dbcore',\n    name: 'idGenerationMiddleware',\n    level: 1,\n    create: (core) => {\n      return {\n        ...core,\n        table: (tableName) => {\n          const table = core.table(tableName);\n\n          function generateOrVerifyAtKeys(\n            req: DBCoreAddRequest | DBCorePutRequest,\n            idPrefix: string\n          ) {\n            let valueClones: null | object[] = null;\n            const keys = getEffectiveKeys(table.schema.primaryKey, req);\n            keys.forEach((key, idx) => {\n              if (key === undefined) {\n                // Generate the key\n                const colocatedId =\n                  req.values[idx].realmId || db.cloud.currentUserId;\n                const shardKey = colocatedId.substr(colocatedId.length - 3);\n                keys[idx] = generateKey(idPrefix, shardKey);\n                if (!table.schema.primaryKey.outbound) {\n                  if (!valueClones) valueClones = req.values.slice();\n                  valueClones[idx] = Dexie.deepClone(valueClones[idx]);\n                  Dexie.setByKeyPath(\n                    valueClones[idx],\n                    table.schema.primaryKey.keyPath!,\n                    keys[idx]\n                  );\n                }\n              } else if (\n                typeof key !== 'string' ||\n                (!key.startsWith(idPrefix) && !key.startsWith('#' + idPrefix))\n              ) {\n                // Key was specified by caller. Verify it complies with id prefix.\n                throw new Dexie.ConstraintError(\n                  `The ID \"${key}\" is not valid for table \"${tableName}\". ` +\n                    `Primary '@' keys requires the key to be prefixed with \"${idPrefix}\" (or \"#${idPrefix}).\\n` +\n                    `If you want to generate IDs programmatically, remove '@' from the schema to get rid of this constraint. Dexie Cloud supports custom IDs as long as they are random and globally unique.`\n                );\n              }\n            });\n            return table.mutate({\n              ...req,\n              keys,\n              values: valueClones || req.values,\n            });\n          }\n\n          return {\n            ...table,\n            mutate: (req) => {\n              const idbtrans = req.trans as DBCoreTransaction & IDBTransaction & TXExpandos;\n              if (idbtrans.mode === 'versionchange') {\n                // Tell all the other middlewares to skip bothering. We're in versionchange mode.\n                // dexie-cloud is not initialized yet.\n                idbtrans.disableChangeTracking = true;\n                idbtrans.disableAccessControl = true;\n              }\n              if (idbtrans.disableChangeTracking) {\n                // Disable ID policy checks and ID generation\n                return table.mutate(req);\n              }\n              if (req.type === 'add' || req.type === 'put') {\n                const cloudTableSchema = db.cloud.schema?.[tableName];\n                if (!cloudTableSchema?.generatedGlobalId) {\n                  if (cloudTableSchema?.markedForSync) {\n                    // Just make sure primary key is of a supported type:\n                    const keys = getEffectiveKeys(table.schema.primaryKey, req);\n                    keys.forEach((key, idx) => {\n                      if (!isValidSyncableID(key)) {\n                        const type = Array.isArray(key)\n                          ? key.map(toStringTag).join(',')\n                          : toStringTag(key);\n                        throw new Dexie.ConstraintError(\n                          `Invalid primary key type ${type} for table ${tableName}. Tables marked for sync has primary keys of type string or Array of string (and optional numbers)`\n                        );\n                      }\n                    });\n                  }\n                } else {\n                  if (db.cloud.options?.databaseUrl && !db.initiallySynced) {\n                    // A database URL is configured but no initial sync has been performed.\n                    const keys = getEffectiveKeys(table.schema.primaryKey, req);\n                    // Check if the operation would yield any INSERT. If so, complain! We never want wrong ID prefixes stored.\n                    return table\n                      .getMany({ keys, trans: req.trans, cache: 'immutable' })\n                      .then((results) => {\n                        if (results.length < keys.length) {\n                          // At least one of the given objects would be created. Complain since\n                          // the generated ID would be based on a locally computed ID prefix only - we wouldn't\n                          // know if the server would give the same ID prefix until an initial sync has been\n                          // performed.\n                          throw new Error(\n                            `Unable to create new objects without an initial sync having been performed.`\n                          );\n                        }\n                        return table.mutate(req);\n                      });\n                  }\n                  return generateOrVerifyAtKeys(\n                    req,\n                    cloudTableSchema.idPrefix!\n                  );\n                }\n              }\n              return table.mutate(req);\n            },\n          };\n        },\n      };\n    },\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/middlewares/createImplicitPropSetterMiddleware.ts",
    "content": "import Dexie, { DBCore, DBCoreTransaction, Middleware } from 'dexie';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { TXExpandos } from '../types/TXExpandos';\nimport { UNAUTHORIZED_USER } from '../authentication/UNAUTHORIZED_USER';\n\nexport function createImplicitPropSetterMiddleware(\n  db: DexieCloudDB\n): Middleware<DBCore> {\n  return {\n    stack: 'dbcore',\n    name: 'implicitPropSetterMiddleware',\n    level: 1,\n    create: (core) => {\n      return {\n        ...core,\n        table: (tableName) => {\n          const table = core.table(tableName);\n          return {\n            ...table,\n            mutate: (req) => {\n              const trans = req.trans as DBCoreTransaction & TXExpandos & IDBTransaction;\n\n              if (trans.disableChangeTracking) {\n                return table.mutate(req);\n              }\n\n              const currentUserId = trans.currentUser?.userId ?? UNAUTHORIZED_USER.userId;\n\n              if (db.cloud.schema?.[tableName]?.markedForSync) {\n                if (req.type === 'add' || req.type === 'put') {\n                  if (tableName === 'members') {\n                    for (const member of req.values) {\n                      if (typeof member.email === 'string') {\n                        // Resolve https://github.com/dexie/dexie-cloud/issues/4\n                        // If adding a member, make sure email is lowercase and trimmed.\n                        // This is to avoid issues where the APP does not check this\n                        // and just allows the user to enter an email address that might\n                        // have been pasted by the user from a source that had a trailing\n                        // space or was in uppercase. We want to avoid that the user\n                        // creates a new member with a different email address than\n                        // the one he/she intended to create.\n                        member.email = member.email.trim().toLowerCase();\n                      }\n                    }\n                  }\n                  // No matter if user is logged in or not, make sure \"owner\" and \"realmId\" props are set properly.\n                  // If not logged in, this will be changed upon syncification of the tables (next sync after login),\n                  // however, application code will work better if we can always rely on that the properties realmId\n                  // and owner are set. Application code may index them and query them based on db.cloud.currentUserId,\n                  // and expect them to be returned. That scenario must work also when db.cloud.currentUserId === 'unauthorized'.\n                  for (const obj of req.values) {\n                    if (!obj.owner) {\n                      obj.owner = currentUserId;\n                    }\n                    if (!obj.realmId) {\n                      obj.realmId = currentUserId;\n                    }\n                    const key = table.schema.primaryKey.extractKey?.(obj);\n                    if (typeof key === 'string' && key[0] === '#') {\n                      // Add $ts prop for put operations and\n                      // disable update operations as well as consistent\n                      // modify operations. Reason: Server may not have\n                      // the object. Object should be created on server only\n                      // if is being updated. An update operation won't create it\n                      // so we must delete req.changeSpec to degrade operation to\n                      // an upsert operation with timestamp so that it will be created.\n                      // We must also degrade from consistent modify operations for the\n                      // same reason - object might be there on server. Must but put up instead.\n\n                      // FUTURE: This clumpsy behavior of private IDs could be refined later.\n                      // Suggestion is to in future, treat private IDs as we treat all objects \n                      // and sync operations normally. Only that deletions should become soft deletes\n                      // for them - so that server knows when a private ID has been deleted on server\n                      // not accept insert/upserts on them.\n                      if (req.type === 'put') {\n                        delete req.criteria;\n                        delete req.changeSpec;\n                        if (!req.upsert) delete req.updates;\n                        obj.$ts = Date.now();\n                      }\n                    }\n                  }\n                }\n              }\n              return table.mutate(req);\n            },\n          };\n        },\n      };\n    },\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/middlewares/createMutationTrackingMiddleware.ts",
    "content": "import {\n  DBCore,\n  DBCoreAddRequest,\n  DBCoreDeleteRequest,\n  DBCoreMutateResponse,\n  DBCorePutRequest,\n  DBCoreTable,\n  DBCoreTransaction,\n  Middleware,\n  RangeSet,\n} from 'dexie';\nimport { DBOperation, DBUpdateOperation } from 'dexie-cloud-common';\nimport { BehaviorSubject } from 'rxjs';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { UserLogin } from '../db/entities/UserLogin';\nimport { getMutationTable } from '../helpers/getMutationTable';\nimport { randomString } from '../helpers/randomString';\nimport { throwVersionIncrementNeeded } from '../helpers/throwVersionIncrementNeeded';\nimport { guardedTable } from '../middleware-helpers/guardedTable';\nimport { registerSyncEvent } from '../sync/registerSyncEvent';\nimport { TXExpandos } from '../types/TXExpandos';\nimport { outstandingTransactions } from './outstandingTransaction';\nimport { isEagerSyncDisabled } from '../isEagerSyncDisabled';\nimport { triggerSync } from '../sync/triggerSync';\n\nexport interface MutationTrackingMiddlewareArgs {\n  currentUserObservable: BehaviorSubject<UserLogin>;\n  db: DexieCloudDB;\n}\n\n/** Tracks all mutations in the same transaction as the mutations -\n * so it is guaranteed that no mutation goes untracked - and if transaction\n * aborts, the mutations won't be tracked.\n *\n * The sync job will use the tracked mutations as the source of truth when pushing\n * changes to server and cleanup the tracked mutations once the server has\n * ackowledged that it got them.\n */\nexport function createMutationTrackingMiddleware({\n  currentUserObservable,\n  db,\n}: MutationTrackingMiddlewareArgs): Middleware<DBCore> {\n  return {\n    stack: 'dbcore',\n    name: 'MutationTrackingMiddleware',\n    level: 1,\n    create: (core) => {\n      const allTableNames = new Set(core.schema.tables.map((t) => t.name));\n      const ordinaryTables = core.schema.tables.filter(\n        (t) => !/^\\$/.test(t.name)\n      );\n      const mutTableMap = new Map<string, DBCoreTable>();\n      for (const tbl of ordinaryTables) {\n        const mutationTableName = `$${tbl.name}_mutations`;\n        if (allTableNames.has(mutationTableName)) {\n          mutTableMap.set(tbl.name, core.table(mutationTableName));\n        }\n      }\n\n      return {\n        ...core,\n        transaction: (tables, mode) => {\n          let tx: DBCoreTransaction & IDBTransaction & TXExpandos;\n          if (mode === 'readwrite') {\n            const mutationTables = tables\n              .filter((tbl) => db.cloud.schema?.[tbl]?.markedForSync)\n              .map((tbl) => getMutationTable(tbl));\n            tx = core.transaction(\n              [...tables, ...mutationTables],\n              mode\n            ) as DBCoreTransaction & IDBTransaction & TXExpandos;\n          } else {\n            tx = core.transaction(tables, mode) as DBCoreTransaction &\n              IDBTransaction &\n              TXExpandos;\n          }\n\n          if (mode === 'readwrite') {\n            // Give each transaction a globally unique id.\n            tx.txid = randomString(16);\n            tx.opCount = 0;\n            // Introduce the concept of current user that lasts through the entire transaction.\n            // This is important because the tracked mutations must be connected to the user.\n            tx.currentUser = currentUserObservable.value;\n            outstandingTransactions.value.add(tx);\n            outstandingTransactions.next(outstandingTransactions.value);\n            const removeTransaction = () => {\n              tx.removeEventListener('complete', txComplete);\n              tx.removeEventListener('error', removeTransaction);\n              tx.removeEventListener('abort', removeTransaction);\n              outstandingTransactions.value.delete(tx);\n              outstandingTransactions.next(outstandingTransactions.value);\n            };\n            const txComplete = () => {\n              if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {\n                triggerSync(db, 'push');\n              }\n              removeTransaction();\n            };\n            tx.addEventListener('complete', txComplete);\n            tx.addEventListener('error', removeTransaction);\n            tx.addEventListener('abort', removeTransaction);\n          }\n          return tx;\n        },\n        table: (tableName) => {\n          const table = core.table(tableName);\n          if (/^\\$/.test(tableName)) {\n            if (tableName.endsWith('_mutations')) {\n              // In case application code adds items to ..._mutations tables,\n              // make sure to set the mutationsAdded flag on transaction.\n              // This is also done in mutateAndLog() as that function talks to a\n              // lower level DBCore and wouldn't be catched by this code.\n              return {\n                ...table,\n                mutate: (req) => {\n                  if (req.type === 'add' || req.type === 'put') {\n                    (\n                      req.trans as DBCoreTransaction & TXExpandos\n                    ).mutationsAdded = true;\n                  }\n                  return table.mutate(req);\n                },\n              };\n            } else if (tableName === '$logins') {\n              return {\n                ...table,\n                mutate: (req) => {\n                  //console.debug('Mutating $logins table', req);\n                  return table\n                    .mutate(req)\n                    .then((res) => {\n                      //console.debug('Mutating $logins');\n                      (\n                        req.trans as DBCoreTransaction & TXExpandos\n                      ).mutationsAdded = true;\n                      //console.debug('$logins mutated');\n                      return res;\n                    })\n                    .catch((err) => {\n                      console.debug('Failed mutation $logins', err);\n                      return Promise.reject(err);\n                    });\n                },\n              };\n            } else {\n              return table;\n            }\n          }\n          const { schema } = table;\n          const mutsTable = mutTableMap.get(tableName)!;\n          if (!mutsTable) {\n            // We cannot track mutations on this table because there is no mutations table for it.\n            // This might happen in upgraders that executes before cloud schema is applied.\n            return table; \n          }\n          return guardedTable({\n            ...table,\n            mutate: (req) => {\n              const trans = req.trans as DBCoreTransaction & TXExpandos;\n              if (!trans.txid) return table.mutate(req); // Upgrade transactions not guarded by us.\n              if (trans.disableChangeTracking) return table.mutate(req);\n              if (!db.cloud.schema?.[tableName]?.markedForSync)\n                return table.mutate(req);\n              if (!trans.currentUser?.isLoggedIn) {\n                // Unauthorized user should not log mutations.\n                // Instead, after login all local data should be logged at once.\n                return table.mutate(req);\n              }\n\n              return req.type === 'deleteRange'\n                ? table\n                    // Query the actual keys (needed for server sending correct rollback to us)\n                    .query({\n                      query: { range: req.range, index: schema.primaryKey },\n                      trans: req.trans,\n                      values: false,\n                    })\n                    // Do a delete request instead, but keep the criteria info for the server to execute\n                    .then((res) => {\n                      return mutateAndLog({\n                        type: 'delete',\n                        keys: res.result,\n                        trans: req.trans,\n                        criteria: { index: null, range: req.range },\n                      });\n                    })\n                : mutateAndLog(req);\n            },\n          });\n\n          function mutateAndLog(\n            req: DBCoreDeleteRequest | DBCoreAddRequest | DBCorePutRequest\n          ): Promise<DBCoreMutateResponse> {\n            const trans = req.trans as DBCoreTransaction & TXExpandos;\n            const unsyncedProps =\n              db.cloud.options?.unsyncedProperties?.[tableName];\n            const {\n              txid,\n              currentUser: { userId },\n            } = trans;\n            const { type } = req;\n            const opNo = ++trans.opCount;\n\n            function stripChangeSpec(changeSpec: { [keyPath: string]: any }) {\n              if (!unsyncedProps) return changeSpec;\n              let rv = changeSpec;\n              for (const keyPath of Object.keys(changeSpec)) {\n                if (\n                  unsyncedProps.some(\n                    (p) => keyPath === p || keyPath.startsWith(p + '.')\n                  )\n                ) {\n                  if (rv === changeSpec) rv = { ...changeSpec }; // clone on demand\n                  delete rv[keyPath];\n                }\n              }\n              return rv;\n            }\n\n            return table.mutate(req).then((res) => {\n              const { numFailures: hasFailures, failures } = res;\n              let keys = type === 'delete' ? req.keys! : res.results!;\n              let values = 'values' in req ? req.values : [];\n              let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;\n              let updates = 'updates' in req ? req.updates : undefined;\n              let upsert = updates && 'upsert' in req ? req.upsert : false;\n\n              if (hasFailures) {\n                keys = keys.filter((_, idx) => !failures[idx]);\n                values = values.filter((_, idx) => !failures[idx]);\n              }\n              if (unsyncedProps) {\n                // Filter out unsynced properties\n                values = values.map((value) => {\n                  const newValue = { ...value };\n                  for (const prop of unsyncedProps) {\n                    delete newValue[prop];\n                  }\n                  return newValue;\n                });\n                if (changeSpec) {\n                  // modify operation with criteria and changeSpec.\n                  // We must strip out unsynced properties from changeSpec.\n                  // We deal with criteria later.\n                  changeSpec = stripChangeSpec(changeSpec);\n                  if (Object.keys(changeSpec).length === 0) {\n                    // Nothing to change on server\n                    return res;\n                  }\n                }\n                if (updates) {\n                  let strippedChangeSpecs =\n                    updates.changeSpecs.map(stripChangeSpec);\n                  let newUpdates: DBCorePutRequest['updates'] = {\n                    keys: [],\n                    changeSpecs: [],\n                  };\n                  const validKeys = new RangeSet();\n                  let anyChangeSpecBecameEmpty = false;\n                  if (!upsert) {\n                    for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {\n                      if (Object.keys(strippedChangeSpecs[i]).length > 0) {\n                        newUpdates.keys.push(updates.keys[i]);\n                        newUpdates.changeSpecs.push(strippedChangeSpecs[i]);\n                        validKeys.addKey(updates.keys[i]);\n                      } else {\n                        anyChangeSpecBecameEmpty = true;\n                      }\n                    }\n                    updates = newUpdates;\n                    if (anyChangeSpecBecameEmpty) {\n                      // Some keys were stripped. We must also strip them from keys and values\n                      // unless this is an upsert operation in which case we want to send them all.\n                      let newKeys: any[] = [];\n                      let newValues: any[] = [];\n                      for (let i = 0, l = keys.length; i < l; ++i) {\n                        if (validKeys.hasKey(keys[i])) {\n                          newKeys.push(keys[i]);\n                          newValues.push(values[i]);\n                        }\n                      }\n                      keys = newKeys;\n                      values = newValues;\n                    }\n                  }\n                }\n              }\n              const ts = Date.now();\n              // Canonicalize req.criteria.index to null if it's on the primary key.\n              let criteria =\n                'criteria' in req && req.criteria\n                  ? {\n                      ...req.criteria,\n                      index:\n                        req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key\n                          ? null // This will disable the server from trying to log consistent operations where it shouldnt.\n                          : req.criteria.index,\n                    }\n                  : undefined;\n              if (unsyncedProps && criteria?.index) {\n                const keyPaths = schema.indexes.find(\n                  (idx) => idx.name === criteria!.index\n                )?.keyPath;\n                const involvedProps = keyPaths\n                  ? typeof keyPaths === 'string'\n                    ? [keyPaths]\n                    : keyPaths\n                  : [];\n                if (involvedProps.some((p) => unsyncedProps?.includes(p))) {\n                  // Don't log criteria on unsynced properties as the server could not test them.\n                  criteria = undefined;\n                }\n              }\n\n              const mut: DBOperation =\n                req.type === 'delete'\n                  ? {\n                      type: 'delete',\n                      ts,\n                      opNo,\n                      keys,\n                      criteria,\n                      txid,\n                      userId,\n                    }\n                  : req.type === 'add'\n                  ? {\n                      type: 'insert',\n                      ts,\n                      opNo,\n                      keys,\n                      txid,\n                      userId,\n                      values,\n                    }\n                  : upsert && updates ? {\n                      type: 'upsert',\n                      ts,\n                      opNo,\n                      keys,\n                      values,\n                      changeSpecs: updates.changeSpecs.filter((_, idx) => !failures[idx]),\n                      txid,\n                      userId,\n                  }\n                  : criteria && changeSpec\n                  ? {\n                      // Common changeSpec for all keys\n                      type: 'modify',\n                      ts,\n                      opNo,\n                      keys,\n                      criteria,\n                      changeSpec,\n                      txid,\n                      userId,\n                    }\n                  : changeSpec\n                  ? {\n                      // In case criteria involved an unsynced property, we go for keys instead.\n                      type: 'update',\n                      ts,\n                      opNo,\n                      keys,\n                      changeSpecs: keys.map(() => changeSpec!),\n                      txid,\n                      userId,\n                    }\n                  : updates\n                  ? {\n                      // One changeSpec per key\n                      type: 'update',\n                      ts,\n                      opNo,\n                      keys: updates.keys,\n                      changeSpecs: updates.changeSpecs,\n                      txid,\n                      userId,\n                    }\n                  : {\n                      type: 'upsert',\n                      ts,\n                      opNo,\n                      keys,\n                      values,\n                      txid,\n                      userId,\n                    };\n\n              if ('isAdditionalChunk' in req && req.isAdditionalChunk) {\n                mut.isAdditionalChunk = true;\n              }\n              return keys.length > 0 || criteria\n                ? mutsTable\n                    .mutate({ type: 'add', trans, values: [mut] }) // Log entry\n                    .then(() => {\n                      trans.mutationsAdded = true; // Mark transaction as having added mutations to trigger eager sync\n                      return res; // Return original response\n                    })\n                : res;\n            });\n          }\n        },\n      };\n    },\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/middlewares/outstandingTransaction.ts",
    "content": "import { DBCoreTransaction } from 'dexie';\nimport { BehaviorSubject } from 'rxjs';\nimport { TXExpandos } from '../types/TXExpandos';\n\nexport const outstandingTransactions = new BehaviorSubject<Set<DBCoreTransaction & IDBTransaction & TXExpandos>>(new Set());\n"
  },
  {
    "path": "addons/dexie-cloud/src/overrideParseStoresSpec.ts",
    "content": "import Dexie, { DbSchema } from 'dexie';\nimport { DEXIE_CLOUD_SCHEMA } from './db/DexieCloudDB';\nimport { generateTablePrefix } from './middleware-helpers/idGenerationHelpers';\n\nexport function overrideParseStoresSpec(origFunc: Function, dexie: Dexie) {\n  return function(stores: {[tableName: string]: string}, dbSchema: DbSchema) {\n    const storesClone = {\n      ...DEXIE_CLOUD_SCHEMA,\n      ...stores,\n    };\n    // Merge indexes of DEXIE_CLOUD_SCHEMA with stores\n    Object.keys(DEXIE_CLOUD_SCHEMA).forEach((tableName: keyof typeof DEXIE_CLOUD_SCHEMA) => {\n      const schemaSrc = storesClone[tableName];\n      // Verify that they don't try to delete a table that is needed for access control of Dexie Cloud\n      if (schemaSrc == null) {\n        // They try to delete one of the built-in schema tables.\n        throw new Error(`Cannot delete table ${tableName} as it is needed for access control of Dexie Cloud`);\n      }\n      // If not trying to override a built-in table, then we can skip this and continue to next table.\n      if (!stores[tableName]) {\n        // They haven't tried to declare this table. No need to merge indexes.\n        return; // Continue\n      }\n\n      // They have declared this table. Merge indexes in case they didn't declare all indexes we need.\n      const requestedIndexes = schemaSrc.split(',').map(spec => spec.trim());\n      const builtInIndexes = DEXIE_CLOUD_SCHEMA[tableName].split(',').map(spec => spec.trim());\n      const requestedIndexSet = new Set(requestedIndexes.map(index => index.replace(/([&*]|\\+\\+)/g, \"\")));\n      // Verify that primary key is unchanged\n      if (requestedIndexes[0] !== builtInIndexes[0]) {\n        // Primary key must match exactly\n        throw new Error(`Cannot override primary key of table ${tableName}. Please declare it as {${\n          tableName}: ${\n            JSON.stringify(DEXIE_CLOUD_SCHEMA[tableName])\n          }`);\n      }\n      // Merge indexes\n      for (let i=1; i<builtInIndexes.length; ++i) {\n        const builtInIndex = builtInIndexes[i];\n        if (!requestedIndexSet.has(builtInIndex.replace(/([&*]|\\+\\+)/g, \"\"))) {\n          // Add built-in index if not already requested\n          storesClone[tableName] += `,${builtInIndex}`;\n        }\n      }\n    });\n\n    // Populate dexie.cloud.schema\n    const cloudSchema = dexie.cloud.schema || (dexie.cloud.schema = {});\n    const allPrefixes = new Set<string>();\n    Object.keys(storesClone).forEach(tableName => {\n      const schemaSrc = storesClone[tableName]?.trim(); \n      const cloudTableSchema = cloudSchema[tableName] || (cloudSchema[tableName] = {});\n      if (schemaSrc != null) {\n        if (/^\\@/.test(schemaSrc)) {\n          storesClone[tableName] = storesClone[tableName].substr(1);\n          cloudTableSchema.generatedGlobalId = true;\n          cloudTableSchema.idPrefix = generateTablePrefix(tableName, allPrefixes);\n          allPrefixes.add(cloudTableSchema.idPrefix);\n        }\n        if (!/^\\$/.test(tableName)) {\n          storesClone[`$${tableName}_mutations`] = '++rev';\n          cloudTableSchema.markedForSync = true;\n          \n          // Add sparse index for _hasBlobRefs (for BlobRef resolution tracking)\n          // IndexedDB sparse indexes have zero overhead when the property doesn't exist\n          if (!storesClone[tableName].includes('_hasBlobRefs')) {\n            storesClone[tableName] += ',_hasBlobRefs';\n          }\n        }\n        if (cloudTableSchema.deleted) {\n          cloudTableSchema.deleted = false;\n        }\n      } else {\n        cloudTableSchema.deleted = true;\n        cloudTableSchema.markedForSync = false;\n        storesClone[`$${tableName}_mutations`] = null;\n      }\n    });\n    const rv = origFunc.call(this, storesClone, dbSchema);\n    for (const [tableName, spec] of Object.entries(dbSchema)) {\n      if (spec.yProps?.length) {\n        const cloudTableSchema = cloudSchema[tableName];\n        if (cloudTableSchema) {\n          cloudTableSchema.yProps = spec.yProps.map((yProp) => yProp.prop);\n        }\n      }\n    }\n    return rv;\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/performInitialSync.ts",
    "content": "import { DexieCloudSchema } from 'dexie-cloud-common';\nimport { DexieCloudDB } from './db/DexieCloudDB';\nimport { DexieCloudOptions } from './DexieCloudOptions';\nimport { CURRENT_SYNC_WORKER, sync } from './sync/sync';\nimport { performGuardedJob } from './sync/performGuardedJob';\n\nexport async function performInitialSync(\n  db: DexieCloudDB,\n  cloudOptions: DexieCloudOptions,\n  cloudSchema: DexieCloudSchema\n) {\n  console.debug('Performing initial sync');  \n  await performGuardedJob(\n    db,\n    CURRENT_SYNC_WORKER,\n    () => sync(db, cloudOptions, cloudSchema, { isInitialSync: true })\n  );\n  console.debug('Done initial sync');\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/permissions.ts",
    "content": "import Dexie, { liveQuery } from 'dexie';\nimport { DBRealmMember } from 'dexie-cloud-common';\nimport { from, Observable } from 'rxjs';\nimport { map, startWith } from 'rxjs/operators';\nimport { mergePermissions } from './mergePermissions';\nimport {\n  getPermissionsLookupObservable,\n  PermissionsLookup,\n} from './getPermissionsLookupObservable';\nimport { PermissionChecker } from './PermissionChecker';\nimport './extend-dexie-interface';\n\nexport function permissions(\n  dexie: Dexie,\n  obj: { owner?: string; realmId?: string; table?: () => string },\n  tableName?: string\n): Observable<PermissionChecker<any>> {\n  if (!obj)\n    throw new TypeError(\n      `Cannot check permissions of undefined or null. A Dexie Cloud object with realmId and owner expected.`\n    );\n  const { owner, realmId } = obj;\n  if (!tableName) {\n    if (typeof obj.table !== 'function') {\n      throw new TypeError(\n        `Missing 'table' argument to permissions and table could not be extracted from entity`\n      );\n    }\n    tableName = obj.table();\n  }\n  const source = getPermissionsLookupObservable(dexie);\n  const mapper = (permissionsLookup: PermissionsLookup) => {\n    // If realmId is undefined, it can be due to that the object is not yet syncified - it exists\n    // locally only as the user might not yet be authenticated. This is ok and we shall treat it\n    // as if the realmId is dexie.cloud.currentUserId (which is \"unauthorized\" by the way)\n    const realm = permissionsLookup[realmId || dexie.cloud.currentUserId];\n    if (!realm)\n      return new PermissionChecker(\n        {},\n        tableName!,\n        !owner || owner === dexie.cloud.currentUserId\n      );\n    return new PermissionChecker(\n      realm.permissions,\n      tableName!,\n      realmId === undefined || realmId === dexie.cloud.currentUserId || owner === dexie.cloud.currentUserId\n    );\n  };\n  const o = source.pipe(map(mapper)) as Observable<PermissionChecker<any>> & {\n    getValue: () => PermissionChecker<any>;\n  };\n  o.getValue = () => mapper(source.getValue());\n  return o;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/prodLog.ts",
    "content": "/** A way to log to console in production without terser stripping out\n * it from the release bundle.\n * This should be used very rarely and only in places where it's\n * absolutely necessary to log something in production.\n * \n * @param level \n * @param args \n */\nexport function prodLog(level: 'log' | 'warn' | 'error' | 'debug', ...args: any[]) {\n  globalThis[\"con\"+\"sole\"][level](...args);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/service-worker.ts",
    "content": "import Dexie from 'dexie';\nimport { DexieCloudDB } from './db/DexieCloudDB';\nimport dexieCloud from './dexie-cloud-client';\nimport { DISABLE_SERVICEWORKER_STRATEGY } from './DISABLE_SERVICEWORKER_STRATEGY';\nimport { isSafari, safariVersion } from './isSafari';\nimport { syncIfPossible } from './sync/syncIfPossible';\nimport { SWMessageEvent } from './types/SWMessageEvent';\nimport { SyncEvent } from './types/SWSyncEvent';\n\n// In case the SW lives for a while, let it reuse already opened connections:\nconst managedDBs = new Map<string, DexieCloudDB>();\n\nfunction getDbNameFromTag(tag: string) {\n  return tag.startsWith('dexie-cloud:') && tag.split(':')[1];\n}\n\nconst syncDBSemaphore = new Map<string, Promise<void>>();\n\nfunction syncDB(dbName: string, purpose: 'push' | 'pull') {\n  // We're taking hight for being double-signalled both\n  // via message event and sync event.\n  // Which one comes first doesnt matter, just\n  // that we return the existing promise if there is\n  // an ongoing sync.\n  let promise = syncDBSemaphore.get(dbName + '/' + purpose);\n  if (!promise) {\n    promise = _syncDB(dbName, purpose)\n      .then(() => {\n        // When legacy enough across browsers, use .finally() instead of then() and catch():\n        syncDBSemaphore.delete(dbName + '/' + purpose);\n      })\n      .catch((error) => {\n        syncDBSemaphore.delete(dbName + '/' + purpose);\n        return Promise.reject(error);\n      });\n    syncDBSemaphore.set(dbName + '/' + purpose, promise!);\n  }\n  return promise!;\n\n  async function _syncDB(dbName: string, purpose: 'push' | 'pull') {\n    let db = managedDBs.get(dbName);\n\n    if (!db) {\n      console.debug('Dexie Cloud SW: Creating new Dexie instance for', dbName);\n      const dexie = new Dexie(dbName, { addons: [dexieCloud] });\n      db = DexieCloudDB(dexie);\n      db.cloud.isServiceWorkerDB = true;\n      dexie.on('versionchange', stopManagingDB);\n      await db.dx.open(); // Makes sure db.cloud.options and db.cloud.schema are read from db,\n      if (managedDBs.get(dbName)) {\n        // Avoid race conditions.\n        db.close();\n        return await _syncDB(dbName, purpose);\n      }\n      managedDBs.set(dbName, db);\n    }\n    if (!db.cloud.options?.databaseUrl) {\n      console.error(`Dexie Cloud: No databaseUrl configured`);\n      return; // Nothing to sync.\n    }\n    if (!db.cloud.schema) {\n      console.error(`Dexie Cloud: No schema persisted`);\n      return; // Nothing to sync.\n    }\n\n    function stopManagingDB() {\n      db!.dx.on.versionchange.unsubscribe(stopManagingDB);\n      if (managedDBs.get(db!.name) === db) {\n        // Avoid race conditions.\n        managedDBs.delete(db!.name);\n      }\n      console.debug(`Dexie Cloud SW: Closing Dexie instance for ${dbName}`);\n      db!.dx.close();\n      return false;\n    }\n\n    try {\n      console.debug('Dexie Cloud SW: Syncing');\n      await syncIfPossible(db, db.cloud.options, db.cloud.schema, {\n        retryImmediatelyOnFetchError: true,\n        purpose,\n      });\n      console.debug('Dexie Cloud SW: Done Syncing');\n    } catch (e) {\n      console.error(`Dexie Cloud SW Error`, e);\n      // Error occured. Stop managing this DB until we wake up again by a sync event,\n      // which will open a new Dexie and start trying to sync it.\n      stopManagingDB();\n      if (e.name !== Dexie.errnames.NoSuchDatabase) {\n        // Unless the error was that DB doesn't exist, rethrow to trigger sync retry.\n        throw e; // Throw e to make syncEvent.waitUntil() receive a rejected promis, so it will retry.\n      }\n    }\n  }\n}\n\n// Avoid taking care of events if browser bugs out by using dexie cloud from a service worker.\nif (!DISABLE_SERVICEWORKER_STRATEGY) {\n  self.addEventListener('sync', (event: SyncEvent) => {\n    console.debug('SW \"sync\" Event', event.tag);\n    const dbName = getDbNameFromTag(event.tag);\n    if (dbName) {\n      event.waitUntil(syncDB(dbName, \"push\")); // The purpose of sync events are \"push\"\n    }\n  });\n\n  self.addEventListener('periodicsync', (event: SyncEvent) => {\n    console.debug('SW \"periodicsync\" Event', event.tag);\n    const dbName = getDbNameFromTag(event.tag);\n    if (dbName) {\n      event.waitUntil(syncDB(dbName, \"pull\")); // The purpose of periodic sync events are \"pull\"\n    }\n  });\n\n  self.addEventListener('message', (event: SWMessageEvent) => {\n    console.debug('SW \"message\" Event', event.data);\n    if (event.data.type === 'dexie-cloud-sync') {\n      const { dbName } = event.data;\n      // Mimic background sync behavior - retry in X minutes on failure.\n      // But lesser timeout and more number of times.\n      const syncAndRetry = (num = 1) => {\n        return syncDB(dbName, event.data.purpose || \"pull\").catch(async (e) => {\n          if (num === 3) throw e;\n          await sleep(60_000); // 1 minute\n          syncAndRetry(num + 1);\n        });\n      };\n      if ('waitUntil' in event) {\n        event.waitUntil(syncAndRetry().catch(error => console.error(error)));\n      } else {\n        syncAndRetry().catch(error => console.error(error));\n      }\n    }\n  });\n}\n\nfunction sleep(ms: number) {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/BLOB_TODO.md",
    "content": "\n- [v] Bug: we're always resolving BlobRefs to Uint8Array but it could be a Blob, ArrayBuffer or other binary type. The exact type should have been stored in the TSON structure. Now it's $t: \"Blob\". Probably that is misleading and we should call it BlobRef for this.\n- Continue review the rest of the code\n\n# Beta Checklist\n\n- [x] ~~Remove `blobRefAwareTypeDefs` from TSON.ts~~ NOT NEEDED — these are required for lazy/eager blob resolution\n- [ ] Unskip lazy mode E2E test and verify it passes\n- [ ] Unskip blobProgress E2E test and verify it passes\n- [ ] David: review & merge PR #2255 (Dexie.js) + PR #75 (dexie-cloud)\n- [ ] Debug logging via `DEXIE_CLOUD_DEBUG` env var / `__DEBUG__` rollup guards (nice-to-have)\n\n# Reviewed files in the ongoing PR\n\n- [v] .github/workflows/publish-ci.yml\n- [v] addons/dexie-cloud/src/sync/BlobSavingQueue.ts\n- [v] addons/dexie-cloud/src/sync/blobResolve.ts\n- [v] addons/dexie-cloud/src/middlewares/blobResolveMiddleware.ts\n  - [v] openCursor\n  - [v] get\n  - [v] getMany\n  - [v] query\n- [v] addons/dexie-cloud/src/DexieCloudAPI.ts\n- [v] addons/dexie-cloud/src/DexieCloudOptions.ts\n- [v] addons/dexie-cloud/src/dexie-cloud-client.ts\n- [v] addons/dexie-cloud/src/overrideParseStoresSpec.ts\n- [v] addons/dexie-cloud/src/sync/eagerBlobDownloader.ts\n- [v] samples/dexie-cloud-todo-app/src/db/TodoDB.ts\n- [v] addons/dexie-cloud/src/sync/blobProgress.ts\n- [v] addons/dexie-cloud/src/sync/syncWithServer.ts ()\n- [v] addons/dexie-cloud/src/sync/applyServerChanges.ts (light)\n- [v] addons/dexie-cloud/src/sync/blobOffloading.ts (extensive)\n- [v] addons/dexie-cloud/src/sync/sync.ts (medium)\n- [v] docs/CI-PUBLISHING.md\n\n# Error handling\n- [ ] eagerBlobDownloader: see line 114 TODO. Depending on error, retry or stop trying. Maybe only retry N times over a time period of T. Right now, it will continue with next object and retry next time over and over.\n\n# Optimize middleware to skip intercepting when not necessary:\n\n- [ ] In applyServerChanges transaction, let syncState reflect which tables that have unresolved blobs, also let this momentarily be reflected in a memory set.\n- [ ] In the end of eager blob downloader, do a transaction that verifies all blobs are resolved and then remove the tables from the sync state. Within that same transaction, reset memory set.\n- [ ] in dexie-cloud-client, on-ready event, populate the memory set from sync state.\n- [ ] In middleware, skip doing the blob dance if the requested table isn't listed in the memory set.\n\n# Refactoring To-dos\n- [v] Rename `$unresolved` to `$hasBlobRefs` as it is more explanatory\n- [x] Rename `$t` → `_bt` and `$hasBlobRefs` → `_hasBlobRefs` (done 2026-03-09)\n- [x] Remove BlobRef-aware TSON type defs — TSON is transparent to BlobRefs (done 2026-03-09)\n- [ ] Move `BlobRef.ts` from `dexie-cloud-common/src/tson/types/` to `dexie-cloud-common/src/blob/` — it has nothing to do with TSON\n- [ ] Consolidate `isBlobRef` into one place in `dexie-cloud-common/src/blob/` — currently duplicated in client (`blobResolve.ts`), server (`BlobRefManager.ts`, `getObjectDiff.ts`, `expandBlobRefsForExport.ts`, `validateImportData.ts`) and E2E tests\n- [ ] Move Blob-offloading code in dexie-cloud-addon into its own sub directory to collect this feature into a single place\n- [ ] Review: `BlobRefContext`/`createBlobRefContext` in dexie-cloud-common — currently unused by server code, verify if still needed or can be removed\n- [ ] Review: `TSONRef` class in dexie-cloud-common — is it still used anywhere now that TSON doesn't revive BlobRefs? If not, remove.\n\n# Beware-ofs\n\n- If exception happens during blob uploading phase in sync.ts, the entire flow will be forgotten even if some blobs were uploaded. Could there be a risk of eternal loop if some object causes a failure and the client keeps uploading blobs over and over with new IDs? If so, can could handle partial failures specifically? For example, to catch each operation in the loop in offloadBlobsInOperations() and if that specific situation occurs only (some succeed and then failure), update the \"XXX_changes\" with the BlobRef entries uploaded so far. Next sync would then continue to retry with the failed ones only. Don't know if this could be a real problem or not. Probably the common case is network failure during upload and a re-upload eternal loop might not even be a problem unless we have a bug in the code triggered by certain data.\n\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/BlobDownloadTracker.ts",
    "content": "import type { DexieCloudDB } from \"../db/DexieCloudDB\";\nimport { BlobRef } from \"./blobResolve\";\nimport { loadCachedAccessToken } from \"./loadCachedAccessToken\";\n\n/**\n * Deduplicates in-flight blob downloads.\n *\n * Both the blob-resolve middleware and the eager blob downloader may\n * try to fetch the same blob concurrently. This tracker ensures each\n * unique blob ref is only downloaded once — subsequent requests for\n * the same ref piggyback on the existing promise.\n *\n * Instantiate once per DexieCloudDB.\n */\nexport class BlobDownloadTracker {\n  private inFlight = new Map<string, Promise<Uint8Array>>();\n  private db: DexieCloudDB;\n\n  constructor (db: DexieCloudDB) {\n    this.db = db;\n  }\n\n  /**\n   * Download a blob, deduplicating concurrent requests for the same ref.\n   *\n   * @param blobRef - The BlobRef to download\n   * @param dbUrl - Base URL for the database (e.g., 'https://mydb.dexie.cloud')\n   */\n  download(blobRef: BlobRef, dbUrl: string): Promise<Uint8Array> {\n    let promise = this.inFlight.get(blobRef.ref);\n    if (!promise) {\n      promise = loadCachedAccessToken(this.db).then(accessToken => {\n        if (!accessToken) throw new Error(\"No access token available for blob download\");\n        return downloadBlob(blobRef, dbUrl, accessToken);\n      }).finally(() => this.inFlight.delete(blobRef.ref));\n      // When the promise settles (either fulfilled or rejected), remove it from the in-flight map\n      this.inFlight.set(blobRef.ref, promise);\n    }\n    return promise;\n  }\n}\n/**\n * Download blob data from server via proxy endpoint.\n * Uses auth header for authentication (same as sync).\n *\n * @param blobRef - The BlobRef to download\n * @param dbUrl - Base URL for the database (e.g., 'https://mydb.dexie.cloud')\n * @param accessToken - Access token for authentication\n */\n\nexport async function downloadBlob(\n  blobRef: BlobRef,\n  dbUrl: string,\n  accessToken: string\n): Promise<Uint8Array> {\n  const downloadUrl = `${dbUrl}/blob/${blobRef.ref}`;\n  const response = await fetch(downloadUrl, {\n    headers: {\n      'Authorization': `Bearer ${accessToken}`\n    }\n  });\n\n  if (!response.ok) {\n    throw new Error(`Failed to download blob ${blobRef.ref}: ${response.status} ${response.statusText}`);\n  }\n\n  const arrayBuffer = await response.arrayBuffer();\n  return new Uint8Array(arrayBuffer);\n}\n\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/BlobSavingQueue.ts",
    "content": "/**\n * BlobSavingQueue - Queues resolved blobs for saving back to IndexedDB\n * \n * Uses setTimeout(fn, 0) instead of queueMicrotask to completely isolate\n * from Dexie's Promise.PSD context. This prevents the save operation\n * from inheriting any ongoing transaction.\n * \n * Each blob is saved atomically using downCore transaction with the specific\n * keyPath to avoid race conditions with other property changes.\n */\n\nimport Dexie, { UpdateSpec } from 'dexie';\nimport { isBlobRef, ResolvedBlob } from './blobResolve';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { TXExpandos } from '../types/TXExpandos';\n\ninterface QueuedBlob {\n  tableName: string;\n  primaryKey: any;\n  resolvedBlobs: ResolvedBlob[];\n}\n\nexport class BlobSavingQueue {\n  private queue: QueuedBlob[] = [];\n  private isProcessing = false;\n  private db: DexieCloudDB;\n\n  constructor(db: DexieCloudDB) {\n    this.db = db;\n  }\n\n  /**\n   * Queue a resolved blob for saving.\n   * Only the specific blob property will be updated atomically.\n   */\n  saveBlobs(tableName: string, primaryKey: any, resolvedBlobs: ResolvedBlob[]): void {\n    this.queue.push({ tableName, primaryKey, resolvedBlobs });\n    this.startConsumer();\n  }\n\n  /**\n   * Start the consumer if not already processing.\n   * Uses setTimeout(fn, 0) to completely break out of any\n   * Dexie transaction context (Promise.PSD).\n   */\n  private startConsumer(): void {\n    if (this.isProcessing) return;\n    \n    this.isProcessing = true;\n    \n    // Use setTimeout to completely isolate from Dexie's PSD context\n    // queueMicrotask would risk inheriting the current transaction\n    setTimeout(() => {\n      this.processQueue();\n    }, 0);\n  }\n\n  /**\n   * Process all queued blobs.\n   * Runs in a completely isolated context (no inherited transaction).\n   * Uses atomic updates to avoid race conditions.\n   */\n  private processQueue(): void {\n    const item = this.queue.shift();\n    if (!item) {\n      this.isProcessing = false;\n      return;\n    }\n\n    // Atomic update of just the blob property\n    this.db.transaction('rw', item.tableName, (tx) => {\n      const trans = tx.idbtrans as IDBTransaction & TXExpandos;\n      trans.disableChangeTracking = true; // Don't regard this as a change for sync purposes\n      trans.disableAccessControl = true; // Bypass any access control checks since this is an internal operation\n      trans.disableBlobResolve = true; // Custom flag to skip blob resolve middleware during this transaction\n      const updateSpec: UpdateSpec<any> = {};\n      for (const blob of item.resolvedBlobs) {\n        updateSpec[blob.keyPath] = blob.data;\n      }\n      tx.table(item.tableName).update(item.primaryKey, obj => {\n        // Check that object still has the same unresolved blob refs before applying update (i.e. it hasn't been modified since we read it)\n        for (const blob of item.resolvedBlobs) {\n          // Verify atomicity - none of the blob properties has been modified since we read it. If any of them was modified, skip updating this item to avoid overwriting user changes.\n          const currentValue = Dexie.getByKeyPath(obj, blob.keyPath);\n          if (currentValue === undefined) {\n            // Blob property was removed - skip updating this blob\n            continue;\n          }\n          if (!isBlobRef(currentValue)) {\n            // Blob property was modified to a non-blob-ref value - skip updating this blob\n            continue;\n          }\n          if (currentValue.ref !== blob.ref) {\n            // Blob property was modified - skip updating this blob\n            return; // Stop. Another items has been queued to fully fix the object.\n          }\n          Dexie.setByKeyPath(obj, blob.keyPath, blob.data);\n        }\n        delete obj._hasBlobRefs; // Clear the _hasBlobRefs marker if all refs was resolved.\n      });\n    }).catch((error) => {\n      console.error(`Error saving resolved blobs on ${item.tableName}:${item.primaryKey}:`, error);\n    }).finally(() => {\n      // Process next item in the queue\n      return this.processQueue();\n    });\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/DEXIE_CLOUD_SYNCER_ID.ts",
    "content": "\nexport const DEXIE_CLOUD_SYNCER_ID = 'dexie-cloud-syncer';\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/LocalSyncWorker.ts",
    "content": "import { Subscription } from 'rxjs';\nimport { syncIfPossible } from './syncIfPossible';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { SECONDS } from '../helpers/date-constants';\nimport { DexieCloudOptions } from '../DexieCloudOptions';\nimport { DexieCloudSchema } from 'dexie-cloud-common';\n\nexport function LocalSyncWorker(\n  db: DexieCloudDB,\n  cloudOptions: DexieCloudOptions,\n  cloudSchema: DexieCloudSchema\n) {\n  let localSyncEventSubscription: Subscription | null = null;\n  let cancelToken = { cancelled: false };\n  let nextRetryTime = 0;\n  let syncStartTime = 0;\n\n  function syncAndRetry(retryNum = 1) {\n    // Use setTimeout() to get onto a clean stack and\n    // break free from possible active transaction:\n    setTimeout(() => {\n      const purpose = pullSignalled ? 'pull' : 'push';\n      syncStartTime = Date.now();\n      syncIfPossible(db, cloudOptions, cloudSchema, {\n        cancelToken,\n        retryImmediatelyOnFetchError: true, // workaround for \"net::ERR_NETWORK_CHANGED\" in chrome.\n        purpose,\n      }).then(()=>{\n        if (cancelToken.cancelled) {\n          stop();\n        } else {\n          if (pullSignalled || pushSignalled) {\n            // If we have signalled for more sync, do it now.\n            pullSignalled = false;\n            pushSignalled = false;\n            return syncAndRetry();\n          }\n        }\n        ongoingSync = false;\n        nextRetryTime = 0;\n        syncStartTime = 0;\n      }).catch((error: unknown) => {\n        console.error('error in syncIfPossible()', error);\n        if (cancelToken.cancelled) {\n          stop();\n          ongoingSync = false;\n          nextRetryTime = 0;\n          syncStartTime = 0;\n        } else if (retryNum < 5) {\n          // Mimic service worker sync event but a bit more eager: retry 4 times\n          // * first retry after 20 seconds\n          // * second retry 40 seconds later\n          // * third retry 5 minutes later\n          // * last retry 15 minutes later\n          const retryIn = [0, 20, 40, 300, 900][retryNum] * SECONDS\n          nextRetryTime = Date.now() + retryIn;\n          syncStartTime = 0;\n          setTimeout(\n            () => syncAndRetry(retryNum + 1),\n            retryIn\n          );\n        } else {\n          ongoingSync = false;\n          nextRetryTime = 0;\n          syncStartTime = 0;\n        }\n      });\n    }, 0);\n  }\n\n  let pullSignalled = false;\n  let pushSignalled = false;\n  let ongoingSync = false;\n  const consumer = (purpose: 'pull' | 'push') =>{\n    if (cancelToken.cancelled) return;\n    if (purpose === 'pull') {\n      pullSignalled = true;\n    }\n    if (purpose === 'push') {\n      pushSignalled = true;\n    }\n    if (ongoingSync) {\n      if (nextRetryTime) {\n        console.debug(`Sync is paused until ${new Date(nextRetryTime).toISOString()} due to error in last sync attempt`);\n      } else if (syncStartTime > 0 && Date.now() - syncStartTime > 20 * SECONDS) {\n        console.debug(`An existing sync operation is taking more than 20 seconds. Will resync when done.`)\n      }\n      return;\n    }\n    ongoingSync = true;\n    syncAndRetry();\n  };\n\n  const start = () => {\n    // Sync eagerly whenever a change has happened (+ initially when there's no syncState yet)\n    // This initial subscribe will also trigger an sync also now.\n    console.debug('Starting LocalSyncWorker', db.localSyncEvent['id']);\n    localSyncEventSubscription = db.localSyncEvent.subscribe(({ purpose }) => {\n      consumer(purpose || 'pull');\n    });\n  };\n\n  const stop = () => {\n    console.debug('Stopping LocalSyncWorker');\n    cancelToken.cancelled = true;\n    if (localSyncEventSubscription) localSyncEventSubscription.unsubscribe();\n  };\n\n  return {\n    start,\n    stop,\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/SyncRequiredError.ts",
    "content": "export class SyncRequiredError extends Error {\n  name = \"SyncRequiredError\";\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/applyServerChanges.ts",
    "content": "import { DexieCloudDB } from '../db/DexieCloudDB';\nimport Dexie from 'dexie';\nimport { bulkUpdate } from '../helpers/bulkUpdate';\nimport { DBOperationsSet } from 'dexie-cloud-common';\nimport { hasBlobRefs } from './blobResolve';\n\n/**\n * If the incoming value contains BlobRefs (e.g. offloaded strings or binaries),\n * mark it with _hasBlobRefs = 1 so the blobResolveMiddleware will resolve them\n * on the next read.\n */\nfunction markIfHasBlobRefs(obj: unknown): void {\n  if (\n    obj !== null &&\n    typeof obj === 'object' &&\n    (obj as any).constructor === Object &&\n    hasBlobRefs(obj)\n  ) {\n    (obj as any)._hasBlobRefs = 1;\n  }\n}\n\n\nexport async function applyServerChanges(\n  changes: DBOperationsSet<string>,\n  db: DexieCloudDB\n) {\n  console.debug('Applying server changes', changes, Dexie.currentTransaction);\n  for (const { table: tableName, muts } of changes) {\n    if (!db.dx._allTables[tableName]) {\n      console.debug(\n        `Server sent changes for table ${tableName} that we don't have. Ignoring.`\n      );\n      continue;\n    }\n    const table = db.table(tableName);\n    const { primaryKey } = table.core.schema;\n    const keyDecoder = (key: string) => {\n      switch (key[0]) {\n        case '[':\n          // Decode JSON array\n          if (key.endsWith(']'))\n            try {\n              // On server, array keys are transformed to JSON string representation\n              return JSON.parse(key);\n            } catch {}\n          return key;\n        case '#':\n          // Decode private ID (do the opposite from what's done in encodeIdsForServer())\n          if (key.endsWith(':' + db.cloud.currentUserId)) {\n            return key.substr(\n              0,\n              key.length - db.cloud.currentUserId.length - 1\n            );\n          }\n          return key;\n        default:\n          return key;\n      }\n    };\n    for (const mut of muts) {\n      const keys = mut.keys.map(keyDecoder);\n      switch (mut.type) {\n        case 'insert':\n          mut.values.forEach(markIfHasBlobRefs);\n          if (primaryKey.outbound) {\n            await table.bulkAdd(mut.values, keys);\n          } else {\n            keys.forEach((key, i) => {\n              // Make sure inbound keys are consistent\n              Dexie.setByKeyPath(mut.values[i], primaryKey.keyPath!, key);\n            });\n            await table.bulkAdd(mut.values);\n          }\n          break;\n        case 'upsert':\n          mut.values.forEach(markIfHasBlobRefs);\n          if (primaryKey.outbound) {\n            await table.bulkPut(mut.values, keys);\n          } else {\n            keys.forEach((key, i) => {\n              // Make sure inbound keys are consistent\n              Dexie.setByKeyPath(mut.values[i], primaryKey.keyPath!, key);\n            });\n            await table.bulkPut(mut.values);\n          }\n          break;\n        case 'modify':\n          if (keys.length === 1) {\n            await table.update(keys[0], mut.changeSpec);\n          } else {\n            await table.where(':id').anyOf(keys).modify(mut.changeSpec);\n          }\n          break;\n        case 'update':\n          await bulkUpdate(table, keys, mut.changeSpecs);\n          break;\n        case 'delete':\n          await table.bulkDelete(keys);\n          break;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/blobOffloading.test.ts",
    "content": "// Unit test for shouldOffloadBlob behavior\n// Verifies: Blob/File always offloaded; ArrayBuffer/TypedArray use 4KB threshold\n\nimport { shouldOffloadBlob } from '../../src/sync/blobOffloading';\n\nfunction assert(cond: boolean, msg: string) {\n  if (!cond) throw new Error('FAIL: ' + msg);\n  console.log('OK: ' + msg);\n}\n\n// Blob - always offloaded regardless of size\nassert(shouldOffloadBlob(new Blob([])), 'empty Blob (0 bytes) is offloaded');\nassert(shouldOffloadBlob(new Blob(['x'])), '1-byte Blob is offloaded');\nassert(shouldOffloadBlob(new Blob([new Uint8Array(1024)])), '1KB Blob is offloaded');\nassert(shouldOffloadBlob(new Blob([new Uint8Array(8192)])), '8KB Blob is offloaded');\n\n// File - always offloaded\nassert(shouldOffloadBlob(new File(['x'], 'test.txt')), '1-byte File is offloaded');\n\n// ArrayBuffer - threshold at 4096\nassert(!shouldOffloadBlob(new ArrayBuffer(1)), '1-byte ArrayBuffer is NOT offloaded');\nassert(!shouldOffloadBlob(new ArrayBuffer(4095)), '4095-byte ArrayBuffer is NOT offloaded');\nassert(shouldOffloadBlob(new ArrayBuffer(4096)), '4096-byte ArrayBuffer IS offloaded');\nassert(shouldOffloadBlob(new ArrayBuffer(8192)), '8KB ArrayBuffer IS offloaded');\n\n// Uint8Array - threshold at 4096\nassert(!shouldOffloadBlob(new Uint8Array(1)), '1-byte Uint8Array is NOT offloaded');\nassert(shouldOffloadBlob(new Uint8Array(4096)), '4096-byte Uint8Array IS offloaded');\n\n// Primitives - never offloaded\nassert(!shouldOffloadBlob(null), 'null is not offloaded');\nassert(!shouldOffloadBlob('string'), 'string is not offloaded');\nassert(!shouldOffloadBlob(42), 'number is not offloaded');\nassert(!shouldOffloadBlob({}), 'plain object is not offloaded');\n\nconsole.log('\\nAll tests passed!');\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/blobOffloading.ts",
    "content": "/**\n * Blob Offloading for Dexie Cloud\n * \n * Handles uploading large blobs to blob storage before sync,\n * and resolving BlobRefs when reading from the database.\n */\n\nimport { newId, DBOperationsSet, DBOperation } from 'dexie-cloud-common';\nimport { BlobRef, BlobRefOrigType, isBlobRef as isBlobRefFromResolve } from './blobResolve';\n\n// Blobs >= 4KB are offloaded to blob storage\nconst BLOB_OFFLOAD_THRESHOLD = 4096;\n\n// Default max string length before offloading (32KB characters)\nexport const DEFAULT_MAX_STRING_LENGTH = 32768;\n\n// Cache: once we know the server doesn't support blob storage, skip future uploads.\n// Maps databaseUrl → boolean (true = supported, false = not supported).\nconst blobEndpointSupported = new Map<string, boolean>();\n\n// Re-export BlobRef type\nexport type { BlobRef, BlobRefOrigType };\n\n// Re-export isBlobRef from blobResolve\nexport const isBlobRef = isBlobRefFromResolve;\n\n/**\n * Cross-realm type detection helpers (performance-optimized)\n * \n * When code runs in different JavaScript realms (e.g., Service Worker context),\n * `instanceof` checks can fail because each realm has its own global constructors.\n * We use Object.prototype.toString which works reliably across realms.\n * \n * Performance considerations (this is a hot path - every property is checked):\n * - Early return for primitives via typeof\n * - Static Set for O(1) TypedArray tag lookup\n * - Single typeTag call per check\n */\n\n// TypedArray/DataView tags for size check\nconst ARRAYBUFFER_VIEW_TAGS = new Set([\n  'Int8Array', 'Uint8Array', 'Uint8ClampedArray',\n  'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array',\n  'Float32Array', 'Float64Array', 'BigInt64Array', 'BigUint64Array',\n  'DataView'\n]);\n\n// Static Set for O(1) lookup of binary type tags\nconst BINARY_TYPE_TAGS = new Set([\n  'Blob',\n  'File',\n  'ArrayBuffer',\n  ...ARRAYBUFFER_VIEW_TAGS,\n]);\n\n/**\n * Get the [[Class]] internal property via Object.prototype.toString\n */\nfunction getTypeTag(value: unknown): string {\n  return Object.prototype.toString.call(value).slice(8, -1);\n}\n\n/**\n * Get the original type name for a value\n */\nfunction getOrigType(value: Blob | ArrayBuffer | ArrayBufferView): BlobRefOrigType {\n  const tag = getTypeTag(value);\n  if (tag === 'Blob' || tag === 'File') return 'Blob';\n  if (tag === 'ArrayBuffer') return 'ArrayBuffer';\n  return tag as BlobRefOrigType;\n}\n\n/**\n * Check if a value should be offloaded to blob storage\n * Performance-optimized for hot path traversal.\n */\nexport function shouldOffloadBlob(value: unknown): value is Blob | ArrayBuffer | ArrayBufferView {\n  // Fast path: primitives (most common case)\n  // typeof returns: \"string\", \"number\", \"boolean\", \"undefined\", \"symbol\", \"bigint\", \"function\", \"object\"\n  const t = typeof value;\n  if (t !== 'object' || value === null) return false;\n  \n  // Get type tag once (cross-realm safe)\n  const tag = getTypeTag(value);\n  \n  // Quick check: is this even a binary type?\n  if (!BINARY_TYPE_TAGS.has(tag)) return false;\n  \n  // Blob/File: always offload regardless of size.\n  // This ensures blobs are never stored inline in IndexedDB, which avoids\n  // issues with synchronous blob reading (e.g. in service workers where\n  // XMLHttpRequest is unavailable — see #2182).\n  if (tag === 'Blob' || tag === 'File') {\n    return true;\n  }\n  // ArrayBuffer/TypedArray/DataView: only offload above threshold\n  if (tag === 'ArrayBuffer') {\n    return (value as ArrayBuffer).byteLength >= BLOB_OFFLOAD_THRESHOLD;\n  }\n  // TypedArray or DataView\n  return (value as ArrayBufferView).byteLength >= BLOB_OFFLOAD_THRESHOLD;\n}\n\n/**\n * Upload a blob to the blob storage endpoint\n */\nexport async function uploadBlob(\n  databaseUrl: string,\n  getCachedAccessToken: () => Promise<string | null>,\n  blob: Blob | ArrayBuffer | ArrayBufferView\n): Promise<BlobRef | null> {\n  const accessToken = await getCachedAccessToken();\n  if (!accessToken) {\n    throw new Error('Failed to load access token for blob upload');\n  }\n\n  const blobId = newId();\n  // URL format: {databaseUrl}/blob/{blobId}\n  const url = `${databaseUrl}/blob/${blobId}`;\n  \n  let body: Blob | ArrayBuffer;\n  let contentType: string;\n  let size: number;\n  const origType = getOrigType(blob);\n  \n  // Use type tag for cross-realm compatible checks\n  const tag = getTypeTag(blob);\n  if (tag === 'Blob' || tag === 'File') {\n    body = blob as Blob;\n    contentType = (blob as Blob).type || 'application/octet-stream';\n    size = (blob as Blob).size;\n  } else if (tag === 'ArrayBuffer') {\n    body = blob as ArrayBuffer;\n    contentType = 'application/octet-stream';\n    size = (blob as ArrayBuffer).byteLength;\n  } else if (ARRAYBUFFER_VIEW_TAGS.has(tag)) {\n    // ArrayBufferView (TypedArray or DataView) - create a proper ArrayBuffer copy\n    const view = blob as ArrayBufferView;\n    const arrayBuffer = new ArrayBuffer(view.byteLength);\n    new Uint8Array(arrayBuffer).set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));\n    body = arrayBuffer;\n    contentType = 'application/octet-stream';\n    size = view.byteLength;\n  } else {\n    throw new Error(`Unsupported blob type: ${tag}`);\n  }\n  \n  // Add content type as query param for the server to store\n  const uploadUrl = `${url}?ct=${encodeURIComponent(contentType)}`;\n  \n  const response = await fetch(uploadUrl, {\n    method: 'PUT',\n    headers: {\n      'Authorization': `Bearer ${accessToken}`,\n      'Content-Type': contentType,\n    },\n    body,\n  });\n  \n  if (!response.ok) {\n    if (response.status === 404 || response.status === 405) {\n      // Server doesn't support blob storage endpoint — fall back to inline storage.\n      // This happens when a new client connects to an older server (pre-3.0).\n      return null;\n    }\n    throw new Error(`Failed to upload blob: ${response.status} ${response.statusText}`);\n  }\n  \n  // The server returns the ref with version prefix (e.g., \"1:blobId\")\n  const result = await response.json();\n  \n  // Return BlobRef with server's ref (includes version) and original type preserved in _bt\n  return {\n    _bt: origType,\n    ref: result.ref,\n    size: size,\n    ...(origType === 'Blob' ? { ct: contentType } : {}) // Only include content type for Blobs\n  };\n}\n\nexport async function offloadBlobsAndMarkDirty(\n  obj: unknown,\n  databaseUrl: string,\n  getCachedAccessToken: () => Promise<string | null>,\n  maxStringLength = DEFAULT_MAX_STRING_LENGTH\n): Promise<unknown> {\n  const dirtyFlag = { dirty: false };\n  const result = await offloadBlobs(obj, databaseUrl, getCachedAccessToken, maxStringLength, dirtyFlag);  \n  // Mark the object as dirty for sync if any blobs were offloaded\n  if (dirtyFlag.dirty && typeof result === 'object' && result !== null && result.constructor === Object) {\n    (result as any)._hasBlobRefs = 1;\n  }\n  \n  return result;\n}\n\n/**\n * Recursively scan an object for large blobs and upload them\n * Returns a new object with blobs replaced by BlobRefs\n */\nexport async function offloadBlobs(\n  obj: unknown,\n  databaseUrl: string,\n  getCachedAccessToken: () => Promise<string | null>,\n  maxStringLength = DEFAULT_MAX_STRING_LENGTH,\n  dirtyFlag = { dirty: false },\n  visited = new WeakSet()\n): Promise<unknown> {\n  if (obj === null || obj === undefined) {\n    return obj;\n  }\n  \n  // Check if this is a long string that should be offloaded\n  if (typeof obj === 'string' && obj.length > maxStringLength && maxStringLength !== Infinity) {\n    if (blobEndpointSupported.get(databaseUrl) === false) {\n      return obj;\n    }\n    const blob = new Blob([obj], { type: 'text/plain;charset=utf-8' });\n    const blobRef = await uploadBlob(databaseUrl, getCachedAccessToken, blob);\n    if (blobRef === null) {\n      blobEndpointSupported.set(databaseUrl, false);\n      return obj;\n    }\n    blobEndpointSupported.set(databaseUrl, true);\n    dirtyFlag.dirty = true;\n    // Mark as string type so it's resolved back to string, not Blob\n    return {\n      ...blobRef,\n      _bt: 'string',\n    };\n  }\n\n  // Check if this is a blob that should be offloaded\n  if (shouldOffloadBlob(obj)) {\n    if (blobEndpointSupported.get(databaseUrl) === false) {\n      // Server known to not support blob storage — keep inline\n      return obj;\n    }\n    const blobRef = await uploadBlob(databaseUrl, getCachedAccessToken, obj);\n    if (blobRef === null) {\n      // Server doesn't support blob storage — keep original inline\n      blobEndpointSupported.set(databaseUrl, false);\n      return obj;\n    }\n    blobEndpointSupported.set(databaseUrl, true);\n    dirtyFlag.dirty = true;\n    return blobRef;\n  }\n  \n  if (typeof obj !== 'object') {\n    return obj;\n  }\n  \n  // Avoid circular references - check BEFORE processing\n  if (visited.has(obj)) {\n    return obj;\n  }\n  visited.add(obj);\n  \n  // Handle arrays\n  if (Array.isArray(obj)) {\n    const result: unknown[] = [];\n    for (const item of obj) {\n      result.push(await offloadBlobs(item, databaseUrl, getCachedAccessToken, maxStringLength, dirtyFlag, visited));\n    }\n    return result;\n  }\n    \n  // Traverse plain objects (POJO-like) - use prototype check since IndexedDB\n  // may return objects where constructor !== Object\n  const proto = Object.getPrototypeOf(obj);\n  if (proto !== Object.prototype && proto !== null) {\n    return obj;\n  }\n  \n  const result: Record<string, unknown> = {};\n  for (const [key, value] of Object.entries(obj)) {\n    result[key] = await offloadBlobs(value, databaseUrl, getCachedAccessToken, maxStringLength, dirtyFlag, visited);\n  }\n  return result;\n}\n\n/**\n * Process a DBOperationsSet and offload any large blobs\n * Returns a new DBOperationsSet with blobs replaced by BlobRefs\n */\nexport async function offloadBlobsInOperations(\n  operations: DBOperationsSet,\n  databaseUrl: string,\n  getCachedAccessToken: () => Promise<string | null>,\n  maxStringLength = DEFAULT_MAX_STRING_LENGTH\n): Promise<DBOperationsSet> {\n  const result: DBOperationsSet = [];\n  \n  for (const tableOps of operations) {\n    const processedMuts: DBOperation[] = [];\n    \n    for (const mut of tableOps.muts) {\n      const processedMut = await offloadBlobsInOperation(mut, databaseUrl, getCachedAccessToken, maxStringLength);\n      processedMuts.push(processedMut);\n    }\n    \n    result.push({\n      table: tableOps.table,\n      muts: processedMuts,\n    });\n  }\n  \n  return result;\n}\n\nasync function offloadBlobsInOperation(\n  op: DBOperation,\n  databaseUrl: string,\n  getCachedAccessToken: ()=> Promise<string | null>,\n  maxStringLength = DEFAULT_MAX_STRING_LENGTH\n): Promise<DBOperation> {\n  switch (op.type) {\n    case 'insert':\n    case 'upsert': {\n      const processedValues = await Promise.all(\n        op.values.map(value => offloadBlobsAndMarkDirty(value, databaseUrl, getCachedAccessToken, maxStringLength))\n      );\n      return {\n        ...op,\n        values: processedValues,\n      };\n    }\n    \n    case 'update': {\n      const processedChangeSpecs = await Promise.all(\n        op.changeSpecs.map(spec => offloadBlobsAndMarkDirty(spec, databaseUrl, getCachedAccessToken, maxStringLength))\n      );\n      return {\n        ...op,\n        changeSpecs: processedChangeSpecs as { [keyPath: string]: any }[],\n      };\n    }\n    \n    case 'modify': {\n      const processedChangeSpec = await offloadBlobsAndMarkDirty(op.changeSpec, databaseUrl, getCachedAccessToken, maxStringLength);\n      return {\n        ...op,\n        changeSpec: processedChangeSpec as { [keyPath: string]: any },\n      };\n    }\n    \n    case 'delete':\n      // No blobs in delete operations\n      return op;\n    \n    default:\n      return op;\n  }\n}\n\n/**\n * Check if there are any large blobs in the operations that need offloading\n * This is a quick check to avoid unnecessary processing\n */\nexport function hasLargeBlobsInOperations(operations: DBOperationsSet, maxStringLength = DEFAULT_MAX_STRING_LENGTH): boolean {\n  for (const tableOps of operations) {\n    for (const mut of tableOps.muts) {\n      if (hasLargeBlobsInOperation(mut, maxStringLength)) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nfunction hasLargeBlobsInOperation(op: DBOperation, maxStringLength: number): boolean {\n  switch (op.type) {\n    case 'insert':\n    case 'upsert':\n      return op.values.some(value => hasLargeBlobs(value, maxStringLength));\n    case 'update':\n      return op.changeSpecs.some(spec => hasLargeBlobs(spec, maxStringLength));\n    case 'modify':\n      return hasLargeBlobs(op.changeSpec, maxStringLength);\n    default:\n      return false;\n  }\n}\n\nfunction hasLargeBlobs(obj: unknown, maxStringLength: number, visited = new WeakSet()): boolean {\n  if (obj === null || obj === undefined) {\n    return false;\n  }\n  \n  // Check long strings\n  if (typeof obj === 'string' && obj.length > maxStringLength && maxStringLength !== Infinity) {\n    return true;\n  }\n  \n  if (shouldOffloadBlob(obj)) {\n    return true;\n  }\n  \n  if (typeof obj !== 'object') {\n    return false;\n  }\n  \n  // Avoid circular references - check BEFORE processing\n  if (visited.has(obj)) {\n    return false;\n  }\n  visited.add(obj);\n  \n  if (Array.isArray(obj)) {\n    return obj.some(item => hasLargeBlobs(item, maxStringLength, visited));\n  }\n  \n  // Traverse plain objects (POJO-like) - use duck typing since IndexedDB\n  // may return objects where constructor !== Object\n  const proto = Object.getPrototypeOf(obj);\n  if (proto === Object.prototype || proto === null) {\n    return Object.values(obj).some(value => hasLargeBlobs(value, maxStringLength, visited));\n  }\n  \n  return false;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/blobProgress.ts",
    "content": "/**\n * Blob Progress Tracking\n *\n * Uses liveQuery to reactively track unresolved blob refs.\n * Any change to _hasBlobRefs in any syncable table automatically\n * triggers a re-scan — no manual updateBlobProgress() needed.\n */\n\nimport { BehaviorSubject, Observable, combineLatest, from, timer } from 'rxjs';\nimport { map, share } from 'rxjs/operators';\nimport { liveQuery } from 'dexie';\nimport { BlobProgress } from '../DexieCloudAPI';\nimport { isBlobRef, isSerializedTSONRef } from './blobResolve';\nimport { getSyncableTables } from '../helpers/getSyncableTables';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { TSONRef } from 'dexie-cloud-common';\n\n/**\n * Unified reference info for both BlobRef and TSONRef\n */\ninterface RefInfo {\n  ref: string;\n  size: number;\n}\n\n/**\n * BehaviorSubject for the isDownloading flag, controlled by eagerBlobDownloader.\n */\nexport function createDownloadingState(): BehaviorSubject<boolean> {\n  return new BehaviorSubject<boolean>(false);\n}\n\n/**\n * Set downloading state.\n */\nexport function setDownloadingState(\n  downloading$: BehaviorSubject<boolean>,\n  isDownloading: boolean\n): void {\n  if (downloading$.value !== isDownloading) {\n    downloading$.next(isDownloading);\n  }\n}\n\n/**\n * Create a liveQuery-based Observable<BlobProgress>.\n *\n * Combines a liveQuery (blobsRemaining, bytesRemaining) with an external\n * isDownloading flag controlled by the eager downloader.\n */\nexport function observeBlobProgress(\n  db: DexieCloudDB,\n  downloading$: BehaviorSubject<boolean>\n): Observable<BlobProgress> {\n  const blobStats$ = from(liveQuery(async () => {\n    let blobsRemaining = 0;\n    let bytesRemaining = 0;\n\n    const syncedTables = getSyncableTables(db);\n\n    await db.dx.transaction('r', syncedTables, async (tx) => {\n      (tx.idbtrans as any).disableBlobResolve = true;\n\n      for (const table of syncedTables) {\n        const hasIndex = !!table.schema.idxByName['_hasBlobRefs'];\n        if (!hasIndex) continue;\n\n        const unresolvedObjects = await table\n          .where('_hasBlobRefs')\n          .equals(1)\n          .toArray();\n\n        for (const obj of unresolvedObjects) {\n          const blobs = findBlobRefs(obj);\n          blobsRemaining += blobs.length;\n          bytesRemaining += blobs.reduce(\n            (sum, blob) => sum + (blob.size || 0),\n            0\n          );\n        }\n      }\n    });\n\n    return { blobsRemaining, bytesRemaining };\n  }));\n\n  return combineLatest([blobStats$, downloading$]).pipe(\n    map(([stats, isDownloading]) => ({\n      isDownloading: isDownloading && stats.blobsRemaining > 0,\n      blobsRemaining: stats.blobsRemaining,\n      bytesRemaining: stats.bytesRemaining,\n    })),\n    share({ resetOnRefCountZero: () => timer(2000) }) // Keep alive for 2s after last unsubscription to avoid rapid re-subscriptions during UI updates  \n  );\n}\n\n/**\n * Find all unresolved refs (BlobRef or TSONRef) in an object (recursive).\n * Handles both live TSONRef instances and serialized TSONRefs (after IndexedDB).\n */\nfunction findBlobRefs(obj: unknown): RefInfo[] {\n  const refs: RefInfo[] = [];\n\n  function scan(value: unknown): void {\n    if (value === null || value === undefined) return;\n    if (typeof value !== 'object') return;\n\n    if (TSONRef.isTSONRef(value)) {\n      refs.push({ ref: value.ref, size: value.size });\n      return;\n    }\n\n    if (isSerializedTSONRef(value)) {\n      const obj = value as { type: string; ref: string; size: number };\n      refs.push({ ref: obj.ref, size: obj.size });\n      return;\n    }\n\n    if (isBlobRef(value)) {\n      refs.push({ ref: value.ref, size: value.size || 0 });\n      return;\n    }\n\n    if (Array.isArray(value)) {\n      value.forEach(scan);\n    } else if (value.constructor === Object) {\n      Object.values(value).forEach(scan);\n    }\n  }\n\n  scan(obj);\n  return refs;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/blobResolve.ts",
    "content": "import { BlobDownloadTracker } from './BlobDownloadTracker';\n\n/**\n * BlobRef Resolution for Dexie Cloud\n * \n * Handles lazy resolution of BlobRefs when reading from the database.\n * BlobRefs are symbolic references to blobs stored in blob storage.\n * They get resolved on-demand when the object is read.\n * \n * The server sends offloaded binary data in the format:\n * { _bt: 'Uint8Array', ref: '1:blobId', size: 1234 }\n * { _bt: 'Blob', ref: '1:blobId', size: 1234, ct: 'image/png' }\n * \n * The _bt field preserves the original JavaScript type.\n * The ref format is '{version}:{blobId}' where version identifies\n * the storage backend configuration.\n */\n\n\n/**\n * Original type that was offloaded to blob storage.\n * Matches the TSON type names.\n */\nexport type BlobRefOrigType = \n  | 'Blob'\n  | 'ArrayBuffer'\n  | 'Uint8Array'\n  | 'Int8Array'\n  | 'Uint8ClampedArray'\n  | 'Int16Array'\n  | 'Uint16Array'\n  | 'Int32Array'\n  | 'Uint32Array'\n  | 'Float32Array'\n  | 'Float64Array'\n  | 'BigInt64Array'\n  | 'BigUint64Array'\n  | 'DataView'\n  | 'string';\n\n/**\n * BlobRef represents a reference to binary data stored in blob storage.\n * The _bt field contains the original JavaScript type (Uint8Array, Blob, etc.)\n * The presence of 'ref' instead of 'v' indicates this is an offloaded blob.\n */\nexport interface BlobRef {\n  _bt: BlobRefOrigType;\n  ref: string;           // Versioned ref: '{version}:{blobId}'\n  size: number;          // Size in bytes\n  ct?: string;           // Content-type (only for Blob type)\n}\n\n/**\n * Resolved blob with its keyPath for queueing\n */\nexport interface ResolvedBlob {\n  keyPath: string;\n  data: Blob | ArrayBuffer | ArrayBufferView | string;\n  ref: string;\n}\n\n/**\n * Check if a value is a BlobRef (offloaded binary data)\n * A BlobRef has _bt (type), ref (blob ID), but no v (inline data)\n */\nexport function isBlobRef(value: unknown): value is BlobRef {\n  if (typeof value !== 'object' || value === null) return false;\n  const obj = value as any;\n  return (\n    typeof obj._bt === 'string' &&\n    typeof obj.ref === 'string' &&\n    obj.v === undefined  // No inline data = it's a reference\n  );\n}\n\n/**\n * Serialized TSONRef shape (after IndexedDB structured clone).\n * The Symbol is lost but properties remain.\n */\nexport interface SerializedTSONRef {\n  type: string;  // 'Uint8Array', 'Blob', etc.\n  ref: string;   // '1:blobId'\n  size: number;\n  contentType?: string;\n}\n\n/**\n * Check if a value is a serialized TSONRef (after IndexedDB storage)\n * Has 'type' instead of '$t', and no Symbol marker\n */\nexport function isSerializedTSONRef(value: unknown): value is SerializedTSONRef {\n  if (typeof value !== 'object' || value === null) return false;\n  const obj = value as any;\n  return (\n    typeof obj.type === 'string' &&\n    typeof obj.ref === 'string' &&\n    typeof obj.size === 'number' &&\n    obj._bt === undefined  // Not a raw BlobRef\n  );\n}\n\n/**\n * Recursively check if an object contains any BlobRefs\n */\nexport function hasBlobRefs(obj: unknown, visited = new WeakSet()): boolean {\n  if (obj === null || obj === undefined) {\n    return false;\n  }\n\n  if (isBlobRef(obj)) {\n    return true;\n  }\n\n  if (typeof obj !== 'object') {\n    return false;\n  }\n\n  // Avoid circular references - check BEFORE processing\n  if (visited.has(obj)) {\n    return false;\n  }\n  visited.add(obj);\n\n  // Skip special objects that can't contain BlobRefs\n  if (obj instanceof Date || obj instanceof RegExp || obj instanceof Blob) {\n    return false;\n  }\n  if (obj instanceof ArrayBuffer || ArrayBuffer.isView(obj)) {\n    return false;\n  }\n\n  if (Array.isArray(obj)) {\n    return obj.some(item => hasBlobRefs(item, visited));\n  }\n\n  // Only traverse POJOs\n  if (obj.constructor === Object) {\n    return Object.values(obj).some(value => hasBlobRefs(value, visited));\n  }\n\n  return false;\n}\n\n/**\n * Convert downloaded Uint8Array to the original type specified in BlobRef\n */\nexport function convertToOriginalType(\n  data: Uint8Array, \n  ref: BlobRef\n): Blob | ArrayBuffer | ArrayBufferView | string {\n  // String type: decode UTF-8 back to string\n  if (ref._bt === 'string') {\n    return new TextDecoder().decode(data);\n  }\n\n  // Get the underlying ArrayBuffer (handle shared buffer case)\n  const buffer = data.buffer.byteLength === data.byteLength\n    ? data.buffer as ArrayBuffer\n    : data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;\n  \n  switch (ref._bt) {\n    case 'Blob':\n      return new Blob([new Uint8Array(buffer)], { type: ref.ct || '' });\n    case 'ArrayBuffer':\n      return buffer;\n    case 'Uint8Array':\n      return data;\n    case 'Int8Array':\n      return new Int8Array(buffer);\n    case 'Uint8ClampedArray':\n      return new Uint8ClampedArray(buffer);\n    case 'Int16Array':\n      return new Int16Array(buffer);\n    case 'Uint16Array':\n      return new Uint16Array(buffer);\n    case 'Int32Array':\n      return new Int32Array(buffer);\n    case 'Uint32Array':\n      return new Uint32Array(buffer);\n    case 'Float32Array':\n      return new Float32Array(buffer);\n    case 'Float64Array':\n      return new Float64Array(buffer);\n    case 'BigInt64Array':\n      return new BigInt64Array(buffer);\n    case 'BigUint64Array':\n      return new BigUint64Array(buffer);\n    case 'DataView':\n      return new DataView(buffer);\n    default:\n      // Fallback to Uint8Array for unknown types\n      return data;\n  }\n}\n\n/**\n * Recursively resolve all BlobRefs in an object and collect them for queueing.\n * Returns a new object with BlobRefs replaced by their original type data,\n * and populates the resolvedBlobs array with keyPath info for each blob.\n * \n * @param obj - Object to resolve\n * @param dbUrl - Base URL for the database\n * @param accessToken - Access token for blob downloads\n * @param resolvedBlobs - Array to collect resolved blob info\n * @param currentPath - Current property path (for tracking)\n * @param visited - WeakMap for circular reference detection\n */\nexport async function resolveAllBlobRefs(\n  obj: unknown,\n  dbUrl: string,\n  resolvedBlobs: ResolvedBlob[] = [],\n  currentPath: string = '',\n  visited = new WeakMap(),\n  tracker: BlobDownloadTracker\n): Promise<unknown> {\n  if (obj == null) { // null or undefined\n    return obj;\n  }\n\n  // Check if this is a BlobRef - resolve it and track it\n  if (isBlobRef(obj)) {\n    const rawData = await tracker.download(obj, dbUrl);\n    const data = convertToOriginalType(rawData, obj);\n    resolvedBlobs.push({ keyPath: currentPath, data, ref: obj.ref });\n    return data;\n  }\n\n  // Handle arrays\n  if (Array.isArray(obj)) {\n    // Avoid circular references - check and set BEFORE iterating\n    if (visited.has(obj)) {\n      return visited.get(obj);\n    }\n    const result: unknown[] = [];\n    visited.set(obj, result);  // Set before iterating to handle self-references\n    for (let i = 0; i < obj.length; i++) {\n      const itemPath = currentPath ? `${currentPath}.${i}` : `${i}`;\n      result.push(await resolveAllBlobRefs(obj[i], dbUrl, resolvedBlobs, itemPath, visited, tracker));\n    }\n    return result;\n  }\n\n  // Handle POJO objects only (not Date, RegExp, Blob, ArrayBuffer, etc.)\n  if (typeof obj === 'object' && obj.constructor === Object) {\n    // Avoid circular references\n    if (visited.has(obj)) {\n      return visited.get(obj);\n    }\n\n    const result: Record<string, unknown> = {};\n    visited.set(obj, result);\n\n    for (const [propName, value] of Object.entries(obj)) {\n      // Skip the _hasBlobRefs marker itself\n      if (propName === '_hasBlobRefs') {\n        continue;\n      }\n      const propPath = currentPath ? `${currentPath}.${propName}` : propName;\n      result[propName] = await resolveAllBlobRefs(value, dbUrl, resolvedBlobs, propPath, visited, tracker);\n    }\n\n    return result;\n  }\n\n  return obj;\n}\n\n/**\n * Check if an object has unresolved BlobRefs\n */\nexport function hasUnresolvedBlobRefs(obj: unknown): boolean {\n  return (\n    typeof obj === 'object' &&\n    obj !== null &&\n    (obj as any)._hasBlobRefs === 1\n  );\n}\n\n\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/connectWebSocket.ts",
    "content": "import { BehaviorSubject, firstValueFrom, from, Observable, of, throwError, merge } from 'rxjs';\nimport {\n  catchError,\n  combineLatestAll,\n  debounceTime,\n  delay,\n  distinctUntilChanged,\n  filter,\n  map,\n  mergeMap,\n  switchMap,\n  take,\n  tap,\n} from 'rxjs/operators';\nimport { refreshAccessToken } from '../authentication/authenticate';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { PersistedSyncState } from '../db/entities/PersistedSyncState';\nimport { computeRealmSetHash } from '../helpers/computeRealmSetHash';\nimport {\n  userDoesSomething,\n  userIsActive,\n  userIsReallyActive,\n} from '../userIsActive';\nimport {\n  ReadyForChangesMessage,\n  WSConnectionMsg,\n  WSObservable,\n} from '../WSObservable';\nimport { InvalidLicenseError } from '../InvalidLicenseError';\nimport { read } from 'fs';\n\nfunction sleep(ms: number) {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function waitAndReconnectWhenUserDoesSomething(error: Error) {\n  console.error(\n    `WebSocket observable: error but revive when user does some active thing...`,\n    error\n  );\n  // Sleep some seconds...\n  await sleep(3000);\n  // Wait til user does something (move mouse, tap, scroll, click etc)\n  console.debug('waiting for someone to do something');\n  await firstValueFrom(userDoesSomething);\n  console.debug('someone did something!');\n}\n\nexport function connectWebSocket(db: DexieCloudDB) {\n  if (!db.cloud.options?.databaseUrl) {\n    throw new Error(`No database URL to connect WebSocket to`);\n  }\n\n  const readyForChangesMessage = db.messageConsumer.readyToServe.pipe(\n    filter((isReady) => isReady), // When consumer is ready for new messages, produce such a message to inform server about it\n    switchMap(() => db.getPersistedSyncState()), // We need the info on which server revision we are at:\n    filter((syncState) => syncState && syncState.serverRevision), // We wont send anything to server before inital sync has taken place\n    switchMap<PersistedSyncState, Promise<ReadyForChangesMessage>>(async (syncState) => ({\n      // Produce the message to trigger server to send us new messages to consume:\n      type: 'ready',\n      rev: syncState.serverRevision,\n      realmSetHash: await computeRealmSetHash(syncState)\n    } satisfies ReadyForChangesMessage))\n  );\n\n  const messageProducer = merge(\n    readyForChangesMessage,\n    db.messageProducer\n  );\n\n  function createObservable(): Observable<WSConnectionMsg | null> {\n    return db.cloud.persistedSyncState.pipe(\n      filter((syncState) => syncState?.serverRevision), // Don't connect before there's no initial sync performed.\n      take(1), // Don't continue waking up whenever syncState change\n      switchMap((syncState) =>\n        db.cloud.currentUser.pipe(\n          map((userLogin) => [userLogin, syncState] as const)\n        )\n      ),\n      switchMap(([userLogin, syncState]) => {\n        /*if (userLogin.license?.status && userLogin.license.status !== 'ok') {\n          throw new InvalidLicenseError();\n        }*/\n        return userIsReallyActive.pipe(\n          map((isActive) => [isActive ? userLogin : null, syncState] as const)\n        );\n      }),\n      switchMap(([userLogin, syncState]) => {\n        if (userLogin?.isLoggedIn && !syncState?.realms.includes(userLogin.userId!)) {\n          // We're in an in-between state when user is logged in but the user's realms are not yet synced.\n          // Don't make this change reconnect the websocket just yet. Wait till syncState is updated\n          // to iclude the user's realm.\n          return db.cloud.persistedSyncState.pipe(\n            filter((syncState) => syncState?.realms.includes(userLogin!.userId!) || false),\n            take(1),\n            map((syncState) => [userLogin, syncState] as const)\n          );\n        }\n        return new BehaviorSubject([userLogin, syncState] as const);\n      }),\n      switchMap(\n        async ([userLogin, syncState]) =>\n          [userLogin, await computeRealmSetHash(syncState!)] as const\n      ),\n      distinctUntilChanged(([prevUser, prevHash], [currUser, currHash]) => prevUser === currUser && prevHash === currHash ),\n      switchMap(([userLogin, realmSetHash]) => {\n        if (!db.cloud.persistedSyncState?.value) {\n          // Restart the flow if persistedSyncState is not yet available.\n          return createObservable();\n        }\n        // Let server end query changes from last entry of same client-ID and forward.\n        // If no new entries, server won't bother the client. If new entries, server sends only those\n        // and the baseRev of the last from same client-ID.\n        if (userLogin) {\n          return new WSObservable(\n              db,\n              db.cloud.persistedSyncState!.value!.serverRevision,\n              db.cloud.persistedSyncState!.value!.yServerRevision,\n              realmSetHash,\n              db.cloud.persistedSyncState!.value!.clientIdentity,\n              messageProducer,\n              db.cloud.webSocketStatus,\n              userLogin\n            );\n        } else {\n          return from([] as WSConnectionMsg[]);\n        }}),\n      catchError((error) => {\n        if (error?.name === 'TokenExpiredError') {\n          console.debug(\n            'WebSocket observable: Token expired. Refreshing token...'\n          );\n          return of(true).pipe(\n            switchMap(async () => {\n              // Refresh access token\n              const user = await db.getCurrentUser();\n              const refreshedLogin = await refreshAccessToken(\n                db.cloud.options!.databaseUrl,\n                user\n              );\n              // Persist updated access token\n              await db.table('$logins').update(user.userId, {\n                accessToken: refreshedLogin.accessToken,\n                accessTokenExpiration: refreshedLogin.accessTokenExpiration,\n                claims: refreshedLogin.claims,\n                license: refreshedLogin.license,\n                data: refreshedLogin.data\n              });\n            }),\n            switchMap(() => createObservable())\n          );\n        } else {\n          return throwError(()=>error);\n        }\n      }),\n      catchError((error) => {\n        db.cloud.webSocketStatus.next(\"error\");\n        if (error instanceof InvalidLicenseError) {\n          // Don't retry. Just throw and don't try connect again.\n          return throwError(() => error);\n        }\n        return from(waitAndReconnectWhenUserDoesSomething(error)).pipe(\n          switchMap(() => createObservable())\n        );\n      })\n    ) as Observable<WSConnectionMsg | null>;\n  }\n\n  return createObservable().subscribe({\n    next: (msg) => {\n      if (msg) {\n        console.debug('WS got message', msg);\n        db.messageConsumer.enqueue(msg);\n      }\n    },\n    error: (error) => {\n      console.error('WS got error', error);\n    },\n    complete: () => {\n      console.debug('WS observable completed');\n    },\n  });\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/eagerBlobDownloader.ts",
    "content": "/**\n * Eager Blob Downloader\n * \n * Downloads unresolved blobs in the background when blobMode='eager'.\n * Called after sync completes to prefetch blobs for offline access.\n * \n * Progress is tracked automatically via liveQuery in blobProgress.ts —\n * no manual progress reporting needed here.\n */\n\nimport Dexie, { UpdateSpec } from 'dexie';\nimport { BehaviorSubject } from 'rxjs';\nimport { TSONRef, hasTSONRefs } from 'dexie-cloud-common';\nimport {\n  BlobRef,\n  isBlobRef,\n  hasBlobRefs,\n  hasUnresolvedBlobRefs,\n  isSerializedTSONRef,\n  resolveAllBlobRefs,\n  ResolvedBlob,\n} from './blobResolve';\nimport { loadCachedAccessToken } from './loadCachedAccessToken';\nimport { setDownloadingState } from './blobProgress';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { getSyncableTables } from '../helpers/getSyncableTables';\n\n/**\n * Download all unresolved blobs in the background.\n * \n * This is called when blobMode='eager' (default) after sync completes.\n * BlobRef URLs are signed (SAS tokens) so no auth header needed.\n * \n * Each blob is saved atomically using Table.update() to avoid race conditions.\n */\nexport async function downloadUnresolvedBlobs(\n  db: DexieCloudDB,\n  downloading$: BehaviorSubject<boolean>,\n  signal?: AbortSignal\n): Promise<void> {\n  const debugLog = (msg: string) => console.debug(`[dexie-cloud] ${msg}`);\n  \n  debugLog('Eager download: Starting...');\n\n  // Scan for unresolved blobs\n  const syncedTables = getSyncableTables(db);\n  let hasWork = false;\n\n  for (const table of syncedTables) {\n    try {\n      const hasIndex = !!table.schema.idxByName['_hasBlobRefs'];\n      if (!hasIndex) continue;\n      const count = await table.where('_hasBlobRefs').equals(1).count();\n      if (count > 0) {\n        hasWork = true;\n        break;\n      }\n    } catch {\n      // skip\n    }\n  }\n\n  if (!hasWork) {\n    debugLog('Eager download: No blobs remaining, exiting');\n    return;\n  }\n\n  setDownloadingState(downloading$, true);\n\n  try {\n    debugLog(`Eager download: Found ${syncedTables.length} syncable tables: ${syncedTables.map(t => t.name).join(', ')}`);\n  \n    for (const table of syncedTables) {\n      if (signal?.aborted) break;\n\n      try {\n        // Check if table has _hasBlobRefs index\n        const hasIndex = table.schema.indexes.some(idx => idx.name === '_hasBlobRefs');\n        if (!hasIndex) continue;\n\n        // Query objects with _hasBlobRefs marker\n        const unresolvedObjects = await table\n          .where('_hasBlobRefs')\n          .equals(1)\n          .toArray();\n\n        debugLog(`Eager download: Table ${table.name} has ${unresolvedObjects.length} unresolved objects`);\n\n        const databaseUrl = db.cloud.options?.databaseUrl;\n        if (!databaseUrl) throw new Error('Database URL is required to download blobs');\n\n        // Download up to MAX_CONCURRENT blobs in parallel\n        const MAX_CONCURRENT = 6;\n        const primaryKey = table.schema.primKey;\n\n        // Filter to actionable objects first\n        const pending = unresolvedObjects.filter(obj => {\n          if (!hasUnresolvedBlobRefs(obj)) return false;\n          const key = primaryKey.keyPath\n            ? Dexie.getByKeyPath(obj, primaryKey.keyPath as string)\n            : undefined;\n          return key !== undefined;\n        });\n\n        // Process in parallel with concurrency limit\n        let i = 0;\n        const runNext = async (): Promise<void> => {\n          while (i < pending.length) {\n            if (signal?.aborted) return;\n            const obj = pending[i++];\n            const key = Dexie.getByKeyPath(obj, primaryKey.keyPath as string);\n\n            try {\n              // Refresh token per object — cheap (returns cached) but ensures\n              // we pick up renewed tokens during long download sessions.\n              const resolvedBlobs: ResolvedBlob[] = [];\n              await resolveAllBlobRefs(obj, databaseUrl, resolvedBlobs, '', new WeakMap(), db.blobDownloadTracker);\n\n              const updateSpec: UpdateSpec<any> = {\n                _hasBlobRefs: undefined,\n              };\n              for (const blob of resolvedBlobs) {\n                updateSpec[blob.keyPath] = blob.data;\n              }\n\n              debugLog(`Eager download: Updating ${table.name}:${key} with ${resolvedBlobs.length} blobs`);\n              await table.update(key, updateSpec);\n              // liveQuery in blobProgress.ts auto-detects this change\n            } catch (err) {\n              console.error(`Failed to download blobs for ${table.name}:${key}:`, err);\n            }\n          }\n        };\n\n        // Launch up to MAX_CONCURRENT workers\n        const workers: Promise<void>[] = [];\n        for (let w = 0; w < Math.min(MAX_CONCURRENT, pending.length); w++) {\n          workers.push(runNext());\n        }\n        await Promise.all(workers);\n      } catch (err) {\n        // Table might not have _hasBlobRefs index or other issues - skip silently\n      }\n    }\n  } finally {\n    setDownloadingState(downloading$, false);\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/encodeIdsForServer.ts",
    "content": "import Dexie, { DBCoreSchema } from 'dexie';\nimport {\n  DBInsertOperation,\n  DBOperation,\n  DBOperationsSet,\n  DBOpPrimaryKey,\n} from 'dexie-cloud-common';\nimport { UserLogin } from '../db/entities/UserLogin';\n\nexport function encodeIdsForServer(\n  schema: DBCoreSchema,\n  currentUser: UserLogin,\n  changes: DBOperationsSet\n): DBOperationsSet {\n  const rv: DBOperationsSet = [];\n  for (let change of changes) {\n    const { table, muts } = change;\n    const tableSchema = schema.tables.find((t) => t.name === table);\n    if (!tableSchema)\n      throw new Error(\n        `Internal error: table ${table} not found in DBCore schema`\n      );\n    const { primaryKey } = tableSchema;\n    let changeClone = change;\n    muts.forEach((mut, mutIndex) => {\n      const rewriteValues =\n        !primaryKey.outbound &&\n        (mut.type === 'upsert' || mut.type === 'insert');\n      mut.keys.forEach((key, keyIndex) => {\n        if (Array.isArray(key)) {\n          // Server only support string keys. Dexie Cloud client support strings or array of strings.\n          if (changeClone === change)\n            changeClone = cloneChange(change, rewriteValues);\n          const mutClone = changeClone.muts[mutIndex];\n          const rewrittenKey = JSON.stringify(key);\n          mutClone.keys[keyIndex] = rewrittenKey;\n          /* Bug (#1777)\n            We should not rewrite values. It will fail because the key is array and the value is string.\n            Only the keys should be rewritten and it's already done on the server.\n            We should take another round of revieweing how key transformations are being done between\n            client and server and let the server do the key transformations entirely instead now that\n            we have the primary key schema on the server making it possible to do so.\n            if (rewriteValues) {\n            Dexie.setByKeyPath(\n              (mutClone as DBInsertOperation).values[keyIndex],\n              primaryKey.keyPath!,\n              rewrittenKey\n            );\n          }*/\n        } else if (key[0] === '#') {\n          // Private ID - translate!\n          if (changeClone === change)\n            changeClone = cloneChange(change, rewriteValues);\n          const mutClone = changeClone.muts[mutIndex];\n          if (!currentUser.isLoggedIn)\n            throw new Error(\n              `Internal error: Cannot sync private IDs before authenticated`\n            );\n          const rewrittenKey = `${key}:${currentUser.userId}`;\n          mutClone.keys[keyIndex] = rewrittenKey;\n          if (rewriteValues) {\n            Dexie.setByKeyPath(\n              (mutClone as DBInsertOperation).values[keyIndex],\n              primaryKey.keyPath!,\n              rewrittenKey\n            );\n          }\n        }\n      });\n    });\n    rv.push(changeClone);\n  }\n  return rv;\n}\n\nfunction cloneChange(change: DBOperationsSet[number], rewriteValues: boolean) {\n  // clone on demand:\n  return {\n    ...change,\n    muts: rewriteValues\n      ? change.muts.map((m) => {\n          return (m.type === 'insert' || m.type === 'upsert') && m.values\n            ? {\n                ...m,\n                keys: m.keys.slice(),\n                values: m.values.slice(),\n              }\n            : {\n                ...m,\n                keys: m.keys.slice(),\n              };\n        })\n      : change.muts.map((m) => ({ ...m, keys: m.keys.slice() })),\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/extractRealm.ts",
    "content": "import { EntityCommon } from \"../db/entities/EntityCommon\";\n\nexport function extractRealm(obj: EntityCommon) {\n  return obj?.realmId;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/getLatestRevisionsPerTable.ts",
    "content": "import { DBOperationsSet } from 'dexie-cloud-common';\n\nexport function getLatestRevisionsPerTable(\n  clientChangeSet: DBOperationsSet,\n  lastRevisions = {} as { [table: string]: number; }) {\n  for (const { table, muts } of clientChangeSet) {\n    const lastRev = muts.length > 0 ? muts[muts.length - 1].rev : null;\n    lastRevisions[table] = lastRev || lastRevisions[table] || 0;\n  }\n  return lastRevisions;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/getTablesToSyncify.ts",
    "content": "import { getSyncableTables } from \"../helpers/getSyncableTables\";\nimport { DexieCloudDB } from \"../db/DexieCloudDB\";\nimport { PersistedSyncState } from \"../db/entities/PersistedSyncState\";\n\nexport function getTablesToSyncify(db: DexieCloudDB, syncState: PersistedSyncState | undefined) {\n  const syncedTables = syncState?.syncedTables || [];\n  const syncableTables = getSyncableTables(db);\n  const tablesToSyncify = syncableTables.filter(\n    (tbl) => !syncedTables.includes(tbl.name)\n  );\n  return tablesToSyncify;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/isOnline.ts",
    "content": "/* Need this because navigator.onLine seems to say \"false\" when it is actually online.\n  This function relies initially on navigator.onLine but then uses online and offline events\n  which seem to be more reliable.\n*/\nexport let isOnline = false;\nif (typeof self !== 'undefined' && typeof navigator !== 'undefined') {\n  isOnline = navigator.onLine;\n  self.addEventListener('online', ()=>isOnline = true);\n  self.addEventListener('offline', ()=>isOnline = false);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/isSyncNeeded.ts",
    "content": "import { DexieCloudDB } from \"../db/DexieCloudDB\";\nimport { sync } from \"./sync\";\n\nexport async function isSyncNeeded(db: DexieCloudDB) {\n  return db.cloud.options?.databaseUrl && db.cloud.schema\n    ? await sync(db, db.cloud.options, db.cloud.schema, {justCheckIfNeeded: true})\n    : false;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/listClientChanges.ts",
    "content": "import { PropModification, Table, UpdateSpec } from 'dexie';\nimport { getTableFromMutationTable } from '../helpers/getTableFromMutationTable';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { DBOperation, DBOperationsSet, DBUpdateOperation } from 'dexie-cloud-common';\nimport { flatten } from '../helpers/flatten';\n\nexport async function listClientChanges(\n  mutationTables: Table[],\n  db: DexieCloudDB,\n  { since = {} as { [table: string]: number }, limit = Infinity } = {}\n): Promise<DBOperationsSet> {\n  const allMutsOnTables = await Promise.all(\n    mutationTables.map(async (mutationTable) => {\n      const tableName = getTableFromMutationTable(mutationTable.name);\n      const lastRevision = since[tableName];\n\n      let query = lastRevision\n        ? mutationTable.where('rev').above(lastRevision)\n        : mutationTable;\n\n      if (limit < Infinity) query = query.limit(limit);\n\n      let muts: DBOperation[] = await query.toArray();\n\n      muts = canonicalizeToUpdateOps(muts);\n\n      muts = removeRedundantUpdateOps(muts);\n\n      const rv = muts.map((mut) => ({\n        table: tableName,\n        mut,\n      }));\n\n      return rv;\n    })\n  );\n\n  // Sort by time to get a true order of the operations (between tables)\n  const sorted = flatten(allMutsOnTables).sort((a, b) => a.mut.txid === b.mut.txid\n    ? a.mut.opNo! - b.mut.opNo! // Within same transaction, sort by opNo\n    : a.mut.ts! - b.mut.ts! // Different transactions - sort by timestamp when mutation resolved\n  );\n  const result: DBOperationsSet = [];\n  let currentEntry: {\n    table: string;\n    muts: DBOperation[];\n  } | null = null;\n  let currentTxid: string | null = null;\n  for (const { table, mut } of sorted) {\n    if (\n      currentEntry &&\n      currentEntry.table === table &&\n      currentTxid === mut.txid\n    ) {\n      currentEntry.muts.push(mut);\n    } else {\n      currentEntry = {\n        table,\n        muts: [mut],\n      };\n      currentTxid = mut.txid!;\n      result.push(currentEntry);\n    }\n  }\n\n  // Filter out those tables that doesn't have any mutations:\n  return result;\n}\n\n\nfunction removeRedundantUpdateOps(muts: DBOperation[]) {\n  const updateCoverage = new Map<string, Array<{ txid: string; updateSpec: UpdateSpec<any>; }>>();\n  for (const mut of muts) {\n    if (mut.type === 'update') {\n      if (mut.keys.length !== 1 || mut.changeSpecs.length !== 1) {\n        continue; // Don't optimize multi-key updates\n      }\n      const strKey = '' + mut.keys[0];\n      const changeSpecs = mut.changeSpecs[0];\n      if (Object.values(changeSpecs).some(v => typeof v === \"object\" && v && \"@@propmod\" in v)) {\n        continue; // Cannot optimize if any PropModification is present\n      }\n      let keyCoverage = updateCoverage.get(strKey);\n      if (keyCoverage) {\n        keyCoverage.push({ txid: mut.txid!, updateSpec: changeSpecs });\n      } else {\n        updateCoverage.set(strKey, [{ txid: mut.txid!, updateSpec: changeSpecs }]);\n      }\n    }\n  }\n  muts = muts.filter(mut => {\n    // Only apply optimization to update mutations that are single-key\n    if (mut.type !== 'update') return true;\n    if (mut.keys.length !== 1 || mut.changeSpecs.length !== 1) return true;\n    \n    // Check if this has PropModifications - if so, skip optimization\n    const changeSpecs = mut.changeSpecs[0];\n    if (Object.values(changeSpecs).some(v => typeof v === \"object\" && v && \"@@propmod\" in v)) {\n      return true; // Cannot optimize if any PropModification is present\n    }\n    \n    // Keep track of properties that aren't overlapped by later transactions\n    const unoverlappedProps = new Set(Object.keys(mut.changeSpecs[0]));\n    const strKey = '' + mut.keys[0];\n    const keyCoverage = updateCoverage.get(strKey);\n    if (!keyCoverage) return true; // No coverage info - cannot optimize\n\n    for (let i = keyCoverage.length - 1; i >= 0; --i) {\n      const { txid, updateSpec } = keyCoverage[i];\n      if (txid === mut.txid) break; // Stop when reaching own txid\n\n\n      // If all changes in updateSpec are covered by all props on all mut.changeSpecs then\n      // txid is redundant and can be removed.\n      for (const keyPath of Object.keys(updateSpec)) {\n        unoverlappedProps.delete(keyPath);\n      }\n    }\n    if (unoverlappedProps.size === 0) {\n      // This operation is completely overlapped by later operations. It can be removed.\n      return false;\n    }\n    return true;\n  });\n  return muts;\n}\n\nfunction canonicalizeToUpdateOps(muts: DBOperation[]) {\n  muts = muts.map(mut => {\n    if (mut.type === 'modify' && mut.criteria.index === null) {\n      // The criteria is on primary key. Convert to an update operation instead.\n      // It is simpler for the server to handle and also more efficient.\n      const updateMut = {\n        ...mut,\n        criteria: undefined,\n        changeSpec: undefined,\n        type: 'update',\n        keys: mut.keys,\n        changeSpecs: [mut.changeSpec],\n      };\n      delete updateMut.criteria;\n      delete updateMut.changeSpec;\n      return updateMut as DBUpdateOperation;\n    }\n\n    return mut;\n  });\n  return muts;\n}\n\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/listSyncifiedChanges.ts",
    "content": "import { UserLogin } from '../db/entities/UserLogin';\nimport { randomString } from '../helpers/randomString';\nimport { EntityCommon } from '../db/entities/EntityCommon';\nimport { Table } from 'dexie';\nimport {\n  DBOperationsSet,\n  DBUpsertOperation,\n  DexieCloudSchema,\n  isValidAtID,\n  isValidSyncableID,\n} from 'dexie-cloud-common';\n\nexport async function listSyncifiedChanges(\n  tablesToSyncify: Table<EntityCommon>[],\n  currentUser: UserLogin,\n  schema: DexieCloudSchema,\n  alreadySyncedRealms?: string[]\n): Promise<DBOperationsSet> {\n  const txid = `upload-${randomString(8)}`;\n  if (currentUser.isLoggedIn) {\n    if (tablesToSyncify.length > 0) {\n      const ignoredRealms = new Set(alreadySyncedRealms || []);\n      const upserts = await Promise.all(\n        tablesToSyncify.map(async (table) => {\n          const { extractKey } = table.core.schema.primaryKey;\n          if (!extractKey) return { table: table.name, muts: [] }; // Outbound tables are not synced.\n\n          const dexieCloudTableSchema = schema[table.name];\n          const query = dexieCloudTableSchema?.generatedGlobalId\n            ? table.filter((item) => {\n                const id = extractKey(item);\n                return (\n                  !ignoredRealms.has(item.realmId || '') &&\n                  //(id[0] !== '#' || !!item.$ts) && // Private obj need no sync if not changed\n                  isValidAtID(extractKey(item), dexieCloudTableSchema?.idPrefix)\n                );\n              })\n            : table.filter((item) => {\n                const id = extractKey(item);\n\n                return (\n                  !ignoredRealms.has(item.realmId || '') &&\n                  //(id[0] !== '#' || !!item.$ts) && // Private obj need no sync if not changed\n                  isValidSyncableID(id)\n                );\n              });\n          const unsyncedObjects = await query.toArray();\n          if (unsyncedObjects.length > 0) {\n            const mut: DBUpsertOperation = {\n              type: 'upsert',\n              values: unsyncedObjects,\n              keys: unsyncedObjects.map(extractKey),\n              userId: currentUser.userId,\n              txid,\n            };\n            return {\n              table: table.name,\n              muts: [mut],\n            };\n          } else {\n            return {\n              table: table.name,\n              muts: [],\n            };\n          }\n        })\n      );\n      return upserts.filter((op) => op.muts.length > 0);\n    }\n  }\n  return [];\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/loadCachedAccessToken.ts",
    "content": "import Dexie from 'dexie';\nimport { loadAccessToken } from '../authentication/authenticate';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { MINUTES } from '../helpers/date-constants';\n\nconst wm = new WeakMap<DexieCloudDB, {accessToken: string, expiration: number}>();\nexport function loadCachedAccessToken(db: DexieCloudDB): Promise<string | null> {\n  let cached = wm.get(db);\n  if (cached && cached.expiration > Date.now() + 5 * MINUTES) {\n    return Promise.resolve(cached.accessToken);\n  }\n  const currentUser = db.cloud.currentUser.value;\n  if (currentUser && currentUser.accessToken && (currentUser.accessTokenExpiration?.getTime() ?? Infinity) > Date.now() + 5 * MINUTES) {\n    wm.set(db, {\n      accessToken: currentUser.accessToken,\n      expiration: currentUser.accessTokenExpiration?.getTime() ?? Infinity\n    });\n    return Promise.resolve(currentUser.accessToken);\n  }\n  return Dexie.ignoreTransaction(() => loadAccessToken(db).then(user => {\n    if (user?.accessToken) {\n      wm.set(db, {\n        accessToken: user.accessToken,\n        expiration: user.accessTokenExpiration?.getTime() ?? Infinity\n      });\n    }\n    return user?.accessToken || null;\n  }));\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/messageConsumerIsReady.ts",
    "content": "import { BehaviorSubject } from \"rxjs\";\n\nexport const messageConsumerIsReady = new BehaviorSubject(false);\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/messagesFromServerQueue.ts",
    "content": "import { BehaviorSubject, firstValueFrom } from 'rxjs';\nimport { filter, take } from 'rxjs/operators';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { WSConnectionMsg } from '../WSObservable';\nimport { triggerSync } from './triggerSync';\nimport Dexie from 'dexie';\nimport { computeRealmSetHash } from '../helpers/computeRealmSetHash';\nimport { DBOperationsSet } from 'dexie-cloud-common';\nimport { getSyncableTables } from '../helpers/getSyncableTables';\nimport { getMutationTable } from '../helpers/getMutationTable';\nimport { listClientChanges } from './listClientChanges';\nimport { filterServerChangesThroughAddedClientChanges } from './sync';\nimport { applyServerChanges } from './applyServerChanges';\nimport { updateBaseRevs } from './updateBaseRevs';\nimport { getLatestRevisionsPerTable } from './getLatestRevisionsPerTable';\nimport { refreshAccessToken } from '../authentication/authenticate';\n\nconst LIMIT_NUM_MESSAGES_PER_TIME = 10; // Allow a maximum of 10 messages per...\nconst TIME_WINDOW = 10_000;             // ...10 seconds.\nconst PAUSE_PERIOD = 1_000;             // Pause for 1 second if reached\n\nexport type MessagesFromServerConsumer = ReturnType<\n  typeof MessagesFromServerConsumer\n>;\n\nexport function MessagesFromServerConsumer(db: DexieCloudDB) {\n  const queue: WSConnectionMsg[] = [];\n  const readyToServe = new BehaviorSubject(true);\n  const event = new BehaviorSubject(null);\n  let isWorking = false;\n\n  let loopDetection = new Array(LIMIT_NUM_MESSAGES_PER_TIME).fill(0);\n\n  event.subscribe(async () => {\n    if (isWorking) return;\n    if (queue.length > 0) {\n      isWorking = true;\n      loopDetection.shift();\n      loopDetection.push(Date.now());\n      readyToServe.next(false);\n      try {\n        await consumeQueue();\n      } finally {\n        if (\n          loopDetection[loopDetection.length - 1] - loopDetection[0] <\n          TIME_WINDOW\n        ) {\n          // Ten loops within 10 seconds. Slow down!\n          // This is a one-time event. Just pause 10 seconds.\n          console.warn(`Slowing down websocket loop for ${PAUSE_PERIOD} milliseconds`);\n          await new Promise((resolve) => setTimeout(resolve, PAUSE_PERIOD));\n        }\n        isWorking = false;\n        readyToServe.next(true);\n      }\n    }\n  });\n\n  function enqueue(msg: WSConnectionMsg) {\n    queue.push(msg);\n    event.next(null);\n  }\n\n  async function consumeQueue() {\n    while (queue.length > 0) {\n      const msg = queue.shift();\n      try {\n        // If the sync worker or service worker is syncing, wait 'til thei're done.\n        // It's no need to have two channels at the same time - even though it wouldnt\n        // be a problem - this is an optimization.\n        await firstValueFrom(\n          db.cloud.syncState.pipe(\n            filter(({ phase }) => phase === 'in-sync' || phase === 'error')\n          )\n        );\n        console.debug('processing msg', msg);\n        const persistedSyncState = db.cloud.persistedSyncState.value;\n        //syncState.\n        if (!msg) continue;\n        switch (msg.type) {\n          case 'token-expired':\n            console.debug(\n              'WebSocket observable: Token expired. Refreshing token...'\n            );\n            const user = db.cloud.currentUser.value;\n            // Refresh access token\n            const refreshedLogin = await refreshAccessToken(\n              db.cloud.options!.databaseUrl,\n              user\n            );\n            // Persist updated access token\n            await db.table('$logins').update(user.userId, {\n              accessToken: refreshedLogin.accessToken,\n              accessTokenExpiration: refreshedLogin.accessTokenExpiration,\n              claims: refreshedLogin.claims,\n              license: refreshedLogin.license,\n              data: refreshedLogin.data,\n            });\n            // Updating $logins will trigger emission of db.cloud.currentUser observable, which\n            // in turn will lead to that connectWebSocket.ts will reconnect the socket with the\n            // new token. So we don't need to do anything more here.\n            break;\n          case 'realm-added':\n            if (\n              !persistedSyncState?.realms?.includes(msg.realm) &&\n              !persistedSyncState?.inviteRealms?.includes(msg.realm)\n            ) {\n              await db.cloud.sync({ purpose: 'pull', wait: true });\n              //triggerSync(db, 'pull');\n            }\n            break;\n          case 'realm-accepted':\n            if (!persistedSyncState?.realms?.includes(msg.realm)) {\n              await db.cloud.sync({ purpose: 'pull', wait: true });\n              //triggerSync(db, 'pull');\n            }\n            break;\n          case 'realm-removed':\n            if (\n              persistedSyncState?.realms?.includes(msg.realm) ||\n              persistedSyncState?.inviteRealms?.includes(msg.realm)\n            ) {\n              await db.cloud.sync({ purpose: 'pull', wait: true });\n              //triggerSync(db, 'pull');\n            }\n            break;\n          case 'realms-changed':\n            //triggerSync(db, 'pull');\n            await db.cloud.sync({ purpose: 'pull', wait: true });\n            break;\n          case 'changes': {\n            console.debug('changes');\n            if (db.cloud.syncState.value?.phase === 'error') {\n              triggerSync(db, 'pull');\n              break;\n            }\n            const didApplyChanges = await db.transaction('rw', db.dx.tables, async (tx) => {\n              // @ts-ignore\n              tx.idbtrans.disableChangeTracking = true;\n              // @ts-ignore\n              tx.idbtrans.disableAccessControl = true;\n              const [schema, syncState, currentUser] = await Promise.all([\n                db.getSchema(),\n                db.getPersistedSyncState(),\n                db.getCurrentUser(),\n              ]);\n              console.debug('ws message queue: in transaction');\n              if (!syncState || !schema || !currentUser) {\n                console.debug('required vars not present', {\n                  syncState,\n                  schema,\n                  currentUser,\n                });\n                return false; // Initial sync must have taken place - otherwise, ignore this.\n              }\n              // Verify again in ACID tx that we're on same server revision.\n              if (msg.baseRev !== syncState.serverRevision) {\n                console.debug(\n                  `baseRev (${msg.baseRev}) differs from our serverRevision in syncState (${syncState.serverRevision})`\n                );\n                // Should we trigger a sync now? No. This is a normal case\n                // when another local peer (such as the SW or a websocket channel on other tab) has\n                // updated syncState from new server information but we are not aware yet. It would\n                // be unnescessary to do a sync in that case. Instead, the caller of this consumeQueue()\n                // function will do readyToServe.next(true) right after this return, which will lead\n                // to a \"ready\" message being sent to server with the new accurate serverRev we have,\n                // so that the next message indeed will be correct.\n                if (\n                  typeof msg.baseRev === 'string' && // v2 format\n                  (typeof syncState.serverRevision === 'bigint' || // v1 format\n                    typeof syncState.serverRevision === 'object') // v1 format old browser\n                ) {\n                  // The reason for the diff seems to be that server has migrated the revision format.\n                  // Do a full sync to update revision format.\n                  // If we don't do a sync request now, we could stuck in an endless loop.\n                  triggerSync(db, 'pull');\n                }\n                return false; // Ignore message\n              }\n              // Verify also that the message is based on the exact same set of realms\n              const ourRealmSetHash = await Dexie.waitFor(\n                // Keep TX in non-IDB work\n                computeRealmSetHash(syncState)\n              );\n              console.debug('ourRealmSetHash', ourRealmSetHash);\n              if (ourRealmSetHash !== msg.realmSetHash) {\n                console.debug('not same realmSetHash', msg.realmSetHash);\n                triggerSync(db, 'pull');\n                // The message isn't based on the same realms.\n                // Trigger a sync instead to resolve all things up.\n                return false;\n              }\n\n              // Get clientChanges\n              let clientChanges: DBOperationsSet = [];\n              if (currentUser.isLoggedIn) {\n                const mutationTables = getSyncableTables(db).map((tbl) =>\n                  db.table(getMutationTable(tbl.name))\n                );\n                clientChanges = await listClientChanges(mutationTables, db);\n                console.debug('msg queue: client changes', clientChanges);\n              }\n              if (msg.changes.length > 0) {\n                const filteredChanges =\n                  filterServerChangesThroughAddedClientChanges(\n                    msg.changes,\n                    clientChanges\n                  );\n\n                //\n                // apply server changes\n                //\n                console.debug(\n                  'applying filtered server changes',\n                  filteredChanges\n                );\n                await applyServerChanges(filteredChanges, db);\n              }\n\n              // Update latest revisions per table in case there are unsynced changes\n              // This can be a real case in future when we allow non-eagery sync.\n              // And it can actually be realistic now also, but very rare.\n              syncState.latestRevisions = getLatestRevisionsPerTable(\n                clientChanges,\n                syncState.latestRevisions\n              );\n\n              syncState.serverRevision = msg.newRev;\n\n              // Update base revs\n              console.debug('Updating baseRefs', syncState.latestRevisions);\n              await updateBaseRevs(\n                db,\n                schema!,\n                syncState.latestRevisions,\n                msg.newRev\n              );\n\n              //\n              // Update syncState\n              //\n              console.debug('Updating syncState', syncState);\n              await db.$syncState.put(syncState, 'syncState');\n              return true;\n            });\n            console.debug('msg queue: done with rw transaction');\n            // Trigger eager blob download for any BlobRefs received via WebSocket.\n            // This mirrors the behavior after normal HTTP sync (syncCompleteEvent).\n            // Only emit if changes were actually applied (not on early returns).\n            if (didApplyChanges && msg.changes.length > 0) {\n              db.syncCompleteEvent.next();\n            }\n            break;\n          }\n        }\n      } catch (error) {\n        console.error(`Error in msg queue`, error);\n      }\n    }\n  }\n\n  return {\n    enqueue,\n    readyToServe,\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/modifyLocalObjectsWithNewUserId.ts",
    "content": "import { Table } from \"dexie\";\nimport { EntityCommon } from \"../db/entities/EntityCommon\";\nimport { UserLogin } from \"../db/entities/UserLogin\";\nimport { Member } from \"../db/entities/Member\";\nimport { UNAUTHORIZED_USER } from \"../authentication/UNAUTHORIZED_USER\";\nimport { Realm } from \"../db/entities/Realm\";\n\nexport async function modifyLocalObjectsWithNewUserId(\n  syncifiedTables: Table<EntityCommon>[],\n  currentUser: UserLogin,\n  alreadySyncedRealms?: string[]) {\n  const ignoredRealms = new Set(alreadySyncedRealms || []);\n  for (const table of syncifiedTables) {\n    if (table.name === \"members\") {\n      // members\n      await table.toCollection().modify((member: Member) => {\n        if (!ignoredRealms.has(member.realmId) && (!member.userId || member.userId === UNAUTHORIZED_USER.userId)) {\n          member.userId = currentUser.userId;\n        }\n      });\n    } else if (table.name === \"roles\") {\n      // roles\n      // No changes needed.\n    } else if (table.name === \"realms\") {\n      // realms\n      await table.toCollection().modify((realm: Realm) => {\n        if (!ignoredRealms.has(realm.realmId) && (realm.owner === undefined || realm.owner === UNAUTHORIZED_USER.userId)) {\n          realm.owner = currentUser.userId;\n        }\n      });\n    } else {\n      // application entities\n      await table.toCollection().modify((obj) => {\n        if (!obj.realmId || !ignoredRealms.has(obj.realmId)) {\n          if (!obj.owner || obj.owner === UNAUTHORIZED_USER.userId)\n            obj.owner = currentUser.userId;\n          if (!obj.realmId || obj.realmId === UNAUTHORIZED_USER.userId) {\n            obj.realmId = currentUser.userId;\n          }\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/myId.ts",
    "content": "import { randomString } from \"../helpers/randomString\";\n\nexport const myId = randomString(16);\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/numUnsyncedMutations.ts",
    "content": "import Dexie, { liveQuery } from \"dexie\";\nimport { getMutationTable } from \"../helpers/getMutationTable\";\nimport { getSyncableTables } from \"../helpers/getSyncableTables\";\nimport { combineLatest, forkJoin, from } from \"rxjs\";\nimport { distinctUntilChanged, filter, map } from \"rxjs/operators\";\nimport { DexieCloudDB } from \"../db/DexieCloudDB\";\n\nexport function getNumUnsyncedMutationsObservable(db: DexieCloudDB) {\n  const syncableTables = getSyncableTables(db);\n  const mutationTables = syncableTables.map((table) =>\n    db.table(getMutationTable(table.name))\n  );\n  const queries = mutationTables.map((mt) => from(liveQuery(() => mt.count())));\n  return forkJoin(queries).pipe(\n    // Compute the sum of all tables' unsynced changes:\n    map((counts) => counts.reduce((x, y) => x + y)),\n    // Swallow false positives - when the number was the same as before:\n    distinctUntilChanged()\n  );\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/old_startSyncingClientChanges.ts",
    "content": "\n    /** TODO:\n     * 1. Convert these unsynced changes into sync mutations by resolving values from ordinaryTables.\n     * 2. Append a convertion of mutReqs[table].muts into sync mutations.\n     * 3. Send all this using fetch\n     * 4. if successful response, delete muts from mutTables by\n     *    1. a new rw-transaction where we start by finding revs above those we generated.\n     *    2. for tables where none such found, clear the table. if some of those found, deleteRange on the table.\n     * \n     * Tankar att följa upp:\n     *  * Varje DOM environmnet får en MutationSyncer\n     *  * Håller koll på huruvida den tror att en sync redan pågår eller inte.\n     *  * Bro\n     * \n     * Leader election\n     *  * IDB persisted value: leaderId\n     *  * Varje browsing-env får en nodeId (random string).\n     *  * db.open: Kolla om det finns en leader. Om inte, sätt dig själv som leader.\n     *  * Leaders: Ha sync consumer uppe.\n     *  * Service worker: Ta ledarskap. Spara även en prop som säger att detta är service worker (för att workaround BroadCastChannel props)\n     *  * Om det finns en non-service worker ledare, försök kontakta den (via BroadCastChannel)\n     *  * Om ledaren inte svarar inom 1 sekund, ta ledarskap.\n     *  * Ledare: Lyssna på ändring av ledarskap och sluta vara ledare i så fall.\n     *  * Följare: Lyssna på ändring av ledarskap så att du alltid kontaktar rätt ledare.\n     *  * När man postar något till non-SW ledaren så ska den ACKa inom 1 sek. Annars, ta ledarskapet.\n     * \n     * \n     * Enklare:\n     *  * Vi har en som leker service worker per window. Två aktiva flikar:\n     *     * Båda vaknar upp och låser tabellen X för skrivning, om ingen annan tagit jobbet, skriver timestamp till X och\n     *       börjar sedan synka. Under tiden för sync, uppdaterar den timestamp kontinuerligt för att signallera att den\n     *       jobbar. När klar, clearar ut timestampen och avslutar jobbet.\n     *       - Om det inte fanns nåt att göra så clearas timestamp ut och man schedulerar inget mer uppvaknande.\n     *     * Den som kom sist ser att annan tagit jobbet inom rimlig timestamp och väljer då att polla igen ifall timestampet\n     *       skulle timat ut.\n     *     * Om jobbaren verkar tajma ut: gör nytt försök från början. Kan leda till att du blir ledare denna gång.\n     *     * Om jobbaren clearar ut timestampen pga avslutat jobb så kan väntaren ädnå för säkerhets-skull göra om'et från början.\n     * \n     * \n     */\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/performGuardedJob.ts",
    "content": "import { DexieCloudDB } from \"../db/DexieCloudDB\";\n\nexport function performGuardedJob<T>(\n  db: DexieCloudDB,\n  jobName: string,\n  job: () => Promise<T>\n): Promise<T> {\n  if (typeof navigator === 'undefined' || !navigator.locks) {\n    // No support for guarding jobs. IE11, node.js, etc.\n    return job();\n  }\n  // @ts-expect-error - LockManager callback type inference issue with generics\n  return navigator.locks.request(db.name + '|' + jobName, job);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/ratelimit.ts",
    "content": "import { DexieCloudDB } from '../db/DexieCloudDB';\n\n// If we get Ratelimit-Limit and Ratelimit-Remaining where Ratelimit-Remaining is below\n// (Ratelimit-Limit / 2), we should delay the next sync by (Ratelimit-Reset / Ratelimit-Remaining)\n// seconds (given that there is a Ratelimit-Reset header).\n\nlet syncRatelimitDelays = new WeakMap<DexieCloudDB, Date>();\n\nexport async function checkSyncRateLimitDelay(db: DexieCloudDB) {\n  const delatMilliseconds = (syncRatelimitDelays.get(db)?.getTime() ?? 0) - Date.now();\n  if (delatMilliseconds > 0) {\n    console.debug(`Stalling sync request ${delatMilliseconds} ms to spare ratelimits`);\n    await new Promise(resolve => setTimeout(resolve, delatMilliseconds));\n  }\n}\n\nexport function updateSyncRateLimitDelays(db: DexieCloudDB, res: Response) {\n  const limit = res.headers.get('Ratelimit-Limit');\n  const remaining = res.headers.get('Ratelimit-Remaining');\n  const reset = res.headers.get('Ratelimit-Reset');\n  if (limit && remaining && reset) {\n    const limitNum = Number(limit);\n    const remainingNum = Math.max(0, Number(remaining));\n    const willResetInSeconds = Number(reset);\n    if (remainingNum < limitNum / 2) {\n      const delay = Math.ceil(willResetInSeconds / (remainingNum + 1));\n      syncRatelimitDelays.set(db, new Date(Date.now() + delay * 1000));\n      console.debug(`Sync ratelimit delay set to ${delay} seconds`);\n    } else {\n      syncRatelimitDelays.delete(db);\n      console.debug(`Sync ratelimit delay cleared`);\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/registerSyncEvent.ts",
    "content": "import { DexieCloudDB } from '../db/DexieCloudDB';\n\n//const hasSW = 'serviceWorker' in navigator;\nlet hasComplainedAboutSyncEvent = false;\n\nexport async function registerSyncEvent(db: DexieCloudDB, purpose: \"push\" | \"pull\") {\n  try {\n    // Send sync event to SW:\n    const sw: ServiceWorkerRegistration & {sync?: any} = await navigator.serviceWorker.ready;\n    if (purpose === \"push\" && sw.sync) {\n      await sw.sync.register(`dexie-cloud:${db.name}`);\n    }\n    if (sw.active) {\n      // Use postMessage for pull syncs and for browsers not supporting sync event (Firefox, Safari).\n      // Also chromium based browsers with sw.sync as a fallback for sleepy sync events not taking action for a while.\n      sw.active.postMessage({\n        type: 'dexie-cloud-sync',\n        dbName: db.name,\n        purpose\n      });\n    } else {\n      throw new Error(`Failed to trigger sync - there's no active service worker`);\n    }\n    return;\n  } catch (e) {\n    if (!hasComplainedAboutSyncEvent) {\n      console.debug(`Dexie Cloud: Could not register sync event`, e);\n      hasComplainedAboutSyncEvent = true;\n    }\n  }\n}\n\nexport async function registerPeriodicSyncEvent(db: DexieCloudDB) {\n  try {\n    // Register periodicSync event to SW:\n    // @ts-ignore\n    const { periodicSync } = await navigator.serviceWorker.ready;\n    if (periodicSync) {\n      try {\n        await periodicSync.register(\n          `dexie-cloud:${db.name}`,\n          db.cloud.options?.periodicSync\n        );\n        console.debug(\n          `Dexie Cloud: Successfully registered periodicsync event for ${db.name}`\n        );\n      } catch (e) {\n        console.debug(`Dexie Cloud: Failed to register periodic sync. Your PWA must be installed to allow background sync.`, e);\n      }\n    } else {\n      console.debug(`Dexie Cloud: periodicSync not supported.`);\n    }\n  } catch (e) {\n    console.debug(\n      `Dexie Cloud: Could not register periodicSync for ${db.name}`,\n      e\n    );\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/sync.ts",
    "content": "import { getMutationTable } from '../helpers/getMutationTable';\nimport { getSyncableTables } from '../helpers/getSyncableTables';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { listSyncifiedChanges } from './listSyncifiedChanges';\nimport { getTablesToSyncify } from './getTablesToSyncify';\nimport { listClientChanges } from './listClientChanges';\nimport { syncWithServer } from './syncWithServer';\nimport { modifyLocalObjectsWithNewUserId } from './modifyLocalObjectsWithNewUserId';\nimport { throwIfCancelled } from '../helpers/CancelToken';\nimport { DexieCloudOptions } from '../DexieCloudOptions';\nimport { BaseRevisionMapEntry } from '../db/entities/BaseRevisionMapEntry';\nimport { getTableFromMutationTable } from '../helpers/getTableFromMutationTable';\nimport {\n  applyOperations,\n  DBKeyMutationSet,\n  DBOperationsSet,\n  DexieCloudSchema,\n  randomString,\n  subtractChanges,\n  SyncResponse,\n  toDBOperationSet,\n} from 'dexie-cloud-common';\nimport { PersistedSyncState } from '../db/entities/PersistedSyncState';\nimport { isOnline } from './isOnline';\nimport { updateBaseRevs } from './updateBaseRevs';\nimport { getLatestRevisionsPerTable } from './getLatestRevisionsPerTable';\nimport { applyServerChanges } from './applyServerChanges';\nimport { checkSyncRateLimitDelay } from './ratelimit';\nimport { listYClientMessagesAndStateVector } from '../yjs/listYClientMessagesAndStateVector';\nimport { applyYServerMessages } from '../yjs/applyYMessages';\nimport { hasLargeBlobsInOperations, offloadBlobsInOperations } from './blobOffloading';\nimport { updateYSyncStates } from '../yjs/updateYSyncStates';\nimport { downloadYDocsFromServer } from '../yjs/downloadYDocsFromServer';\nimport { UpdateSpec } from 'dexie';\nimport { loadCachedAccessToken } from './loadCachedAccessToken';\n\nexport const CURRENT_SYNC_WORKER = 'currentSyncWorker';\n\nexport interface SyncOptions {\n  isInitialSync?: boolean;\n  cancelToken?: { cancelled: boolean };\n  justCheckIfNeeded?: boolean;\n  retryImmediatelyOnFetchError?: boolean;\n  purpose?: 'pull' | 'push';\n}\n\nexport function sync(\n  db: DexieCloudDB,\n  options: DexieCloudOptions,\n  schema: DexieCloudSchema,\n  syncOptions?: SyncOptions\n): Promise<boolean> {\n  return _sync(db, options, schema, syncOptions)\n    .then((result) => {\n      if (!syncOptions?.justCheckIfNeeded) { // && syncOptions?.purpose !== 'push') {\n        db.syncStateChangedEvent.next({\n          phase: 'in-sync',\n        });\n      }\n      return result;\n    })\n    .catch(async (error: any) => {\n      if (syncOptions?.justCheckIfNeeded) return Promise.reject(error); // Just rethrow.\n      console.debug('Error from _sync', {\n        isOnline,\n        syncOptions,\n        error,\n      });\n      if (\n        isOnline &&\n        syncOptions?.retryImmediatelyOnFetchError &&\n        error?.name === 'TypeError' &&\n        /fetch/.test(error?.message)\n      ) {\n        db.syncStateChangedEvent.next({\n          phase: 'error',\n          error,\n        });\n        // Retry again in 500 ms but if it fails again, don't retry.\n        await new Promise((resolve) => setTimeout(resolve, 500));\n        return await sync(db, options, schema, {\n          ...syncOptions,\n          retryImmediatelyOnFetchError: false,\n        });\n      }\n      // Make sure that no matter whether sync() explodes or not,\n      // always update the timestamp. Also store the error.\n      await db.$syncState.update('syncState', {\n        timestamp: new Date(),\n        error: '' + error,\n      });\n      db.syncStateChangedEvent.next({\n        phase: isOnline ? 'error' : 'offline',\n        error: new Error('' + error?.message || error),\n      });\n      return Promise.reject(error);\n    });\n}\n\nasync function _sync(\n  db: DexieCloudDB,\n  options: DexieCloudOptions,\n  schema: DexieCloudSchema,\n  { isInitialSync, cancelToken, justCheckIfNeeded, purpose }: SyncOptions = {\n    isInitialSync: false,\n  }\n): Promise<boolean> {\n  if (!justCheckIfNeeded) {\n    console.debug('SYNC STARTED', { isInitialSync, purpose });\n  }\n  if (!db.cloud.options?.databaseUrl)\n    throw new Error(\n      `Internal error: sync must not be called when no databaseUrl is configured`\n    );\n  const { databaseUrl } = options;\n  const currentUser = await db.getCurrentUser(); // Keep same value across entire sync flow:\n  const tablesToSync = currentUser.isLoggedIn ? getSyncableTables(db) : [];\n\n  const mutationTables = tablesToSync.map((tbl) =>\n    db.table(getMutationTable(tbl.name))\n  );\n\n  // If this is not the initial sync,\n  // go through tables that were previously not synced but should now be according to\n  // logged in state and the sync table whitelist in db.cloud.options.\n  //\n  // Prepare for syncification by modifying locally unauthorized objects:\n  //\n  const persistedSyncState = await db.getPersistedSyncState();\n  const readyForSyncification = currentUser.isLoggedIn;\n  const tablesToSyncify = readyForSyncification\n    ? getTablesToSyncify(db, persistedSyncState)\n    : [];\n  throwIfCancelled(cancelToken);\n  const doSyncify = tablesToSyncify.length > 0;\n\n  if (doSyncify) {\n    if (justCheckIfNeeded) return true;\n    //console.debug('sync doSyncify is true');\n    await db.transaction('rw', tablesToSyncify, async (tx) => {\n      // @ts-ignore\n      tx.idbtrans.disableChangeTracking = true;\n      // @ts-ignore\n      tx.idbtrans.disableAccessControl = true; // TODO: Take care of this flag in access control middleware!\n      await modifyLocalObjectsWithNewUserId(\n        tablesToSyncify,\n        currentUser,\n        persistedSyncState?.realms\n      );\n    });\n    throwIfCancelled(cancelToken);\n  }\n  //\n  // List changes to sync\n  //\n  const [clientChangeSet, syncState, baseRevs, {yMessages, lastUpdateIds}] = await db.transaction(\n    'r',\n    db.tables,\n    async () => {\n      const syncState = await db.getPersistedSyncState();\n      let baseRevs = await db.$baseRevs.toArray();\n      \n      // Resolve #2168\n      baseRevs = baseRevs.filter(br => tablesToSync.some(tbl => tbl.name === br.tableName));\n\n      let clientChanges = await listClientChanges(mutationTables, db);\n      const yResults = await listYClientMessagesAndStateVector(db, tablesToSync);\n      throwIfCancelled(cancelToken);\n      if (doSyncify) {\n        const alreadySyncedRealms = [\n          ...(persistedSyncState?.realms || []),\n          ...(persistedSyncState?.inviteRealms || []),\n        ];\n        const syncificationInserts = await listSyncifiedChanges(\n          tablesToSyncify,\n          currentUser,\n          schema!,\n          alreadySyncedRealms\n        );\n        throwIfCancelled(cancelToken);\n        clientChanges = clientChanges.concat(syncificationInserts);\n        return [clientChanges, syncState, baseRevs, yResults];\n      }\n      return [clientChanges, syncState, baseRevs, yResults];\n    }\n  );\n\n  const pushSyncIsNeeded = clientChangeSet.some((set) =>\n    set.muts.some((mut) => mut.keys.length > 0)\n  ) || yMessages.some(m => m.type === 'u-c');\n  if (justCheckIfNeeded) {\n    console.debug('Sync is needed:', pushSyncIsNeeded);\n    return pushSyncIsNeeded;\n  }\n  if (purpose === 'push' && !pushSyncIsNeeded) {\n    // The purpose of this request was to push changes\n    return false;\n  }\n\n  const latestRevisions = getLatestRevisionsPerTable(\n    clientChangeSet,\n    syncState?.latestRevisions\n  );\n\n  const clientIdentity = syncState?.clientIdentity || randomString(16);\n\n  //\n  // Offload large blobs to blob storage before sync\n  //\n  let processedChangeSet = clientChangeSet;\n  const maxStringLength = db.cloud.options?.maxStringLength ?? 32768;\n  const hasLargeBlobs = hasLargeBlobsInOperations(clientChangeSet, maxStringLength);\n  if (hasLargeBlobs) {\n    processedChangeSet = await offloadBlobsInOperations(\n      clientChangeSet,\n      databaseUrl,\n      () => loadCachedAccessToken(db),\n      maxStringLength\n    );\n  }\n\n  //\n  // Push changes to server\n  //\n  throwIfCancelled(cancelToken);\n  const res = await syncWithServer(\n    processedChangeSet,\n    yMessages,\n    syncState,\n    baseRevs,\n    db,\n    databaseUrl,\n    schema,\n    clientIdentity,\n    currentUser\n  );\n  console.debug('Sync response', res);\n\n  //\n  // Apply changes locally and clear old change entries:\n  //\n  const {done, newSyncState} = await db.transaction('rw', db.tables, async (tx) => {\n    // @ts-ignore\n    tx.idbtrans.disableChangeTracking = true;\n    // @ts-ignore\n    tx.idbtrans.disableAccessControl = true; // TODO: Take care of this flag in access control middleware!\n\n    // Update db.cloud.schema from server response.\n    // Local schema MAY include a subset of tables, so do not force all tables into local schema.\n    for (const tableName of Object.keys(schema)) {\n      if (res.schema[tableName]) {\n        // Write directly into configured schema. This code can only be executed alone.\n        schema[tableName] = res.schema[tableName];\n      }\n    }\n    await db.$syncState.put(schema, 'schema');\n\n    // List mutations that happened during our exchange with the server:\n    const addedClientChanges = await listClientChanges(mutationTables, db, {\n      since: latestRevisions,\n    });\n\n    //\n    // Delete changes now as server has return success\n    // (but keep changes that haven't reached server yet)\n    //\n    for (const mutTable of mutationTables) {\n      const tableName = getTableFromMutationTable(mutTable.name);\n      if (\n        !addedClientChanges.some(\n          (ch) => ch.table === tableName && ch.muts.length > 0\n        )\n      ) {\n        // No added mutations for this table during the time we sent changes\n        // to the server.\n        // It is therefore safe to clear all changes (which is faster than\n        // deleting a range)\n        await Promise.all([\n          mutTable.clear(),\n          db.$baseRevs.where({ tableName }).delete(),\n        ]);\n      } else if (latestRevisions[tableName]) {\n        const latestRev = latestRevisions[tableName] || 0;\n        await Promise.all([\n          mutTable.where('rev').belowOrEqual(latestRev).delete(),\n          db.$baseRevs\n            .where(':id')\n            .between(\n              [tableName, -Infinity],\n              [tableName, latestRev + 1],\n              true,\n              true\n            )\n            .reverse()\n            .offset(1) // Keep one entry (the one mapping muts that came during fetch --> previous server revision)\n            .delete(),\n        ]);\n      } else {\n        // In this case, the mutation table only contains added items after sending empty changeset to server.\n        // We should not clear out anything now.\n      }\n    }\n\n    // Update latestRevisions object according to additional changes:\n    getLatestRevisionsPerTable(addedClientChanges, latestRevisions);\n\n    // Update/add new entries into baseRevs map.\n    // * On tables without mutations since last serverRevision,\n    //   this will update existing entry.\n    // * On tables where mutations have been recorded since last\n    //   serverRevision, this will create a new entry.\n    // The purpose of this operation is to mark a start revision (per table)\n    // so that all client-mutations that come after this, will be mapped to current\n    // server revision.\n    await updateBaseRevs(db, schema, latestRevisions, res.serverRevision);\n\n    const syncState = await db.getPersistedSyncState();\n\n    //\n    // Delete objects from removed realms\n    //\n    await deleteObjectsFromRemovedRealms(db, res, syncState);\n\n    //\n    // Update syncState\n    //\n    const newSyncState: PersistedSyncState = syncState || {\n      syncedTables: [],\n      latestRevisions: {},\n      realms: [],\n      inviteRealms: [],\n      clientIdentity,\n    };\n    if (readyForSyncification) {\n      newSyncState.syncedTables = tablesToSync\n        .map((tbl) => tbl.name)\n        .concat(tablesToSyncify.map((tbl) => tbl.name));\n    }\n    newSyncState.latestRevisions = latestRevisions;\n    newSyncState.remoteDbId = res.dbId;\n    newSyncState.initiallySynced = true;\n    newSyncState.realms = res.realms;\n    newSyncState.inviteRealms = res.inviteRealms;\n    newSyncState.serverRevision = res.serverRevision;\n    newSyncState.yServerRevision = res.serverRevision;\n    newSyncState.timestamp = new Date();\n    delete newSyncState.error;\n\n    const filteredChanges = filterServerChangesThroughAddedClientChanges(\n      res.changes,\n      addedClientChanges\n    );\n\n    //\n    // apply server changes\n    //\n    await applyServerChanges(filteredChanges, db);\n\n    if (res.yMessages) {\n      //\n      // apply yMessages\n      //\n      const {receivedUntils, resyncNeeded, yServerRevision} = await applyYServerMessages(res.yMessages, db);\n      if (yServerRevision) {\n        newSyncState.yServerRevision = yServerRevision;\n      }\n\n      //\n      // update Y SyncStates\n      //\n      await updateYSyncStates(lastUpdateIds, receivedUntils, db);\n\n      if (resyncNeeded) {\n        newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...\n      }\n    }\n\n    //\n    // Update regular syncState\n    //\n    db.$syncState.put(newSyncState, 'syncState');\n\n    return {\n      done: addedClientChanges.length === 0,\n      newSyncState\n    };\n  });\n  if (!done) {\n    console.debug('MORE SYNC NEEDED. Go for it again!');\n    await checkSyncRateLimitDelay(db);\n    return await _sync(db, options, schema, { isInitialSync, cancelToken });\n  }\n  const usingYProps = Object.values(schema).some(tbl => tbl.yProps?.length);\n  const serverSupportsYprops = !!res.yMessages;\n  if (usingYProps && serverSupportsYprops) {\n    try {\n      await downloadYDocsFromServer(db, databaseUrl, newSyncState);\n    } catch (error) {\n      console.error('Failed to download Yjs documents from server', error);\n    }\n  }\n  console.debug('SYNC DONE', { isInitialSync });\n  db.syncCompleteEvent.next();\n  return false; // Not needed anymore\n}\n\nasync function deleteObjectsFromRemovedRealms(\n  db: DexieCloudDB,\n  res: SyncResponse,\n  syncState: PersistedSyncState | undefined\n) {\n  const deletedRealms = new Set<string>();\n  const rejectedRealms = new Set<string>();\n  const previousRealmSet = syncState ? syncState.realms : [];\n  const previousInviteRealmSet = syncState ? syncState.inviteRealms : [];\n  const updatedRealmSet = new Set(res.realms);\n  const updatedTotalRealmSet = new Set(res.realms.concat(res.inviteRealms));\n  for (const realmId of previousRealmSet) {\n    if (!updatedRealmSet.has(realmId)) {\n      rejectedRealms.add(realmId);\n      if (!updatedTotalRealmSet.has(realmId)) {\n        deletedRealms.add(realmId);\n      }\n    }\n  }\n  for (const realmId of previousInviteRealmSet.concat(previousRealmSet)) {\n    if (!updatedTotalRealmSet.has(realmId)) {\n      deletedRealms.add(realmId);\n    }\n  }\n  if (deletedRealms.size > 0 || rejectedRealms.size > 0) {\n    const tables = getSyncableTables(db);\n    for (const table of tables) {\n      let realmsToDelete = ['realms', 'members', 'roles'].includes(table.name)\n        ? deletedRealms // These tables should spare rejected ones.\n        : rejectedRealms; // All other tables shoudl delete rejected+deleted ones\n      if (realmsToDelete.size === 0) continue;\n      if (\n        table.schema.indexes.some(\n          (idx) =>\n            idx.keyPath === 'realmId' ||\n            (Array.isArray(idx.keyPath) && idx.keyPath[0] === 'realmId')\n        )\n      ) {\n        // There's an index to use:\n        //console.debug(`REMOVAL: deleting all ${table.name} where realmId anyOf `, JSON.stringify([...realmsToDelete]));\n        await table\n          .where('realmId')\n          .anyOf([...realmsToDelete])\n          .delete();\n      } else {\n        // No index to use:\n        //console.debug(`REMOVAL: deleting all ${table.name} where realmId is any of `, JSON.stringify([...realmsToDelete]), realmsToDelete.size);\n        await table\n          .filter((obj) => !!obj?.realmId && realmsToDelete.has(obj.realmId))\n          .delete();\n      }\n    }\n  }\n  if (rejectedRealms.size > 0 && syncState?.yDownloadedRealms) {\n    // Remove rejected/deleted realms from yDownloadedRealms because of the following use case:\n    // 1. User becomes added to the realm\n    // 2. User syncs and all documents of the realm is downloaded (downloadYDocsFromServer.ts)\n    // 3. User leaves the realm and all docs are deleted locally (built-in-trigger of deleting their rows in this file)\n    // 4. User is yet again added to the realm. At this point, we must make sure the docs are not considered already downloaded.\n    const updateSpec: UpdateSpec<PersistedSyncState> = {};\n    for (const realmId of rejectedRealms) {\n      delete syncState.yDownloadedRealms[realmId];\n    } \n  }\n}\n\nexport function filterServerChangesThroughAddedClientChanges(\n  serverChanges: DBOperationsSet<string>,\n  addedClientChanges: DBOperationsSet\n): DBOperationsSet<string> {\n  const changes: DBKeyMutationSet = {};\n  applyOperations(changes, serverChanges);\n  const localPostChanges: DBKeyMutationSet = {};\n  applyOperations(localPostChanges, addedClientChanges);\n  subtractChanges(changes, localPostChanges);\n  return toDBOperationSet(changes);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/syncIfPossible.ts",
    "content": "import { IS_SERVICE_WORKER } from '../helpers/IS_SERVICE_WORKER';\nimport { performGuardedJob } from './performGuardedJob';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { sync, CURRENT_SYNC_WORKER, SyncOptions } from './sync';\nimport { DexieCloudOptions } from '../DexieCloudOptions';\nimport { assert, DexieCloudSchema } from 'dexie-cloud-common';\nimport { checkSyncRateLimitDelay } from './ratelimit';\n\nconst ongoingSyncs = new WeakMap<\n  DexieCloudDB,\n  { promise: Promise<void>; pull: boolean }\n>();\n\nexport function syncIfPossible(\n  db: DexieCloudDB,\n  cloudOptions: DexieCloudOptions,\n  cloudSchema: DexieCloudSchema,\n  options?: SyncOptions\n) {\n  const ongoing = ongoingSyncs.get(db);\n  if (ongoing) {\n    if (ongoing.pull || options?.purpose === 'push') {\n      console.debug('syncIfPossible(): returning the ongoing sync promise.');\n      return ongoing.promise;\n    } else {\n      // Ongoing sync may never do anything in case there are no outstanding changes\n      // to sync (because its purpose was \"push\" not \"pull\")\n      // Now, however, we are asked to do a sync with the purpose of \"pull\"\n      // We want to optimize here. We must wait for the ongoing to complete\n      // and then, if the ongoing sync never resulted in a sync request,\n      // we must redo the sync.\n\n      // To inspect what is happening in the ongoing request, let's subscribe\n      // to db.cloud.syncState and look for if it is doing any \"pulling\" phase:\n      let hasPullTakenPlace = false;\n      const subscription = db.cloud.syncState.subscribe((syncState) => {\n        if (syncState.phase === 'pulling') {\n          hasPullTakenPlace = true;\n        }\n      });\n      // Ok, so now we are watching. At the same time, wait for the ongoing to complete\n      // and when it has completed, check if we're all set or if we need to redo\n      // the call:\n      return (\n        ongoing.promise\n          // This is a finally block but we are still running tests on\n          // browsers that don't support it, so need to do it like this:\n          .then(() => {\n            subscription.unsubscribe();\n          })\n          .catch((error) => {\n            subscription.unsubscribe();\n            return Promise.reject(error);\n          })\n          .then(() => {\n            if (!hasPullTakenPlace) {\n              // No pull took place in the ongoing sync but the caller had \"pull\" as\n              // an explicit purpose of this call - so we need to redo the call!\n              return syncIfPossible(db, cloudOptions, cloudSchema, options);\n            }\n          })\n      );\n    }\n  }\n\n  const promise = _syncIfPossible();\n  ongoingSyncs.set(db, { promise, pull: options?.purpose !== 'push' });\n  return promise;\n\n  async function _syncIfPossible() {\n    try {\n      // Check if should delay sync due to ratelimit:\n      await checkSyncRateLimitDelay(db);      \n      await performGuardedJob(db, CURRENT_SYNC_WORKER, () =>\n        sync(db, cloudOptions, cloudSchema, options)\n      );\n      ongoingSyncs.delete(db);\n    } catch (error) {\n      ongoingSyncs.delete(db);\n      console.error(`Failed to sync client changes`, error);\n      throw error; // Make sure we rethrow error so that sync event is retried.\n      // I don't think we should setTimout or so here.\n      // Unless server tells us to in some response.\n      // Then we could follow that advice but not by waiting here but by registering\n      // Something that triggers an event listened to in startPushWorker()\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/syncWithServer.ts",
    "content": "import { DexieCloudDB } from '../db/DexieCloudDB';\nimport { PersistedSyncState } from '../db/entities/PersistedSyncState';\nimport { loadAccessToken } from '../authentication/authenticate';\nimport { TSON } from '../TSON';\nimport { getSyncableTables } from '../helpers/getSyncableTables';\nimport { BaseRevisionMapEntry } from '../db/entities/BaseRevisionMapEntry';\nimport { HttpError } from '../errors/HttpError';\nimport {\n  DBOperationsSet,\n  DexieCloudSchema,\n  SyncRequest,\n  SyncResponse,\n  YClientMessage,\n} from 'dexie-cloud-common';\nimport { encodeIdsForServer } from './encodeIdsForServer';\nimport { UserLogin } from '../db/entities/UserLogin';\nimport { updateSyncRateLimitDelays } from './ratelimit';\n//import {BisonWebStreamReader} from \"dreambase-library/dist/typeson-simplified/BisonWebStreamReader\";\n\nexport async function syncWithServer(\n  changes: DBOperationsSet,\n  y: YClientMessage[],\n  syncState: PersistedSyncState | undefined,\n  baseRevs: BaseRevisionMapEntry[],\n  db: DexieCloudDB,\n  databaseUrl: string,\n  schema: DexieCloudSchema | null,\n  clientIdentity: string,\n  currentUser: UserLogin\n): Promise<SyncResponse> {\n  //\n  // Push changes to server using fetch\n  //\n  const headers: HeadersInit = {\n    Accept: 'application/json',\n    'Content-Type': 'application/tson',\n  };\n  const updatedUser = await loadAccessToken(db);\n  /*\n  if (updatedUser?.license && changes.length > 0) {\n    if (updatedUser.license.status === 'expired') {\n      throw new Error(`License has expired`);\n    }\n    if (updatedUser.license.status === 'deactivated') {\n      throw new Error(`License deactivated`);\n    }\n  }\n  */\n  const accessToken = updatedUser?.accessToken;\n  if (accessToken) {\n    headers.Authorization = `Bearer ${accessToken}`;\n  }\n\n  const syncRequest: SyncRequest = {\n    v: 3, // v3 = supports BlobRef\n    dbID: syncState?.remoteDbId,\n    clientIdentity,\n    schema: schema || {},\n    lastPull: syncState\n      ? {\n          serverRevision: syncState.serverRevision!,\n          yServerRevision: syncState.yServerRevision,\n          realms: syncState.realms,\n          inviteRealms: syncState.inviteRealms,\n        }\n      : undefined,\n    baseRevs,\n    changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes),\n    y,\n    dxcv: db.cloud.version\n  };\n  console.debug('Sync request', syncRequest);\n  db.syncStateChangedEvent.next({\n    phase: 'pushing',\n  });\n  const body = TSON.stringify(syncRequest);\n  const res = await fetch(`${databaseUrl}/sync`, {\n    method: 'post',\n    headers,\n    credentials: 'include', // For Arr Affinity cookie only, for better Rate-Limit counting only.\n    body,\n  });\n  //const contentLength = Number(res.headers.get('content-length'));\n  db.syncStateChangedEvent.next({\n    phase: 'pulling',\n  });\n\n  updateSyncRateLimitDelays(db, res);\n\n  if (!res.ok) {\n    throw new HttpError(res);\n  }\n\n  switch (res.headers.get('content-type')) {\n    case 'application/x-bison':\n    case 'application/x-bison-stream':\n      // BISON format deprecated - throw error if server sends it\n      throw new Error('BISON format no longer supported. Server should send application/json.');\n    default:\n    case 'application/json': {\n      const text = await res.text();\n      const syncRes = TSON.parse(text);\n      return syncRes;\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/sync/triggerSync.ts",
    "content": "import { DexieCloudDB } from \"../db/DexieCloudDB\";\nimport { registerSyncEvent } from \"./registerSyncEvent\";\n\nexport function triggerSync(db: DexieCloudDB, purpose: \"push\" | \"pull\") {\n  if (db.cloud.usingServiceWorker) {\n    console.debug('registering sync event');\n    registerSyncEvent(db, purpose);\n  } else {\n    db.localSyncEvent.next({purpose});\n  }\n}"
  },
  {
    "path": "addons/dexie-cloud/src/sync/updateBaseRevs.ts",
    "content": "import { DexieCloudDB } from '../db/DexieCloudDB';\nimport { DexieCloudSchema, SyncResponse } from 'dexie-cloud-common';\n\nexport async function updateBaseRevs(db: DexieCloudDB, schema: DexieCloudSchema, latestRevisions: { [table: string]: number; }, serverRev: any) {\n  await db.$baseRevs.bulkPut(\n    Object.keys(schema)\n      .filter((table) => schema[table].markedForSync)\n      .map((tableName) => {\n        const lastClientRevOnPreviousServerRev = latestRevisions[tableName] || 0;\n        return {\n          tableName,\n          clientRev: lastClientRevOnPreviousServerRev + 1,\n          serverRev,\n        };\n      })\n  );\n  // Clean up baseRevs for tables that do not exist anymore or are no longer marked for sync\n  // Resolve #2168 by also cleaning up baseRevs for tables that are not marked for sync\n  await db.$baseRevs.where('tableName').noneOf(\n    Object.keys(schema).filter((table) => schema[table].markedForSync)\n  ).delete();\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es2015\",\n    \"target\": \"es2020\",\n    \"declaration\": true,\n    \"importHelpers\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"ES2020\", \"DOM\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"sourceMap\": true,\n    \"rootDir\": \".\",\n    \"jsx\": \"react\",\n    \"jsxFactory\": \"h\",\n    \"jsxFragmentFactory\": \"Fragment\",\n    \"downlevelIteration\": true\n  },\n  \"include\": [\"**/*.ts\"],\n  \"references\": [{ \"path\": \"../../../libs/dexie-cloud-common\" }]\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/types/DXCAlert.ts",
    "content": "\nexport type DXCAlert = DXCErrorAlert | DXCWarningAlert | DXCInfoAlert;\n\nexport interface DXCErrorAlert {\n  type: 'error';\n  messageCode: 'INVALID_OTP' | 'INVALID_EMAIL' | 'LICENSE_LIMIT_REACHED' | 'GENERIC_ERROR';\n  message: string;\n  messageParams: { [paramName: string]: string; };\n  /** Optional text that users can copy to clipboard (e.g. a CLI command) */\n  copyText?: string;\n}\n\nexport interface DXCWarningAlert {\n  type: 'warning';\n  messageCode: 'GENERIC_WARNING' | 'LOGOUT_CONFIRMATION';\n  message: string;\n  messageParams: { [paramName: string]: string; };\n  /** Optional text that users can copy to clipboard (e.g. a CLI command) */\n  copyText?: string;\n}\n\nexport interface DXCInfoAlert {\n  type: 'info';\n  messageCode: 'GENERIC_INFO' | 'OTP_SENT';\n  message: string;\n  messageParams: { [paramName: string]: string; };\n  /** Optional text that users can copy to clipboard (e.g. a CLI command) */\n  copyText?: string;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/types/DXCInputField.ts",
    "content": "\nexport type DXCInputField = DXCTextField | DXCPasswordField;\n\nexport interface DXCTextField {\n  type: 'text' | 'email' | 'otp';\n  label?: string;\n  placeholder?: string;\n}\n\nexport interface DXCPasswordField {\n  type: 'password';\n  label?: string;\n  placeholder?: string;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/types/DXCUserInteraction.ts",
    "content": "import { DXCAlert } from './DXCAlert';\nimport { DXCInputField } from './DXCInputField';\n\nexport type DXCUserInteraction =\n  | DXCGenericUserInteraction\n  | DXCEmailPrompt\n  | DXCOTPPrompt\n  | DXCMessageAlert\n  | DXCLogoutConfirmation;\n\n/** A selectable option that can appear in any user interaction.\n * \n * Similar to an HTML `<option>` element:\n * - `name` identifies the field name in the result (like input name attribute)\n * - `value` is the value to return when selected (like option value attribute)\n * - `displayName` is the human-readable label\n * \n * When an option is selected, call `onSubmit({ [option.name]: option.value })`.\n */\nexport interface DXCOption {\n  /** Field name for the result (like HTML input name attribute) */\n  name: string;\n  /** Value to return when selected (like HTML option value attribute) */\n  value: string;\n  /** Human-readable display label */\n  displayName: string;\n  /** URL to an icon image (can be a regular URL or a data: URL for inline images) */\n  iconUrl?: string;\n  /** Optional style hint for the UI (e.g., 'google', 'github', 'microsoft', 'apple', 'otp') */\n  styleHint?: string;\n}\n\nexport interface DXCGenericUserInteraction<Type extends string=\"generic\", TFields extends {[name: string]: DXCInputField} = any> {\n  type: Type;\n  title: string;\n  alerts: DXCAlert[];\n  fields: TFields;\n  /** Optional selectable options. When present, render as clickable buttons.\n   * When user clicks an option, call `onSubmit({ [option.name]: option.value })`.\n   */\n  options?: DXCOption[];\n  submitLabel: string;\n  cancelLabel: string | null;\n  onSubmit: (params: { [P in keyof TFields]: string} ) => void;\n  onCancel: () => void;\n}\n\n/** When the system needs to prompt for an email address for login.\n * \n * May include `options` when social login providers are available.\n * Options should be rendered as clickable buttons above the email field.\n */\nexport interface DXCEmailPrompt {\n  type: 'email';\n  title: string;\n  alerts: DXCAlert[];\n  fields: {\n    email: {\n      type: 'text';\n      placeholder: string;\n    };\n  };\n  /** Optional OAuth provider options. Render as clickable buttons. */\n  options?: DXCOption[];\n  submitLabel: string;\n  cancelLabel: string;\n  onSubmit: (params: { email: string } | { [paramName: string]: string }) => void;\n  onCancel: () => void;\n}\n\n/** When the system needs to prompt for an OTP code.\n * \n*/\nexport interface DXCOTPPrompt {\n  type: 'otp';\n  title: string;\n  alerts: DXCAlert[];\n  fields: {\n    otp: {\n      type: 'text';\n      label: string;\n    }\n  };\n  submitLabel: string;\n  cancelLabel: string;\n  onSubmit: (params: { otp: string } | { [paramName: string]: string }) => void;\n  onCancel: () => void;\n}\n\n/** When the system must inform about errors, warnings or information */\nexport interface DXCMessageAlert {\n  type: 'message-alert';\n  title: string;\n  alerts: DXCAlert[];\n  fields: {\n    [name: string]: DXCInputField;\n  };\n  submitLabel: string;\n  cancelLabel?: null;\n  onSubmit: (params: { [paramName: string]: string }) => void;\n  onCancel: () => void;\n}\n\n/** When the system needs confirmation to logout current user when\n * there are unsynced changes that would be lost.\n */\nexport interface DXCLogoutConfirmation {\n  type: 'logout-confirmation';\n  title: string;\n  alerts: DXCAlert[];\n  fields: {\n    [name: string]: DXCInputField;\n  };\n  submitLabel: string;\n  cancelLabel: string;\n  onSubmit: (params: { [paramName: string]: string }) => void;\n  onCancel: () => void;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/types/NewIdOptions.ts",
    "content": "export interface NewIdOptions {\n  colocateWith?: string;\n}"
  },
  {
    "path": "addons/dexie-cloud/src/types/SWMessageEvent.ts",
    "content": "export interface SWMessageEvent extends MessageEvent {\n  waitUntil(promise: Promise<any>): void;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/types/SWSyncEvent.ts",
    "content": "export type SyncEvent = Event & {\n  tag: string;\n  waitUntil (promise: Promise<any>): void;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/types/SyncState.ts",
    "content": "export type SyncStatePhase = \"initial\" | \"not-in-sync\" | \"pushing\" | \"pulling\" | \"in-sync\" | 'error' | 'offline';\nexport type SyncStatus = \"not-started\" | \"connecting\" | \"connected\" | \"disconnected\" | \"error\" | \"offline\";\nexport interface SyncState {\n  status: SyncStatus;\n  phase: SyncStatePhase;\n  progress?: number; // 0..100\n  error?: Error; // If phase === \"error\"\n  license?: 'ok' | 'expired' | 'deactivated';\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/types/TXExpandos.ts",
    "content": "import { DBCoreMutateRequest } from \"dexie\";\nimport { DexieCloudSchema } from \"dexie-cloud-common\";\nimport { UserLogin } from '../db/entities/UserLogin';\n\nexport interface TXExpandos {\n  txid: string;\n  currentUser: UserLogin;\n  schema: DexieCloudSchema\n  disableChangeTracking?: boolean;\n  disableAccessControl?: boolean;\n  disableBlobResolve?: boolean;\n  mutationsAdded?: boolean;\n  opCount: number;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/updateSchemaFromOptions.ts",
    "content": "import { DexieCloudSchema } from \"dexie-cloud-common\";\nimport { DexieCloudOptions } from \"./DexieCloudOptions\";\n\nexport function updateSchemaFromOptions(schema?: DexieCloudSchema | null, options?: DexieCloudOptions | null) {\n  if (schema && options) {\n    if (options.unsyncedTables) {\n      for (const tableName of options.unsyncedTables) {\n        if (schema[tableName]) {\n          schema[tableName].markedForSync = false;\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "addons/dexie-cloud/src/userIsActive.ts",
    "content": "import { BehaviorSubject, fromEvent, merge, of } from 'rxjs';\nimport {\n  debounceTime,\n  delay,\n  distinctUntilChanged,\n  filter,\n  map,\n  skip,\n  startWith,\n  switchMap,\n  tap,\n} from 'rxjs/operators';\n\nconst USER_INACTIVITY_TIMEOUT = 180_000; // 3 minutes\nconst ACTIVE_WAIT_TIME = 0; // For now, it's nicer to react instantly on user activity\nconst INACTIVE_WAIT_TIME = 20_000;\n\n// This observable will be emitted to later down....\nexport const userIsActive = new BehaviorSubject<boolean>(true);\n\n// A refined version that waits before changing state:\n// * Wait another INACTIVE_WAIT_TIME before accepting that the user is inactive.\n//   Reason 1: Spare resources - no need to setup the entire websocket flow when\n//             switching tabs back and forth.\n//   Reason 2: Less flickering for the end user when switching tabs back and forth.\n// * Wait another ACTIVE_WAIT_TIME before accepting that the user is active.\n//   Possible reason to have a value here: Sparing resources if users often temporary click the tab\n//   for just a short time.\nexport const userIsReallyActive = new BehaviorSubject<boolean>(true);\nuserIsActive\n  .pipe(\n    switchMap((isActive) => {\n      //console.debug('SyncStatus: DUBB: isActive changed to', isActive);\n      return isActive\n        ? ACTIVE_WAIT_TIME\n          ? of(true).pipe(delay(ACTIVE_WAIT_TIME))\n          : of(true)\n        : INACTIVE_WAIT_TIME\n        ? of(false).pipe(delay(INACTIVE_WAIT_TIME))\n        : of(false);}\n    ),\n    distinctUntilChanged()\n  )\n  .subscribe(userIsReallyActive);\n\n//\n// First create some corner-stone observables to build the flow on\n//\n\n// document.onvisibilitychange:\nexport const visibilityStateIsChanged =\n  typeof document !== 'undefined'\n    ? fromEvent(document, 'visibilitychange')\n    : of({});\n\n// document.onvisibilitychange makes document hidden:\nexport const documentBecomesHidden = visibilityStateIsChanged.pipe(\n  filter(() => document.visibilityState === 'hidden')\n);\n\n// document.onvisibilitychange makes document visible\nexport const documentBecomesVisible = visibilityStateIsChanged.pipe(\n  filter(() => document.visibilityState === 'visible')\n);\n\n// Any of various user-activity-related events happen:\nexport const userDoesSomething =\n  typeof window !== 'undefined'\n    ? merge(\n        documentBecomesVisible,\n        fromEvent(window, 'mousedown'),\n        fromEvent(window, 'mousemove'),\n        fromEvent(window, 'keydown'),\n        fromEvent(window, 'wheel'),\n        fromEvent(window, 'touchmove')\n      )\n    : of({});\n\nif (typeof document !== 'undefined') {\n  //\n  // Now, create a final observable and start subscribing to it in order\n  // to make it emit values to userIsActive BehaviourSubject (which is the\n  // most important global hot observable we have here)\n  //\n  // Live test: https://jsitor.com/LboCDHgbn\n  //\n  merge(\n    of(true), // Make sure something is always emitted from start\n    documentBecomesHidden, // so that we can eagerly emit false!\n    userDoesSomething\n  )\n    .pipe(\n      // No matter event source, compute whether user is visible using visibilityState:\n      map(() => document.visibilityState === 'visible'),\n      // Make sure to emit it\n      tap((isActive) => {\n        if (userIsActive.value !== isActive) {\n          // Emit new value unless it already has that value\n          userIsActive.next(isActive);\n        }\n      }),\n      // Now, if true was emitted, make sure to set a timeout to emit false\n      // unless new user activity things happen (in that case, the timeout will be cancelled!)\n      switchMap((isActive) =>\n        isActive\n          ? of(0).pipe(\n              delay(USER_INACTIVITY_TIMEOUT - INACTIVE_WAIT_TIME),\n              tap(() => userIsActive.next(false))\n            )\n          : of(0)\n      )\n    )\n    .subscribe(() => {}); // Unless we subscribe nothing will be propagated to userIsActive observable\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/verifyConfig.ts",
    "content": "import Dexie from \"dexie\";\nimport { DexieCloudOptions } from \"./DexieCloudOptions\";\n\nexport function verifyConfig({databaseUrl}: DexieCloudOptions = {databaseUrl: \"\"}) {\n  if (!databaseUrl) {\n    // Allow not providing databaseURL! Instead, when URL at last is provided,\n    // verify after initial handshake that our locally generated schema is compatible\n    // with the one from the cloud service. Also check in case we had been connected\n    // to another database URL, that we are connecting to the same database ID.\n    \n    // Clients that are configurable for database url must also be configurable for database name!\n  }\n}"
  },
  {
    "path": "addons/dexie-cloud/src/verifySchema.ts",
    "content": "import Dexie from \"dexie\";\nimport { DexieCloudDB } from \"./db/DexieCloudDB\";\n\nexport function verifySchema(db: DexieCloudDB) {\n  for (const table of db.tables) {\n    if (db.cloud.schema?.[table.name]?.markedForSync) {\n      if (table.schema.primKey.auto) {\n        throw new Dexie.SchemaError(\n          `Table ${table.name} is both autoIncremented and synced. ` +\n            `Use db.cloud.configure({unsyncedTables: [${JSON.stringify(\n              table.name\n            )}]}) to blacklist it from sync`\n        );\n      }\n      if (!table.schema.primKey.keyPath) {\n        throw new Dexie.SchemaError(\n          `Table ${table.name} cannot be both synced and outbound. ` +\n            `Use db.cloud.configure({unsyncedTables: [${JSON.stringify(\n              table.name\n            )}]}) to blacklist it from sync`\n        );\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/YDexieCloudSyncState.ts",
    "content": "import type { YSyncState } from 'y-dexie';\nexport interface YDexieCloudSyncState extends YSyncState {\n  //serverRev: string;\n}"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/YTable.ts",
    "content": "import { EntityTable } from \"dexie\";\nimport type { YUpdateRow } from \"y-dexie\";\n\nexport type YTable = EntityTable<YUpdateRow, \"i\">;\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/applyYMessages.ts",
    "content": "import { cmp, InsertType } from 'dexie';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { YServerMessage } from 'dexie-cloud-common';\nimport { DEXIE_CLOUD_SYNCER_ID } from '../sync/DEXIE_CLOUD_SYNCER_ID';\nimport { getUpdatesTable } from './getUpdatesTable';\nimport { DexieYProvider, YSyncState, YUpdateRow } from 'y-dexie';\n\nexport async function applyYServerMessages(\n  yMessages: YServerMessage[],\n  db: DexieCloudDB\n): Promise<{\n  receivedUntils: { [yTable: string]: number };\n  resyncNeeded: boolean;\n  yServerRevision?: string;\n}> {\n  const receivedUntils: { [yTable: string]: number } = {};\n  let resyncNeeded = false;\n  let yServerRevision: string | undefined;\n  for (const m of yMessages) {\n    try {\n      switch (m.type) {\n        case 'u-s': {\n          const utbl = getUpdatesTable(db, m.table, m.prop);\n          if (utbl) {\n            const updateRow: InsertType<YUpdateRow, 'i'> = {\n              k: m.k,\n              u: m.u,\n            };\n            if (m.r) {\n              // @ts-ignore\n              updateRow.r = m.r;\n              yServerRevision = m.r;\n            }\n            receivedUntils[utbl.name] = await utbl.add(updateRow);\n          }\n          break;\n        }\n        case 'u-ack': {\n          const utbl = getUpdatesTable(db, m.table, m.prop);\n          if (utbl) {\n            await db.transaction('rw', utbl, async (tx) => {\n              let syncer = (await tx\n                .table(utbl.name)\n                .get(DEXIE_CLOUD_SYNCER_ID)) as YSyncState | undefined;\n              await tx.table(utbl.name).put({\n                ...(syncer || { i: DEXIE_CLOUD_SYNCER_ID }),\n                unsentFrom: Math.max(syncer?.unsentFrom || 1, m.i + 1),\n              } as YSyncState);\n            });\n          }\n          break;\n        }\n        case 'u-reject': {\n          // Acces control or constraint rejected the update.\n          // We delete it. It's not going to be sent again.\n          // What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.\n          // This is only an issue when the document is open. We could find the open document and\n          // in a perfect world, we should send a reverse update to the open document to undo the change.\n          // See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765\n          console.debug(`Y update rejected. Deleting it.`);\n          const utbl = getUpdatesTable(db, m.table, m.prop);\n          if (!utbl) break;\n          // Delete the rejected update and all local updates since (avoid holes in the CRDT)\n          // and destroy it's open document if there is one.\n          const primaryKey = (await utbl.get(m.i))?.k;\n          if (primaryKey != null) {\n            await db.transaction('rw', utbl, (tx) => {\n              // @ts-ignore\n              tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC\n              return utbl\n                .where('i')\n                .aboveOrEqual(m.i)\n                .filter(\n                  (u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1\n                )\n                .delete();\n            });\n            // Destroy active doc\n            const activeDoc = DexieYProvider.getDocCache(db.dx).find(\n              m.table,\n              primaryKey,\n              m.prop\n            );\n            if (activeDoc) activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it\n          }\n          break;\n        }\n        case 'in-sync': {\n          const doc = DexieYProvider.getDocCache(db.dx).find(\n            m.table,\n            m.k,\n            m.prop\n          );\n          if (doc && !doc.isSynced) {\n            doc.emit('sync', [true, doc]);\n          }\n          break;\n        }\n        case 'y-complete-sync-done': {\n          yServerRevision = m.yServerRev;\n          break;\n        }\n        case 'outdated-server-rev':\n          resyncNeeded = true;\n          break;\n      }\n    } catch (e) {\n      console.error(`Failed to apply YMessage`, m, e);\n    }\n  }\n\n  return {\n    receivedUntils,\n    resyncNeeded,\n    yServerRevision,\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/awareness.ts",
    "content": "import type { Awareness } from 'y-protocols/awareness';\n\nexport const awarenessWeakMap = new WeakMap<any, Awareness>();\n\nexport const getDocAwareness = (doc: any) => awarenessWeakMap.get(doc);\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/createYClientUpdateObservable.ts",
    "content": "import { Observable, from, merge, mergeMap, switchMap, tap } from 'rxjs';\nimport { YClientMessage, YUpdateFromClientRequest } from 'dexie-cloud-common';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { flatten } from '../helpers/flatten';\nimport { liveQuery } from 'dexie';\nimport { DEXIE_CLOUD_SYNCER_ID } from '../sync/DEXIE_CLOUD_SYNCER_ID';\nimport { listUpdatesSince } from './listUpdatesSince';\nimport { YDexieCloudSyncState } from './YDexieCloudSyncState';\n\nexport function createYClientUpdateObservable(\n  db: DexieCloudDB\n): Observable<YClientMessage> {\n  const yTableRecords = flatten(\n    db.tables\n      .filter(\n        (table) =>\n          db.cloud.schema?.[table.name]?.markedForSync && table.schema.yProps\n      )\n      .map((table) =>\n        table.schema.yProps!.map((p) => ({\n          table: table.name,\n          ydocProp: p.prop,\n          updatesTable: p.updatesTable,\n        }))\n      )\n  );\n  return merge(\n    ...yTableRecords.map(({ table, ydocProp, updatesTable }) => {\n      // Per updates table (table+prop combo), we first read syncer.unsentFrom,\n      // and then start listening for updates since that number.\n      const yTbl = db.table(updatesTable);\n      return from(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(\n        switchMap((syncer: YDexieCloudSyncState) => {\n          let currentUnsentFrom = syncer?.unsentFrom || 1;\n          return from(\n            liveQuery(async () => {\n              const addedUpdates = await listUpdatesSince(\n                yTbl,\n                currentUnsentFrom\n              );\n              return addedUpdates\n                .filter((update) => update.f && update.f & 1) // Only include local updates\n                .map((update) => {\n                  return {\n                    type: 'u-c',\n                    table,\n                    prop: ydocProp,\n                    k: update.k,\n                    u: update.u,\n                    i: update.i,\n                  } satisfies YUpdateFromClientRequest;\n                });\n            })\n          ).pipe(\n            tap((addedUpdates) => {\n              // Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.\n              // (Before, we did this within the liveQuery, but that caused a bug because\n              // a cancelled emittion of a liveQuery would update the currentUnsentFrom without\n              // emitting anything, leading to that we jumped over some updates. Here we update it\n              // after the liveQuery has emitted its updates)\n              if (addedUpdates.length > 0) {\n                currentUnsentFrom = addedUpdates.at(-1)!.i + 1;\n              }\n            })\n          );\n        })\n      );\n    })\n  ).pipe(\n    // Flatten the array of messages.\n    // If messageProducer emits empty array, nothing is emitted\n    // but if messageProducer emits array of messages, they are\n    // emitted one by one.\n    mergeMap((messages) => messages)\n  );\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/createYHandler.ts",
    "content": "import { cmp, Table } from 'dexie';\nimport type { DexieCloudDB } from '../db/DexieCloudDB';\nimport { awarenessWeakMap } from './awareness';\nimport * as awap from 'y-protocols/awareness';\nimport { DEXIE_CLOUD_SYNCER_ID } from '../sync/DEXIE_CLOUD_SYNCER_ID';\nimport * as Y from 'yjs';\nimport { combineLatest, startWith } from 'rxjs';\nimport { YDocumentOpen } from 'dexie-cloud-common';\nimport { isEagerSyncDisabled } from '../isEagerSyncDisabled';\nimport { PersistedSyncState } from '../db/entities/PersistedSyncState';\nimport { getOpenDocSignal } from './reopenDocSignal';\nimport { DexieYProvider, DexieYDocMeta } from 'y-dexie';\n\nexport function createYHandler(db: DexieCloudDB) {\n  return (provider: import('y-dexie').DexieYProvider) => {\n    const doc = provider.doc;\n    if (!doc) {\n      throw new Error(\n        'Internal error: DexieYProvider.createYHandler called without a doc. This is unexpected.'\n      );\n    }\n    const { parentTable } = doc.meta || ({} as DexieYDocMeta);\n    if (!db.cloud.schema?.[parentTable].markedForSync) {\n      return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.\n    }\n    let awareness: import('y-protocols/awareness').Awareness;\n    Object.defineProperty(provider, 'awareness', {\n      get() {\n        if (awareness) return awareness;\n        awareness = createAwareness(db, doc, provider);\n        awarenessWeakMap.set(doc, awareness);\n        return awareness;\n      },\n    });\n  };\n}\n\nfunction createAwareness(\n  db: DexieCloudDB,\n  doc: Y.Doc,\n  provider: DexieYProvider\n) {\n  const { parentTable, parentId, parentProp, updatesTable } =\n    doc.meta as DexieYDocMeta;\n  const awareness = new awap.Awareness(doc);\n  const reopenDocSignal = getOpenDocSignal(doc);\n\n  awareness.on('update', ({ added, updated, removed }, origin: any) => {\n    // Send the update\n    const changedClients = added.concat(updated).concat(removed);\n    const user = db.cloud.currentUser.value;\n    if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {\n      const update = awap.encodeAwarenessUpdate(awareness!, changedClients);\n      db.messageProducer.next({\n        type: 'aware',\n        table: parentTable,\n        prop: parentProp,\n        k: doc.meta.parentId,\n        u: update,\n      });\n      if (provider.destroyed) {\n        // We're called from awareness.on('destroy') that did\n        // removeAwarenessStates.\n        // It's time to also send the doc-close message that dexie-cloud understands\n        // and uses to stop subscribing for updates and awareness updates and brings\n        // down the cached information in memory on the WS connection for this.\n        db.messageProducer.next({\n          type: 'doc-close',\n          table: parentTable,\n          prop: parentProp,\n          k: doc.meta.parentId,\n        });\n      }\n    }\n  });\n  awareness.on('destroy', () => {\n    // Signal to server that this provider is destroyed (the update event will be triggered, which\n    // in turn will trigger db.messageProducer that will send the message to the server if WS is connected)\n    awap.removeAwarenessStates(\n      awareness!,\n      [doc.clientID],\n      'provider destroyed'\n    );\n  });\n\n  // Open the document on the server\n  (async () => {\n    if (provider.destroyed) return;\n    let connected = false;\n    let currentFlowId = 1;\n    const subscription = combineLatest([\n      db.cloud.webSocketStatus, // Wake up when webSocket status changes\n      reopenDocSignal.pipe(startWith(null)), // Wake up when reopenDocSignal emits\n    ]).subscribe(([wsStatus]) => {\n      if (provider.destroyed) return;\n      // Keep \"connected\" state in a variable so we can check it after async operations\n      connected = wsStatus === 'connected';\n\n      // We are or got connected. Open the document on the server.\n      const user = db.cloud.currentUser.value;\n      if (\n        wsStatus === 'connected' &&\n        user.isLoggedIn &&\n        !isEagerSyncDisabled(db)\n      ) {\n        ++currentFlowId;\n        openDocumentOnServer().catch((error) => {\n          console.warn(`Error catched in createYHandler.ts: ${error}`);\n        });\n      }\n    });\n    // Wait until WebSocket is connected\n    provider.addCleanupHandler(subscription);\n\n    /** Sends an 'doc-open' message to server whenever websocket becomes\n     * connected, or if it is already connected.\n     * The flow is aborted in case websocket is disconnected while querying\n     * information required to compute the state vector. Flow is also\n     * aborted in case document or provider has been destroyed during\n     * the async parts of the task.\n     *\n     * The state vector is only computed from the updates that have occured\n     * after the last full sync - which could very often be zero - in which\n     * case no state vector is sent (then the server already knows us by\n     * revision)\n     *\n     * When server gets the doc-open message, it will authorize us for\n     * whether we are allowed to read / write to this document, and then\n     * keep the cached information in memory on the WS connection for this\n     * particular document, as well as subscribe to updates and awareness updates\n     * from other clients on the document.\n     */\n    async function openDocumentOnServer() {\n      const myFlow = currentFlowId; // So we can abort when a new flow is started\n      const yTbl = db.table(updatesTable);\n      const syncStateTbl = db.$syncState as Table<\n        PersistedSyncState,\n        'syncState'\n      >;\n      const [receivedUntil, yServerRev] = await db.transaction(\n        'r',\n        syncStateTbl,\n        yTbl,\n        async () => {\n          const syncState = await yTbl.get(DEXIE_CLOUD_SYNCER_ID);\n          const persistedSyncState = await syncStateTbl.get('syncState');\n          return [\n            syncState?.receivedUntil || 0,\n            persistedSyncState?.yServerRevision ||\n              persistedSyncState?.serverRevision,\n          ];\n        }\n      );\n\n      // After every await, check if we still should be working on this task.\n      if (provider.destroyed || currentFlowId !== myFlow || !connected) return;\n\n      const docOpenMsg: YDocumentOpen = {\n        type: 'doc-open',\n        table: parentTable,\n        prop: parentProp,\n        k: parentId,\n        serverRev: yServerRev,\n      };\n      const serverUpdatesSinceLastSync = await yTbl\n        .where('i')\n        .between(receivedUntil, Infinity, false)\n        .filter(\n          (update) =>\n            cmp(update.k, parentId) === 0 && // Only updates for this document\n            ((update.f || 0) & 1) === 0 // Don't include local changes\n        )\n        .toArray();\n      // After every await, check if we still should be working on this task.\n      if (provider.destroyed || currentFlowId !== myFlow || !connected) return;\n\n      if (serverUpdatesSinceLastSync.length > 0) {\n        const mergedUpdate = Y.mergeUpdatesV2(\n          serverUpdatesSinceLastSync.map((update) => update.u)\n        );\n        const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);\n        docOpenMsg.sv = stateVector;\n      }\n      db.messageProducer.next(docOpenMsg);\n    }\n  })();\n  return awareness;\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/downloadYDocsFromServer.ts",
    "content": "import {\n  asyncIterablePipeline,\n  consumeChunkedBinaryStream,\n  getFetchResponseBodyGenerator,\n} from 'dexie-cloud-common';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { PersistedSyncState } from '../db/entities/PersistedSyncState';\nimport { TSON } from '../TSON';\nimport { loadAccessToken } from '../authentication/authenticate';\nimport {\n  Decoder,\n  readUint8,\n  readVarString,\n  readAny,\n  readVarUint8Array,\n  hasContent,\n} from 'lib0/decoding';\nimport { getUpdatesTable } from './getUpdatesTable';\nimport Dexie, { InsertType } from 'dexie';\nimport type { YUpdateRow } from 'y-dexie';\n\nconst BINSTREAM_TYPE_REALMID = 1;\nconst BINSTREAM_TYPE_TABLE_AND_PROP = 2;\nconst BINSTREAM_TYPE_DOCUMENT = 3;\n\nexport async function downloadYDocsFromServer(\n  db: DexieCloudDB,\n  databaseUrl: string,\n  { yDownloadedRealms, realms }: PersistedSyncState\n) {\n  if (\n    yDownloadedRealms &&\n    realms &&\n    realms.every((realmId) => yDownloadedRealms[realmId] === '*')\n  ) {\n    return; // Already done!\n  }\n  console.debug('Downloading Y.Docs from added realms');\n  const user = await loadAccessToken(db);\n  const headers: HeadersInit = {\n    'Content-Type': 'application/json',\n    Accept: 'application/octet-stream',\n  };\n  if (user) {\n    headers.Authorization = `Bearer ${user.accessToken}`;\n  }\n  const res = await fetch(`${databaseUrl}/y/download`, {\n    body: TSON.stringify({ downloadedRealms: yDownloadedRealms || {} }),\n    method: 'POST',\n    headers,\n    credentials: 'include',\n  });\n  if (!res.ok) {\n    throw new Error(\n      `Failed to download Yjs documents from server. Status: ${res.status}`\n    );\n  }\n  await asyncIterablePipeline(\n    getFetchResponseBodyGenerator(res),\n    consumeChunkedBinaryStream,\n    consumeDownloadChunks\n  );\n\n  async function* consumeDownloadChunks(chunks: AsyncIterable<Uint8Array>) {\n    let currentRealmId: string | null = null;\n    let currentTable: string | null = null;\n    let currentProp: string | null = null;\n    let docsToInsert: InsertType<YUpdateRow, 'i'>[] = [];\n\n    async function storeCollectedDocs(completedRealm: boolean) {\n      const lastDoc = docsToInsert[docsToInsert.length - 1];\n      if (docsToInsert.length > 0) {\n        if (!currentRealmId || !currentTable || !currentProp) {\n          throw new Error(`Protocol error from ${databaseUrl}/y/download`);\n        }\n        const yTable = getUpdatesTable(db, currentTable, currentProp);\n        if (yTable) {\n          await yTable.bulkAdd(docsToInsert);\n        }\n        docsToInsert = [];\n      }\n      if (\n        currentRealmId &&\n        ((currentTable && currentProp && lastDoc) || completedRealm)\n      ) {\n        await db.$syncState.update('syncState', (syncState: PersistedSyncState) => {\n          const yDownloadedRealms = syncState.yDownloadedRealms || {};\n          yDownloadedRealms[currentRealmId!] = completedRealm\n            ? '*'\n            : {\n                tbl: currentTable!,\n                prop: currentProp!,\n                key: lastDoc.k!,\n              };\n          syncState.yDownloadedRealms = yDownloadedRealms;\n        });\n      }\n    }\n\n    try {\n      for await (const chunk of chunks) {\n        const decoder = new Decoder(chunk);\n        while (hasContent(decoder)) {\n          switch (readUint8(decoder)) {\n            case BINSTREAM_TYPE_REALMID:\n              await storeCollectedDocs(true);\n              currentRealmId = readVarString(decoder);\n              break;\n            case BINSTREAM_TYPE_TABLE_AND_PROP:\n              await storeCollectedDocs(false); // still on same realm\n              currentTable = readVarString(decoder);\n              currentProp = readVarString(decoder);\n              break;\n            case BINSTREAM_TYPE_DOCUMENT: {\n              const k = readAny(decoder);\n              const u = readVarUint8Array(decoder);\n              docsToInsert.push({\n                k,\n                u,\n              });\n              break;\n            }\n          }\n        }\n        await storeCollectedDocs(false); // Chunk full - migth still be on same realm\n      }\n      await storeCollectedDocs(true); // Everything downloaded - finalize last downloaded realm to \"*\"\n    } catch (error) {\n      if (!(error instanceof Dexie.DexieError)) {\n        // Network error might have happened.\n        // Store what we've collected so far:\n        await storeCollectedDocs(false);\n      }\n      throw error;\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/getUpdatesTable.ts",
    "content": "import { DexieCloudDB } from \"../db/DexieCloudDB\";\nimport { YTable } from \"./YTable\";\n\nexport function getUpdatesTable(db: DexieCloudDB, table: string, ydocProp: string): YTable | undefined {\n  if (!db.dx._allTables[table]) return undefined;\n  const utbl = db.table(table)?.schema.yProps?.find(p => p.prop === ydocProp)?.updatesTable;\n  if (!utbl) {\n    console.debug(`No updatesTable found for ${table}.${ydocProp}`);\n    return undefined;\n  }\n  if (!db.dx._allTables[utbl]) return undefined;\n  return db.table(utbl);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/listUpdatesSince.ts",
    "content": "import type { Table } from 'dexie';\nimport type { YUpdateRow } from 'y-dexie';\n\nexport function listUpdatesSince(yTable: Table, sinceIncluding: number): Promise<YUpdateRow[]> {\n  return yTable\n    .where('i')\n    .between(sinceIncluding, Infinity, true)\n    .toArray();\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/listYClientMessagesAndStateVector.ts",
    "content": "import type { Table } from 'dexie';\nimport type { YClientMessage } from 'dexie-cloud-common';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { DEXIE_CLOUD_SYNCER_ID } from '../sync/DEXIE_CLOUD_SYNCER_ID';\nimport { listUpdatesSince } from './listUpdatesSince';\nimport * as Y from 'yjs';\nimport { EntityCommon } from '../db/entities/EntityCommon';\nimport type { YSyncState } from 'y-dexie';\n\n/** Queries the local database for YMessages to send to server.\n * \n * There are 2 messages that this function can provide:\n *   YUpdateFromClientRequest ( for local updates )\n *   YStateVector ( for state vector of foreign updates so that server can reduce the number of udpates to send back )\n *\n * Notice that we do not do a step 1 sync phase here to get a state vector from the server. Reason we can avoid\n * the 2-step sync is that we are client-server and not client-client here and we keep track of the client changes\n * sent to server by letting server acknowledge them. There is always a chance that some client update has already\n * been sent and that the client failed to receive the ack. However, if this happens it does not matter - the change\n * would be sent again and Yjs handles duplicate changes anyway. And it's rare so we earn the cost of roundtrips by\n * avoiding the step1 sync and instead keep track of this in the `unsentFrom` property of the SyncState.\n * \n * @param db \n * @returns \n */\nexport async function listYClientMessagesAndStateVector(\n  db: DexieCloudDB,\n  tablesToSync: Table<EntityCommon>[]\n): Promise<{yMessages: YClientMessage[], lastUpdateIds: {[yTable: string]: number}}> {\n  const result: YClientMessage[] = [];\n  const lastUpdateIds: {[yTable: string]: number} = {};\n  for (const table of tablesToSync) {\n    if (table.schema.yProps) {\n      for (const yProp of table.schema.yProps) {\n        const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName\n        const syncState = (await yTable.get(DEXIE_CLOUD_SYNCER_ID)) as\n          | YSyncState\n          | undefined;\n\n        // unsentFrom = the `i` value of updates that aren't yet sent to server (or at least not acked by the server yet)\n        const unsentFrom = syncState?.unsentFrom || 1;\n        // receivedUntil = the `i` value of updates that both we and the server knows we already have (we know it by the outcome from last syncWithServer() because server keep track of its revision numbers\n        const receivedUntil = syncState?.receivedUntil || 0;\n        // Compute the least value of these two (but since receivedUntil is inclusive we need to add +1 to it)\n        const unsyncedFrom = Math.min(unsentFrom, receivedUntil + 1);\n        // Query all these updates for all docs of this table+prop combination\n        const updates = await listUpdatesSince(yTable, unsyncedFrom);\n        if (updates.length > 0) lastUpdateIds[yTable.name] = updates[updates.length -1].i;\n\n        // Now sort them by document and whether they are local or not + ignore local updates already sent:\n        const perDoc: {\n          [docKey: string]: {\n            i: number;\n            k: any;\n            isLocal: boolean;\n            u: Uint8Array[];\n          };\n        } = {};\n        for (const update of updates) {\n          // Sort updates into buckets of the doc primary key + the flag (whether it's local or foreign)\n          const isLocal = ((update.f || 0) & 0x01) === 0x01;\n          if (isLocal && update.i < unsentFrom) continue; // This local update has already been sent and acked.\n          const docKey = JSON.stringify(update.k) + '/' + isLocal;\n          let entry = perDoc[docKey];\n          if (!entry) {\n            perDoc[docKey] = entry = {\n              i: update.i,\n              k: update.k,\n              isLocal,\n              u: [],\n            };\n            entry.u.push(update.u);\n          } else {\n            entry.u.push(update.u);\n            entry.i = Math.max(update.i, entry.i);\n          }\n        }\n\n        // Now, go through all these and:\n        // * For local updates, compute a merged update per document.\n        // * For foreign updates, compute a state vector to pass to server, so that server can\n        //   avoid re-sending updates that we already have (they might have been sent of websocket\n        //   and when that happens, we do not mark them in any way nor do we update receivedUntil -\n        //   we only update receivedUntil after a \"full sync\" (syncWithServer()))\n        for (const { k, isLocal, u, i } of Object.values(perDoc)) {\n          const mergedUpdate = u.length === 1 ? u[0] : Y.mergeUpdatesV2(u);\n          if (isLocal) {\n            result.push({\n              type: 'u-c',\n              table: table.name,\n              prop: yProp.prop,\n              k,\n              u: mergedUpdate,\n              i,\n            });\n          } else {\n            const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);\n            result.push({\n              type: 'sv',\n              table: table.name,\n              prop: yProp.prop,\n              k,\n              sv: stateVector,\n            });\n          }\n        }\n      }\n    }\n  }\n  return {\n    yMessages: result,\n    lastUpdateIds\n  };\n}\n"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/reopenDocSignal.ts",
    "content": "import { Subject } from \"rxjs\";\nimport type * as Y from \"yjs\";\n\nconst wm = new WeakMap<Y.Doc, Subject<void>>();\n\n/** A property (package-private) on Y.Doc that is used\n * to signal that the server wants us to send a 'doc-open' message\n * to the server for this document.\n * \n * @param doc \n * @returns \n */\nexport function getOpenDocSignal(doc: Y.Doc) {\n  let signal = wm.get(doc);\n  if (!signal) {\n    signal = new Subject<void>();\n    wm.set(doc, signal);\n  }\n  return signal;\n}"
  },
  {
    "path": "addons/dexie-cloud/src/yjs/updateYSyncStates.ts",
    "content": "import { UpdateSpec } from 'dexie';\nimport { DexieCloudDB } from '../db/DexieCloudDB';\nimport { PersistedSyncState } from '../db/entities/PersistedSyncState';\nimport { DEXIE_CLOUD_SYNCER_ID } from '../sync/DEXIE_CLOUD_SYNCER_ID';\nimport { YDexieCloudSyncState } from './YDexieCloudSyncState';\n\nexport async function updateYSyncStates(\n  lastUpdateIdsBeforeSync: { [yTable: string]: number },\n  receivedUntilsAfterSync: { [yTable: string]: number },\n  db: DexieCloudDB\n) {\n  // We want to update unsentFrom for each yTable to the value specified in first argument\n  //  because we got those values before we synced with server and here we are back from server\n  //  that has successfully received all those messages - no matter if the last update was a client or server update,\n  //  we can safely store unsentFrom to a value of the last update + 1 here.\n  // We also want to update receivedUntil for each yTable to the value specified in the second argument,\n  //  because that contains the highest resulted id of each update from server after storing it.\n  // We could do these two tasks separately, but that would require two update calls on the same YSyncState, so\n  // to optimize the dexie calls, we merge these two maps into a single one so we can do a single update request\n  // per yTable.\n  const mergedSpec: {\n    [yTable: string]: { unsentFrom?: number; receivedUntil?: number };\n  } = {};\n  for (const [yTable, lastUpdateId] of Object.entries(\n    lastUpdateIdsBeforeSync\n  )) {\n    mergedSpec[yTable] ??= {};\n    mergedSpec[yTable].unsentFrom = lastUpdateId + 1;\n  }\n  for (const [yTable, lastUpdateId] of Object.entries(\n    receivedUntilsAfterSync\n  )) {\n    mergedSpec[yTable] ??= {};\n    mergedSpec[yTable].receivedUntil = lastUpdateId;\n  }\n\n  // Now go through all yTables and update their YSyncStates:\n  const allYTables = Object.values(db.dx._dbSchema)\n    .filter((tblSchema) => tblSchema.yProps)\n    .map((tblSchema) => tblSchema.yProps!.map((yProp) => yProp.updatesTable))\n    .flat();\n  for (const yTable of allYTables) {\n    const mergedEntry = mergedSpec[yTable];\n    const unsentFrom = mergedEntry?.unsentFrom ?? 1;\n    const receivedUntil =\n      mergedEntry?.receivedUntil ?? // If not received anything on this table, pick the current last update id\n      // from local because we are in the same parent transaction (in sync.ts) that\n      // applied all updates from the server\n      ((\n        await db\n          .table(yTable)\n          .where('i')\n          .between(1, Infinity) // Because i might be string DEXIE_CLOUD_SYNCER_ID if not a number.\n          .reverse()\n          .limit(1)\n          .primaryKeys()\n      )[0] as number) ??\n      0;\n    // We're already in a transaction, but for the sake of\n    // code readability and correctness, let's launch an atomic sub transaction:\n    await db.transaction('rw', yTable, async () => {\n      const state: YDexieCloudSyncState | undefined = await db\n        .table(yTable)\n        .get(DEXIE_CLOUD_SYNCER_ID);\n      if (!state) {\n        await db.table<YDexieCloudSyncState>(yTable).add({\n          i: DEXIE_CLOUD_SYNCER_ID,\n          unsentFrom,\n          receivedUntil\n        });\n      } else {\n        state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);\n        state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);\n        await db.table(yTable).put(state);\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "addons/dexie-cloud/test/promisedTest.ts",
    "content": "import {asyncTest, start, stop, ok, equal} from 'qunit';\n\nexport function promisedTest(name: string, tester: ()=>Promise<any>) {\n  asyncTest(name, async ()=>{\n    try {\n      await tester();\n    } catch (error) {\n      ok(false, \"Got error: \" + (error ?\n        error +\n          (error.code ? ` (code: ${error.code})` : ``) + \n          (error.stack ? \"\\n\" + error.stack : '') :\n        error));\n    } finally {\n      start();\n    }\n  });\n}\n"
  },
  {
    "path": "addons/dexie-cloud/test/qunit.d.ts",
    "content": "declare module 'qunit' {\n  function module(name: string, options?: {\n    setup?: () => void;\n    teardown?: () => void\n  });\n  function test(name: string, fn: ()=>void);\n  function asyncTest(name: string, fn: ()=>void);\n  function start();\n  function stop();\n  function strictEqual(a: any, b: any, description: string);\n  function deepEqual(a: any, b: any, description: string);\n  function equal(a: any, b: any, description: string);\n  function ok(x: any, description: string);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es2015\",\n    \"target\": \"ES5\",\n    \"declaration\": true,\n    \"importHelpers\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"skipLibCheck\": true,\n    \"lib\": [\"ES2020\", \"DOM\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"sourceMap\": true,\n    \"rootDir\": \"..\",\n    \"jsx\": \"react\",\n    \"jsxFactory\": \"h\",\n    \"jsxFragmentFactory\": \"Fragment\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../tools/tmp/\",\n  },\n  \"include\": [\n    \"../src/\",\n    \"./unit/\",\n    \"./*.ts\",\n    \"./qunit.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "addons/dexie-cloud/test/unit/index.ts",
    "content": "import \"./tests-github-issues\";\nimport \"./tests-migrate-to-cloud\";\n"
  },
  {
    "path": "addons/dexie-cloud/test/unit/karma-env.js",
    "content": ""
  },
  {
    "path": "addons/dexie-cloud/test/unit/karma.conf.cjs",
    "content": "// Include common configuration\nconst {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../../../test/karma.common');\n\nmodule.exports = function (config) {\n  const browserMatrixOverrides = {\n    // Be fine with testing on local travis firefox + browserstack chrome, latest supported.\n    ci: [\"Chrome\"],\n    // Safari fails to reply on browserstack. Need to not have it here.\n    // Just complement with old chrome browser that is not part of CI test suite.\n    pre_npm_publish: [\n      \"Chrome\",\n    ]\n  };\n\n  const cfg = getKarmaConfig(browserMatrixOverrides, {\n    // Base path should point at the root \n    basePath: '../../../../',\n    files: karmaCommon.files.concat([\n      'dist/dexie.js',\n      'addons/dexie-cloud/node_modules/rxjs/dist/bundles/rxjs.umd.js',\n      'addons/dexie-cloud/test/unit/bundle.js',\n      { pattern: 'addons/dexie-cloud/test/*.map', watched: false, included: false },\n      { pattern: 'addons/dexie-cloud/dist/*.map', watched: false, included: false }\n    ]),\n    // Override plugins to exclude karma-webdriver-launcher for local testing\n    plugins: [\n      'karma-qunit',\n      'karma-mocha-reporter', \n      'karma-chrome-launcher',\n      'karma-firefox-launcher'\n    ]\n  });\n\n  cfg.hostname = 'localhost';\n  cfg.port = 9876;\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "addons/dexie-cloud/test/unit/run-unit-tests.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Dexie.Cloud Unit tests</title>\n  <link rel=\"stylesheet\" href=\"../../../../node_modules/qunitjs/qunit/qunit.css\">\n</head>\n<body>\n    <div id=\"qunit\"></div>\n    <div id=\"qunit-fixture\"></div>\n    <script src=\"../../../../test/babel-polyfill/polyfill.min.js\"></script>\n    <script src=\"../../../../node_modules/qunitjs/qunit/qunit.js\"></script>\n    <script src=\"../../node_modules/rxjs/dist/bundles/rxjs.umd.js\"></script>\n    <script src=\"../../../../dist/dexie.js\"></script>\n    <script src=\"bundle.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "addons/dexie-cloud/test/unit/test-dexie-cloud-client.ts",
    "content": "import {\n  module,\n  test,\n  asyncTest,\n  start,\n  stop,\n  strictEqual,\n  ok,\n  equal\n} from 'qunit';\nimport { promisedTest } from '../promisedTest';\nimport Dexie from 'dexie';\nimport dexieCloud from '../../src/dexie-cloud-client';\n\nmodule('dexie-cloud-client');\nconst DATABASE_URL = 'http://localhost:3000/ziud0envo';\n\nconst db = new Dexie('argur', { addons: [dexieCloud] });\ndb.version(2).stores({\n  friends: '@id, name',\n  products: '@id, title, realmId'\n});\n\n/*db.open().then(async ()=>{\n  const id = await db.table(\"friends\").bulkPut([{name: \"Foo\", age: 33}]);\n  console.log(await db.table(\"friends\").toArray());\n}).catch(console.error);*/\n\npromisedTest('basic-test', async () => {\n  await Dexie.delete(db.name);\n  db.cloud.configure({\n    databaseUrl: DATABASE_URL,\n    requireAuth: false\n  });\n  console.log('Waiting for open to resolve');\n  await db.open();\n  console.log('open resolved. Adding a friend:');\n  const id = await db.table('friends').add({ name: 'Foo' });\n  console.log('Friend added and got id', id);\n  ok(true, `id was ${id}`);\n  let obj = await db.table('friends').get(id);\n  equal(obj.name, 'Foo', 'We have the right name');\n  await db.table('friends').put({ id, name: 'Bar' });\n  console.log(\"Friend updated with name = 'Bar'\", id);\n  obj = await db.table('friends').get(id);\n  equal(obj.name, 'Bar', 'We have the new name');\n  const numFriends = await db.table('friends').count();\n  ok(true, `Num friends: ${numFriends}`);\n\n  console.log('Before login', db.cloud.currentUserId, db.cloud.currentUser);\n  await db.cloud.login({grant_type: \"demo\", userId: 'foo@demo.local'});\n  console.log('Done login', db.cloud.currentUserId, db.cloud.currentUser);\n  await new Promise(resolve => setTimeout(resolve, 2000));\n  console.log(\"Deleting friend\");\n  await db.table('friends').delete(id);\n  await new Promise(resolve => setTimeout(resolve, 1500));\n  await db.table('products').add({title: \"My private new fantastic product that wont be accepted by server\", realmId: \"rlm-public\"});\n  console.log(\"Products before\", await db.table('products').toArray());\n  await db.table('products').where({realmId: db.cloud.currentUserId}).delete();\n  console.log(\"Products after\", await db.table('products').toArray());\n  await new Promise(resolve => setTimeout(resolve, 4000));\n  console.log(\"Products\", await db.table('products').toArray());\n  console.log(\"Friends\", await db.table('friends').toArray());\n});\n\npromisedTest('add-realm', async ()=> {\n  const db = new Dexie('argur2', { addons: [dexieCloud] });\n  await Dexie.delete(db.name);\n  db.version(1).stores({\n    todoLists: '@id, realmId, title',\n    todoItems: '@id, realmId, title, todoListId',\n\n    // Access Control tables\n    realms: \"@realmId\",\n    members: \"@id, realmId\", // Optionally, index things also, like \"realmId\" or \"email\".\n    roles: \"[realmId+name]\",    \n  });\n  db.cloud.configure({\n    databaseUrl: DATABASE_URL,\n    requireAuth: false\n  });\n\n  await db.open();\n  console.log('DB opened...', db.cloud.currentUserId, db.cloud.currentUser);\n  await new Promise(resolve => setTimeout(resolve, 500));\n  console.log('Before login', db.cloud.currentUserId, db.cloud.currentUser);\n  await db.cloud.login({grant_type: \"demo\", userId: 'foo@demo.local'});\n  console.log('Done login', db.cloud.currentUserId, db.cloud.currentUser);\n  await db.cloud.sync();\n  console.log(\"In sync now\");\n\n  //const allMyRealms = await db.table(\"realms\").toCollection().primaryKeys();\n  /*console.log(\"Cleaning up EVERYTHING!\");\n  await db.transaction('rw', 'todoLists', 'todoItems', 'members', 'realms', ()=>{\n    db.table('todoLists').where('realmId').notEqual('rlm-public').delete();\n    db.table('todoItems').where('realmId').notEqual('rlm-public').delete();\n    db.table('members').where('realmId').notEqual('rlm-public').delete();\n    db.table('realms').where('realmId').notEqual('rlm-public').delete();\n  });*/\n\n  await db.cloud.sync();\n\n  // Add a realm\n  const realmId = await db.transaction('rw', 'realms', 'members', 'todoLists', 'todoItems', async ()=>{\n    const realmId = await db.realms.add({\n      name: \"My new realm\"\n    });\n    await db.members.bulkAdd([{\n      realmId,\n      userId: db.cloud.currentUserId\n    },{\n      realmId,\n      email: \"david@dexie.org\",\n      name: \"David (dexie)\",\n      invite: true,\n      permissions: {\n        manage: \"*\"\n      }\n    }]);\n    const todoListId = await db.table(\"todoLists\").add({\n      title: \"My todo list\",\n      realmId,\n    });\n    const todoItems = await db.table(\"todoItems\").bulkAdd([{\n      realmId,\n      todoListId,\n      title: \"Make Dexie Cloud work\"\n    }]);\n    return realmId;\n  });\n\n  console.log(\"Before syncing new realm and todo list\");\n  await db.cloud.sync();\n  console.log(\"After syncing new realm and todo list.\");\n  console.log(\"Now adding another member for invite\");\n  await Promise.all([db.table(\"members\").add({\n    realmId,\n    email: \"david.fahlander@gmail.com\",\n    name: \"David (gmail)\",\n    permissions: {\n      manage: \"*\"\n    }    \n  }),\n  db.table(\"members\").add({\n    realmId,\n    userId: \"gkjlgfdfg\" // Should fail\n  })]);\n  await db.cloud.sync();\n  console.log(\"Added two transactions where the second should have failed by now\");\n\n  console.log(\"Now cleaning up all:\");\n  await db.transaction('rw', 'realms', 'members', 'todoLists', 'todoItems', async ()=>{\n    db.table('todoItems').where({realmId}).delete();\n    db.table('todoLists').where({realmId}).delete();\n    db.table('members').where({realmId}).delete();\n    db.table('realms').where({realmId}).delete();\n  });\n  console.log(\"Before syncing the realm removal\");\n  await db.cloud.sync();\n  console.log(\"After syncing the realm removal\");\n});\n\n/*promisedTest('require-auth', async () => {\n  await Dexie.delete(db.name);\n  db.cloud.configure({\n    databaseUrl: DATABASE_URL,\n    requireAuth: true\n  });\n  console.log('Waiting for open to resolve');\n  await db.open();\n  console.log('open resolved. Listing friends:');\n  const friends = await db.table('friends').toArray();\n  console.log('Got friends', friends);\n});\n*/"
  },
  {
    "path": "addons/dexie-cloud/test/unit/tests-github-issues.ts",
    "content": "import {\n  module,\n  test,\n  asyncTest,\n  start,\n  stop,\n  strictEqual,\n  ok,\n  equal,\n  deepEqual,\n} from 'qunit';\nimport { promisedTest } from '../promisedTest';\nimport Dexie, { add, remove } from 'dexie';\nimport dexieCloud, { DexieCloudOptions, DexieCloudTable, getTiedRealmId } from '../../src/dexie-cloud-client';\n\nconst DBURL = 'https://zv8n7bwcs.dexie.cloud'; // Shall exist in cloud.\n\nmodule('github-issues');\n\npromisedTest('https://github.com/dexie/Dexie.js/issues/2228', async () => {\n  const DBNAME = 'Issue2228_DB';\n  const db = new Dexie(DBNAME, { addons: [dexieCloud] }) as Dexie & {\n    items2228: DexieCloudTable<{ id: string; drinks: string[] }, 'id'>;\n  };\n  db.version(1).stores({ items2228: '@id' });\n  db.cloud.configure({\n    databaseUrl: DBURL,\n    requireAuth: { email: 'issue2228@demo.local', grant_type: 'demo' }\n  });\n  await db.open();\n\n  ok(true, 'Cleared existing items in items2228 table');\n  await db.items2228.clear();\n  ok(true, 'Now syncing to cloud');\n  await db.cloud.sync({purpose: 'push', wait: true });\n\n  await db.items2228.update('nonExistingId', { drinks: add(['coffee']) });\n  await db.items2228.update('nonExistingId', { drinks: remove(['coffee']) });\n\n  ok(true, 'Now syncing to cloud after adding and removing from non-existing item');\n  await db.cloud.sync({purpose: 'push', wait: true });\n\n  const itemId = await db.items2228.add({ drinks: ['coffee', 'tea'] });\n  ok(true, `Added item with id ${itemId} and drinks ['coffee', 'tea']`);\n\n  await db.items2228.update(itemId, { drinks: add(['milk']) });\n  await db.items2228.update(itemId, { drinks: remove(['milk']) });\n\n  ok(true, 'Now syncing to cloud after adding and removing from existing item');\n  await db.cloud.sync({purpose: 'push', wait: true });\n\n  ok(true, 'Test completed successfully');\n  db.close();\n  await Dexie.delete(DBNAME);\n  console.log('Database deleted successfully');\n});\n\n/** Dexie issue #2185\n * \n * Here are the steps to reproduce the issue:\n\n    1. Add `item1`\n    2. Add `item2`\n    3. Share `item2` with another user.\n    4. Export data, ensuring all Dexie Cloud-related tables are excluded using `skipTables`\n    5. Delete `item2` (or both `item1` and `item2`)\n    6. Import the previously exported data.\n    7. The deleted items temporarily reappear in the local database.\n    8. The subsequent sync removes them again.\n */\npromisedTest('https://github.com/dexie/Dexie.js/issues/2185', async () => {\n  const DBNAME = 'issue2185';\n  const DBURL = 'https://zv8n7bwcs.dexie.cloud'; // Shall exist in cloud.\n  const DEMOUSER1 = 'foo@demo.local'; // This user is imported into the cloud database using `npx dexie-cloud import dexie-cloud-import.json`\n  const DEMOUSER2 = 'bar@demo.local'; // This user is also imported from the same file.\n  const REALM_ID = 'rlm~issue2185';\n\n  const db = new Dexie(DBNAME, { addons: [dexieCloud] }) as Dexie & {\n    items2185: DexieCloudTable<{ id: string; name: string }, 'id'>;\n  };\n  db.version(1).stores({ items2185: '@id, name' });\n  db.cloud.configure({\n    databaseUrl: DBURL,\n    requireAuth: { email: DEMOUSER1, grant_type: 'demo' }\n  });\n  await db.open();\n  ok(true, 'DB opened and synced successfully');\n  // Clear any existing data\n  await db.transaction('rw', db.items2185, db.members, db.realms, tx => {\n    tx.items2185.clear();\n    tx.members.where({ realmId: REALM_ID }).delete();\n    tx.realms.delete(REALM_ID);\n  });\n  ok(true, 'Existing data cleared successfully');\n  await db.cloud.sync({purpose: 'push', wait: true });\n  ok(true, 'Cloud sync completed successfully. Now ready to execute the test steps');\n  \n  // 1. Add `item1`\n  const item1Id = await db.items2185.add({ name: 'Item 1' });\n  // 2. Add `item2`\n  const item2Id = await db.items2185.add({ name: 'Item 2' });\n  // 3. Share `item2` with another user\n  await db.transaction('rw', db.items2185, db.members, db.realms, async () => {\n    const realmId = await db.realms.add({ name: 'Test Realm', realmId: REALM_ID });\n    db.members.bulkAdd([{\n      realmId,\n      email: DEMOUSER1,\n      permissions: { manage: '*' }\n    },{\n      realmId,\n      email: DEMOUSER2,\n      permissions: { manage: '*' }\n    }]);\n    db.items2185.update(item2Id, { realmId });\n  });\n\n  await db.cloud.sync();\n  // 4. Export data, ensuring all Dexie Cloud-related tables are excluded using `skipTables`\n  // (do it without dexie-export-import addon to avoid adding a dependency in this test)\n  let items = await db.items2185.toArray();\n  let realms = [await db.realms.get(REALM_ID)];\n  let members = await db.members.where({ realmId: REALM_ID }).toArray();\n  const exportJSON = JSON.stringify({\n    items,\n    //realms,\n    //members\n  });\n  // 5. Delete `item2` (or both `item1` and `item2`)\n  await db.items2185.clear();\n  await db.cloud.sync();\n  const itemsAfterClear = await db.items2185.toArray();\n  deepEqual(itemsAfterClear, [], 'Items cleared successfully');\n  // 6. Import the previously exported data\n  const importedData = JSON.parse(exportJSON);\n  await db.transaction('rw', db.items2185, db.realms, db.members, async () => {\n    await db.items2185.bulkAdd(importedData.items);\n  });\n  // 7. The deleted items temporarily reappear in the local database.\n  items = await db.items2185.toArray();\n  equal(items.length, 2, 'Two items imported successfully');\n  // 8. The subsequent sync must not remove them again.\n  await db.cloud.sync({purpose: 'push', wait: true });\n  const itemsAfterSync = await db.items2185.toArray();\n  equal(itemsAfterSync.length, 2, 'Items NOT removed after sync');\n\n  // Clean up\n  await db.transaction('rw', db.items2185, db.members, db.realms, tx => {\n    tx.items2185.clear();\n    tx.members.where({ realmId: REALM_ID }).delete();\n    tx.realms.delete(REALM_ID);\n  });\n  await db.cloud.sync({purpose: 'push', wait: true });\n  ok(true, 'Test completed successfully');\n  db.close();\n  await Dexie.delete(DBNAME);\n  console.log('Database deleted successfully');\n});\n\n\nfunction strip(...props: string[]) {\n  return (obj: any) => {\n    const newObj: any = {};\n    for (const key in obj) {\n      if (!props.includes(key)) {\n        newObj[key] = obj[key];\n      }\n    }\n    return newObj;\n  };\n}\n\n"
  },
  {
    "path": "addons/dexie-cloud/test/unit/tests-migrate-to-cloud.ts",
    "content": "import {\n  module,\n  test,\n  asyncTest,\n  start,\n  stop,\n  strictEqual,\n  ok,\n  equal,\n  deepEqual,\n} from 'qunit';\nimport { promisedTest } from '../promisedTest';\nimport Dexie from 'dexie';\nimport dexieCloud, { DexieCloudOptions } from '../../src/dexie-cloud-client';\n\nmodule('migrate-to-cloud');\n\n// Verify that an existing Dexie database with upgraders attached\n// can be migrated to cloud and that dexie-cloud-addon doesn't\n// forbid dexie upgraders if they are applied before entering\n// cloud.\npromisedTest('allow-upgrader-before-going-to-cloud', async () => {\n  const DBNAME = 'allowprecloudupgrader';\n  const DBURL = 'https://zv8n7bwcs.dexie.cloud'; // Shall exist in cloud.\n  const DEMOUSER = 'foo@demo.local'; // Shall exist in cloud.\n\n  function v1DB() {\n    // Create a vanilla Dexie on version 1\n    const db = new Dexie(DBNAME, { addons: [] }) as Dexie & {\n      friends: Dexie.Table<{ guid: string }, string>;\n    };\n    db.version(1).stores({ friends: 'guid' });\n    db.on('populate', (tx) =>\n      tx.table('friends').bulkAdd([{ guid: '1' }, { guid: '2' }])\n    );\n    return db;\n  }\n\n  function v2DB() {\n    // Create a new vanilla Dexie on version 2 with an upgrader\n    const db = new Dexie(DBNAME, { addons: [] }) as Dexie & {\n      friends: Dexie.Table<{ guid: string; name: string }, string>;\n    };\n    db.version(2)\n      .stores({ friends: 'guid, name' })\n      .upgrade(async (tx) => {\n        ok(true, 'Executing upgrader for version 2');\n        await tx\n          .table('friends')\n          .toCollection()\n          .modify((friend) => {\n            friend.name = `Name${friend.guid}`;\n          });\n      });\n    return db;\n  }\n\n  function cloudDB(\n    requireAuth: DexieCloudOptions['requireAuth'],\n    { skipMigration } = { skipMigration: false }\n  ) {\n    // Create a new Dexie with cloud addon\n    const db = new Dexie(DBNAME, { addons: [dexieCloud] }) as Dexie & {\n      friends: Dexie.Table<{ guid: string; name: string }, string>;\n    };\n    db.version(10).stores({ friends: 'guid, name' });\n    if (!skipMigration) {\n      db.version(2)\n        .stores({ friends: 'guid, name' })\n        .upgrade(async (tx) => {\n          ok(true, 'Executing upgrader for version 2');\n          await tx\n            .table('friends')\n            .toCollection()\n            .modify((friend) => {\n              friend.name = `Name${friend.guid}`;\n            });\n        });\n    }\n    db.cloud.configure({\n      databaseUrl: DBURL,\n      requireAuth,\n      nameSuffix: false, // For migrating the same local DB as the vanilla one and not just create a new local DB for cloud.\n    });\n    return db;\n  }\n\n  async function stepByStep() {\n    ok(true, 'Opening version 1');\n    // Create a vanilla Dexie on version 1\n    let db = v1DB();\n    let friends = await db.friends.toArray();\n    equal(friends.length, 2, 'We have 2 friends in version 1');\n    ok(\n      friends.every((friend) => !('name' in friend)),\n      'No names set in version 1'\n    );\n    db.close();\n\n    ok(true, 'Opening version 2 (with an upgrader)');\n    let db2 = v2DB();\n    let friends2 = await db2.friends.toArray();\n    equal(friends2.length, 2, 'We still have 2 friends in version 2');\n    deepEqual(\n      friends2,\n      [\n        { guid: '1', name: 'Name1' },\n        { guid: '2', name: 'Name2' },\n      ],\n      'Names are set in version 2'\n    );\n    db2.close();\n\n    ok(true, 'Opening cloud version 10');\n    // Now migrate to cloud\n    let dbCloud = cloudDB({ email: DEMOUSER, grant_type: 'demo' });\n    await dbCloud.open();\n    equal(\n      dbCloud.cloud.currentUserId,\n      DEMOUSER,\n      'We are logged in as DEMOUSER'\n    );\n    let friendsCloud = await dbCloud.friends.toArray();\n    deepEqual(\n      friendsCloud,\n      [\n        { guid: '1', name: 'Name1', owner: DEMOUSER, realmId: DEMOUSER },\n        { guid: '2', name: 'Name2', owner: DEMOUSER, realmId: DEMOUSER },\n      ],\n      'Names are set in cloud version and owner/realmId are set'\n    );\n    dbCloud.close();\n  }\n\n  async function stepOver2() {\n    ok(\n      true,\n      'Opening version 1 and then directly on last version (skip version 2)'\n    );\n    // Create a vanilla Dexie on version 1\n    let db = v1DB();\n    let friends = await db.friends.toArray();\n    equal(friends.length, 2, 'We have 2 friends in version 1');\n    db.close();\n\n    // Now migrate to cloud\n    let dbCloud = cloudDB({ email: DEMOUSER, grant_type: 'demo' });\n    await dbCloud.open();\n    equal(\n      dbCloud.cloud.currentUserId,\n      DEMOUSER,\n      'We are logged in as DEMOUSER'\n    );\n    let friendsCloud = await dbCloud.friends.toArray();\n    deepEqual(\n      friendsCloud,\n      [\n        { guid: '1', name: 'Name1', owner: DEMOUSER, realmId: DEMOUSER },\n        { guid: '2', name: 'Name2', owner: DEMOUSER, realmId: DEMOUSER },\n      ],\n      'Names are set in cloud version'\n    );\n    dbCloud.close();\n  }\n\n  async function openLastVersionDirectly() {\n    ok(true, 'Opening last version directly');\n    // Open cloud\n    let dbCloud = cloudDB({ email: DEMOUSER, grant_type: 'demo' });\n    await dbCloud.open();\n    equal(\n      dbCloud.cloud.currentUserId,\n      DEMOUSER,\n      'We are logged in as DEMOUSER'\n    );\n    let friendsCloud = await dbCloud.friends.toArray();\n    ok(true, `${friendsCloud.length} friends in cloud version`);\n    dbCloud.close();\n  }\n\n  async function clearDataOnServer() {\n    ok(true, 'Clearing data on server');\n    const db = cloudDB(\n      { email: DEMOUSER, grant_type: 'demo' },\n      { skipMigration: true }\n    );\n    await db.open();\n    ok(true, 'Clearing friends');\n    try {\n      await db.friends.clear();\n    } catch (e) {\n      console.error(e);\n      debugger;\n    }\n    ok(true, 'Syncing');\n    await db.cloud.sync();\n    ok(true, 'Closing');\n    db.close();\n    ok(true, 'Deleting');\n    await Dexie.delete(DBNAME);\n  }\n\n  await Dexie.delete(DBNAME);\n  await clearDataOnServer();\n  await openLastVersionDirectly();\n  await Dexie.delete(DBNAME);\n  await stepByStep();\n  await Dexie.delete(DBNAME);\n  await stepOver2();\n  await Dexie.delete(DBNAME);\n});\n"
  },
  {
    "path": "addons/dexie-cloud/tools/build-configs/banner.txt",
    "content": "/* ========================================================================== \n *                           dexie-cloud-addon.js\n * ==========================================================================\n *\n * Dexie addon that syncs IndexedDB with Dexie Cloud.\n *\n * By David Fahlander, david@dexie.org\n *\n * ==========================================================================\n *\n * Version {version}, {date}\n *\n * https://dexie.org\n *\n * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/\n * \n */\n"
  },
  {
    "path": "addons/dexie-cloud/tools/build-configs/rollup.config.mjs",
    "content": "// @ts-check\n//import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport { readFileSync, writeFileSync } from 'fs';\nimport typescript from '@rollup/plugin-typescript';\nimport terser from '@rollup/plugin-terser';\nimport replace from '@rollup/plugin-replace';\n// @ts-ignore: requires tsconfig settings that we don't need for the web build but is ok here in the build config.\nimport pkg from '../../package.json' with { type: 'json' };\nimport * as fs from 'fs';\n\n//const ERRORS_TO_IGNORE = ['THIS_IS_UNDEFINED'];\n\nexport function createBanner() {\n  // Create a copy of banner.txt with version and date replaced.\n  let banner = readFileSync('tools/build-configs/banner.txt', 'utf-8');\n  banner = banner\n    .replace(/{version}/g, pkg.version)\n    .replace(/{date}/g, new Date().toDateString());\n  fs.mkdirSync('tools/tmp', { recursive: true });\n  writeFileSync('tools/tmp/banner.txt', banner, 'utf-8');\n}\n\n/**\n *\n * @param {String} entry such as src/dexie-cloud-client.ts\n * @param {String} outputName such as dexie-cloud-addon\n * @returns\n */\nexport function createRollupConfigs(entry, outputName) {\n  // TypeScript plugin for modern build (generates .d.ts files)\n  const modernTypescriptPlugin = typescript({\n    tsconfig: 'src/tsconfig.json',\n    compilerOptions: {\n      target: 'es2016',\n      declaration: true,\n      declarationDir: 'dist/modern',\n    },\n    inlineSources: true,\n  });\n\n  // TypeScript plugin for UMD build (no declarations needed)\n  const umdTypescriptPlugin = typescript({\n    tsconfig: 'src/tsconfig.json',\n    compilerOptions: {\n      target: 'es2016',\n      declaration: false,\n    },\n    inlineSources: true,\n  });\n\n  const COMMON_NON_TS_PLUGINS = [\n    //sourcemaps(),\n    nodeResolve({\n      browser: true,\n      preferBuiltins: false,\n    }),\n    commonjs(),\n    replace({\n      preventAssignment: true,\n      values: {\n        __VERSION__: JSON.stringify(pkg.version),\n      },\n    }),\n  ];\n\n  // DEV and PRODUCTION build plugins\n  const DEV_BUILD_PLUGINS = [];\n\n  // PROD build plugins removes console logs and minifies code\n  const PRODUCTION_BUILD_PLUGINS = [\n    terser({\n      compress: {\n        // Set DEXIE_CLOUD_DEBUG=1 to preserve console.log (useful for E2E test builds)\n        drop_console: !process.env.DEXIE_CLOUD_DEBUG,\n        drop_debugger: true,\n      },\n      mangle: true,\n      sourceMap: true,\n      output: {\n        comments: false,\n      },\n    }),\n  ];\n\n  return [\n    //\n    // Modern ES builds\n    //\n    {\n      input: entry,\n      output: [\n        // Modern DEV build\n        {\n          file: `dist/modern/${outputName}.js`,\n          format: 'es',\n          banner: readFileSync('tools/tmp/banner.txt', 'utf-8'),\n          sourcemap: true,\n          plugins: DEV_BUILD_PLUGINS,\n        },\n        // Modern PROD build\n        {\n          file: `dist/modern/${outputName}.min.js`,\n          format: 'es',\n          banner: readFileSync('tools/tmp/banner.txt', 'utf-8'),\n          sourcemap: true,\n          plugins: PRODUCTION_BUILD_PLUGINS,\n        },\n      ],\n      plugins: [modernTypescriptPlugin, ...COMMON_NON_TS_PLUGINS],\n      external: ['dexie', 'rxjs', 'rxjs/operators', 'y-dexie', 'lib0', 'lib0/encoding', 'lib0/decoding', 'yjs', 'y-protocols/awareness'],\n    },\n\n    //\n    // UMD build\n    //\n    {\n      input: entry,\n      output: [\n        // UMD DEV build\n        {\n          file: `dist/umd/${outputName}.js`,\n          format: 'umd',\n          globals: {\n            dexie: 'Dexie',\n            rxjs: 'rxjs',\n            'rxjs/operators': 'rxjs.operators',\n          },\n          name: 'DexieCloud',\n          banner: readFileSync('tools/tmp/banner.txt', 'utf-8'),\n          sourcemap: true,\n          exports: 'named',\n          plugins: DEV_BUILD_PLUGINS,\n        },\n        // UMD PROD build\n        {\n          file: `dist/umd/${outputName}.min.js`,\n          format: 'umd',\n          globals: {\n            dexie: 'Dexie',\n            rxjs: 'rxjs',\n            'rxjs/operators': 'rxjs.operators',\n          },\n          name: 'DexieCloud',\n          banner: readFileSync('tools/tmp/banner.txt', 'utf-8'),\n          sourcemap: true,\n          exports: 'named',\n          plugins: PRODUCTION_BUILD_PLUGINS,\n        },\n      ],\n      plugins: [umdTypescriptPlugin, ...COMMON_NON_TS_PLUGINS],\n      external: ['dexie', 'rxjs', 'rxjs/operators'],\n    },\n  ];\n}\n\ncreateBanner();\n\nexport default [\n  ...createRollupConfigs('src/dexie-cloud-addon.ts', 'dexie-cloud-addon'),\n  ...createRollupConfigs('src/service-worker.ts', 'service-worker'),\n];\n"
  },
  {
    "path": "addons/dexie-cloud/tools/build-configs/rollup.test.unit.config.js",
    "content": "// @ts-check\nimport sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport replace from '@rollup/plugin-replace';\n// @ts-ignore: requires tsconfig settings that we don't need for the web build but is ok here in the build config.\nimport pkg from '../../package.json' with { type: 'json' };\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\",\n];\n\nexport default {\n  input: 'tools/tmp/test/unit/index.js',\n  output: [{\n    file: 'test/unit/bundle.js',\n    format: 'umd',\n    globals: {\n      dexie: \"Dexie\",\n      qunit: \"QUnit\",\n      rxjs: \"rxjs\",\n      'rxjs/operators': 'rxjs.operators',\n      \"dexie-cloud\": \"Dexie.Cloud\"\n    },\n    name: 'DexieCloudTests',\n    sourcemap: true,\n    exports: 'named'\n  }],\n  external: ['dexie', 'dexie-observable', 'dexie-syncable', \"dexie-cloud\", \"qunit\", \"rxjs\"],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true}),\n    commonjs(),\n      replace({\n        preventAssignment: true,\n        values: {\n          __VERSION__: JSON.stringify(pkg.version),\n        },\n      })\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/dexie-cloud/tools/release-dexie-cloud-addon.sh",
    "content": "#!/bin/bash -e\ncd ../..\npnpm install\npnpm build\ncd libs/dexie-cloud-common\npnpm install\npnpm build\ncd ../../addons/y-dexie\npnpm install\npnpm build\ncd ../../addons/dexie-cloud\npnpm install\npnpm build\necho \"All built. To publish, run 'pnpm publish [--tag test|next]'\""
  },
  {
    "path": "addons/dexie-cloud/tools/replaceVersionAndDate.cjs",
    "content": "const fs = require('fs');\nconst files = process.argv.slice(2);\nconst version = require('../package.json').version;\n\nfiles.forEach(file => {\n    let fileContent = fs.readFileSync(file, \"utf-8\");\n    fileContent = fileContent\n        .replace(/{version}/g, version)\n        .replace(/{date}/g, new Date().toDateString());\n    fs.writeFileSync(file, fileContent, \"utf-8\");\n});\n"
  },
  {
    "path": "addons/dexie-export-import/.gitignore",
    "content": "dist/*.js\ndist/*.js.map\ndist/*.mjs\ndist/*.mjs.map\ndist/*.d.ts\n"
  },
  {
    "path": "addons/dexie-export-import/.npmignore",
    "content": "tools/\nsrc/\n.*\ntmp/\n**/tmp/\ntest\n*.log\n"
  },
  {
    "path": "addons/dexie-export-import/README.md",
    "content": "# Export and Import IndexedDB Database\n\nExport / Import IndexedDB <---> Blob\n\nThis module extends 'dexie' module with new methods for importing / exporting databases to / from blobs.\n\n# Install\n```\nnpm install dexie\nnpm install dexie-export-import\n```\n\n# Usage\n\nHere's the basic usage. There's a lot you can do by supplying optional `[options]` arguments. The available options are described later on in this README (See Typescript interfaces below).\n\n*NOTE:* Typescript users using dexie@2.x will get compilation errors if using the static import method `Dexie.import()`. \n\n```js\nimport { Dexie } from \"dexie\";\nimport \"dexie-export-import\";\n\n//\n// Import from Blob or File to Dexie instance:\n//\nconst db = await Dexie.import(blob, [options]);\n\n//\n// Export to Blob\n//\nconst blob = await db.export([options]);\n\n//\n// Import from Blob or File to existing Dexie instance\n//\nawait db.import(blob, [options]);\n\n```\n\n# Sample\n\n[Here's a working sample](https://codepen.io/dfahlander/pen/RqwoaB/) on CodePen. It uses [downloadjs](https://www.npmjs.com/package/downloadjs) to deliver the blob as a \"file download\" to the user. For receiving an import file, it uses a drop area where you can drop your JSON file. Click the Console tab in the bottom to see what progressCallbacks receive.\n\nEven though this sample doesn't show it, blobs can also be sent or retrieved to/from a server, using the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). \n\n# Features\n\n* Export of IndexedDB Database to JSON Blob.\n* Import from Blob back to IndexedDB Database.\n* An import Blob can be retrieved from an URL (using fetch()) or from a user-input file (dropped or browsed to).\n* An export Blob can be either given end-user to be stored in Downloaded Files, or be send to a server over HTTP(S) using fetch().\n* Chunk-wise / Streaming - does not read the entire DB into RAM\n* Progress callback (typically for showing progress bar)\n* Optional filter allows to import/export subset of data\n* Support for all structured clonable exotic types (Date, ArrayBuffer, Blob, etc) except CryptoKeys (which by design cannot be exported)\n* Atomic - import / export within one database transaction (optional)\n* Export speed: Using getAll() in chunks rather than openCursor().\n* Import speed: Using bulkPut() in chunks rather than put().\n* Can well be run from a Web Worker (better speed + doesn't lock GUI).\n* Can also export IndexedDB databases that was not created with Dexie.\n\n# Compatibility\n\n| Product | Supported versions        |\n| ------- | ------------------------- |\n| dexie   | ^2.0.4 or ^3.0.0-alpha.5  |\n| Safari  | ^10.1                     |\n| IE      | 11                        |\n| Edge    | any version               |\n| Chrome  | any version               |\n| FF      | any version               |\n\n# Similar Libraries\n## [indexeddb-export-import](https://github.com/Polarisation/indexeddb-export-import)\n \nMuch smaller in size, but also much lighter than dexie-export-import.\n\n[Indexeddb-export-import](https://github.com/Polarisation/indexeddb-export-import) can be better choice if:\n\n* your data contains no Dates, ArrayBuffers, TypedArrays or Blobs (only objects, strings, numbers, booleans and arrays).\n* your database is small enough to fit in RAM on your target devices.\n\nDexie-export-import was build to scale when exporting large databases without consuming much RAM. It does also support importing/exporting exotic types.\n\n# Interface\n\nImporting this module will extend Dexie and Dexie.prototype as follows.\nEven though this is conceptually a Dexie.js addon, there is no addon instance.\nExtended interface is done into Dexie and Dexie.prototype as a side effect when\nimporting the module.\n\n```ts\n//\n// Extend Dexie interface (typescript-wise)\n//\ndeclare module 'dexie' {\n  // Extend methods on db\n  interface Dexie {\n    export(options?: ExportOptions): Promise<Blob>;\n    import(blob: Blob, options?: ImportOptions): Promise<void>;\n  }\n  interface DexieConstructor {\n    import(blob: Blob, options?: StaticImportOptions): Promise<Dexie>;\n  }\n}\n```\n\n## StaticImportOptions and ImportOptions\n\nThese are the interfaces of the `options` optional arguments to Dexie.import() and Dexie.prototype.import(). All options are optional and defaults to undefined (falsy).\n\n```ts\nexport interface StaticImportOptions {\n  noTransaction?: boolean;\n  chunkSizeBytes?: number; // Default: DEFAULT_KILOBYTES_PER_CHUNK ( 1MB )\n  filter?: (table: string, value: any, key?: any) => boolean;\n  progressCallback?: (progress: ImportProgress) => boolean;\n  name?: string;\n}\n\nexport interface ImportOptions extends StaticImportOptions {\n  acceptMissingTables?: boolean;\n  acceptVersionDiff?: boolean;\n  acceptNameDiff?: boolean;\n  acceptChangedPrimaryKey?: boolean;\n  overwriteValues?: boolean;\n  clearTablesBeforeImport?: boolean;\n  noTransaction?: boolean;\n  chunkSizeBytes?: number; // Default: DEFAULT_KILOBYTES_PER_CHUNK ( 1MB )\n  filter?: (table: string, value: any, key?: any) => boolean;\n  progressCallback?: (progress: ImportProgress) => boolean;\n}\n\n```\n\n## ImportProgress\n\nThis is the interface sent to the progressCallback.\n\n```ts\nexport interface ImportProgress {\n  totalTables: number;\n  completedTables: number;\n  totalRows: number;\n  completedRows: number;\n  done: boolean;\n}\n``` \n\n## ExportOptions\n\nThis is the interface of the `options` optional arguments to Dexie.prototype.export(). All options are optional and defaults to undefined (falsy).\n\n```ts\nexport interface ExportOptions {\n  noTransaction?: boolean;\n  numRowsPerChunk?: number;\n  prettyJson?: boolean;\n  filter?: (table: string, value: any, key?: any) => boolean;\n  progressCallback?: (progress: ExportProgress) => boolean;\n}\n```\n\n## ExportProgress\n\nThis is the interface sent to the ExportOptions.progressCallback.\n\n```ts\nexport interface ExportProgress {\n  totalTables: number;\n  completedTables: number;\n  totalRows: number;\n  completedRows: number;\n  done: boolean;\n}\n```\n\n## Defaults\n\nThese are the default chunk sizes used when not specified in the options object. We allow quite large chunks, but still not that large (1MB RAM is not much even for a small device).\n\n```ts\nconst DEFAULT_KILOBYTES_PER_CHUNK = 1024; // When importing blob\nconst DEFAULT_ROWS_PER_CHUNK = 2000; // When exporting db\n```\n\n# JSON Format\n\nThe JSON format is described in the Typescript interface below. This JSON format is streamable as it is generated\nin a streaming fashion, and imported also using a streaming fashion. Therefore, it is important that the data come\nlast in the file.\n\n```ts\nexport interface DexieExportJsonStructure {\n  formatName: 'dexie';\n  formatVersion: 1;\n  data: {\n    databaseName: string;\n    databaseVersion: number;\n    tables: Array<{\n      name: string;\n      schema: string; // '++id,name,age'\n      rowCount: number;\n    }>;\n    data: Array<{ // This property must be last (for streaming purpose)\n      tableName: string;\n      inbound: boolean;\n      rows: any[]; // This property must be last (for streaming purpose)\n    }>;\n  }\n}\n```\n\n## Example JSON File\n\n```json\n{\n  \"formatName\": \"dexie\",\n  \"formatVersion\": 1,\n  \"data\": {\n    \"databaseName\": \"dexie-export-import-basic-tests\",\n    \"databaseVersion\": 1,\n    \"tables\": [\n      {\n        \"name\": \"outbound\",\n        \"schema\": \"\",\n        \"rowCount\": 2\n      },\n      {\n        \"name\": \"inbound\",\n        \"schema\": \"++id\",\n        \"rowCount\": 3\n      }\n    ],\n    \"data\": [{\n      \"tableName\": \"outbound\",\n      \"inbound\": false,\n      \"rows\": [\n        [\n          1,\n          {\n            \"foo\": \"bar\"\n          }\n        ],\n        [\n          2,\n          {\n            \"bar\": \"foo\"\n          }\n        ]\n      ]\n    },{\n      \"tableName\": \"inbound\",\n      \"inbound\": true,\n      \"rows\": [\n        {\n          \"id\": 1,\n          \"date\": 1,\n          \"fullBlob\": {\n            \"type\": \"\",\n            \"data\": \"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==\"\n          },\n          \"binary\": {\n            \"buffer\": \"AQID\",\n            \"byteOffset\": 0,\n            \"length\": 3\n          },\n          \"text\": \"foo\",\n          \"bool\": false,\n          \"$types\": {\n            \"date\": \"date\",\n            \"fullBlob\": \"blob2\",\n            \"binary\": \"uint8array2\",\n            \"binary.buffer\": \"arraybuffer\"\n          }\n        },\n        {\n          \"id\": 2,\n          \"foo\": \"bar\"\n        },\n        {\n          \"id\": 3,\n          \"bar\": \"foo\"\n        }\n      ]\n    }]\n  }\n\n```\n\n# Exporting IndexedDB Databases that wasn't generated with Dexie\nAs Dexie can dynamically open non-Dexie IndexedDB databases, this is not an issue.\nSample provided here:\n\n```js\nimport { Dexie } from 'dexie';\nimport 'dexie-export-import';\n\nasync function exportDatabase(databaseName) {\n  const db = await new Dexie(databaseName).open();\n  const blob = await db.export();\n  return blob;\n}\n\nasync function importDatabase(file) {\n  const db = await Dexie.import(file);\n  return db.backendDB();\n}\n```\n\n\n## Background / Why\n\nThis feature has been asked for a lot:\n\n* https://github.com/dexie/Dexie.js/issues/391\n* https://github.com/dexie/Dexie.js/issues/99\n* https://stackoverflow.com/questions/46025699/dumping-indexeddb-data\n\nMy simple answer initially was this:\n\n```js\nfunction export(db) {\n    return db.transaction('r', db.tables, ()=>{\n        return Promise.all(\n            db.tables.map(table => table.toArray()\n                .then(rows => ({table: table.name, rows: rows})));\n    });\n}\n\nfunction import(data, db) {\n    return db.transaction('rw', db.tables, () => {\n        return Promise.all(data.map (t =>\n            db.table(t.table).clear()\n              .then(()=>db.table(t.table).bulkAdd(t.rows)));\n    });\n}\n```\n\nLooks simple!\n\nBut:\n\n1. The whole database has to fit in RAM. Can be issue on small devices.\n2. If using JSON.stringify() / JSON.parse() on the data, we won't support exotic types (Dates, Blobs, ArrayBuffers, etc)\n3. Not possible to show a progress while importing.\n\nThis addon solves these issues, and some more, with the help of some libraries.\n\n## Libraries Used\nTo accomplish a streamable export/import, and allow exotic types, I use the libraries listed below. Note that these libraries are listed as devDependencies because they are bundles using rollupjs - so there's no real dependency from the library user persective.\n\n### [typeson](https://www.npmjs.com/package/typeson) and [typeson-registry](https://www.npmjs.com/package/typeson-registry)\nThese modules enables something similar as JSON.stringify() / JSON.parse() for exotic or custom types.\n\n### [clarinet](https://www.npmjs.com/package/clarinet)\nThis module allow to read JSON in a streaming fashion\n\n## Streaming JSON\nI must admit that I had to do some research before I understood how to accomplish streaming JSON from client-side Javascript (both reading / writing). It is really not obvious that this would even be possible. Looking at the Blob interface, it does not provide any way of either reading or writing in a streamable fashion.\n\nWhat I found though (after some googling) was that it is indeed possible to do that based on the current DOM platform (including IE11 !).\n\n### Reading JSON in Chunks\n\nA File or Blob represents something that can lie on a disk file and not yet be in RAM. So how do we read the first 100 bytes from a Blob without reading it all?\n\n```js\nconst firstPart = blob.slice(0,100);\n```\nOk, and in the next step we use a FileReader to really read this sliced Blob into memory.\n\n```ts\n\nconst first100Chars = await readBlob(firstPart);\n\nfunction readBlob(blob: Blob): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onabort = ev => reject(new Error(\"file read aborted\"));\n    reader.onerror = ev => reject((ev.target as any).error);\n    reader.onload = ev => resolve((ev.target as any).result);\n    reader.readAsText(blob);\n  });\n}\n```\nVoila!\n\nBut! How can we keep transactions alive when calling this non-indexedDB async call?\n\nI use two different solutions for this:\n1. If we are in a Worker, I use `new FileReaderSync()` instead of `new FileReader()`.\n2. If in the main thread, I use `Dexie.waitFor()` to while reading this short elapsed chunk, keeping the transaction alive still.\n\nOk, fine, but how do we parse the chunk then? Cannot use JSON.parse(firstPart) because it will most defenitely be incomplete.\n\n[Clarinet](https://www.npmjs.com/package/clarinet) to the rescue. This library can read JSON and callback whenever JSON tokens come in.\n\n### Writing JSON in Chunks\n\nWriting JSON is solved more easily. As the BlobBuilder interface was deprecated from the DOM, I firstly found this task impossible. But after digging around, I found that also this SHOULD be possible if browers implement the Blob interface correctly.\n\nBlobs can be constructeed from an array of other Blobs. This is the key.\n\n1. Let's say we generate 1000 Blobs of 1MB each on a device with 512 MB RAM. If the browser does its job well, it will allow the first 200 blobs or so to reside in RAM. But then, it should start putting the remanding blobs onto temporary files.\n2. We put all these 1000 blobs into an array and generate a final Blob from that array.\n\nAnd that's pretty much it.\n"
  },
  {
    "path": "addons/dexie-export-import/package.json",
    "content": "{\n  \"name\": \"dexie-export-import\",\n  \"version\": \"4.4.0\",\n  \"description\": \"Dexie addon that adds export and import capabilities\",\n  \"main\": \"dist/dexie-export-import.js\",\n  \"module\": \"dist/dexie-export-import.mjs\",\n  \"typings\": \"dist/dexie-export-import.d.ts\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dexie/Dexie.js.git\"\n  },\n  \"scripts\": {\n    \"test\": \"just-build test && npx karma start test/karma.conf.js --single-run\",\n    \"build\": \"just-build\",\n    \"watch\": \"just-build --watch\",\n    \"clean\": \"rm -rf tools/tmp dist/*.js dist/*.mjs dist/*.map dist/*.d.ts test/bundle.*\",\n    \"test:ltcloud\": \"cross-env LAMBDATEST=true pnpm run test:ltTunnel & sleep 10 && pnpm run test; UNIT_STATUS=$?; kill $(cat tunnel.pid); exit $UNIT_STATUS\",\n    \"test:ltTunnel\": \"node ../../test/lt-local\"\n  },\n  \"just-build\": {\n    \"default\": [\n      \"just-build src\",\n      \"just-build test\"\n    ],\n    \"src\": [\n      \"tsc -p src\",\n      \"rollup -c tools/build-configs/rollup.config.mjs\"\n    ],\n    \"test\": [\n      \"tsc -p test [--watch 'Watching for file changes.']\",\n      \"rollup -c tools/build-configs/rollup.config.mjs\",\n      \"rollup -c tools/build-configs/rollup.tests.config.mjs\"\n    ]\n  },\n  \"author\": \"david.fahlander@gmail.com\",\n  \"license\": \"Apache-2.0\",\n  \"devDependencies\": {\n    \"@rollup/plugin-alias\": \"^3.1.2\",\n    \"@types/node\": \"^18.11.18\",\n    \"base64-arraybuffer-es6\": \"*\",\n    \"clarinet\": \"dfahlander/clarinet\",\n    \"just-build\": \"^0.9.24\",\n    \"qunit\": \"2.10.0\",\n    \"qunitjs\": \"1.23.1\",\n    \"rollup-plugin-alias\": \"^2.2.0\",\n    \"typescript\": \"^5.3.3\",\n    \"typeson\": \"^5.8.2\",\n    \"typeson-registry\": \"^1.0.0-alpha.21\"\n  },\n  \"peerDependencies\": {\n    \"dexie\": \"workspace:^\"\n  }\n}\n"
  },
  {
    "path": "addons/dexie-export-import/src/dexie-export-import.ts",
    "content": "import Dexie from 'dexie';\nimport { ExportOptions, ExportProgress, exportDB } from './export';\nimport { importDB, peakImportFile, ImportOptions, importInto, StaticImportOptions } from './import';\nimport { DexieExportJsonMeta } from './json-structure';\n\nexport { exportDB, ExportOptions, ExportProgress};\nexport { importDB, importInto, peakImportFile, ImportOptions, DexieExportJsonMeta};\n\n//\n// Extend Dexie interface (typescript-wise)\n//\ndeclare module 'dexie' {\n  // Extend methods on db\n  interface Dexie {\n    export(options?: ExportOptions): Promise<Blob>;\n    import(blob: Blob, options?: ImportOptions): Promise<void>;\n  }\n  interface DexieConstructor {\n    import(blob: Blob, options?: StaticImportOptions): Promise<Dexie>;\n  }\n}\n\n//\n// Extend Dexie interface (runtime wise)\n//\n\nDexie.prototype.export = function (this: Dexie, options?: ExportOptions) {\n  return exportDB(this, options);\n};\nDexie.prototype.import = function (this: Dexie, blob: Blob, options?: ImportOptions) {\n  return importInto(this, blob, options);\n};\nDexie.import = (blob: Blob, options?: StaticImportOptions) => importDB(blob, options);\n\nexport default ()=>{\n  throw new Error(\"This addon extends Dexie.prototype globally and does not have be included in Dexie constructor's addons options.\")\n};\n"
  },
  {
    "path": "addons/dexie-export-import/src/export.ts",
    "content": "\nimport Dexie from 'dexie';\nimport { getSchemaString, extractDbSchema } from './helpers';\nimport { DexieExportedTable, DexieExportJsonStructure } from './json-structure';\nimport { TSON } from './tson';\n\nexport interface ExportOptions {\n  skipTables?: string[],\n  noTransaction?: boolean;\n  numRowsPerChunk?: number;\n  prettyJson?: boolean;\n  filter?: (table: string, value: any, key?: any) => boolean;\n  transform?: (table: string, value: any, key?: any) => ({value: any, key?: any});\n  progressCallback?: (progress: ExportProgress) => boolean;\n}\n\nexport interface ExportProgress {\n  totalTables: number;\n  completedTables: number;\n  totalRows: number | undefined;\n  completedRows: number;\n  done: boolean;\n}\n\nconst DEFAULT_ROWS_PER_CHUNK = 2000;\n\nexport async function exportDB(db: Dexie, options?: ExportOptions): Promise<Blob> {\n  options = options || {};\n  const skipTables = options.skipTables? options.skipTables: []\n  const targetTables = db.tables.filter((x)=> !skipTables.includes(x.name))\n  const slices: (string | Blob)[] = [];\n  const tables = targetTables.map(table => ({\n    name: table.name,\n    schema: getSchemaString(table),\n    rowCount: 0\n  }));\n  const {prettyJson} = options!;\n  const emptyExport: DexieExportJsonStructure = {\n    formatName: \"dexie\",\n    formatVersion: 1,\n    data: {\n      databaseName: db.name,\n      databaseVersion: db.verno,\n      tables: tables,\n      data: []\n    }\n  };\n  \n  const {progressCallback} = options!;\n  const progress: ExportProgress = {\n    done: false,\n    completedRows: 0,\n    completedTables: 0,\n    totalRows: NaN,\n    totalTables: tables.length\n  };\n\n  try {\n    if (options!.noTransaction) {\n      await exportAll();\n    } else {\n      await db.transaction('r', db.tables, exportAll);\n    }\n  } finally {\n    TSON.finalize(); // Free up mem if error has occurred\n  }\n\n  return new Blob(slices,{type: \"text/json\"});\n\n  async function exportAll() {\n    // Count rows:\n    const tablesRowCounts = await Promise.all(targetTables.map(table => table.count()));\n    tablesRowCounts.forEach((rowCount, i) => tables[i].rowCount = rowCount);\n    progress.totalRows = tablesRowCounts.reduce((p,c)=>p+c);\n\n    // Write first JSON slice\n    const emptyExportJson = JSON.stringify(emptyExport, undefined, prettyJson ? 2 : undefined);\n    const posEndDataArray = emptyExportJson.lastIndexOf(']');\n    const firstJsonSlice = emptyExportJson.substring(0, posEndDataArray);\n    slices.push(firstJsonSlice);\n\n    const filter = options!.filter;\n    const transform = options!.transform;\n\n    for (const {name: tableName} of tables) {\n      const table = db.table(tableName);\n      const {primKey} = table.schema;\n      const inbound = !!primKey.keyPath;\n      const LIMIT = options!.numRowsPerChunk || DEFAULT_ROWS_PER_CHUNK;\n      const emptyTableExport: DexieExportedTable = inbound ? {\n        tableName: table.name,\n        inbound: true,\n        rows: []\n      } : {\n        tableName: table.name,\n        inbound: false,\n        rows: []\n      };\n      let emptyTableExportJson = JSON.stringify(emptyTableExport, undefined, prettyJson ? 2 : undefined);\n      if (prettyJson) {\n        // Increase indentation according to this:\n        // {\n        //   ...\n        //   data: [\n        //     ...\n        //     data: [\n        // 123456<---- here\n        //     ] \n        //   ]\n        // }\n        emptyTableExportJson = emptyTableExportJson.split('\\n').join('\\n    ');\n      }\n      const posEndRowsArray = emptyTableExportJson.lastIndexOf(']');\n      slices.push(emptyTableExportJson.substring(0, posEndRowsArray));\n      let lastKey: any = null;\n      let lastNumRows = 0;\n      let mayHaveMoreRows = true;\n      while (mayHaveMoreRows) {\n        if (progressCallback) {\n          // Keep ongoing transaction private\n          Dexie.ignoreTransaction(()=>progressCallback(progress));\n        }\n        const chunkedCollection = lastKey == null ?\n          table.limit(LIMIT) :\n          table.where(':id').above(lastKey).limit(LIMIT);\n\n        const values = await chunkedCollection.toArray();\n\n        if (values.length === 0) break;\n\n        if (lastKey != null && lastNumRows > 0) {\n          // Not initial chunk. Must add a comma:\n          slices.push(\",\");\n          if (prettyJson) {\n            slices.push(\"\\n      \");\n          }\n        }\n\n        mayHaveMoreRows = values.length === LIMIT;\n        \n        if (inbound) {\n          const filteredValues = filter ?\n            values.filter(value => filter(tableName, value)) :\n            values;\n\n          const transformedValues = transform ?\n            filteredValues.map(value => transform(tableName, value).value) :\n            filteredValues;\n\n          const tsonValues = transformedValues.map(value => TSON.encapsulate(value));\n          if (TSON.mustFinalize()) {\n            await Dexie.waitFor(TSON.finalize(tsonValues));\n          }\n\n          let json = JSON.stringify(tsonValues, undefined, prettyJson ? 2 : undefined);\n          if (prettyJson) json = json.split('\\n').join('\\n      ');\n\n          // By generating a blob here, we give web platform the opportunity to store the contents\n          // on disk and release RAM.\n          slices.push(new Blob([json.substring(1, json.length - 1)]));\n          lastNumRows = transformedValues.length;\n          lastKey = values.length > 0 ?\n            Dexie.getByKeyPath(values[values.length -1], primKey.keyPath as string) :\n            null;\n        } else {\n          const keys = await chunkedCollection.primaryKeys();\n          let keyvals = keys.map((key, i) => [key, values[i]]);\n          if (filter) keyvals = keyvals.filter(([key, value]) => filter(tableName, value, key));\n          if (transform) keyvals = keyvals.map(([key, value]) => {\n            const transformResult = transform(tableName, value, key);\n            return [transformResult.key, transformResult.value];\n          });\n\n          const tsonTuples = keyvals.map(tuple => TSON.encapsulate(tuple));\n          if (TSON.mustFinalize()) {\n            await Dexie.waitFor(TSON.finalize(tsonTuples));\n          }\n\n          let json = JSON.stringify(tsonTuples, undefined, prettyJson ? 2 : undefined);\n          if (prettyJson) json = json.split('\\n').join('\\n      ');\n\n          // By generating a blob here, we give web platform the opportunity to store the contents\n          // on disk and release RAM.\n          slices.push(new Blob([json.substring(1, json.length - 1)]));\n          lastNumRows = keyvals.length;\n          lastKey = keys.length > 0 ?\n            keys[keys.length - 1] :\n            null;\n        }\n        progress.completedRows += values.length;\n      }\n      slices.push(emptyTableExportJson.substr(posEndRowsArray)); // \"]}\"\n      progress.completedTables += 1;\n      if (progress.completedTables < progress.totalTables) {\n        slices.push(\",\");\n      }\n    }\n    slices.push(emptyExportJson.substr(posEndDataArray));\n    progress.done = true;\n    if (progressCallback) {\n      // Keep ongoing transaction private\n      Dexie.ignoreTransaction(()=>progressCallback(progress));\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-export-import/src/helpers.ts",
    "content": "import Dexie from 'dexie';\nimport { DexieExportedDatabase } from './json-structure';\n\nexport function getSchemaString(table: Dexie.Table<any, any>) {\n  const primKeyAndIndexes = [table.schema.primKey].concat(table.schema.indexes);\n  return primKeyAndIndexes.map(index => index.src).join(',');\n}\n\nexport function extractDbSchema(exportedDb: DexieExportedDatabase) {\n  const schema: {\n    [tableName: string]: string;\n  } = {};\n  for (const table of exportedDb.tables) {\n    schema[table.name] = table.schema;\n  }\n  return schema;\n}\n\n\n// Missing FileReaderSync type in standard typescript libs:\ninterface FileReaderSync {\n  readAsArrayBuffer(blob: Blob): ArrayBuffer;\n  readAsBinaryString(blob: Blob): string;\n  readAsDataURL(blob: Blob): string;\n  readAsText(blob: Blob, encoding?: string): string;\n}\ndeclare var FileReaderSync: {\n  prototype: FileReaderSync;\n  new(): FileReaderSync;\n};\n// -----------------------------------------------\n\nexport interface TypeMapper {\n  binary: ArrayBuffer;\n  text: string;\n}\n\nexport function readBlobAsync<T extends keyof TypeMapper>(blob: Blob, type: T): Promise<TypeMapper[T]> {\n  return new Promise<TypeMapper[T]>((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onabort = ev => reject(new Error(\"file read aborted\"));\n    reader.onerror = ev => reject((ev.target as any).error);\n    reader.onload = ev => resolve((ev.target as any).result);\n    if (type === 'binary')\n      reader.readAsArrayBuffer(blob);\n    else\n      reader.readAsText(blob);\n  });\n}\n\nexport function readBlobSync<T extends keyof TypeMapper>(blob: Blob, type: T): TypeMapper[T] {\n  if (typeof FileReaderSync === 'undefined') {\n    throw new Error('FileReaderSync missing. Reading blobs synchronously requires code to run from within a web worker. Use TSON.encapsulateAsync() to do it from the main thread.');\n  }\n  const reader = new FileReaderSync(); // Requires worker environment\n  const data = type === 'binary' ?\n    reader.readAsArrayBuffer(blob) :\n    reader.readAsText(blob);\n\n  return data as TypeMapper[T];\n}\n"
  },
  {
    "path": "addons/dexie-export-import/src/import.ts",
    "content": "import Dexie from 'dexie';\nimport { extractDbSchema } from './helpers';\nimport { DexieExportJsonMeta, DexieExportJsonStructure, VERSION } from './json-structure';\nimport { TSON } from './tson';\nimport { JsonStream } from './json-stream';\n\nexport interface StaticImportOptions {\n  noTransaction?: boolean;\n  chunkSizeBytes?: number; // Default: DEFAULT_KILOBYTES_PER_CHUNK ( 1MB )\n  filter?: (table: string, value: any, key?: any) => boolean;\n  transform?: (table: string, value: any, key?: any) => ({value: any, key?: any});\n  progressCallback?: (progress: ImportProgress) => boolean;\n  name?: string;\n}\n\nexport interface ImportOptions extends StaticImportOptions {\n  acceptMissingTables?: boolean;\n  acceptVersionDiff?: boolean;\n  acceptNameDiff?: boolean;\n  acceptChangedPrimaryKey?: boolean;\n  overwriteValues?: boolean;\n  clearTablesBeforeImport?: boolean;\n  skipTables?: string[],\n  noTransaction?: boolean;\n  chunkSizeBytes?: number; // Default: DEFAULT_KILOBYTES_PER_CHUNK ( 1MB )\n  filter?: (table: string, value: any, key?: any) => boolean;\n  transform?: (table: string, value: any, key?: any) => ({value: any, key?: any});\n  progressCallback?: (progress: ImportProgress) => boolean;\n}\n\nconst DEFAULT_KILOBYTES_PER_CHUNK = 1024;\n\nexport interface ImportProgress {\n  totalTables: number;\n  completedTables: number;\n  totalRows: number | undefined;\n  completedRows: number;\n  done: boolean;\n}\n\nexport async function importDB(exportedData: Blob | JsonStream<DexieExportJsonStructure>, options?: StaticImportOptions): Promise<Dexie> {\n  options = options || {}; // All booleans defaults to false.\n  const CHUNK_SIZE = options!.chunkSizeBytes || (DEFAULT_KILOBYTES_PER_CHUNK * 1024);\n  const stream = await loadUntilWeGotEnoughData(exportedData, CHUNK_SIZE);\n  const dbExport = stream.result.data!;\n  const db = new Dexie(options.name !== undefined ? options.name : dbExport.databaseName);\n  db.version(dbExport.databaseVersion).stores(extractDbSchema(dbExport));\n  await importInto(db, stream, options.name !== undefined ? {...options, acceptNameDiff: true } : options);\n  return db;\n}\n\nexport async function peakImportFile(exportedData: Blob): Promise<DexieExportJsonMeta> {\n  const stream = JsonStream<DexieExportJsonStructure>(exportedData);\n  while (!stream.eof()) {\n    await stream.pullAsync(5 * 1024); // 5 k is normally enough for the headers. If not, it will just do another go.\n    if (stream.result.data && stream.result.data!.data) {\n      // @ts-ignore - TS won't allow us to delete a required property - but we are going to cast it.\n      delete stream.result.data.data; // Don't return half-baked data array.\n      break;\n    }\n  }\n  return stream.result as DexieExportJsonMeta;\n}\n\nexport async function importInto(db: Dexie, exportedData: Blob | JsonStream<DexieExportJsonStructure>, options?: ImportOptions): Promise<void> {\n  options = options || {}; // All booleans defaults to false.\n  const CHUNK_SIZE = options!.chunkSizeBytes || (DEFAULT_KILOBYTES_PER_CHUNK * 1024);\n  const jsonStream = await loadUntilWeGotEnoughData(exportedData, CHUNK_SIZE);\n  let dbExportFile = jsonStream.result;\n  const readBlobsSynchronously = 'FileReaderSync' in self; // true in workers only.\n\n  const dbExport = dbExportFile.data!;\n  const skipTables = options.skipTables? options.skipTables: []\n\n  if (!options!.acceptNameDiff && db.name !== dbExport.databaseName)\n    throw new Error(`Name differs. Current database name is ${db.name} but export is ${dbExport.databaseName}`);\n  if (!options!.acceptVersionDiff && db.verno !== dbExport.databaseVersion) {\n    // Possible feature: Call upgraders in some isolated way if this happens... ?\n    throw new Error(`Database version differs. Current database is in version ${db.verno} but export is ${dbExport.databaseVersion}`);\n  }\n  \n  const { progressCallback } = options;\n  const progress: ImportProgress = {\n    done: false,\n    completedRows: 0,\n    completedTables: 0,\n    totalRows: dbExport.tables.reduce((p, c) => p + c.rowCount, 0),\n    totalTables: dbExport.tables.length\n  };\n  if (progressCallback) {\n    // Keep ongoing transaction private\n    Dexie.ignoreTransaction(()=>progressCallback(progress));\n  }\n\n  if (options!.clearTablesBeforeImport) {\n    for (const table of db.tables) {\n      if(skipTables.includes(table.name) ) continue;\n      await table.clear();\n    }\n  }\n\n  if (options.noTransaction) {\n    await importAll();\n  } else {\n    await db.transaction('rw', db.tables, importAll);\n  }  \n\n  async function importAll () {\n    do {\n      for (const tableExport of dbExport.data) {\n        if(skipTables.includes(tableExport.tableName)) continue;\n        if (!tableExport.rows) break; // Need to pull more!\n        if (!(tableExport.rows as any).incomplete && tableExport.rows.length === 0)\n          continue;\n\n        if (progressCallback) {\n          // Keep ongoing transaction private\n          Dexie.ignoreTransaction(()=>progressCallback(progress));\n        }\n        const tableName = tableExport.tableName;\n        const table = db.table(tableName);\n        const tableSchemaStr = dbExport.tables.filter(t => t.name === tableName)[0].schema;\n        if (!table) {\n          if (!options!.acceptMissingTables)\n            throw new Error(`Exported table ${tableExport.tableName} is missing in installed database`);\n          else\n            continue;\n        }\n        if (!options!.acceptChangedPrimaryKey &&\n          tableSchemaStr.split(',')[0] != table.schema.primKey.src) {\n          throw new Error(`Primary key differs for table ${tableExport.tableName}. `);\n        }\n\n        const sourceRows = tableExport.rows\n        \n        // Our rows may be partial, so we need to ensure each one is completed before using it\n        const rows: any[] = [];\n        for(let i = 0; i < sourceRows.length; i++) {\n          const obj = sourceRows[i];\n          if (!obj.incomplete) {\n            rows.push(TSON.revive(obj));\n          } else {\n            break;\n          }\n        }\n\n        const filter = options!.filter;\n        const transform = options!.transform;\n\n        let filteredRows = filter ?\n          tableExport.inbound ?\n            rows.filter(value => filter(tableName, value)) :\n            rows.filter(([key, value]) => filter(tableName, value, key)) :\n          rows;\n        if (transform) {\n          filteredRows = filteredRows.map(tableExport.inbound ?\n            value => transform(tableName, value).value :\n            ([key, value]) => {\n              const res = transform(tableName, value, key)\n              return [res.key, res.value];\n            });\n        }\n        const [keys, values] = tableExport.inbound ?\n          [undefined, filteredRows] :\n          [filteredRows.map(row=>row[0]), rows.map(row=>row[1])];\n\n        if (options!.overwriteValues)\n          await table.bulkPut(values, keys);\n        else\n          await table.bulkAdd(values, keys);\n          \n        progress.completedRows += rows.length;\n        if (!(rows as any).incomplete) {\n          progress.completedTables += 1;\n        }\n        sourceRows.splice(0, rows.length); // Free up RAM, keep existing array instance.\n      }\n\n      // Avoid unnescessary loops in \"for (const tableExport of dbExport.data)\" \n      while (dbExport.data.length > 0 && dbExport.data[0].rows && !(dbExport.data[0].rows as any).incomplete) {\n        // We've already imported all rows from the first table. Delete its occurrence\n        dbExport.data.splice(0, 1); \n      }\n      if (!jsonStream.done() && !jsonStream.eof()) {\n        // Pull some more (keeping transaction alive)\n        if (readBlobsSynchronously) {\n          // If we can pull from blob synchronically, we don't have to\n          // keep transaction alive using Dexie.waitFor().\n          // This will only be possible in workers.\n          jsonStream.pullSync(CHUNK_SIZE);\n        } else {\n          await Dexie.waitFor(jsonStream.pullAsync(CHUNK_SIZE));\n        }\n      } else break;\n    } while (true)\n  }\n  progress.done = true;\n  if (progressCallback) {\n    // Keep ongoing transaction private\n    Dexie.ignoreTransaction(()=>progressCallback(progress));\n  }\n}\n\nasync function loadUntilWeGotEnoughData(exportedData: Blob | JsonStream<DexieExportJsonStructure>, CHUNK_SIZE: number): Promise<JsonStream<DexieExportJsonStructure>> {\n  const stream = ('slice' in exportedData ?\n    JsonStream<DexieExportJsonStructure>(exportedData) :\n    exportedData);\n\n  while (!stream.eof()) {\n    await stream.pullAsync(CHUNK_SIZE);\n\n    if (stream.result.data && stream.result.data!.data)\n      break;\n  }\n  const dbExportFile = stream.result;\n  if (!dbExportFile || dbExportFile.formatName != \"dexie\")\n    throw new Error(`Given file is not a dexie export`);\n  if (dbExportFile.formatVersion! > VERSION) {\n    throw new Error(`Format version ${dbExportFile.formatVersion} not supported`);\n  }\n  if (!dbExportFile.data!) {\n    throw new Error(`No data in export file`);\n  }\n  if (!dbExportFile.data!.databaseName) {\n    throw new Error(`Missing databaseName in export file`);\n  }\n  if (!dbExportFile.data!.databaseVersion) {\n    throw new Error(`Missing databaseVersion in export file`);\n  }\n  if (!dbExportFile.data!.tables) {\n    throw new Error(`Missing tables in export file`);\n  }\n  return stream;  \n}\n"
  },
  {
    "path": "addons/dexie-export-import/src/index.ts",
    "content": "export * from './dexie-export-import';\n"
  },
  {
    "path": "addons/dexie-export-import/src/json-stream.ts",
    "content": "import clarinet from 'clarinet';\nimport { readBlobAsync, readBlobSync } from './helpers';\n\nexport interface JsonStream<T> {\n  pullAsync(numBytes: number): Promise<Partial<T>>;\n  pullSync(numBytes: number): Partial<T>;\n  done(): boolean;\n  eof(): boolean;\n  result: Partial<T>;\n}\n\nexport function JsonStream<T>(blob: Blob):  JsonStream<T> {\n  let pos = 0;\n  const parser = JsonParser(true);\n  // Use TextDecoder in streaming mode so that multi-byte UTF-8 sequences\n  // split across chunk boundaries are handled correctly (fixes #2105).\n  const decoder = new TextDecoder('utf-8', { fatal: true });\n\n  const rv = {\n    async pullAsync(numBytes: number): Promise<Partial<T>> {\n      const slize = blob.slice(pos, pos + numBytes);\n      pos += numBytes;\n      const buf = await readBlobAsync(slize, 'binary');\n      // stream: true tells the decoder to keep incomplete multi-byte sequences\n      // buffered until the next chunk rather than emitting a replacement char.\n      const jsonPart = decoder.decode(buf, { stream: pos < blob.size });\n      const result = parser.write(jsonPart);\n      rv.result = result || {};\n      return result;\n    },\n    pullSync(numBytes: number): Partial<T> {\n      const slize = blob.slice(pos, pos + numBytes);\n      pos += numBytes;\n      const buf = readBlobSync(slize, 'binary');\n      const jsonPart = decoder.decode(buf, { stream: pos < blob.size });\n      const result = parser.write(jsonPart);\n      rv.result = result || {};\n      return result;\n    },\n    done() {\n      return parser.done();\n    },\n    eof() {\n      return pos >= blob.size;\n    },\n    result: {}\n  }\n\n  return rv;\n}\n\n\nexport function JsonParser (allowPartial: boolean) {\n  const parser = (clarinet as any).parser();\n  let level = 0;\n  let result: any;\n  const stack: any[][] = [];\n  let obj: any;\n  let key: string | null;\n  let done = false;\n  let array = false;\n\n  parser.onopenobject = newKey => {\n    const newObj = {};\n    (newObj as any).incomplete = true;\n    if (!result) result = newObj;\n    if (obj) {\n      stack.push([key,obj,array])\n      if (allowPartial) {\n        if (array) {\n          obj.push(newObj);\n        } else {\n          obj[key!] = newObj;\n        }\n      }\n    }\n    obj = newObj;\n    key = newKey;\n    array = false;\n    ++level;\n  }\n  parser.onkey = newKey => key = newKey;\n  parser.onvalue = value => array ? obj.push(value) : obj[key!] = value;\n  parser.oncloseobject = ()=>{\n    delete obj.incomplete;\n    key = null;\n    if (--level === 0) {\n      done = true;\n    } else {\n      const completedObj = obj;\n      [key, obj, array] = stack.pop()!;\n      if (!allowPartial) {\n        if (array) {\n          obj.push(completedObj);\n        } else {\n          obj[key!] = completedObj;\n        }\n      }\n    }\n  }\n  parser.onopenarray = () => {\n    const newObj = [];\n    (newObj as any).incomplete = true;\n    if (!result) result = newObj;\n    if (obj) {\n      stack.push([key,obj,array])\n      if (allowPartial) {\n        if (array) {\n          obj.push(newObj);\n        } else {\n          obj[key!] = newObj;\n        }\n      }\n    }\n    obj = newObj;\n    array = true;\n    key = null;\n    ++level;\n  }\n  parser.onclosearray = () => {\n    delete obj.incomplete;\n    key = null;\n    if (--level === 0) {\n      done = true;\n    } else {\n      const completedObj = obj;\n      [key, obj, array] = stack.pop()!;\n      if (!allowPartial) {\n        if (array) {\n          obj.push(completedObj);\n        } else {\n          obj[key!] = completedObj;\n        }\n      }\n    }\n  }\n\n  return {\n    write(jsonPart: string) {\n      parser.write(jsonPart);\n      return result;\n    },\n    done() {\n      return done;\n    }\n  }\n}\n"
  },
  {
    "path": "addons/dexie-export-import/src/json-structure.ts",
    "content": "export const VERSION = 1;\n\n/** Same as DexieExportJsonStructure but without the data.data array */\nexport interface DexieExportJsonMeta {\n  formatName: 'dexie';\n  formatVersion: typeof VERSION;\n  data: {\n    databaseName: string;\n    databaseVersion: number;\n    tables: Array<{\n      name: string;\n      schema: string;\n      rowCount: number;\n    }>;\n  }\n}\n\nexport interface DexieExportJsonStructure extends DexieExportJsonMeta {\n  formatName: 'dexie';\n  formatVersion: typeof VERSION;\n  data: {\n    databaseName: string;\n    databaseVersion: number;\n    tables: Array<{\n      name: string;\n      schema: string;\n      rowCount: number;\n    }>;\n    data: Array<{\n      tableName: string;\n      inbound: boolean;\n      rows: any[];\n    }>;\n  }\n}\n\nexport type DexieExportedDatabase = DexieExportJsonStructure[\"data\"];\nexport type DexieExportedTable = DexieExportedDatabase[\"data\"][number];\n"
  },
  {
    "path": "addons/dexie-export-import/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"es2015\",\n    \"declaration\": true,\n    \"importHelpers\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"ES2015\", \"DOM\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"outDir\": \"../tools/tmp/src/\",\n    \"declarationDir\": \"../dist/\",\n    \"sourceMap\": true,\n    \"rootDir\": \".\"\n  },\n  \"files\": [\n    \"dexie-export-import.ts\"\n  ]\n}\n"
  },
  {
    "path": "addons/dexie-export-import/src/tson-arraybuffer.ts",
    "content": "import Typeson from 'typeson';\nimport {encode, decode} from 'base64-arraybuffer-es6';\n\nexport default {\n    arraybuffer: {\n        test (x) { return Typeson.toStringTag(x) === 'ArrayBuffer'; },\n        replace (b) {\n            return encode(b, 0, b.byteLength);\n        },\n        revive (b64) {\n            const buffer = decode(b64);\n            return buffer;\n        }\n    }\n};\n\n// See also typed-arrays!\n"
  },
  {
    "path": "addons/dexie-export-import/src/tson-typed-array.ts",
    "content": "declare var global;\n\n/* eslint-env browser, node */\nimport Typeson from 'typeson';\nimport {encode, decode} from 'base64-arraybuffer-es6';\n\nconst _global = typeof self === 'undefined' ? global : self;\n\nconst exportObj = {};\n[\n    'Int8Array',\n    'Uint8Array',\n    'Uint8ClampedArray',\n    'Int16Array',\n    'Uint16Array',\n    'Int32Array',\n    'Uint32Array',\n    'Float32Array',\n    'Float64Array'\n].forEach(function (typeName) {\n    const arrType = typeName;\n    const TypedArray = _global[arrType];\n    if (TypedArray) {\n        exportObj[typeName.toLowerCase()+\"2\"] = {\n            test (x) { return Typeson.toStringTag(x) === arrType; },\n            replace ({buffer, byteOffset, length}) {\n                return {\n                    buffer,\n                    byteOffset,\n                    length\n                };\n            },\n            revive (b64Obj) {\n                const {buffer, byteOffset, length} = b64Obj;\n                return new TypedArray(buffer, byteOffset, length);\n            }\n        };\n    }\n});\n\nexport default exportObj;\n"
  },
  {
    "path": "addons/dexie-export-import/src/tson.ts",
    "content": "import Typeson from 'typeson';\nimport StructuredCloning from 'typeson-registry/dist/presets/structured-cloning';\nimport { encode as encodeB64, decode as decodeB64 } from 'base64-arraybuffer-es6';\nimport Dexie from 'dexie';\nimport { readBlobSync, readBlobAsync } from './helpers';\nimport typedArray from './tson-typed-array';\nimport arrayBuffer from './tson-arraybuffer';\n\nexport const TSON = new Typeson().register(StructuredCloning);\n\nconst readBlobsSynchronously = 'FileReaderSync' in self; // true in workers only.\n\nlet blobsToAwait: any[] = [];\nlet blobsToAwaitPos = 0;\n\n// Need to patch encapsulateAsync as it does not work as of typeson 5.8.2\n// Also, current version of typespn-registry-1.0.0-alpha.21 does not\n// encapsulate/revive Blobs correctly (fails one of the unit tests in\n// this library (test 'export-format'))\nTSON.register([\n  arrayBuffer,\n  typedArray, {\n    blob2: {\n      test(x) { return Typeson.toStringTag(x) === 'Blob'; },\n      replace(b) {\n          if (b.isClosed) { // On MDN, but not in https://w3c.github.io/FileAPI/#dfn-Blob\n            throw new Error('The Blob is closed');\n          }\n          if (readBlobsSynchronously) {\n            const data = readBlobSync(b, 'binary');\n            const base64 = encodeB64(data, 0, data.byteLength);\n            return {\n              type: b.type,\n              data: base64\n            }\n          } else {\n            blobsToAwait.push(b); // This will also make TSON.mustFinalize() return true.\n            const result = {\n              type: b.type,\n              data: {start: blobsToAwaitPos, end: blobsToAwaitPos + b.size}\n            }\n            blobsToAwaitPos += b.size;\n            return result;\n          }\n      },\n      finalize(b, ba: ArrayBuffer) {\n        b.data = encodeB64(ba, 0, ba.byteLength);\n      },\n      revive ({type, data}) {\n        return new Blob([decodeB64(data)], {type});\n      }\n    }\n  }\n]);\n\nTSON.mustFinalize = ()=>blobsToAwait.length > 0;\n\nTSON.finalize = async (items?: any[]) => {\n  const allChunks = await readBlobAsync(new Blob(blobsToAwait), 'binary');\n  if (items) {\n    for (const item of items) {\n      // Manually go through all \"blob\" types in the result\n      // and lookup the data slice they point at.\n      if (item.$types) {\n        let types = item.$types;\n        const arrayType = types.$;\n        if (arrayType) types = types.$;\n        for (let keyPath in types) {\n          const typeName = types[keyPath];\n          const typeSpec = TSON.types[typeName];\n          if (typeSpec && typeSpec.finalize) {\n            const b = Dexie.getByKeyPath(item, arrayType ? \"$.\" + keyPath : keyPath);\n            typeSpec.finalize(b, allChunks.slice(b.data?.start, b.data?.end));\n          }\n        }\n      }\n    }\n  }\n  // Free up memory\n  blobsToAwait = [];\n  blobsToAwaitPos = 0;\n}\n"
  },
  {
    "path": "addons/dexie-export-import/test/.gitignore",
    "content": "bundle.js\nbundle.js.map\n"
  },
  {
    "path": "addons/dexie-export-import/test/basic-tests.ts",
    "content": "import Dexie from 'dexie';\nimport \"dexie-export-import\";\nimport {module, asyncTest, start, stop, strictEqual, ok, equal} from 'qunit';\nimport {promisedTest, readBlob, readBlobBinary, deepEqual} from './tools';\nimport {getSimpleImportData} from './test-data';\n\nmodule(\"basic-tests\");\n\nconst DATABASE_NAME = \"dexie-export-import-basic-tests\";\nconst IMPORT_DATA = getSimpleImportData(DATABASE_NAME);\n\npromisedTest(\"simple-import\", async ()=>{\n  const blob = new Blob([JSON.stringify(IMPORT_DATA)]);\n\n  await Dexie.delete(DATABASE_NAME);\n  const db = await Dexie.import(blob, {\n    chunkSizeBytes: 11,\n  });\n\n  const friends = await db.table(\"friends\").toArray();\n  deepEqual(IMPORT_DATA.data.data[0].rows, friends, \"Imported data should equal\");\n  \n  try {\n    await db.import(blob);\n    ok(false, \"Should not work to reimport without overwriteValues option set\");\n  } catch (error) {\n    equal(error.name, \"BulkError\", \"Should fail with BulkError\");    \n  }\n\n  await db.import(blob, { overwriteValues: true });\n  const friends2 = await db.table(\"friends\").toArray();\n  deepEqual(IMPORT_DATA.data.data[0].rows, friends2, \"Imported data should equal\");\n  db.close();\n\n  await Dexie.delete(DATABASE_NAME);\n});\n\npromisedTest(\"export-format\", async() => {\n  await Dexie.delete(DATABASE_NAME);\n  const db = new Dexie(DATABASE_NAME);\n  db.version(1).stores({\n    outbound: '',\n    inbound: 'id'\n  });\n  await db.table('outbound').bulkAdd([{\n    date: new Date(1),\n    blob: new Blob([\"something\"]),\n    binary: new Uint8Array([1,2,3]),\n    text: \"foo\",\n    bool: false,\n  },{\n    foo: \"bar\"\n  },{\n    bar: \"foo\"\n  }], [\n    new Date(1),\n    2,\n    \"3\"\n  ]);\n  const fullByteArray = new Uint8Array(256);\n  for (let i=0;i<256;++i) {\n    fullByteArray[i] = i;\n  }\n  const blob1 = new Blob([\"1\"])\n  const blob2 = new Blob([\"2\"])\n  const blob3 = new Blob([\"3\"])\n  await db.table(\"inbound\").bulkAdd([{\n    id: 1,\n    date: new Date(1),\n    fullBlob: new Blob([fullByteArray]),\n    imageBlob: blob1,\n    binary: new Uint8Array([1,2,3]),\n    text: \"foo\",\n    bool: false\n  },{\n    id: 2,\n    foo: \"bar\",\n    imageBlob: blob2,\n  },{\n    id: 3,\n    bar: \"foo\",\n    imageBlob: blob3,\n  }]);\n\n  const blob = await db.export({prettyJson: true});\n  const json = await readBlob(blob);\n  console.log(\"json\", json);\n  const parsed = JSON.parse(json);\n  \n  await db.delete();\n  const importedDB = await Dexie.import(blob);\n  const outboundKeys = await importedDB.table('outbound').toCollection().primaryKeys();\n  const outboundValues = await importedDB.table('outbound').toArray();\n  const stringBlob = await readBlob(outboundValues[1].blob)\n  const inboundValues = await importedDB.table('inbound').toArray();\n  equal (outboundKeys[0], 2, \"First key should be 2\");\n  ok('getTime' in (outboundKeys[1] as Date), \"Second outbound key should be a Date instance\");\n  equal((outboundKeys[1] as Date).getTime(), 1, \"The time '1' should be the value of the Date key\");\n  equal (outboundKeys[2], \"3\", \"Third key should be '3'\");\n\n  equal( inboundValues[0].id, 1, \"First id should be 1\");\n  equal( inboundValues[0].date.getTime(), 1, \"First Date should be 1\");\n  const ab = await readBlobBinary(inboundValues[0].fullBlob);\n  const ba = new Uint8Array(ab);\n  console.log(\"byte array\", ba);\n  deepEqual([].slice.call(ba), [].slice.call(fullByteArray), \"The whole byte spectrum supported after redecoding blob\");\n  \n  const blob1A = await blob1.text()\n  const blob1B = await inboundValues[0].imageBlob.text()\n  deepEqual( blob1A, blob1B, \"First Blob should be same as stored\");\n\n  const blob2A = await blob2.text()\n  const blob2B = await inboundValues[1].imageBlob.text()\n  deepEqual( blob2A, blob2B, \"Second Blob should be same as stored\");\n\n  const blob3A = await blob3.text()\n  const blob3B = await inboundValues[2].imageBlob.text()\n  deepEqual( blob3A, blob3B, \"Third Blob should be same as stored\");\n  \n  equal( stringBlob, \"something\", \"First Blob should be 'something'\");\n  equal( inboundValues[0].binary[0], 1, \"First binary[0] should be 1\");\n  equal( inboundValues[0].binary[1], 2, \"First binary[0] should be 2\");\n  equal( inboundValues[0].binary[2], 3, \"First binary[0] should be 3\");\n  equal( inboundValues[0].text, \"foo\", \"First text should be 'foo'\");\n  importedDB.close();\n\n  await Dexie.delete(DATABASE_NAME);\n});\n\n// Regression test for https://github.com/dexie/Dexie.js/issues/2105\n// Non-ASCII (Chinese/Japanese) strings were corrupted during import because\n// blob slicing by byte offset could split multi-byte UTF-8 sequences at chunk\n// boundaries, producing replacement characters (U+FFFD / ��).\npromisedTest(\"unicode-roundtrip-no-corruption\", async () => {\n  const UNICODE_DB = \"dexie-export-import-unicode-test\";\n  await Dexie.delete(UNICODE_DB);\n  const db = new Dexie(UNICODE_DB);\n  db.version(1).stores({ items: \"++id,text\" });\n\n  // Each Chinese character is 3 bytes in UTF-8, so 1000 repetitions × 1000 rows\n  // guarantees chunk boundaries fall inside multi-byte sequences for small chunk sizes.\n  const chineseText = \"让我们说中文\\n\".repeat(1000);\n  const rows = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, text: chineseText }));\n  await db.table(\"items\").bulkAdd(rows);\n\n  // Export then import with a tiny chunk size to force many boundary crossings.\n  const blob = await (db as any).export();\n  db.close();\n  await Dexie.delete(UNICODE_DB);\n\n  const importedDB = await Dexie.import(blob, {\n    chunkSizeBytes: 1024, // 1 KB — maximises chunk boundary splits\n  });\n\n  const importedRows = await importedDB.table(\"items\").toArray();\n  equal(importedRows.length, 1000, \"Should have 1000 rows after import\");\n  let corrupted = 0;\n  for (const row of importedRows) {\n    if (row.text.includes(\"\\uFFFD\")) corrupted++;\n  }\n  equal(corrupted, 0, \"No rows should contain replacement characters (U+FFFD) after import\");\n  importedDB.close();\n  await Dexie.delete(UNICODE_DB);\n});\n"
  },
  {
    "path": "addons/dexie-export-import/test/edge-cases.ts",
    "content": "import Dexie from 'dexie';\nimport \"dexie-export-import\";\nimport {module, asyncTest, start, stop, strictEqual, ok, equal} from 'qunit';\nimport {promisedTest, readBlob, readBlobBinary, deepEqual} from './tools';\nimport {getSimpleImportData} from './test-data';\nimport {importInto} from \"../src\";\n\nmodule(\"edge-cases\");\n\nconst DATABASE_NAME = \"dexie-export-import-edge-cases\";\nconst IMPORT_DATA = getSimpleImportData(DATABASE_NAME);\n\npromisedTest(\"chunkedExport (issue #854)\", async ()=>{\n  const blob = new Blob([JSON.stringify(IMPORT_DATA)]);\n  await Dexie.delete(DATABASE_NAME);\n  const db = await Dexie.import(blob);\n  const exportBlob1 = await db.export({numRowsPerChunk: 1000});\n  ok(true, \"Could export using numRowsPerChunk: 1000\");\n  const exportBlob2 = await db.export({numRowsPerChunk: 1});\n  ok(true, \"Could export using numRowsPerChunk: 1\");\n  const exportBlob3 = await db.export({numRowsPerChunk: 1, prettyJson: true});\n  ok(true, \"Could export using numRowsPerChunk: 1 and prettyJson: true\");\n  const json1 = await readBlob(exportBlob1);\n  ok(true, \"Could read back first blob: \" + json1);\n  const json2 = await readBlob(exportBlob2);\n  ok(true, \"Could read back second blob: \" + json2);\n  const json3 = await readBlob(exportBlob3);\n  ok(true, \"Could read back third blob: \" + json3);\n  const parsed1 = JSON.parse(json1);\n  ok(true, \"Could parse first export\");\n  const parsed2 = JSON.parse(json2);\n  ok(true, \"Could parse second export\");\n  const parsed3 = JSON.parse(json3);\n  ok(true, \"Could parse third export\");\n  const rejson1 = JSON.stringify(parsed1);\n  const rejson2 = JSON.stringify(parsed2);\n  const rejson3 = JSON.stringify(parsed3);\n  equal (rejson1, rejson2, \"First and second exports are equal\");\n  equal (rejson2, rejson3, \"Second and third expots are equal\");\n});\n\npromisedTest(\"filtered-chunkedExport (issue #862)\", async ()=>{\n  const blob = new Blob([JSON.stringify(IMPORT_DATA)]);\n  await Dexie.delete(DATABASE_NAME);\n  const db = await Dexie.import(blob);\n  const exportBlob1 = await db.export({numRowsPerChunk: 1000, filter: () => false});\n  ok(true, \"Could export using numRowsPerChunk: 1000\");\n  const exportBlob2 = await db.export({numRowsPerChunk: 1, filter: () => false});\n  ok(true, \"Could export using numRowsPerChunk: 1\");\n  const exportBlob3 = await db.export({numRowsPerChunk: 1, prettyJson: true, filter: () => false});\n  ok(true, \"Could export using numRowsPerChunk: 1 and prettyJson: true\");\n  const json1 = await readBlob(exportBlob1);\n  ok(true, \"Could read back first blob: \" + json1);\n  const json2 = await readBlob(exportBlob2);\n  ok(true, \"Could read back second blob: \" + json2);\n  const json3 = await readBlob(exportBlob3);\n  ok(true, \"Could read back third blob: \" + json3);\n  const parsed1 = JSON.parse(json1);\n  ok(true, \"Could parse first export\");\n  const parsed2 = JSON.parse(json2);\n  ok(true, \"Could parse second export\");\n  const parsed3 = JSON.parse(json3);\n  ok(true, \"Could parse third export\");\n  const rejson1 = JSON.stringify(parsed1);\n  const rejson2 = JSON.stringify(parsed2);\n  const rejson3 = JSON.stringify(parsed3);\n  equal (rejson1, rejson2, \"First and second exports are equal\");\n  equal (rejson2, rejson3, \"Second and third expots are equal\");\n});\n\npromisedTest(\"import-into (issue #1342)\", async ()=>{\n  const blob = new Blob([JSON.stringify(IMPORT_DATA)]);\n\n  await Dexie.delete(DATABASE_NAME);\n\n  const db = new Dexie(DATABASE_NAME);\n  const dbSchemes = {};\n  for (const table of IMPORT_DATA.data.tables) {\n    dbSchemes[table.name] = table.schema;\n  }\n  db.version(IMPORT_DATA.data.databaseVersion).stores(dbSchemes);\n\n  await importInto(db, blob, {\n    chunkSizeBytes: 11,\n    clearTablesBeforeImport: true,\n  });\n\n  const friends = await db.table(\"friends\").toArray();\n  deepEqual(friends, IMPORT_DATA.data.data[0].rows, \"Imported data should equal\");\n\n  db.close();\n\n  await Dexie.delete(DATABASE_NAME);\n});\n"
  },
  {
    "path": "addons/dexie-export-import/test/gh-actions.sh",
    "content": "#!/bin/bash -e\necho \"Installing dependencies for dexie-export-import\"\npnpm install >/dev/null\npnpm run build\n# This test fails sporadically on Safari 12. Needs to retry it if it fails.\nn=1\nuntil [ $n -ge 4 ]\ndo\n  echo \"Retry $n of 3\"\n  pnpm test:ltcloud && exit 0\n  n=$[$n+1]\ndone\nexit 1\n"
  },
  {
    "path": "addons/dexie-export-import/test/index.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>dexie-export-import unit tests</title>\n  <link rel=\"stylesheet\" href=\"../../../node_modules/qunitjs/qunit/qunit.css\">\n</head>\n<body>\n    <div id=\"qunit\"></div>\n    <div id=\"qunit-fixture\"></div>\n    <script src=\"../../../test/babel-polyfill/polyfill.min.js\"></script>\n    <script src=\"../../../node_modules/qunitjs/qunit/qunit.js\"></script>\n    <script src=\"../../../dist/dexie.js\"></script>\n    <script src=\"../dist/dexie-export-import.js\"></script>\n    <script src=\"bundle.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "addons/dexie-export-import/test/index.ts",
    "content": "import \"./basic-tests\";\nimport \"./edge-cases\";\n"
  },
  {
    "path": "addons/dexie-export-import/test/karma.conf.js",
    "content": "// Include common configuration\nconst {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../../test/karma.common');\n\nmodule.exports = function (config) {\n  const cfg = getKarmaConfig({\n    // Be fine with testing on local travis firefox + browserstack chrome, latest supported.\n    ci: [\"Chrome\"],\n    // Safari fails to reply on browserstack. Need to not have it here.\n    // Just complement with old chrome browser that is not part of CI test suite.\n    pre_npm_publish: [\n      \"Chrome\",\n    ]\n  }, {\n    // Base path should point at the root \n    basePath: '../../../',\n    files: karmaCommon.files.concat([\n      'dist/dexie.js',\n      'addons/dexie-export-import/dist/dexie-export-import.js',\n      'addons/dexie-export-import/test/bundle.js',\n      { pattern: 'addons/dexie-export-import/test/*.map', watched: false, included: false },\n      { pattern: 'addons/dexie-export-import/dist/*.map', watched: false, included: false }\n    ])\n  });\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "addons/dexie-export-import/test/qunit.d.ts",
    "content": "declare module 'qunit' {\n  function module(name: string, options?: {\n    setup?: () => void;\n    teardown?: () => void\n  });\n  function asyncTest(name: string, fn: ()=>void);\n  function start();\n  function stop();\n  function strictEqual(a: any, b: any, description: string);\n  function deepEqual(a: any, b: any, description: string);\n  function equal(a: any, b: any, description: string);\n  function ok(x: any, description: string);\n}\n"
  },
  {
    "path": "addons/dexie-export-import/test/test-data.ts",
    "content": "import {DexieExportJsonStructure} from '../src/json-structure';\n\nexport function getSimpleImportData(databaseName: string): DexieExportJsonStructure {\n  const importData: DexieExportJsonStructure = {\n    formatName: \"dexie\",\n    formatVersion: 1,\n    data: {\n      databaseName,\n      databaseVersion: 1,\n      tables: [{\n        name: \"friends\",\n        schema: \"++id,name,age\",\n        rowCount: NaN\n      }],\n      data: [{\n        inbound: true,\n        tableName: \"friends\",\n        rows: [{\n          id: 1,\n          name: \"Foo\",\n          age: 33\n        },{\n          id: 2,\n          name: \"Bar\",\n          age: 44,\n        },{\n          id: 3,\n          name: \"Bee\",\n          age: 55\n        }]\n      }]\n    }\n  }\n  // Set correct row count:\n  importData.data.tables[0].rowCount = importData.data.data[0].rows.length;\n\n  return importData;\n}\n"
  },
  {
    "path": "addons/dexie-export-import/test/tools.ts",
    "content": "import {asyncTest, start, stop, ok, equal} from 'qunit';\n\nexport function promisedTest(name: string, tester: ()=>Promise<any>) {\n  asyncTest(name, async ()=>{\n    try {\n      await tester();\n    } catch (error) {\n      ok(false, \"Got error: \" + (error ?\n        error +\n          (error.code ? ` (code: ${error.code})` : ``) + \n          (error.stack ? \"\\n\" + error.stack : '') :\n        error));\n    } finally {\n      start();\n    }\n  });\n}\n\nexport function readBlob(blob: Blob): Promise<string> {\n  return blob.text();\n}\n\nexport function readBlobBinary(blob: Blob): Promise<ArrayBuffer> {\n  return blob.arrayBuffer();\n}\n\n// Must use this rather than QUnit's deepEqual() because that one fails on Safari when run via karma-browserstack-launcher\nexport function deepEqual(a: any, b: any, description: string) {\n  equal(JSON.stringify(a, null, 2), JSON.stringify(b, null, 2), description);\n}\n"
  },
  {
    "path": "addons/dexie-export-import/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"es2015\",\n    \"importHelpers\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"es2015\", \"dom\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"outDir\": \"../tools/tmp/\",\n    \"sourceMap\": true,\n    \"rootDir\": \"..\"\n  },\n  \"files\": [\n    \"../src/dexie-export-import.ts\",\n    \"index.ts\",\n    \"qunit.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "addons/dexie-export-import/tools/build-configs/banner.txt",
    "content": "/* ========================================================================== \n *                           dexie-export-import.js\n * ==========================================================================\n *\n * Dexie addon for exporting and importing databases to / from Blobs.\n *\n * By David Fahlander, david.fahlander@gmail.com,\n *\n * ==========================================================================\n *\n * Version {version}, {date}\n *\n * https://dexie.org\n *\n * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/\n * \n */\n"
  },
  {
    "path": "addons/dexie-export-import/tools/build-configs/fake-stream.js",
    "content": "module.exports = {Stream: function(){}};\n\n"
  },
  {
    "path": "addons/dexie-export-import/tools/build-configs/rollup.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport {readFileSync} from 'fs';\nimport path from 'path';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport alias from '@rollup/plugin-alias';\nimport { fileURLToPath } from 'url';\n\n// Define __dirname\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n//const version = require(path.resolve(__dirname, '../../package.json')).version;\nconst packageJsonPath = new URL('../../package.json', import.meta.url).pathname;\nconst { version } = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\",\n];\n\nexport default {\n  input: 'tools/tmp/src/dexie-export-import.js',\n  output: [{\n    file: 'dist/dexie-export-import.js',\n    format: 'umd',\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt'), \"utf-8\")\n        .replace(/{version}/g, version)\n        .replace(/{date}/g, new Date().toDateString()),\n    globals: {dexie: \"Dexie\"},\n    name: 'DexieExportImport',\n    sourcemap: true,\n    exports: 'named'\n  },{\n    file: 'dist/dexie-export-import.mjs',\n    format: 'es',\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt'), \"utf-8\")\n        .replace(/{version}/g, version)\n        .replace(/{date}/g, new Date().toDateString()),\n    sourcemap: true\n  }],\n  external: ['dexie'],\n  plugins: [\n    sourcemaps(),\n    alias({entries: [{\n      find: \"stream\", replacement: path.resolve(__dirname, './fake-stream')}\n    ]}),\n    nodeResolve({\n      browser: true,\n      preferBuiltins: false\n    }),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/dexie-export-import/tools/build-configs/rollup.tests.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport path from 'path';\nimport alias from '@rollup/plugin-alias';\nimport { fileURLToPath } from 'url';\n\n// Define __dirname\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\"\n];\n\nexport default {\n  input: 'tools/tmp/test/index.js',\n  output: [{\n    file: 'test/bundle.js',\n    format: 'umd',\n    globals: {\n      dexie: \"Dexie\",\n      qunit: \"QUnit\",\n      \"dexie-export-import\":\n      \"DexieExportImport\"\n    },\n    name: 'DexieExportImport',\n    sourcemap: true,\n    exports: 'named'\n  }],\n  external: ['dexie', \"qunit\", \"dexie-export-import\"],\n  plugins: [\n    sourcemaps(),\n    alias({entries: [{\n      find: \"stream\", replacement: path.resolve(__dirname, './fake-stream')}\n    ]}),\n    nodeResolve({browser: true}),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/y-dexie/.gitignore",
    "content": "dist/\nesnext/\n.eslintcache\n**/tmp/\ntest/**/bundle.*\n"
  },
  {
    "path": "addons/y-dexie/.npmignore",
    "content": ".DS_Store\ntools/\nsrc/\nbin-src/\n.*\ntmp/\n**/tmp/\ntest\n*.log\n"
  },
  {
    "path": "addons/y-dexie/README.md",
    "content": "Integration of Dexie.js and Y.js\n\n## Install\n\n```\nnpm install dexie\nnpm install yjs\nnpm install y-dexie\n```\n\n## Database Declaration\n\n```ts\nimport { Dexie, EntityTable } from 'dexie';\nimport yDexie from 'y-dexie';\nimport type * as Y from 'yjs';\n\ninterface Friend {\n  id: number;\n  name: string;\n  age: number;\n  notes: Y.Doc;\n}\n\nconst db = new Dexie('myDB', { addons: [yDexie] }) as Dexie & {\n  friends: EntityTable<Friend, 'id'>\n}\n\ndb.version(1).stores({\n  friends: `\n    ++id,\n    name,\n    age,\n    notes: Y.Doc`, // each friend as a 'notes' document\n});\n```\n\nWhen an Y.Doc property has been declared, every object will contain that property. It\nwill be there using a property at the prototype level. The physical `notes` property\nis persisted in its own indexedDB table, unlike `name`, `id` and `age` which are\nstored physically on the object.\n\nEvery time you retrieve a database object, its Y.Doc properties will be available. Y.Doc\nproperties can never be null or undefined. However, actual storage of the document data will not\nbe created until someone accesses the document and manipulates it.\n\n## Basic Document Access\n\n```ts\nimport { db } from './db.js';\nimport { DexieYProvider } from 'y-dexie';\n\n// 1. Fetch an object\nconst friend = await db.friends.get(friendId);\n// 2. Get a reference to the notes Y.Doc\nconst doc = friend.notes;\n// 3. Aquire a DexieYProvider\nconst provider = DexieYProvider.load(doc);\n// 4. Load the document\nawait provider.whenLoaded;\n\n// Manipulate\ndoc.getText().insert(0, 'hello world');\ndoc.getMap('myMap').set('key', 'value');\n...\n\n// Read contents\nconst rootText = doc.getText(); // 'hello world'\nconst subMap = doc.getMap('myMap').get('key'); // 'value'\n\n// 5. When done using the document, release it\nDexieYProvider.release(doc); // Decreases ref-count and destroys doc if not accessed anymore.\n\n```\n\n## Using the `using` keyword (ES2023)\n\nDexieYProvider supports the new `using` keyword available in Typescript and the most modern web frameworks:\n\n```ts\nusing provider = DexieYProvider.load(doc);\nawait provider.whenLoaded;\n...\n```\n\nThe above line is equivalent to the following:\n\n```ts\nconst provider = DexieYProvider.load(doc);\ntry {\n  await provider.whenLoaded;\n  ...\n} finally {\n  DexieYProvider.release(doc);\n}\n```\n\nNotices:\n\nDexieYProvider.load() and DexieYProvider.release() maintains a reference counter\non the document it loads and releases. When the reference count reaches zero, doc.destroy() is called on the Y.Doc instance.\n\nY.Doc properties are declared at prototype level - they are not own properties and not physically located on their host object.\n\nCalling friend.notes twice will return the same Y.Doc instance unless the first one has been destroyed, then the second access will return a new Y.Doc instance representing the same document.\n\n### Rules for Y properties on objects\n\n* Y properties are never nullish. If declared in the dexie schema, they'll exist on all objects from all queries (toArray(), get() etc).\n* Y properties are not `own properties`. They are set on the prototype of the returned object.\n* Y properties are readonly but can be mutated using the [Y.Doc methods](https://docs.yjs.dev/api/y.doc).\n* When adding new objects to a table (using table.add() or bulkAdd()), it's possible to create a new Y.Doc() instance - empty or with content - and put it on the property of the object being inserted to the database.\n* If providing a custom Y.Doc to add() or bulkAdd() its udates will be cloned when added.\n* If not providing the Y.Doc or setting the Y property to null when adding objects, there will still be an Y.Doc property on query results of the object, since Y props are defined by the schema and cannot be null or undefined.\n* Y properties on dexie objects can only be mutated using the [Y.Doc methods](https://docs.yjs.dev/api/y.doc). The property itself is readonly. You cannot replace them with another document or update them using Table.update() or Collection.modify().\n* Y properties are not loaded until using DexieYProvider.load() or the new react hook `useDocument()`\n* Y.Doc instances are kept in a global cache integrated with FinalizationRegistry. First time you access the getter, it will be created, and will stay in cache until it's garbage collected. This means that you'll always get the same Y.Doc instance when querying the same Y property of a the same object. This holds true even if the there are multiple obj instances representing the same ID in the database. All of these will hold one single instance of the Y.Doc because the cache is connected to the primary key of the parent object.\n\n### How it works\n\nInternally, every declared Y property generates a dedicated table for Y.js updates tied to the parent table and the property name. Whenever a document is updated, a new entry in this table is added.\n\nDexieYProvider is responsible of loading and observing updates in both directions.\n\n### Integrations\n\nY.js allows multiple providers on the same document. It is possible to combine DexieYProvider with other providers, but it is also possible for dexie addons to extend the provider behavior - such as adding awareness and sync.\n\n## Adding sync and awareness\n\nThe [dexie-cloud-addon](https://dexie.org/cloud/docs/dexie-cloud-addon) integrates with `y-dexie` and extends the existing DexieYProvider to become a provider also for sync and awareness. Just like other data, Y.Docs\nwill sync to Dexie Cloud Server. A websocket connection will propagate awareness\nand updates between clients.\n\n1. Create a dexie cloud database to sync with:\n\n```\nnpx dexie-cloud create\n```\n\n2. Update database declaration to use dexieCloud addon:\n\n```ts\nimport { Dexie } from 'dexie';\nimport yDexie from 'y-dexie';\nimport dexieCloud, { DexieCloudTable } from 'dexie-cloud-addon';\nimport type * as Y from 'yjs';\n\ninterface Friend {\n  id: string;\n  name: string;\n  age: number;\n  notes: Y.Doc;\n}\n\nconst db = new Dexie('myDB', { addons: [yDexie, dexieCloud] }) as Dexie & {\n  friends: DexieCloudTable<Friend, 'id'>\n}\n\ndb.version(1).stores({\n  friends: `\n    @id,\n    name,\n    age,\n    notes: Y.Doc`, // each friend as a 'notes' document\n});\n\ndb.cloud.configure({\n  databaseUrl: 'https://xxxxx.dexie.cloud' // Obtained from CLI: `npx dexie-cloud create`\n});\n```\n\n\n## Using with React\n\nNew hook `useDocument()` makes use of DexieYProvider as a hook rather than loading and releasing imperatively.\n\n```tsx\n\nimport { useLiveQuery, useDocument } from 'dexie-react-hooks';\n\nfunction MyComponent(friendId: number) {\n  // Query comment object:\n  const friend = useLiveQuery(() => db.friends.get(friendId));\n\n  // Use it's document property (friend is undefined on intial render)\n  const provider = useDocument(friend?.notes);\n\n  // Pass provider and document to some Y.js compliant code in the ecosystem of such (unless undefined)...\n  return provider\n    ? <NotesEditor doc={friend.notes} provider={provider} />\n    : null;\n}\n```\n\nIn the sample above, the `NotesEditor` component could represent any react component backed\nby the ecosystem of text editors supporting Y.js, such as TipTap or Prosemirror.\n\n## Example Applications\n\n### [Dexie Cloud Starter](https://github.com/dexie/dexie-cloud-starter)\n\nThis application showcases the following:\n\n* Collaborative text editing with y-dexie and TipTap\n* Sync and awareness with Dexie Cloud\n* Full-text search with lunr\n* Sharing and access control\n\n### [Lkal.ma](https://lkal.ma/boards)\n\nThe winner of Dexie Cloud Hackathon 2025.\n\nThis application showcases the following:\n\n* Collaborative drawing using TLDraw with Y.js\n* Sync and awareness with Dexie Cloud\n* Sharing and access control\n\n### [To To Do](https://totodo.app)\n\nA commercial ToDo application for iOS, Android and web built on top of\nDexie Cloud, Capacitor, Y.js, TipTap, ChatGPT and NextJS.\n\nThis application showcases the following:\n\n* Collaborative task list sharing\n* Notes taking with Y.js and TipTap\n* Sharing and access control\n* Native app bundling with Capacitor\n* Smart AI suggestions\n"
  },
  {
    "path": "addons/y-dexie/package.json",
    "content": "{\n  \"name\": \"y-dexie\",\n  \"version\": \"4.4.0\",\n  \"description\": \"Integration of Y.js with Dexie\",\n  \"type\": \"module\",\n  \"module\": \"dist/y-dexie.js\",\n  \"homepage\": \"https://dexie.org\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./dist/y-dexie.js\",\n      \"require\": null\n    }\n  },\n  \"types\": \"dist/y-dexie.d.ts\",\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dexie/Dexie.js.git\"\n  },\n  \"scripts\": {\n    \"test\": \"just-build test && pnpm run test-unit\",\n    \"test-unit\": \"karma start test/unit/karma.conf.cjs --single-run\",\n    \"test:ltcloud\": \"cross-env LAMBDATEST=true pnpm run test:ltTunnel & sleep 10 && pnpm run test; UNIT_STATUS=$?; kill $(cat tunnel.pid); exit $UNIT_STATUS\",\n    \"test:ltTunnel\": \"node ../../test/lt-local\",\n    \"build\": \"rollup -c tools/build-configs/rollup.config.mjs\",\n    \"watch\": \"rollup -c tools/build-configs/rollup.config.mjs --watch\",\n    \"clean\": \"rm -rf tools/tmp dist test/unit/bundle.*\",\n    \"prepack\": \"pnpm run build\"\n  },\n  \"just-build\": {\n    \"default\": [\n      \"rollup -c tools/build-configs/rollup.config.mjs\"\n    ],\n    \"test\": [\n      \"just-build test-unit\"\n    ],\n    \"test-unit\": [\n      \"tsc -p test [--watch 'Watching for file changes.']\",\n      \"rollup -c tools/build-configs/rollup.test.unit.config.js\"\n    ]\n  },\n  \"author\": \"david.fahlander@gmail.com\",\n  \"license\": \"Apache-2.0\",\n  \"devDependencies\": {\n    \"@rollup/plugin-commonjs\": \"^29.0.0\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.3\",\n    \"@rollup/plugin-replace\": \"^5.0.4\",\n    \"@rollup/plugin-terser\": \"^0.4.4\",\n    \"@rollup/plugin-typescript\": \"^12.3.0\",\n    \"@types/node\": \"^18.11.18\",\n    \"dexie\": \"workspace:>=4.2.0-alpha.1 <5.0.0\",\n    \"dreambase-library\": \"^1.0.26\",\n    \"just-build\": \"*\",\n    \"karma\": \"*\",\n    \"karma-chrome-launcher\": \"*\",\n    \"karma-firefox-launcher\": \"*\",\n    \"karma-qunit\": \"*\",\n    \"qunit\": \"2.10.0\",\n    \"qunitjs\": \"1.23.1\",\n    \"lib0\": \"^0.2.97\",\n    \"rollup\": \"^4.53.3\",\n    \"terser\": \"^5.20.0\",\n    \"tslib\": \"*\",\n    \"typescript\": \"^5.8.3\",\n    \"y-protocols\": \"^1.0.6\",\n    \"yjs\": \"^13.6.27\"\n  },\n  \"peerDependencies\": {\n    \"dexie\": \"workspace:>=4.2.0-alpha.1 <5.0.0\",\n    \"yjs\": \"^13.6.27\"\n  }\n}\n"
  },
  {
    "path": "addons/y-dexie/src/DexieYProvider.ts",
    "content": "import { Dexie, DexieEvent, DexieEventSet, Unsubscribable } from 'dexie';\nimport * as Y from 'yjs';\nimport { throwIfDestroyed, getDocCache, destroyedDocs } from './docCache';\nimport { getOrCreateDocument } from './getOrCreateDocument';\nimport { observeYDocUpdates } from './observeYDocUpdates';\nimport { promisableChain } from './helpers/promisableChain';\nimport { nonStoppableEventChain } from './helpers/nonStoppableEventChain';\nimport { currentUpdateRow } from './currentUpdateRow';\nimport { Disposable } from './helpers/Disposable';\n\nconst wm = new WeakMap<any, DexieYProvider>();\n\nfunction createEvents() {\n  return (Dexie.Events as any)(null, 'load', 'sync', 'error') as DexieYProvider['on'];\n}\n\ninterface ReleaseOptions {\n  gracePeriod?: number; // Grace period to optimize for unload/reload scenarios\n}\n\nclass DexieYProvider\n{\n  refCount = 1;\n  private stopObserving: () => void;\n  private cleanupHandlers: (() => void)[] = [];\n  private graceTimer: any;\n  private graceTimeout = -1;\n  doc: Y.Doc | null = null;\n  awareness?: any;\n  private _error?: any;\n\n  private _whenLoaded: Promise<void>;\n  private _whenSynced: Promise<void>;\n\n  on: DexieEventSet & ((name: string, f: (...args: any[]) => any) => void);\n  off: (name: string, f: (...args: any[]) => any) => void;\n\n  destroyed = false;\n\n  static on = (Dexie.Events as any)(null, {\n    new: [nonStoppableEventChain],\n    beforeunload: [promisableChain],\n  }) as DexieEventSet & ((name: string, f: (...args: any[]) => any) => void) & {\n    new: DexieEvent;\n    beforeunload: DexieEvent;\n  };\n\n  static getOrCreateDocument(db: Dexie, table: string, prop: string, id: any) {\n    const docCache = getDocCache(db);\n    const updatesTable = db\n      .table(table)\n      .schema.yProps?.find((p) => p.prop === prop)?.updatesTable;\n    if (!updatesTable) {\n      throw new Error(`Updates table for ${table}.${prop} not found`);\n    }\n    // Get or create the Y.Doc for the given table, prop, and id\n    return getOrCreateDocument(db, docCache, table, prop, updatesTable, id);\n  }\n\n  static load(\n    doc: Y.Doc,\n    options?: ReleaseOptions\n  ): DexieYProvider {\n    let p = wm.get(doc);\n    if (p) {\n      ++p.refCount;\n      if (\n        options?.gracePeriod != null &&\n        p.graceTimeout < options.gracePeriod\n      ) {\n        p.graceTimeout = options.gracePeriod;\n      }\n      if (p.graceTimer) {\n        clearTimeout(p.graceTimer);\n        p.graceTimer = null;\n      }\n    } else {\n      p = new DexieYProvider(doc);\n      p.graceTimeout = options?.gracePeriod ?? -1;\n      wm.set(doc, p);\n    }\n    return p;\n  }\n\n  static release(doc: Y.Doc) {\n    if (!doc || destroyedDocs.has(doc)) return; // Document already destroyed.\n    const p = wm.get(doc);\n    if (p) {\n      // There is a provider connected to the doc\n      if (--p.refCount <= 0) {\n        // No references to this provider anymore. Time to release it.\n        if (p.graceTimeout < 0) {\n          // No grace period here or from previous release. Release immediately.\n          p._release();\n        } else if (!p.graceTimer) {\n          p.graceTimer = setTimeout(\n            () => {\n              p.graceTimer = null;\n              if (p.refCount === 0) {\n                // Release only if refCount is still zero\n                p._release();\n              }\n            },\n            p.graceTimeout // Grace period to optimize for unload/reload scenarios\n          );\n        }\n      }\n    } else {\n      doc.destroy();\n    }\n  }\n\n  private _release() {\n    // Allow a listener to beforeunload event to execute while the provider and the document\n    // are still alive and loaded if it needs to compute something from the full document.\n    // Also, in case the event listener uses DexieYProvider.load() without calling DexieYProvider.release(),\n    // it must prevent the release to happen until the provider is finally released.\n    if (!this.doc) return;\n    Promise.resolve(DexieYProvider.on('beforeunload').fire(this)).finally(\n      () => {\n        // Re-check that refCount is zero before actually destroying the document (which\n        // leads to provider.destroy() through the destroy-event on the doc).\n        if (this.refCount === 0) {\n          this.doc?.destroy();\n        }\n        // If refCount is not zero, it means that DexieYProvider.load() has been called from the listener\n        // and the listener has prevented the release from happening. The listener must call DexieYProvider.release()\n        // when it's done with the document.\n      }\n    );\n  }\n\n  static for(doc: Y.Doc): DexieYProvider | undefined {\n    return wm.get(doc);\n  }\n  static getDocCache = getDocCache;\n  static get currentUpdateRow() {\n    return currentUpdateRow;\n  }\n\n  // Use a getter to avoid unhandled rejections when no one bothers about it.\n  get whenLoaded(): Promise<void> {\n    if (!this._whenLoaded) {\n      this._whenLoaded = new Promise((resolve, reject) => {\n        if (!this.doc) {\n          reject(new Error('No Y.Doc associated with this provider'));\n          return;\n        }\n        if (this.doc.isLoaded) resolve();\n        else if (this._error) reject(this._error);\n        else if (destroyedDocs.has(this.doc)) {\n          reject(new Dexie.AbortError('Document was destroyed before loaded'));\n        } else {\n          this.on('load', resolve);\n          this.on('error', reject);\n          this.doc.on('destroy', () =>\n            reject(new Dexie.AbortError('Document was destroyed before loaded'))\n          );\n        }\n      });\n    }\n    return this._whenLoaded;\n  }\n\n  // Use a getter to avoid unhandled rejections when no one bothers about it.\n  get whenSynced(): Promise<void> {\n    if (!this._whenSynced) {\n      this._whenSynced = new Promise((resolve, reject) => {\n        if (!this.doc) {\n          reject(new Error('No Y.Doc associated with this provider'));\n          return;\n        }\n        if (this.doc.isSynced) resolve();\n        else if (this._error) reject(this._error);\n        else if (destroyedDocs.has(this.doc)) {\n          reject(new Dexie.AbortError('Document was destroyed before synced'));\n        } else {\n          this.on('sync', resolve);\n          this.on('error', reject);\n          this.doc.on('destroy', () =>\n            reject(new Dexie.AbortError('Document was destroyed before synced'))\n          );\n        }\n      });\n    }\n    return this._whenSynced;\n  }\n\n  constructor(doc: Y.Doc) {\n    this.on = createEvents();\n    this.doc = doc;\n    this.off = (name: string, f: Function) => this.on[name]?.unsubscribe(f);\n    if ('dispose' in Symbol) {\n      // @ts-ignore\n      this[Symbol.dispose] = () => DexieYProvider.release(doc);\n    }\n    doc.on('load', () => this.on('load').fire());\n    doc.on('sync', (sync) => sync !== false && this.on('sync').fire());\n    doc.on('destroy', this.destroy.bind(this));\n    this.on('error', (error) => {\n      // In case error happens before awaiting provider.whenLoaded or provider.whenSynced.\n      this._error = error;\n    });\n\n    const { db, parentTable, parentId, updatesTable } =\n      (doc as Y.Doc).meta || {};\n    if (!db || !parentTable || !updatesTable) {\n      throw new Error(\n        `Missing Dexie-related metadata in Y.Doc. Documents need to be obtained through Y.Doc properties from dexie queries.`\n      );\n    }\n    // This doc is from Dexie\n    if (!db.table(parentTable) || !db.table(updatesTable)) {\n      throw new Error(\n        `Table ${parentTable} or ${updatesTable} not found in db`\n      );\n    }\n    throwIfDestroyed(doc);\n    this.stopObserving = observeYDocUpdates(\n      this,\n      doc,\n      db,\n      parentTable,\n      updatesTable,\n      parentId\n    );\n    DexieYProvider.on(\"new\").fire(this); // Allow for addons to invoke their sync- and awareness providers here.\n  }\n\n  destroy() {\n    console.debug(`Y.Doc ${this.doc?.meta?.parentId} was destroyed`);\n    wm.delete(this.doc);\n    this.doc = null;\n    this.destroyed = true;\n    this.refCount = 0;\n    this.stopObserving?.();\n    this.on = createEvents(); // Releases listeners for GC\n    this.cleanupHandlers.forEach((cleanup) => cleanup());\n  }\n\n  addCleanupHandler(cleanupHandler: (() => void) | Unsubscribable) {\n    this.cleanupHandlers.push(\n      typeof cleanupHandler === 'function'\n        ? cleanupHandler\n        : () => cleanupHandler.unsubscribe()\n    );\n  }\n}\n\n//\n// Support `using DexieYProvider.load();` syntax\n//\n// Extend DexieYProvider with Disposable interface if Symbol.dispose is supported.\n// (At runtime, this[Symbol.dispose] is created in the constructor, so we indeed implement Disposable interface)\ninterface DexieYProvider extends Disposable {}\n\n//\n// Eliminate dual package hazard \n//\n// Since we're holding static state, make sure to singletonize DexieYProvider\n//\nif (Dexie[\"DexieYProvider\"]) {\n  // @ts-ignore\n  DexieYProvider = Dexie[\"DexieYProvider\"] || DexieYProvider;\n} else {\n  Dexie[\"DexieYProvider\"] = DexieYProvider;\n}\n\nexport { DexieYProvider };\n"
  },
  {
    "path": "addons/y-dexie/src/TODO.md",
    "content": "Files to go through:\n\n- [x] Copy all files from dexie/src/yjs\n- [x] YUpdateRow.ts\n- [x] observeYDocUpdates.ts\n- [x] periodicGC.ts\n- [x] Remove getYLibrary.ts \n- [x] compressYDocs.ts\n- [x] docCache.ts\n- [x] getOrCreateDocument.ts\n- [x] DexieYProvider.ts\n- [x] createYDocProperty.ts\n- [x] createYjsMiddleware.ts\n- [x] Move db.on.y to y-dexie\n- [x] Delete y-related and other y stuff from dexie except the schema syntax somehow\n- [x] Allow for extending schema syntax (type specification) and let y-dexie extend it (\":Y.Doc\" syntax)\n"
  },
  {
    "path": "addons/y-dexie/src/compressYDocs.ts",
    "content": "import { Dexie, cmp } from 'dexie';\nimport { YLastCompressed } from './types/YLastCompressed';\nimport { YSyncState } from './types/YSyncState';\nimport { YUpdateRow } from './types/YUpdateRow';\nimport * as Y from 'yjs';\n\n/** Go through all Y.Doc tables in the entire local db and compress updates\n *\n * @param db Dexie\n * @returns\n */\nexport function compressYDocs(db: Dexie, skipIfRecentlyDoneMillisec?: number) {\n  let p: Promise<any> = Promise.resolve();\n  for (const table of db.tables) {\n    for (const yProp of table.schema.yProps || []) {\n      p = p.then(() => compressYDocsTable(db, yProp, skipIfRecentlyDoneMillisec));\n    }\n  }\n  return p;\n}\n\n/** Compress an individual Y.Doc table */\nfunction compressYDocsTable(\n  db: Dexie,\n  { updatesTable }: { prop: string; updatesTable: string },\n  skipIfRunnedSince?: number // milliseconds\n) {\n  const updTbl = db.table(updatesTable);\n  return Promise.all([\n    // syncers (for example dexie-cloud-addon or other 3rd part syncers) They may have unsentFrom set.\n    updTbl\n      .where('i')\n      .startsWith('') // Syncers have string primary keys while updates have auto-incremented numbers.\n      .toArray(),\n\n    // lastCompressed (pointer to the last compressed update)\n    db.transaction('rw', updatesTable, () =>\n      updTbl.get(0).then((lastCompressed: YLastCompressed | undefined) => {\n        if (\n          skipIfRunnedSince &&\n          lastCompressed &&\n          lastCompressed.lastRun &&\n          lastCompressed.lastRun.getTime() > Date.now() - skipIfRunnedSince\n        ) {\n          // Skip it. It has run recently or is still running.\n          return null;\n        }\n        // isRunning might be true but we don't respect it if started before skipIfRunningSince.\n        lastCompressed = lastCompressed || { i: 0, lastCompressed: 0 };\n        return updTbl\n          .put({\n            ...lastCompressed,\n            lastRun: new Date(),\n          })\n          .then(() => lastCompressed);\n      })\n    ),\n  ]).then(([syncers, stamp]: [YSyncState[], YLastCompressed]) => {\n    if (!stamp) return; // Skip. Already running.\n    const lastCompressedUpdate = stamp.lastCompressed;\n    const unsyncedFrom = Math.min(\n      ...syncers.map((s) =>\n        Math.min(\n          s.unsentFrom || Infinity,\n          s.receivedUntil != null ? s.receivedUntil + 1 : Infinity\n        )\n      )\n    );\n    // Per updates-table:\n    // 1. Find all updates after lastCompressedId. Run toArray() on them.\n    // 2. IF there are any \"mine\" (flagged) updates AFTER unsentFrom, skip all from including this entry, else include all regardless of unsentFrom.\n    // 3. Now we know which keys have updates since last compression. We also know how far we're gonna go (max unsentFrom unless all additional updates are foreign).\n    // 4. For every key that had updates, load their main update (this is one single update per key before the lastCompressedId marker)\n    // 5. For every key that had updates: Compress main update along with additional updates until and including the number that was computed on step 2 (could be Infinity).\n    // 6. Update lastCompressedId to the i of the latest compressed entry.\n    return updTbl\n      .where('i')\n      .between(lastCompressedUpdate, Infinity, false)\n      .toArray((addedUpdates: YUpdateRow[]) => {\n        if (addedUpdates.length === 0) return; // No more updates where added\n        const docsToCompress: { docId: any; updates: YUpdateRow[] }[] = [];\n        let lastUpdateToCompress = lastCompressedUpdate;\n        for (let j = 0; j < addedUpdates.length; ++j) {\n          const updateRow = addedUpdates[j];\n          const { i, f, k } = updateRow;\n          if (i >= unsyncedFrom && f && f & 0x01) break; // An update that need to be synced was found. Stop here and let dontCompressFrom stay.\n          const entry = docsToCompress.find(\n            (entry) => cmp(entry.docId, k) === 0\n          );\n          if (entry) entry.updates.push(updateRow);\n          else docsToCompress.push({ docId: k, updates: [updateRow] });\n          lastUpdateToCompress = i;\n        }\n        if (lastUpdateToCompress === lastCompressedUpdate) return; // No updates to compress\n        let p = Promise.resolve();\n        for (const { docId, updates } of docsToCompress) {\n          p = p.then(() =>\n            compressUpdatesForDoc(db, updatesTable, docId, updates)\n          );\n        }\n        return p.then(() => {\n          // Update lastCompressed atomically to the value we computed.\n          // Do it with respect to the case when another job was done in parallel\n          // that maybe compressed one or more extra updates and updated lastCompressed\n          // before us.\n          return db.transaction('rw', updTbl, () =>\n            updTbl.get(0).then((current: YLastCompressed) => {\n              if (current && lastUpdateToCompress <= current.lastCompressed) {\n                // No need to update. Nothing was done, or another job did more.\n                return;\n              }\n              return updTbl.put({\n                ...current,\n                lastCompressed: lastUpdateToCompress,\n              });\n            })\n          );\n        });\n      });\n  });\n}\n\nexport function compressUpdatesForDoc(\n  db: Dexie,\n  updatesTable: string,\n  parentId: any,\n  addedUpdatesToCompress: YUpdateRow[]\n) {\n  if (addedUpdatesToCompress.length < 1) throw new Error('Invalid input');\n  return db.transaction('rw', updatesTable, (tx) => {\n    const updTbl = tx.table(updatesTable);\n    return updTbl.where({ k: parentId }).first((mainUpdate: YUpdateRow) => {\n      if (!mainUpdate) return; // No main update found. Nothing to compress.\n      const updates = [mainUpdate].concat(\n        addedUpdatesToCompress.filter((u) => u.i !== mainUpdate.i)\n      ); // avoid duplicating the main update (can happen sometimes)\n      const doc = new Y.Doc({ gc: true });\n      updates.forEach((update) => {\n        Y.applyUpdateV2(doc, update.u);\n      });\n      const compressedUpdate = Y.encodeStateAsUpdateV2(doc);\n      const lastUpdate = updates.pop()!;\n      return updTbl\n        .put({\n          i: lastUpdate.i,\n          k: parentId,\n          u: compressedUpdate,\n        })\n        .then(() => updTbl.bulkDelete(updates.map((update) => update.i)));\n    });\n  });\n}\n"
  },
  {
    "path": "addons/y-dexie/src/createYDocProperty.ts",
    "content": "import { Dexie, Table } from 'dexie';\nimport { getDocCache } from './docCache';\nimport { getOrCreateDocument } from './getOrCreateDocument';\n\nconst { getByKeyPath } = Dexie;\n\nexport function createYDocProperty(\n  db: Dexie,\n  table: Table,\n  prop: string,\n  updatesTable: string\n) {\n  const pkKeyPath = table.schema.primKey.keyPath;\n  if (!pkKeyPath) {\n    throw new Error(\n      `Cannot create Y.Doc property for ${table.name}.${prop} because the table has no inbound primary key. See https://dexie.org/docs/inbound`\n    );\n  }\n  const docCache = getDocCache(db);\n  return {\n    set() {\n      throw new TypeError(`Y.Doc properties are read-only`);\n    },\n    get(this: object) {\n      const id = getByKeyPath(this, pkKeyPath);\n      return getOrCreateDocument(\n        db,\n        docCache,\n        table.name,\n        prop,\n        updatesTable,\n        id\n      );\n    },\n  };\n}\n"
  },
  {
    "path": "addons/y-dexie/src/createYjsMiddleware.ts",
    "content": "import { DBCore, DBCoreTable, DBCoreMutateResponse } from 'dexie';\nimport * as Y from 'yjs';\nimport { DbSchema } from 'dexie';\nimport { YUpdateRow } from './types/YUpdateRow';\nimport { hasOwn } from './helpers/hasOwn';\n\nconst EMPTY_ARRAY = [] as const; // Optimization of returning empty array frequently in flatMap operaion.\n\nexport function createYjsMiddleware(dbSchema: DbSchema) {\n  return (downCore: DBCore) =>\n    ({\n      ...downCore,\n      table(tableName: string) {\n        const downTable = downCore.table(tableName);\n        const dbTableSchema = dbSchema[tableName]; // DBCore don't understand Yjs specific schema - need dexie xchema\n        const { yProps } = dbTableSchema;\n        if (!yProps || yProps.length === 0) return downTable;\n        const tableMiddleware: DBCoreTable = {\n          ...downTable,\n          mutate(req): Promise<DBCoreMutateResponse> {\n            if (req.type !== 'add' && req.type !== 'put')\n              return downTable.mutate(req);\n            // From here on, req.type is \"add\":\n            let reqClone = req;\n            const updateSources = yProps\n              .map((p) => ({\n                p,\n                entries: req.values.flatMap<{\n                  // Instead of map().filter() we use flatMap() to avoid creating intermediate arrays.\n                  iter: number;\n                  u: Uint8Array;\n                }>((value, iter) => {\n                  if (!value || typeof value !== 'object')\n                    throw new TypeError(\n                      `Table ${tableName} (with Y-properties) must only contain objects`\n                    );\n                  if (!hasOwn(value, p.prop)) return [];\n                  // Clone req, req.values and each value so that we can delete yProps from being stored:\n                  if (reqClone === req)\n                    reqClone = {\n                      ...req,\n                      values: req.values.map((v) => ({ ...v })),\n                    };\n                  // Delete prop so that it isn't physically stored in DB\n                  delete reqClone.values[iter][p.prop];\n\n                  const doc = value[p.prop] as Y.Doc | undefined | null;\n                  if (doc === null)\n                    throw new TypeError(`Cannot set Y property to null`);\n                  // Allow undefined, treat it as if the object didn't have the property at all.\n                  if (doc === undefined) return EMPTY_ARRAY;\n                  // Check that the property is of type Y.Doc:\n                  if (typeof doc !== 'object' || !('whenLoaded' in doc)) {\n                    throw new TypeError(\n                      `Y properties can only be inited with an Y.Doc instance or undefined to create an empty Y.Doc`\n                    );\n                  }\n                  if (req.type === 'put')\n                    // Don't allow setting y properties on put requests\n                    throw new TypeError(\n                      `Setting ${tableName}.${p.prop} (declared as ${p.prop}:Y) is only allowed when inserting new objects using db.${tableName}.add(), not put() or update().`\n                    );\n                  if (Y.encodeStateVector(doc).length === 1) {\n                    // Document is empty and has no updates\n                    return EMPTY_ARRAY;\n                  }\n                  // Clone the Yjs state before storing it in the database\n                  return {\n                    iter,\n                    u: Y.encodeStateAsUpdateV2(doc),\n                  };\n                }),\n              }))\n              .filter(({ entries }) => entries.length > 0);\n            if (req === reqClone)\n              // No object had their Y-props in own props - no need to intercept.\n              return downTable.mutate(req);\n\n            // We have a reqClone to forward down the stack. The reqClone\n            // is a copy of req, but where some objects have their yProps deleted.\n            return downTable.mutate(reqClone).then((res) => {\n              if (updateSources.length === 0)\n                // No updates to create (but user provided empty Y.Docs so reqClone was still needed)\n                return res;\n              // For each yProp affect, write docs (monolit-updates) to their corresponding tables.\n              return Promise.all(\n                updateSources.map(({ p, entries }) => {\n                  const updatesTable = downCore.table(p.updatesTable);\n                  const updatesToInsert: Omit<YUpdateRow, 'i'>[] = entries.map(\n                    ({ iter, u }) =>\n                      ({\n                        k: res.results![iter],\n                        u,\n                        f: 1, // Flag as local update (to be included when syncing)\n                      } satisfies Omit<YUpdateRow, 'i'>)\n                  );\n                  return updatesTable.mutate({\n                    type: 'add',\n                    values: updatesToInsert,\n                    trans: req.trans,\n                  });\n                })\n              ).then(() => res);\n            });\n          },\n        };\n        return tableMiddleware;\n      },\n    } satisfies DBCore);\n}\n"
  },
  {
    "path": "addons/y-dexie/src/currentUpdateRow.ts",
    "content": "import type { YUpdateRow } from './types/YUpdateRow';\n\nexport let currentUpdateRow: YUpdateRow | null = null;\n\nexport function setCurrentUpdateRow(row: YUpdateRow | null) {\n  currentUpdateRow = row;\n}\n"
  },
  {
    "path": "addons/y-dexie/src/docCache.ts",
    "content": "import { Dexie } from 'dexie';\nimport * as Y from 'yjs';\nimport { YDocCache } from './types/YDocCache';\nimport { DexieYDocMeta } from './types/DexieYDocMeta';\n\n// The finalization registry\nconst docRegistry = new FinalizationRegistry<{cache: any, key: string}>(({cache, key}) => {\n  delete cache[key];\n});\n\n// The Y.Doc cache containing all active documents\nexport function getDocCache(db: Dexie): YDocCache {\n  return db._novip['_docCache'] ??= {\n    cache: {} as { [key: string]: WeakRef<Y.Doc>; },\n    get size() {\n      return Object.keys(this.cache).length;\n    },\n    find(table: string, primaryKey: any, ydocProp: string): Y.Doc | undefined {\n      const cacheKey = getYDocCacheKey(table, primaryKey, ydocProp);\n      const docRef = this.cache[cacheKey];\n      return docRef ? docRef.deref() : undefined;\n    },\n    add(doc: Y.Doc): void {\n      const { parentTable, parentId, parentProp } = doc.meta as DexieYDocMeta;\n      if (!parentTable || !parentProp || parentId == null)\n        throw new Error(`Missing Dexie-related metadata in Y.Doc`);\n      const cacheKey = getYDocCacheKey(parentTable, parentId, parentProp);\n      const existingDoc = this.cache[cacheKey]?.deref();\n      if (existingDoc) {\n        docRegistry.unregister(existingDoc); // Don't run garbage collection on this doc as it is being replaced.\n      }\n      this.cache[cacheKey] = new WeakRef(doc);\n      docRegistry.register(doc, { cache: this.cache, key: cacheKey }, doc);\n    },\n    delete(doc: Y.Doc): void {\n      docRegistry.unregister(doc); // Don't run garbage collection on this doc as it is being deleted here and now.\n      const cacheKey = getYDocCacheKey(doc.meta.parentTable, doc.meta.parentId, doc.meta.parentProp);\n      const cacheEntry = this.cache[cacheKey];\n      if (cacheEntry?.deref() === doc) {\n        delete this.cache[cacheKey]; // Remove the entry from the cache only if it is the same doc.\n      }\n    },\n  };\n}\n\n// Emulate a private boolean property \"destroyed\" on Y.Doc instances that we manage\n// in createYDocProperty.ts:\nexport const destroyedDocs = new WeakSet<object>();\n\nexport function throwIfDestroyed(doc: any) {\n  if (destroyedDocs.has(doc))\n    throw new Error(`Y.Doc ${doc.meta.parentId} has been destroyed`);\n}\n\nexport function getYDocCacheKey(table: string, primaryKey: any, ydocProp: string): string {\n  return `${table}[${primaryKey}].${ydocProp}`;\n}\n"
  },
  {
    "path": "addons/y-dexie/src/getOrCreateDocument.ts",
    "content": "import type { Dexie } from 'dexie';\nimport { destroyedDocs } from './docCache';\nimport { YDocCache } from './types/YDocCache';\nimport * as Y from 'yjs';\nimport { DexieYDocMeta } from './types/DexieYDocMeta';\n\nexport function getOrCreateDocument(\n  db: Dexie,\n  docCache: YDocCache,\n  tableName: string,\n  prop: string,\n  updatesTable: string,\n  id: any\n) {\n  let doc = docCache.find(tableName, id, prop);\n  if (doc) return doc;\n\n  doc = new Y.Doc({\n    meta: {\n      db,\n      updatesTable,\n      parentProp: prop,\n      parentTable: tableName,\n      parentId: id,\n    } satisfies DexieYDocMeta,\n  });\n\n  docCache.add(doc);\n\n  doc.on('destroy', () => {\n    destroyedDocs.add(doc);\n    docCache.delete(doc);\n  });\n\n  return doc;\n}\n"
  },
  {
    "path": "addons/y-dexie/src/helpers/Disposable.ts",
    "content": "export type Disposable = typeof Symbol extends {\n  dispose: symbol;\n}\n  //@ts-ignore\n  ? {[Symbol.dispose]: () => void;\n  }\n  : {};\n// @ts-ignore\nif (typeof Symbol.dispose !== 'symbol') {\n  // @ts-ignore\n  try {Symbol.dispose = Symbol('dispose');} catch {}\n}\n"
  },
  {
    "path": "addons/y-dexie/src/helpers/hasOwn.ts",
    "content": "const _hasOwn = {}.hasOwnProperty;\nexport const hasOwn = (obj: object, prop: PropertyKey): boolean => {\n  return _hasOwn.call(obj, prop);\n};\n"
  },
  {
    "path": "addons/y-dexie/src/helpers/nonStoppableEventChain.ts",
    "content": "import { nop } from \"./nop\";\n\nexport function nonStoppableEventChain(f1: Function, f2: Function) {\n    if (f1 === nop) return f2;\n    return function () {\n        f1.apply(this, arguments);\n        f2.apply(this, arguments);\n    };\n}\n"
  },
  {
    "path": "addons/y-dexie/src/helpers/nop.ts",
    "content": "export function nop() {}"
  },
  {
    "path": "addons/y-dexie/src/helpers/promisableChain.ts",
    "content": "import { nop } from \"./nop\";\n\nexport function promisableChain(f1, f2) {\n    if (f1 === nop) return f2;\n    return function () {\n        var res = f1.apply(this, arguments);\n        if (res && typeof res.then === 'function') {\n            var thiz = this,\n                i = arguments.length,\n                args = new Array(i);\n            while (i--) args[i] = arguments[i];\n            return res.then(function () {\n                return f2.apply(thiz, args);\n            });\n        }\n        return f2.apply(this, arguments);\n    };\n}\n"
  },
  {
    "path": "addons/y-dexie/src/observeYDocUpdates.ts",
    "content": "import type { Dexie } from 'dexie';\nimport * as Y from 'yjs';\nimport { YUpdateRow } from './types/YUpdateRow';\nimport type { EntityTable } from 'dexie';\nimport { throwIfDestroyed } from './docCache';\nimport { liveQuery } from 'dexie';\nimport { cmp } from 'dexie';\nimport { setCurrentUpdateRow } from './currentUpdateRow';\nimport type { DexieYProvider } from './DexieYProvider';\n\nexport function observeYDocUpdates(\n  provider: DexieYProvider,\n  doc: Y.Doc,\n  db: Dexie,\n  parentTableName: string,\n  updatesTableName: string,\n  parentId: any\n): () => void {\n  let lastUpdateId = 0;\n  let initial = true;\n  const subscription = liveQuery(() => {\n    throwIfDestroyed(doc);\n    const updatesTable = db.table(updatesTableName) as EntityTable<\n      YUpdateRow,\n      'i'\n    >;\n    return Promise.all([\n      (lastUpdateId > 0\n        ? updatesTable\n            .where('i')\n            .between(lastUpdateId, Infinity, false)\n            .toArray()\n            .then((updates) =>\n              updates.filter((update) => cmp(update.k, parentId) === 0)\n            )\n        : updatesTable.where({ k: parentId }).toArray()\n      ).then((updates) => {\n        return updates;\n      }),\n      db.table(parentTableName).where(':id').equals(parentId).toArray(), // Why not just count() or get()? Because of cache only works with toArray() currently (optimization)\n    ]);\n  }).subscribe(\n    ([updates, parentRow]) => {\n      if (updates.length > 0) lastUpdateId = updates[updates.length - 1].i;\n      if (parentRow.length === 0) {\n        // Row deleted. Destroy Y.Doc.\n        doc.destroy();\n        return;\n      }\n      throwIfDestroyed(doc);\n      if (updates.length > 0) {\n        Y.transact(\n          doc,\n          () => {\n            updates.forEach((update) => {\n              try {\n                setCurrentUpdateRow(update);\n                Y.applyUpdateV2(doc, update.u);\n              } finally {\n                setCurrentUpdateRow(null);\n              }\n            });\n          },\n          provider,\n          false\n        );\n      }\n      if (initial) {\n        initial = false;\n        doc.emit('load', [doc]);\n      }\n    },\n    (error) => {\n      provider.on('error').fire(error);\n    }\n  );\n\n  const onUpdate = (update: Uint8Array, origin: any) => {\n    if (origin === provider) return; // Already applied.\n    db.table(updatesTableName)\n      .add({\n        k: parentId,\n        u: update,\n        f: 1, // Flag as local update (to be included when syncing)\n      } satisfies Omit<YUpdateRow, 'i'>)\n      .then((i: number) => {\n        // Optimization (not critical): Don't query for this update to put it back into the doc.\n        // However, skip this optimization if the lastUpdateId is behind the current update.\n        // In that case, next liveQuery emission will include also this update and re-apply it into doc,\n        // but it will not be an issue because Y.Doc will ignore duplicate updates.\n        if (i === lastUpdateId - 1) ++lastUpdateId;\n      })\n      .catch((error) => {\n        provider.on('error').fire(error);\n      });\n  };\n\n  const stopObserving = () => {\n    subscription.unsubscribe();\n    doc.off('updateV2', onUpdate);\n    doc.off('destroy', stopObserving);\n  };\n\n  doc.on('updateV2', onUpdate);\n  doc.on('destroy', stopObserving);\n\n  return stopObserving;\n}\n"
  },
  {
    "path": "addons/y-dexie/src/periodicGC.ts",
    "content": "import { Dexie } from 'dexie';\nimport { compressYDocs } from './compressYDocs';\n\nconst GC_DELAY = 10_000; // Delay before starting GC when DB is started\nconst GC_INTERVAL = 300_000; // Every 5 minutes\n\nexport function periodicGC(db: Dexie) {\n  let timer: any = null;\n  db.on(\n    'ready',\n    (db: Dexie) => {\n      if (db.tables.some(tbl => tbl.schema.yProps)) {\n        const gc = () => {\n          if (!db.isOpen()) return;\n          compressYDocs(db, GC_INTERVAL).catch(err => {\n            if (err && err.name === 'DatabaseClosedError') return;\n            console.debug('Error during periodic GC', err);\n          }).then(() => {\n            timer = setTimeout(gc, GC_INTERVAL);\n          });\n        };\n        timer = setTimeout(gc, GC_DELAY);\n      }\n    },\n    true\n  );\n  db.on('close', () => {\n    if (timer) clearTimeout(timer);\n    timer = null;\n  });\n}\n"
  },
  {
    "path": "addons/y-dexie/src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es2015\",\n    \"target\": \"es2020\",\n    \"declaration\": true,\n    \"importHelpers\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"ES2021\", \"DOM\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"sourceMap\": true,\n    \"rootDir\": \".\",\n    \"downlevelIteration\": true\n  },\n  \"include\": [\"**/*.ts\"]\n}\n"
  },
  {
    "path": "addons/y-dexie/src/types/DexieYDocMeta.ts",
    "content": "import type { Dexie } from \"dexie\";\n\nexport interface DexieYDocMeta {\n  db: Dexie;\n  parentTable: string;\n  parentId: any;\n  parentProp: string;\n  updatesTable: string;\n}\n"
  },
  {
    "path": "addons/y-dexie/src/types/YDocCache.ts",
    "content": "import type * as Y from 'yjs';\n\nexport interface YDocCache {\n  readonly size: number;\n  find: (table: string, primaryKey: any, ydocProp: string) => Y.Doc | undefined\n  add: (doc: Y.Doc) => void;\n  delete: (doc: Y.Doc) => void;\n}\n"
  },
  {
    "path": "addons/y-dexie/src/types/YLastCompressed.ts",
    "content": "/** A stamp of the last compressed and garbage collected update in the update table.\n * The garbage collection process will find out which documents have got new updates since the last compressed update\n * and compress them into their corresponding main update.\n * \n * The id of this row is always 0 - which is a reserved id for this purpose.\n*/\nexport interface YLastCompressed {\n  i: 0;\n  lastCompressed: number;\n  lastRun?: Date;\n}\n"
  },
  {
    "path": "addons/y-dexie/src/types/YSyncState.ts",
    "content": "/** Stored in update tables along with YUpdateRows but with a string representing the syncing enging, as primary key\n * A syncing engine can create an YSyncState row with an unsentFrom or receivedUntil value set to the a number representing primary key (i)\n * of updates that has not been sent to server or peer yet. Dexie will compute the least value of unsentFrom and receivedUntil + 1 and\n * spare all updates with an 'i' of that value or greater in the updates table from being compressed and garbage collected into the main update.\n*/\nexport interface YSyncState {\n  i: string;\n  unsentFrom?: number;\n  receivedUntil?: number;\n}\n"
  },
  {
    "path": "addons/y-dexie/src/types/YUpdateRow.ts",
    "content": "import type { IndexableType } from \"dexie\";\n\n/** Stored in the updates table with auto-incremented number as primary key\n * \n */\nexport interface YUpdateRow {\n  /** The primary key in the update-table\n   * \n   */\n  i: number;\n\n  /** The primary key of the row in related table holding the document property.\n   * \n   */\n  k: IndexableType;\n\n  /** The Y update\n   * \n   */\n  u: Uint8Array;\n\n  /** Optional flag\n   * \n   * 1 = LOCAL_CHANGE_MAYBE_UNSYNCED\n   * \n   */\n  f?: number; \n}\n"
  },
  {
    "path": "addons/y-dexie/src/types/index.ts",
    "content": "export * from './DexieYDocMeta';\nexport * from './YDocCache';\nexport * from './YLastCompressed';\nexport * from './YSyncState';\nexport * from './YUpdateRow';\n"
  },
  {
    "path": "addons/y-dexie/src/y-dexie.ts",
    "content": "import { DbSchema, Dexie, ExtendableVersion, IndexSpec, TableSchema } from 'dexie';\nimport { createYjsMiddleware } from './createYjsMiddleware';\nimport { createYDocProperty } from './createYDocProperty';\nimport { periodicGC } from './periodicGC';\n\nconst YJS_MIDDLEWARE_NAME = 'yjsMiddleware';\n\nexport interface YDexieOptions {\n  gc?: boolean; // Enable or disable garbage collection for Y.js documents.\n}\n\nexport { compressYDocs } from './compressYDocs';\nexport { DexieYProvider } from './DexieYProvider';\nexport * from './types';\n\nexport default function yDexie(dbOrOptions: Dexie | YDexieOptions) {\n  // This function is a placeholder for the y-dexie addon.\n  // It can be used to initialize or configure the addon as needed.\n  if (!('transaction' in dbOrOptions)) {\n    // If db is an options object, create a configured yDexie addon that\n    // could be passed to the addons array of Dexie constructor.\n    const options = dbOrOptions;\n    // Return a configured Dexie addon function.\n    return (db: Dexie) => configurableYDexie(db, options);\n  } else {\n    // If db is a Dexie instance, it is being called as an addon.\n    // Do default configuration.\n    return configurableYDexie(dbOrOptions, {});\n  }\n}\n\nfunction configurableYDexie(db: Dexie, options: YDexieOptions) {\n  db.Table = class Table extends (db.Table as (new() => Dexie.Table<any>)) {\n    mapToClass(constructor: Function) {\n      if (this.schema.yProps) {\n        constructor = class extends (constructor as any) {};\n        this.schema.yProps.forEach(({prop, updatesTable}) => {\n          Object.defineProperty(constructor.prototype, prop, createYDocProperty(db, this, prop, updatesTable));\n        });\n      }\n      const result = super.mapToClass(constructor);\n      this.schema.mappedClass = constructor; // Also done in super.mapToClass but we need to set the user-provided class, not our altered class.\n      return result;\n    }\n  };\n\n  db.Version = class Version extends (db.Version as (new() => ExtendableVersion)) {\n    _createTableSchema(\n      name: string,\n      primKey: IndexSpec,\n      indexes: IndexSpec[]\n    ): TableSchema {\n      const yProps = indexes.filter(\n        (idx) => idx.type === 'Y' || idx.type === 'Y.Doc'\n      );\n      indexes = indexes.filter((idx) => !yProps.includes(idx)); // Y marks just the Y.Doc type and is not an index\n      const tableSchema = super._createTableSchema(\n        name,\n        primKey,\n        indexes\n      ) as TableSchema;\n      if (yProps.length > 0) {\n        tableSchema.yProps = yProps.map((idx) => ({\n          prop: idx.name,\n          updatesTable: `$${name}.${idx.name}_updates`,\n        }));\n      }\n      return tableSchema;\n    }\n\n    _parseStoresSpec(\n      stores: { [tableName: string]: string | null },\n      outSchema: DbSchema\n    ): void {\n      // Implementation for parsing stores spec\n      // This is a placeholder; actual implementation would go here\n      super._parseStoresSpec(stores, outSchema);\n\n      // Generate update tables for Y.js properties\n      Object.keys(stores).forEach((tableName) => {\n        const tblSchema = outSchema[tableName];\n        if (tblSchema) {\n          for (const yProp of tblSchema.yProps || []) {\n            super._parseStoresSpec(\n              // Add a table for each yProp containing document updates.\n              // See interface YUpdateRow { i: number, k: IndexableType, u: Uint8Array, f?: number}\n              // where\n              //   i is the auto-incremented primary key of the update table,\n              //   k is the primary key from the other table holding the document in a property.\n              //   u is the update data from Y.js\n              //   f is a flag indicating if the update comes from this client or another.\n              // Index use cases:\n              //   * Load entire document: Use index k\n              //   * After object load, observe updates on a certain document since a given revision: Use index k or i since [k+i] is not supported before Firefox 126.\n              //   * After initial sync, observe flagged updates since a given revision: Use index i and ignore unflagged.\n              //     Could be using an index [f+i] but that wouldn't gain too much and Firefox before 126 doesnt support it.\n              //     Local updates are flagged while remote updates are not.\n              //\n              { [yProp.updatesTable]: '++i,k' },\n              outSchema\n            );\n          }\n        }\n      });\n    }\n\n    stores(schema: { [tableName: string]: string | null }) {\n      const db = this.db as Dexie;\n      // This method is used to define the schema for the database.\n      // It allows you to specify the tables and their indexes.\n      const result = super.stores(schema);\n      const dbschema = db._dbSchema;\n      Object.keys(dbschema).forEach((tableName) => {\n        if (dbschema[tableName].yProps) {\n          // If a table as yProps, make sure to derive a class with generated Y properties.\n          // This is done in the mapToClass method. In case user has called mapToClass already, respect mappedClass,\n          // otherwise use Object as default to create a top-level class with the generated y properties.\n          db.table(tableName).mapToClass(\n            dbschema[tableName].mappedClass || Object\n          );\n        }\n      });\n      if (Object.values(dbschema).some((table) => table.yProps)) {\n        db.use({\n          stack: 'dbcore',\n          name: YJS_MIDDLEWARE_NAME,\n          level: 50,\n          create: createYjsMiddleware(dbschema),\n        });\n      } else {\n        db.unuse({ stack: 'dbcore', name: YJS_MIDDLEWARE_NAME });\n      }\n\n      return result;\n    }\n  };\n\n  if (options?.gc !== false) {\n    periodicGC(db);\n  }\n}\n"
  },
  {
    "path": "addons/y-dexie/test/gh-actions.sh",
    "content": "#!/bin/bash -e\necho \"Installing dependencies for y-dexie\"\npnpm install >/dev/null\npnpm run build\npnpm run test:ltcloud\n"
  },
  {
    "path": "addons/y-dexie/test/promisedTest.ts",
    "content": "import {asyncTest, start, stop, ok, equal} from 'qunit';\n\nexport function promisedTest(name: string, tester: ()=>Promise<any>) {\n  asyncTest(name, async ()=>{\n    try {\n      await tester();\n    } catch (error) {\n      ok(false, \"Got error: \" + (error ?\n        error +\n          (error.code ? ` (code: ${error.code})` : ``) + \n          (error.stack ? \"\\n\" + error.stack : '') :\n        error));\n    } finally {\n      start();\n    }\n  });\n}\n"
  },
  {
    "path": "addons/y-dexie/test/qunit.d.ts",
    "content": "declare module 'qunit' {\n  interface Assert {\n    async(): () => void;\n  }\n  \n  function module(name: string, options?: {\n    setup?: () => void;\n    teardown?: () => void\n  });\n  function test(name: string, fn: (assert: Assert) => void): void;\n  function test(name: string, fn: () => void): void;\n  function asyncTest(name: string, fn: () => void);\n  function start();\n  function stop();\n  function strictEqual(a: any, b: any, description: string);\n  function deepEqual(a: any, b: any, description: string);\n  function equal(a: any, b: any, description: string);\n  function ok(x: any, description: string);\n}\n"
  },
  {
    "path": "addons/y-dexie/test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es2015\",\n    \"target\": \"ES5\",\n    \"declaration\": true,\n    \"importHelpers\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"skipLibCheck\": true,\n    \"allowJs\": true,\n    \"lib\": [\"ES2021\", \"DOM\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"sourceMap\": true,\n    \"rootDir\": \"..\",\n    \"downlevelIteration\": true,\n    \"outDir\": \"../tools/tmp/\",\n  },\n  \"include\": [\n    \"../src/\",\n    \"./unit/\",\n    \"./*.ts\",\n    \"./qunit.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "addons/y-dexie/test/unit/dexie-unittest-utils.js",
    "content": "﻿import Dexie from 'dexie';\nimport {ok, start, test, config} from 'qunit';\n\n// Custom QUnit config options.\nconfig.urlConfig.push(/*{\n    id: \"polyfillIE\", // Remarked because has no effect anymore. Find out why.\n\tlabel: \"Include IE Polyfill\",\n    tooltip: \"Enabling this will include the idb-iegap polyfill that makes\" +\n    \" IE10&IE11 support multiEntry and compound indexes as well as compound\" +\n    \" primary keys\"\n}, {\n    id: \"indexedDBShim\", // Remarked because has no effect anymore. Need to find out why. Should invoke the shim if set!\n    label: \"IndexedDBShim (UseWebSQL as backend)\",\n    tooltip: \"Enable this in Safari browsers without indexedDB support or\" +\n    \" with poor indexedDB support\"\n},*/ {\n    id: \"dontoptimize\",\n    label: \"Dont optimize tests\",\n    tooltip: \"Always delete and recreate the DB between each test\"\n});\n\nvar no_optimize = window.no_optimize || window.location.search.indexOf('dontoptimize') !== -1;\n\nconst ArrayBuffer = window.ArrayBuffer;\n\nfunction stringify (idbKey) {\n    var res = '' + (idbKey && idbKey.constructor && idbKey.constructor === ArrayBuffer ?\n        new Uint8Array(idbKey) : idbKey);\n    return res;\n}\n\nexport function resetDatabase(db) {\n    /// <param name=\"db\" type=\"Dexie\"></param>\n    var Promise = Dexie.Promise;\n    return no_optimize || !db._hasBeenCreated ?\n        // Full Database recreation. Takes much time!\n        db.delete().then(function () {\n            return db.open().then(function() {\n                if (!no_optimize) {\n                    db._hasBeenCreated = true;\n                    var initialState = (db._initialState = {});\n                    // Now, snapshot the database how it looks like initially (what on.populate did)\n                    return db.transaction('r', db.tables, function() {\n                        var trans = Dexie.currentTransaction;\n                        return Promise.all(trans.storeNames.filter(function(tableName) {\n                            // Don't clear 'meta tables'\n                            return tableName[0] != '_' && tableName[0] != '$';\n                        }).map(function (tableName) {\n                            var items = {};\n                            initialState[tableName] = items;\n                            return db.table(tableName).each(function(item, cursor) {\n                                items[stringify(cursor.primaryKey)] = { key: cursor.primaryKey, value: item };\n                            });\n                        }));\n                    });\n                }\n            });\n        })\n\n        :\n\n        // Optimize: Don't delete and recreate database. Instead, just clear all object stores,\n        // and manually run db.on.populate\n        db.transaction('rw!', db.tables, function() {\n            // Got to do an operation in order for backend transaction to be created.\n            var trans = Dexie.currentTransaction;\n            var initialState = db._initialState;\n            return Promise.all(trans.storeNames.filter(function(tableName) {\n                // Don't clear 'meta tables'\n                return tableName[0] != '_' && tableName[0] != '$';\n            }).map(function(tableName) {\n                // Read current state\n                var items = {};\n                return db.table(tableName).each(function(item, cursor) {\n                    items[stringify(cursor.primaryKey)] = { key: cursor.primaryKey, value: item };\n                }).then(function() {\n                    // Diff from initialState\n                    // Go through initialState and diff with current state\n                    var initialItems = initialState[tableName];\n                    return Promise.all(Object.keys(initialItems).map(key => {\n                        var item = items[key];\n                        var initialItem = initialItems[key];\n                        if (!item || JSON.stringify(item.value) != JSON.stringify(initialItem.value))\n                            return (db.table(tableName).schema.primKey.keyPath ? db.table(tableName).put(initialItem.value) :\n                                db.table(tableName).put(initialItem.value, initialItem.key));\n                        return Promise.resolve();\n                    }));\n                }).then(function() {\n                    // Go through current state and diff with initialState\n                    var initialItems = initialState[tableName];\n                    var keysToDelete = Object.keys(items)\n                        .filter(key => !initialItems[key])\n                        .map(key => items[key].key);\n\n                    if (keysToDelete.length > 0) {\n                        return db.table(tableName).bulkDelete(keysToDelete);\n                    }\n                });\n            }));\n        });\n}\n\nexport function deleteDatabase(db) {\n    var Promise = Dexie.Promise;\n    return no_optimize ? db.delete() : db.transaction('rw!', db.tables, function() {\n        // Got to do an operation in order for backend transaction to be created.\n        var trans = Dexie.currentTransaction;\n        return Promise.all(trans.storeNames.filter(function(tableName) {\n            // Don't clear 'meta tables'\n            return tableName[0] != '_' && tableName[0] != '$';\n        }).map(function(tableName) {\n            // Clear all tables\n            return db.table(tableName).clear();\n        }));\n    });\n}\n\nexport const isIE = !(window.ActiveXObject) && \"ActiveXObject\" in window;\nexport const isEdge = /Edge\\/\\d+/.test(navigator.userAgent);\nexport const isChrome = !!window.chrome;\nvar hasPolyfillIE = [].slice.call(document.getElementsByTagName(\"script\")).some(\n    s => s.src.indexOf(\"idb-iegap\") !== -1);\n    \nexport const isSafari = typeof navigator !== 'undefined' &&\n    /Safari\\//.test(navigator.userAgent) &&\n    !/Chrom(e|ium)\\/|Edge\\//.test(navigator.userAgent);\n\n// Safari private mode are being used on LambdaTest's servers and even if Safari does a good job to\n// support IndexedDB in private mode, it comes with some issues. One of them is that it doesn't\n// seem to respect when doing preventDefault() on IDB request error events, which dexie does in order\n// to override the default cancelling of transactions on error events in case they were catched explicitely.\n// We use this vars to omit certain unit tests from the suite so that they don't fail for Safari private mode\n// which is being used in LambdaTest's servers.\nexport const isSafariPrivateMode = isSafari; // Sorry there's no way to distinguish private mode from non-private in modern Safari. Still keep it as separate variable for exposing the purpose where it's being used.\n\nexport function supports (features) {\n    return features.split('+').reduce((result,feature)=>{\n        switch (feature.toLowerCase()) {\n            case \"compound\":\n                return result && Array.isArray(Dexie.maxKey);\n            case \"multientry\":\n                return result && (hasPolyfillIE || (!isIE && !isEdge)); // Should add Safari to\n            case \"deleteobjectstoreafterread\":\n                return result && (!isIE && !isEdge);\n            case \"versionchange\":\n                return result;\n                //return result && (!isIE && !isEdge); // Should add Safari to\n            case \"binarykeys\":\n                try {\n                    return result && Array.isArray(Dexie.maxKey) && indexedDB.cmp(new Uint8Array([1]), new Uint8Array([1])) === 0;\n                } catch (e) {\n                    return false;\n                }\n            case \"domevents\":\n                return typeof window === 'object' && window.addEventListener;\n\n            default:\n                throw new Error (\"Unknown feature: \" + feature);\n        }\n    }, true);\n}\n\nexport function spawnedTest (name, num, promiseGenerator) {\n    if (!promiseGenerator) {\n        promiseGenerator = num;\n        test(name, function(assert) {\n            let done = assert.async();\n            Dexie.spawn(promiseGenerator)\n                .catch(e => ok(false, e.stack || e))\n                .then(done);\n        });\n    } else {\n        test(name, num, function(assert) {\n            let done = assert.async();\n            Dexie.spawn(promiseGenerator)\n                .catch(e => ok(false, e.stack || e))\n                .then(done);\n        });\n    }\n}\n\nexport function promisedTest (name, num, asyncFunction) {\n    if (!asyncFunction) {\n        asyncFunction = num;\n        test(name, (assert) => {\n            let done = assert.async();\n            Promise.resolve().then(asyncFunction)\n              .catch(e => ok(false, e.stack || e))\n              .then(done);\n        });\n    } else {\n        test(name, num, (assert) => {\n            let done = assert.async();\n            Promise.resolve().then(asyncFunction)\n              .catch(e => ok(false, e.stack || e))\n              .then(done);\n        });\n    }\n}\n"
  },
  {
    "path": "addons/y-dexie/test/unit/index.ts",
    "content": "import \"./tests-dummy\";\nimport \"./tests-yjs\";"
  },
  {
    "path": "addons/y-dexie/test/unit/karma-env.js",
    "content": ""
  },
  {
    "path": "addons/y-dexie/test/unit/karma.conf.cjs",
    "content": "// Include common configuration\nconst {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../../../test/karma.common');\n\nmodule.exports = function (config) {\n  const browserMatrixOverrides = {\n    // Be fine with testing on local travis firefox + browserstack chrome, latest supported.\n    ci: [\"Chrome\"],\n    // Safari fails to reply on browserstack. Need to not have it here.\n    // Just complement with old chrome browser that is not part of CI test suite.\n    pre_npm_publish: [\n      \"Chrome\",\n    ]\n  };\n\n  const cfg = getKarmaConfig(browserMatrixOverrides, {\n    // Base path should point at the root \n    basePath: '../../../../',\n    files: karmaCommon.files.concat([\n      'dist/dexie.js',\n      'addons/y-dexie/test/unit/bundle.js',\n      { pattern: 'addons/y-dexie/test/*.map', watched: false, included: false },\n      { pattern: 'addons/y-dexie/dist/*.map', watched: false, included: false }\n    ])\n  });\n\n  cfg.hostname = 'localhost';\n  cfg.port = 9876;\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "addons/y-dexie/test/unit/run-unit-tests.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Y-dexie Unit tests</title>\n  <link rel=\"stylesheet\" href=\"../../../../node_modules/qunitjs/qunit/qunit.css\">\n</head>\n<body>\n    <div id=\"qunit\"></div>\n    <div id=\"qunit-fixture\"></div>\n    <script src=\"../../../../test/babel-polyfill/polyfill.min.js\"></script>\n    <script src=\"../../../../node_modules/qunitjs/qunit/qunit.js\"></script>\n    <script src=\"../../../../dist/dexie.js\"></script>\n    <script src=\"bundle.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "addons/y-dexie/test/unit/tests-dummy.ts",
    "content": "import {\n  module,\n  test,\n  asyncTest,\n  start,\n  stop,\n  strictEqual,\n  ok,\n  equal,\n  deepEqual,\n} from 'qunit';\nimport { promisedTest } from '../promisedTest';\nimport Dexie from 'dexie';\nimport * as Y from 'yjs';\n\nmodule('dummy');\n\npromisedTest('dummy-test', async () => {\n  const db = new Dexie('dummy-test');\n  db.version(1).stores({ friends: 'guid' });\n  await db.open();\n  ok(true, 'Dexie database created successfully');\n  await db.delete();\n  ok(true, 'Dexie database deleted successfully');\n  const yDoc = new Y.Doc();\n  ok(yDoc instanceof Y.Doc, 'Y.js document created successfully');\n});\n"
  },
  {
    "path": "addons/y-dexie/test/unit/tests-yjs.ts",
    "content": "import Dexie from 'dexie';\nimport yDexie, { compressYDocs, DexieYProvider } from '../../src/y-dexie';\nimport { module, stop, start, equal, deepEqual, ok } from 'qunit';\nimport {\n  resetDatabase,\n  promisedTest,\n} from './dexie-unittest-utils';\nimport * as Y from 'yjs';\nimport * as awareness from 'y-protocols/awareness';\n\nconst db = new Dexie('TestYjs', { addons: [yDexie] }) as Dexie & {\n  docs: Dexie.Table<{\n    id: string;\n    title?: string;\n    content?: Y.Doc\n  }, string>;\n}\ndb.version(1).stores({\n  docs: 'id, title, content:Y.Doc',\n});\n\nmodule('yjs', {\n  setup: () => {\n    stop();\n    resetDatabase(db)\n      .catch((e) => {\n        ok(false, 'Error resetting database: ' + e.stack);\n      })\n      .finally(start);\n  },\n  teardown: () => {},\n});\n\npromisedTest('Test Y.js basic support', async () => {\n  await db.docs.put({\n    id: \"doc1\",\n    title: \"Hello\",\n  });\n  let row = await db.docs.get(\"doc1\");\n  equal(row!.title, \"Hello\", \"Title is correct\");\n  let doc = row!.content;\n  ok(doc instanceof Y.Doc, \"Content is a Y.Doc\");\n\n  let row2 = await db.docs.get(\"doc1\");\n  let doc2 = row2!.content;\n  equal(doc, doc2, \"The two doc instances are the same\");\n\n  let rows = await db.docs.toArray();\n  equal(rows[0].content, doc, \"The two doc instances are the same\");\n\n  // Now destroy the doc:\n  doc!.destroy();\n  row2 = await db.docs.get(\"doc1\");\n  doc2 = row2!.content;\n  ok(doc !== doc2, \"After destroying doc, a new instance is retrieved\");\n\n  // Delete document\n  await db.docs.delete(\"doc1\");\n});\n\npromisedTest('Test DexieYProvider', async () => {\n  debugger;\n  await db.docs.put({\n    id: \"doc2\",\n    title: \"Hello2\",\n  });\n  let row = await db.docs.get(\"doc2\");\n  /* @type {Y.Doc} */\n  let doc = row!.content!;\n  let provider = new DexieYProvider(doc);\n  // Await the transaction of doc manipulation to not\n  // bring down the database before it has been stored.\n  await db.transaction('rw', db.docs, () => {\n    doc!.getArray('arr').insert(0, ['a', 'b', 'c']);\n  });\n  //await provider.whenLoaded;\n  doc!.destroy();\n  db.close({disableAutoOpen: false});\n  await db.open();\n  row = await db.docs.get(\"doc2\");\n  doc = row!.content!;\n  provider = new DexieYProvider(doc);\n  await doc!.whenLoaded;\n  // Verify that we got the same data:\n  deepEqual(doc!.getArray('arr').toJSON(), ['a', 'b', 'c'], \"Array is correct after reload\");\n  // Verify we have updates in the update table (this part can be deleted if implementation is changed)\n  let updates = await db.table('$docs.content_updates').toArray();\n  ok(updates.length > 0, \"Got updates in update table\");\n  await db.docs.clear();\n  updates = await db.table('$docs.content_updates').toArray();\n  equal(updates.length, 0, \"No updates in update table after deleting document\");\n});\n\n\npromisedTest('Test Y document compression', async () => {\n  await db.docs.put({\n    id: 'doc1',\n    title: 'Hello',\n  });\n  let row = await db.docs.get('doc1');\n  let doc = row!.content!;\n  let provider = new DexieYProvider(doc);\n\n  // Verify there are no updates in the updates table initially:\n  const updateTable = db.docs.schema.yProps!.find(\n    (p) => p.prop === 'content'\n  )!.updatesTable;\n  equal(await db.table(updateTable).count(), 0, 'No docs stored yet');\n\n  // Create three updates:\n  await db.transaction('rw', db.docs, () => {\n    doc!.getArray('arr').insert(0, ['a', 'b', 'c']);\n    doc!.getArray('arr').insert(0, ['1', '2', '3']);\n    doc!.getArray('arr').insert(0, ['x', 'y', 'z']);\n  });\n  // Verify we have 3 updates:\n  equal(await db.table(updateTable).where('i').between(1,Infinity).count(), 3, 'Three updates stored');\n  // Run the GC:\n  console.debug('Running GC', await db.table(updateTable).toArray());\n  await compressYDocs(db);\n  console.debug('After running GC', await db.table(updateTable).toArray());\n  // Verify we have 1 (compressed) update:\n  equal(await db.table(updateTable).where('i').between(1,Infinity).count(), 1, 'One update stored after gc');\n  // Verify the provider is still alive:\n  ok(!provider.destroyed, \"Provider is not destroyed\");\n  await db.transaction('rw', db.docs, () => {\n    doc!.getArray('arr').insert(0, ['a', 'b', 'c']);\n    doc!.getArray('arr').insert(0, ['1', '2', '3']);\n    doc!.getArray('arr').insert(0, ['x', 'y', 'z']);\n  });\n  equal(await db.table(updateTable).where('i').between(1,Infinity).count(), 4, 'Four updates stored after additional inserts');\n  console.debug('Running GC', await db.table(updateTable).toArray());\n  await compressYDocs(db);\n  console.debug('After running GC', await db.table(updateTable).toArray());\n  equal(await db.table(updateTable).where('i').between(1,Infinity).count(), 1, 'One update stored after gc');\n  await db.docs.put({\n    id: 'doc2',\n    title: 'Hello2',\n  });\n  let row2 = await db.docs.get('doc2');\n  let doc2 = row2!.content!;\n  await new DexieYProvider(doc2).whenLoaded;\n  await db.transaction('rw', db.docs, async () => {    \n    doc2.getArray('arr2').insert(0, ['a', 'b', 'c']);\n    doc2.getArray('arr2').insert(0, ['1', '2', '3']);\n    doc2.getArray('arr2').insert(0, ['x', 'y', 'z']);\n  });\n  console.debug('After adding thigns to other doc', await db.table(updateTable).toArray());\n  equal(await db.table(updateTable).where('i').between(1,Infinity).count(), 4, 'Four updates stored after additional inserts on other doc');\n  await compressYDocs(db);\n  console.debug('After GC where we have 2 docs', await db.table(updateTable).toArray());\n  equal(await db.table(updateTable).where('i').between(1,Infinity).count(), 2, 'Two updates stored after gc (2 different docs)');\n\n  // Now clear the docs table, which should implicitly clear the updates as well as destroying connected providers:\n  await db.docs.clear();\n  console.debug('After db.docs.clear()', await db.table(updateTable).toArray());\n  // Verify there are no updates now:\n  equal(\n    await db.table(updateTable).where('i').between(1,Infinity).count(),\n    0,\n    'Zero update stored after clearing docs'\n  );\n  // Verify the provider has been destroyed:\n  ok(provider.destroyed, \"Provider was destroyed when document was deleted\");\n});\n\n\npromisedTest('Test that syncers prohibit GC from compressing unsynced updates', async () => {\n  await db.docs.put({\n    id: 'doc1',\n    title: 'Hello',\n  });\n  let row = await db.docs.get('doc1');\n  let doc = row!.content!;\n  let provider = new DexieYProvider(doc);\n\n  // Verify there are no updates in the updates table initially:\n  const updateTable = db.docs.schema.yProps!.find(\n    (p) => p.prop === 'content'\n  )!.updatesTable;\n  equal(await db.table(updateTable).where('i').between(1,Infinity).count(), 0, 'No docs stored yet');\n\n  // Create three updates:\n  await db.transaction('rw', db.docs, () => {\n    //Y.transact(doc, () => {\n    doc.getArray('arr').insert(0, ['a', 'b', 'c']);\n    doc.getArray('arr').insert(0, ['1', '2', '3']);\n    doc.getArray('arr').insert(0, ['x', 'y', 'z']);\n    //});\n  });\n  // Verify we have 3 updates:\n  equal(await db.table(updateTable).where('i').between(1,Infinity).count(), 3, 'Three updates stored');\n\n  // Put a syncer in place that will not sync the updates:\n  await db.table(updateTable).put({\n    i: \"MySyncer\",\n    unsentFrom: await db.table(updateTable).orderBy('i').lastKey(), // Keep the last update and updates after that from being compressed\n  });\n\n  console.debug('Running GC');\n  await compressYDocs(db);\n  console.debug('After running GC');\n  // Verify we have 2 updates (the first 2 was compressed but the last one was not):\n  equal(await db.table(updateTable).where('i').between(1, Infinity).count(), 2, '2 updates stored');\n  await db.docs.delete(row!.id);\n  // Verify we have 0 updates after deleting the row holding our Y.Doc property:\n  equal(await db.table(updateTable).where('i').between(1, Infinity).count(), 0, '0 updates stored');\n  ok(provider.destroyed, \"Provider was destroyed when our document was deleted\");\n});\n\n\npromisedTest('Test creating a row with a populated Y.Doc', async () => {\n  const doc = new Y.Doc();\n  doc.getArray('arr').insert(0, ['a', 'b', 'c']);\n  await db.docs.add({\n    id: \"doc1\",\n    title: \"Hello\",\n    content: doc\n  });\n  let row = await db.docs.get(\"doc1\");\n  equal(row!.title, \"Hello\", \"Title is correct\");\n  let doc2 = row!.content;\n  ok(doc2 instanceof Y.Doc, \"Content is a Y.Doc\");\n  DexieYProvider.load(doc2!);\n  await doc2!.whenLoaded;\n  deepEqual(doc2!.getArray('arr').toJSON(), ['a', 'b', 'c'], \"Array is correct\");\n  DexieYProvider.release(doc2!);\n});\n\npromisedTest('Verify error handling of misused Y.Doc properties', async () => {  \n  // Setting to null:\n  await db.docs.put({\n    id: \"doc1\",\n    title: \"Hello\",\n    // @ts-ignore\n    content: null\n  }).then(() => {\n    ok(false, \"Expected error when trying to create an Y.Doc property with null value\");\n  }).catch(error => {\n    ok(error instanceof TypeError, \"Expected TypeError when trying to create an Y.Doc property with null value\");\n  });\n  // Setting to undefined should be allowed (because a class with the property declared must work):\n  await db.docs.put({\n    id: \"doc1\",\n    title: \"Hello\",\n    content: undefined\n  });\n  ok(true, \"could set the property to undefined\");\n\n  // Setting to a string:\n  await db.docs.put({\n    id: \"doc1\",\n    title: \"Hello\",\n    // @ts-ignore\n    content: \"a string\"\n  }).then(() => {\n    ok(false, \"Expected error when trying to create an Y.Doc property to a string\");\n  }).catch(error => {\n    ok(error instanceof TypeError, \"Expected TypeError when trying to create an Y.Doc property to a string\");\n  });\n  // Setting to a Blob:\n  await db.docs.put({\n    id: \"doc1\",\n    title: \"Hello\",\n    // @ts-ignore\n    content: new Blob([])\n  }).then(() => {\n    ok(false, \"Expected error when trying to create an Y.Doc property to a Blob\");\n  }).catch(error => {\n    ok(error instanceof TypeError, \"Expected TypeError when trying to create an Y.Doc property to a Blob\");\n  });\n  // Specifying an Y.Doc when adding is ok:\n  await db.docs.add({\n    id: \"doc2\",\n    title: \"Hola\",\n    content: new Y.Doc()\n  });\n  ok(true, \"Successfully added a new Y.Doc\");\n  await db.docs.put({\n    id: \"doc3\",\n    title: \"Thridi\",\n    content: new Y.Doc()\n  }).then(()=>{\n    ok(false, \"Expected error when trying to put() with content data set on own property\");\n  }).catch(error => {\n    ok(error instanceof TypeError, \"Expected TypeError when trying to use put() with content data set on own property\");\n  })\n  let doc2 = await db.docs.get(\"doc2\");\n  ok(doc2!.content instanceof Y.Doc, \"The content property is an Y.Doc\");\n  try {\n    doc2!.content = new Y.Doc();\n    ok(false, \"Should have thrown when trying to set content\");\n  } catch(error) {\n    ok(true, \"Failed when trying to set content of an existing doc\");\n  }\n  doc2!.title = \"Quattro Pizza\";\n  await db.docs.put(doc2!); // Verify it's ok to use put when an existing ydoc had been retrieved from DB.\n  ok(true, \"Could put back the doc2 we've added\");\n  let doc2Back = await db.docs.get(\"doc2\");\n  equal(doc2Back!.title, \"Quattro Pizza\", \"Verify that the put operation was successful for non-Y.Doc properties\");\n  ok(doc2Back!.content === doc2!.content, \"Verify that the Y.Doc property is equal be reference to the previously retrieved Y.Doc as long as it isn't destroyed\");\n});\n\n/*promisedTest('Test awareness', async () => {\n  const doc = new Y.Doc();\n  const aw = new awareness.Awareness(doc);\n  console.log(Array.from(aw.getStates().keys()));\n  const update = awareness.encodeAwarenessUpdate(aw, Array.from(aw.getStates().keys()));\n  console.log(\"Update length\", update.length);\n});\n*/"
  },
  {
    "path": "addons/y-dexie/tools/build-configs/banner.txt",
    "content": "/* ========================================================================== \n *                           y-dexie.js\n * ==========================================================================\n *\n * Dexie addon that integrates Dexie with Y.js\n *\n * By David Fahlander, david@dexie.org\n *\n * ==========================================================================\n *\n * Version {version}, {date}\n *\n * https://dexie.org\n *\n * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/\n * \n */\n"
  },
  {
    "path": "addons/y-dexie/tools/build-configs/rollup.config.mjs",
    "content": "// @ts-check\n//import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport { readFileSync, writeFileSync } from 'fs';\nimport typescript from '@rollup/plugin-typescript';\nimport terser from '@rollup/plugin-terser';\nimport replace from '@rollup/plugin-replace';\n// @ts-ignore: requires tsconfig settings that we don't need for the web build but is ok here in the build config.\nimport pkg from '../../package.json' with { type: 'json' };\nimport * as fs from 'fs';\n\n//const ERRORS_TO_IGNORE = ['THIS_IS_UNDEFINED'];\n\nexport function createBanner() {\n  // Create a copy of banner.txt with version and date replaced.\n  let banner = readFileSync('tools/build-configs/banner.txt', 'utf-8');\n  banner = banner\n    .replace(/{version}/g, pkg.version)\n    .replace(/{date}/g, new Date().toDateString());\n  fs.mkdirSync('tools/tmp', { recursive: true });\n  writeFileSync('tools/tmp/banner.txt', banner, 'utf-8');\n}\n\n/**\n *\n * @param {String} entry such as src/y-dexie.ts\n * @param {String} outputName such as y-dexie\n * @returns\n */\nexport function createRollupConfig(entry, outputName) {\n  return {\n    input: entry,\n    output: [\n      {\n        file: `dist/${outputName}.js`,\n        format: 'es',\n        banner: readFileSync('tools/tmp/banner.txt', 'utf-8'),\n        sourcemap: true,\n      },\n      {\n        file: `dist/${outputName}.min.js`,\n        format: 'es',\n        banner: readFileSync('tools/tmp/banner.txt', 'utf-8'),\n        sourcemap: true,\n        plugins: [\n          terser({\n            compress: {\n              drop_console: true,\n              drop_debugger: true,\n            },\n            mangle: true,\n            sourceMap: true,\n            output: {\n              comments: false,\n            },\n          }),\n        ],\n      }\n    ],\n    external: ['dexie', 'yjs', 'lib0'],\n    plugins: [\n      typescript({\n        tsconfig: 'src/tsconfig.json',\n        compilerOptions: {\n          target: 'es2016',\n        },\n        declarationDir: 'dist/',\n        inlineSources: true, // We get the real source code in the sourcemap\n      }),\n      nodeResolve({\n        browser: true,\n        preferBuiltins: false,\n      }),\n      commonjs(),\n      replace({\n        preventAssignment: true,\n        values: {\n          __VERSION__: JSON.stringify(pkg.version),\n        },\n      }),\n    ],\n    /*onwarn({ loc, frame, code, message }) {\n      //if (ERRORS_TO_IGNORE.includes(code)) return;\n      if (loc) {\n        console.warn(`${loc.file} (${loc.line}:${loc.column}) ${message}`);\n        if (frame) console.warn(frame);\n      } else {\n        console.warn(`${code} ${message}`);\n      }\n    },*/\n  };\n}\n\ncreateBanner();\n\nexport default [\n  createRollupConfig('src/y-dexie.ts', 'y-dexie')\n];\n"
  },
  {
    "path": "addons/y-dexie/tools/build-configs/rollup.test.unit.config.js",
    "content": "// @ts-check\nimport sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport replace from '@rollup/plugin-replace';\n// @ts-ignore: requires tsconfig settings that we don't need for the web build but is ok here in the build config.\nimport pkg from '../../package.json' with { type: 'json' };\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\",\n];\n\nexport default {\n  input: 'tools/tmp/test/unit/index.js',\n  output: [{\n    file: 'test/unit/bundle.js',\n    format: 'umd',\n    globals: {\n      dexie: \"Dexie\",\n      qunit: \"QUnit\"\n    },\n    name: 'YDexieTestBundle',\n    sourcemap: true,\n    exports: 'named'\n  }],\n  external: ['dexie', \"qunit\"],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true, preferBuiltins: false}),\n    commonjs(),\n      replace({\n        preventAssignment: true,\n        values: {\n          __VERSION__: JSON.stringify(pkg.version),\n        },\n      })\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "addons/y-dexie/tools/release-y-dexie.sh",
    "content": "#!/bin/bash -e\ncd ../..\npnpm install\npnpm build\ncd -\npnpm install\npnpm build\npnpm test\necho \"All built. To publish, run 'pnpm publish [--tag test|next]'\"\n"
  },
  {
    "path": "addons/y-dexie/tools/replaceVersionAndDate.cjs",
    "content": "const fs = require('fs');\nconst files = process.argv.slice(2);\nconst version = require('../package.json').version;\n\nfiles.forEach(file => {\n    let fileContent = fs.readFileSync(file, \"utf-8\");\n    fileContent = fileContent\n        .replace(/{version}/g, version)\n        .replace(/{date}/g, new Date().toDateString());\n    fs.writeFileSync(file, fileContent, \"utf-8\");\n});\n"
  },
  {
    "path": "dist/README.md",
    "content": "## Can't find dexie.js?\nDexie's dist files are no longer checked in to github except temporarily when tagging\na release version (just so that bower will continue to work). The reason for this is because\nchecking in dist files bloats the commit history and makes it error prone to contribute to the\nrepo. To support bower though, we have to continue checking in dist files when releasing,\nbut that is now handled by the release.sh script, who also removes them directly afterwards.\n\nIf you still just want to download dexie.js to include in a test HTML page, go\nto the following download site:\n\n### Download\n[dexie.min.js](https://unpkg.com/dexie/dist/dexie.min.js)\n\n[dexie.min.js.map](https://unpkg.com/dexie/dist/dexie.min.js.map)\n\n### Typings\n[dexie.d.ts](https://unpkg.com/dexie/dist/dexie.d.ts)\n\n### Optional Stuff\n[dexie.js (non-minified version)](https://unpkg.com/dexie/dist/dexie.js)\n\n[dexie.js.map](https://unpkg.com/dexie/dist/dexie.js.map)\n\n[dexie.min.js.gz (Minified and gzipped)](https://unpkg.com/dexie/dist/dexie.min.js.gz)\n\n## Install from NPM\n```\nnpm install dexie\n```\n\n## How to build\n1. cd to dexie package\n2. npm install\n3. npm run build\n\n## Contributing to Dexie.js?\n\nWatch:\n```\nnpm run watch\n```\n\nTest:\n```\nnpm test\n```\n"
  },
  {
    "path": "import-wrapper-prod.d.mts",
    "content": "export * from './dist/dexie.js';\nexport { default } from './dist/dexie.js';\n"
  },
  {
    "path": "import-wrapper-prod.mjs",
    "content": "// Making the module version consumable via require - to prohibit\n// multiple occurrancies of the same module in the same app\n// (dual package hazard, https://nodejs.org/api/packages.html#dual-package-hazard)\nimport _Dexie from \"./dist/dexie.min.js\";\nconst DexieSymbol = Symbol.for(\"Dexie\");\nconst Dexie = globalThis[DexieSymbol] || (globalThis[DexieSymbol] = _Dexie);\nif (_Dexie.semVer !== Dexie.semVer) {\n    throw new Error(`Two different versions of Dexie loaded in the same app: ${_Dexie.semVer} and ${Dexie.semVer}`);\n}\nconst {\n    liveQuery, mergeRanges, rangesOverlap, RangeSet, cmp, Entity,\n    PropModification, replacePrefix, add, remove,\n    DexieYProvider } = Dexie;\nexport { liveQuery, mergeRanges, rangesOverlap, RangeSet, cmp, Dexie, Entity,\n    PropModification, replacePrefix, add, remove,\n    DexieYProvider};\nexport default Dexie;\n"
  },
  {
    "path": "import-wrapper.d.mts",
    "content": "export * from './dist/dexie.js';\nexport { default } from './dist/dexie.js';\n"
  },
  {
    "path": "import-wrapper.mjs",
    "content": "// Making the module version consumable via require - to prohibit\n// multiple occurrancies of the same module in the same app\n// (dual package hazard, https://nodejs.org/api/packages.html#dual-package-hazard)\nimport _Dexie from \"./dist/dexie.js\";\nconst DexieSymbol = Symbol.for(\"Dexie\");\nconst Dexie = globalThis[DexieSymbol] || (globalThis[DexieSymbol] = _Dexie);\nif (_Dexie.semVer !== Dexie.semVer) {\n    throw new Error(`Two different versions of Dexie loaded in the same app: ${_Dexie.semVer} and ${Dexie.semVer}`);\n}\nconst { liveQuery, mergeRanges, rangesOverlap, RangeSet, cmp, Entity,\n    PropModification, replacePrefix, add, remove,\n    DexieYProvider } = Dexie;\nexport { liveQuery, mergeRanges, rangesOverlap, RangeSet, cmp, Dexie, Entity,\n    PropModification, replacePrefix, add, remove,\n    DexieYProvider};\n    \nexport default Dexie;\n"
  },
  {
    "path": "libs/dexie-cloud-common/.gitignore",
    "content": "node_modules\ndist\n"
  },
  {
    "path": "libs/dexie-cloud-common/.npmignore",
    "content": "node_modules\nsrc\ntsconfig.json\n"
  },
  {
    "path": "libs/dexie-cloud-common/jest.config.js",
    "content": "/** @type {import('ts-jest').JestConfigWithTsJest} **/\nexport default {\n  testEnvironment: \"node\",\n  transform: {\n    \"^.+.tsx?$\": [\"ts-jest\",{}],\n  },\n  testPathIgnorePatterns: [\"/dist/\"],\n  moduleNameMapper: {\n    \"^(\\\\.{1,2}/.*)\\\\.js$\": \"$1\"\n  }\n};\n"
  },
  {
    "path": "libs/dexie-cloud-common/package.json",
    "content": "{\n  \"name\": \"dexie-cloud-common\",\n  \"version\": \"1.0.59\",\n  \"description\": \"Library for shared code between dexie-cloud-addon, dexie-cloud (CLI) and dexie-cloud-server\",\n  \"type\": \"module\",\n  \"module\": \"dist/index.js\",\n  \"exports\": \"./dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"prepare\": \"tsc\",\n    \"prepack\": \"pnpm run build\",\n    \"test\": \"jest\",\n    \"tsver\": \"tsc --version\"\n  },\n  \"author\": \"David Fahlander\",\n  \"license\": \"Apache-2.0\",\n  \"devDependencies\": {\n    \"@types/jest\": \"^30.0.0\",\n    \"@types/node\": \"^22.10.1\",\n    \"jest\": \"^30.2.0\",\n    \"ts-jest\": \"^29.4.6\",\n    \"typescript\": \"^5.9.3\"\n  },\n  \"dependencies\": {\n    \"lib0\": \"^0.2.114\"\n  }\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/AuthProvidersResponse.ts",
    "content": "import { OAuthProviderInfo } from './OAuthProviderInfo.js';\n\n/** Response from GET /auth-providers endpoint */\nexport interface AuthProvidersResponse {\n  /** List of available OAuth providers */\n  providers: OAuthProviderInfo[];\n  /** Whether OTP (email) authentication is enabled */\n  otpEnabled: boolean;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/AuthorizationCodeTokenRequest.ts",
    "content": "/** Token request for OAuth authorization code grant */\nexport interface AuthorizationCodeTokenRequest {\n  grant_type: 'authorization_code';\n  /** The Dexie Cloud authorization code received from OAuth callback */\n  code: string;\n  /** Client's public key for token encryption (PEM format) */\n  public_key?: string;\n  /** Requested scopes */\n  scopes: string[];\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/BaseRevisionMapEntry.ts",
    "content": "export interface BaseRevisionMapEntry {\n  tableName: string;\n  clientRev: number;\n  serverRev: any;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/DBOperation.ts",
    "content": "\nexport type DBOpPrimaryKey = string | (string | number)[];\n\nconst enum DBCoreRangeType {\n  Equal = 1,\n  Range = 2,\n  Any = 3,\n  Never = 4\n}\n\n/** This interface must be identical to the interface with same name in dexie.\n * If DBCore ever gets moved out from dexie we could let it be referenced.\n * We could also be dependent on dexie but it would be a pitty just for this reason.\n*/\ninterface DBCoreKeyRange {\n  readonly type: DBCoreRangeType | number;\n  readonly lower: any;\n  readonly lowerOpen?: boolean;\n  readonly upper: any;\n  readonly upperOpen?: boolean;\n}\n\nexport type DBOperation<PK=DBOpPrimaryKey> =\n  | DBInsertOperation<PK>\n  | DBUpsertOperation<PK>\n  | DBUpdateOperation<PK>\n  | DBModifyOperation<PK>\n  | DBDeleteOperation<PK>;\n\nexport interface DBOperationCommon<PK=DBOpPrimaryKey> {\n  rev?: number;\n  ts?: number | null; // timestamp\n  keys: PK[]; // Needed also in delete and update operations when criteria is specificied: for server->client rollback operation\n  txid?: string | null;\n  userId?: string | null;\n  opNo?: number;\n  isAdditionalChunk?: boolean;\n}\nexport interface DBInsertOperation<PK=DBOpPrimaryKey> extends DBOperationCommon<PK> {\n  type: \"insert\";\n  values: readonly any[];\n}\n\nexport interface DBUpsertOperation<PK=DBOpPrimaryKey> extends DBOperationCommon<PK> {\n  type: \"upsert\";\n  values: readonly any[];\n  changeSpecs?: ({ [keyPath: string]: any } | null)[];\n}\n\nexport interface DBUpdateOperation<PK=DBOpPrimaryKey> extends DBOperationCommon<PK> {\n  type: \"update\";\n  changeSpecs: { [keyPath: string]: any }[];\n}\n\nexport interface DBModifyOperation<PK=DBOpPrimaryKey> extends DBOperationCommon<PK> {\n  type: \"modify\";\n  criteria: {\n    index: string | null;\n    range: DBCoreKeyRange;\n  },\n  changeSpec: { [keyPath: string]: any };\n}\n\n\nexport interface DBDeleteOperation<PK=DBOpPrimaryKey> extends DBOperationCommon<PK> {\n  type: \"delete\";\n  criteria?:\n    | {\n        index: string | null;\n        range: DBCoreKeyRange;\n      }\n    | false;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/DBOperationsSet.ts",
    "content": "import { DBOperation, DBOpPrimaryKey } from \"./DBOperation.js\";\n\nexport type DBOperationsSet<PK=DBOpPrimaryKey> = Array<{ table: string; muts: DBOperation<PK>[] }>;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/DBPermissionSet.ts",
    "content": "// add = add new objects + update + delete own objects.\n// update = update given fields on given tables.\n// manage = add/update/delete any object of given type within realm.\nexport interface DBPermissionSet {\n  add?: '*' | string[]; // tableName(s) or \"*\"\n  update?:\n    | '*' // Update all fields on all tables\n    | {\n        [tableName: string]: string[] | '*'; // array of properties or \"*\" (all).\n      };\n  manage?: '*' | string[]; // tableName(s) or \"*\"\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/DexieCloudSchema.ts",
    "content": "export type DexieCloudSchema = {\n  [tableName: string]: {\n    generatedGlobalId?: boolean;\n    idPrefix?: string;\n    deleted?: boolean;\n    markedForSync?: boolean;\n    initiallySynced?: boolean;\n    primaryKey?: string\n    yProps?: string[];\n  };\n};\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/OAuthProviderInfo.ts",
    "content": "/** Information about an OAuth provider available for authentication */\nexport interface OAuthProviderInfo {\n  /** The type of OAuth provider */\n  type: 'google' | 'github' | 'microsoft' | 'apple' | 'custom-oauth2';\n  /** Provider identifier (e.g., 'google' or custom name) */\n  name: string;\n  /** Human-readable display name */\n  displayName: string;\n  /** URL to provider icon */\n  iconUrl?: string;\n  /** OAuth scopes requested from the provider */\n  scopes?: string[];\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/SyncChange.ts",
    "content": "export type SyncChange = UpsertSyncChange | UpdateSyncChange | DeleteSyncChange;\n\nexport interface UpsertSyncChange {\n  type: 'upsert';\n  realmId: string;\n  table: string;\n  key: string;\n  value: object;\n}\nexport interface UpdateSyncChange {\n  type: 'update';\n  realmId: string;\n  table: string;\n  key: string;\n  updates: { [keyPath: string]: any };\n}\nexport interface DeleteSyncChange {\n  type: 'delete';\n  realmId: string;\n  table: string;\n  key: string;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/SyncChangeSet.ts",
    "content": "export type SyncChangeSet = {\n  [table: string]: {\n    upsert?: object[];\n    update?: {\n      [key: string]: { [keyPath: string]: any };\n    };\n    delete?: string[];\n  };\n};\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/SyncRequest.ts",
    "content": "import { BaseRevisionMapEntry } from './BaseRevisionMapEntry.js';\nimport { DBOperationsSet } from './DBOperationsSet.js';\nimport { DexieCloudSchema } from './DexieCloudSchema.js';\nimport { YClientMessage } from './yjs/YMessage.js';\n\nexport interface SyncRequest {\n  v?: number;\n  dbID?: string;\n  clientIdentity?: string;\n  schema: DexieCloudSchema;\n  lastPull?: {\n    serverRevision: string | bigint;\n    yServerRevision?: string;\n    realms: string[];\n    inviteRealms: string[];\n  };\n  baseRevs: BaseRevisionMapEntry[];\n  changes: DBOperationsSet;\n  y?: YClientMessage[];\n  dxcv?: string; // dexie libs and versions\n  //invites: {[inviteId: string]: \"accept\" | \"reject\"}\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/SyncResponse.ts",
    "content": "import { DBOperationsSet } from './DBOperationsSet.js';\nimport { DexieCloudSchema } from './DexieCloudSchema.js';\nimport { YServerMessage } from './yjs/YMessage.js';\n\nexport interface SyncResponse {\n  serverRevision: string; // string \"[1,\\\"2823\\\"]\" in protocol version 2. (was bigint in version 1).\n  dbId: string;\n  realms: string[];\n  inviteRealms: string[];\n  schema: DexieCloudSchema;\n  changes: DBOperationsSet<string>;\n  rejections: { name: string; message: string; txid: string }[];\n  yMessages: YServerMessage[];\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/async-generators/asyncIterablePipeline.ts",
    "content": "\n// TODO: When upgraded to Typescript 5.6 or later,\n// do better typings for this function. ChatGPT suggests the following:\n/*\n// Typdefinition för en asynkron generatorfunktion\ntype AsyncGeneratorFunction<Input, Output> = (input: AsyncGenerator<Input>) => AsyncGenerator<Output>;\n\n// Typdefinition för pipelinen\ntype Pipeline<T> = {\n  [K in keyof T]: AsyncGeneratorFunction<T[K], T[K + 1]>;\n};\n\n// Generisk pipeline-funktion\nasync function pipeline<Input, Output>(\n  ...stages: AsyncGeneratorFunction<Input, Output>[]\n): Promise<void>\n*/\ntype AsyncSourceGeneratorFn<Output> = () => AsyncGenerator<Output>;\ntype AsyncGeneratorFn<Input, Output> = (input: AsyncGenerator<Input>) => AsyncGenerator<Output>;\nexport async function asyncIterablePipeline(source: AsyncSourceGeneratorFn<any>, ...stages: AsyncGeneratorFn<any,any>[]) { \n  // Chain generators by sending outdata from one to another\n  let result = source(); // Start with the source generator\n\n  for (let i = 0; i < stages.length; i++) {\n    result = stages[i](result); // Pass on the result to next generator\n  }\n\n  // Start running the machine. If the last stage is a sink, it will consume the data and never emit anything\n  // to us here...\n  for await (const chunk of result) {}\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/async-generators/consumeChunkedBinaryStream.test.ts",
    "content": "import { set } from 'lib0/encoding';\nimport { consumeChunkedBinaryStream } from './consumeChunkedBinaryStream';\nimport { asyncIterablePipeline } from './asyncIterablePipeline';\n\nconst inputData = generateInputData(256, 4);\n\nfunction generateInputData(chunkSize: number, numChunks = 4) {\n  const buf = new ArrayBuffer((chunkSize + 4) * numChunks);\n  for (let chunkNo = 0; chunkNo < numChunks; ++chunkNo) {\n    const dw = new DataView(buf, chunkNo * (chunkSize + 4), 4);\n    const ba = new Uint8Array(buf, chunkNo * (chunkSize + 4) + 4, chunkSize);\n    dw.setUint32(0, chunkSize, false);\n    for (let i = 0; i < ba.length; ++i) {\n      ba[i] = i % 256;\n    }\n  }\n  const ba = new Uint8Array(buf, 0, buf.byteLength);\n  expect(ba.length).toBe(chunkSize * numChunks + 4 * numChunks);\n  expect(new DataView(ba.buffer, 0, 4).getUint32(0, false)).toBe(chunkSize);\n  expect(ba[4]).toBe(0);\n  expect(ba[5]).toBe(1);\n  expect(ba[6]).toBe(2);\n  expect(ba[7]).toBe(3);\n  return ba;\n}\n\nasync function* generateChunk(chunkSizes: AsyncGenerator<number>) {\n  for await (const chunkSize of chunkSizes) {\n    for (let i = 0; i < inputData.length; i += chunkSize) {\n      //console.log('Yielding chunk of size ' + (''+i)+':'+(i + chunkSize));\n      yield inputData.slice(i, i + chunkSize);\n    }\n  }\n}\n\nasync function* generateChunkSizes() {\n  //console.log('yield 1024');\n  yield 1040;\n  //console.log('yield 512');\n  yield 520;\n  yield 260;\n  yield 1;\n  yield 259;\n  yield 258;\n  yield 257;\n  yield 2;\n  yield 3;\n}\n\ntest('test consumeChunkedBinaryStream', async () => {\n  await asyncIterablePipeline(\n    generateChunkSizes,\n    generateChunk,\n    consumeChunkedBinaryStream,\n    async function* (source: AsyncGenerator<Uint8Array>) {\n      let itRes: IteratorResult<Uint8Array>;\n      for (let i = 0; i < 9; ++i) {\n        itRes = await source.next();\n        expect(itRes.done).toBe(false);\n        expect(itRes.value.byteLength).toBe(256);\n        expect(itRes.value).toEqual(inputData.slice(4, 260));\n        itRes = await source.next();\n        expect(itRes.done).toBe(false);\n        expect(itRes.value.byteLength).toBe(256);\n        expect(itRes.value).toEqual(inputData.slice(4, 260));\n        itRes = await source.next();\n        expect(itRes.done).toBe(false);\n        expect(itRes.value.byteLength).toBe(256);\n        expect(itRes.value).toEqual(inputData.slice(4, 260));\n        itRes = await source.next();\n        expect(itRes.done).toBe(false);\n        expect(itRes.value.byteLength).toBe(256);\n        expect(itRes.value).toEqual(inputData.slice(4, 260));\n      }\n      itRes = await source.next();\n      expect(itRes.done).toBe(true);\n    }\n  );\n});\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/async-generators/consumeChunkedBinaryStream.ts",
    "content": "export async function* consumeChunkedBinaryStream(\n  source: AsyncIterable<Uint8Array>\n) {\n  let state: number = 0;\n  let sizeBuf = new Uint8Array(4);\n  let sizeBufPos = 0;\n  let bufs: Uint8Array[] = [];\n  let len = 0;\n  for await (const chunk of source) {\n    const dw = new DataView(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n    let pos = 0;\n    while (pos < chunk.byteLength) {\n      switch (state) {\n        case 0:\n          // Beginning of a size header\n          if (pos + 4 > chunk.byteLength) {\n            for (const b of chunk.slice(pos)) {\n              if (sizeBufPos === 4) break;\n              sizeBuf[sizeBufPos++] = b;\n              ++pos;\n            }\n            if (sizeBufPos < 4) {\n              // Need more bytes in order to read length.\n              // Will go out from while loop as well because pos is defenitely = chunk.byteLength here.\n              break;\n            }\n          } else if (sizeBufPos > 0 && sizeBufPos < 4) {\n            for (const b of chunk.slice(pos, pos + 4 - sizeBufPos)) {\n              sizeBuf[sizeBufPos++] = b;\n              ++pos;\n            }\n          }\n        // Intentional fall-through...\n        case 1:\n          len =\n            sizeBufPos === 4\n              ? new DataView(sizeBuf.buffer, 0, 4).getUint32(0, false)\n              : dw.getUint32(pos, false);\n          if (sizeBufPos) sizeBufPos = 0; // in this case pos is already forwarded\n          else pos += 4; // else pos is not yet forwarded - that's why we do it now\n        // Intentional fall-through...\n        case 2:\n          // Eat the chunk\n          if (pos >= chunk.byteLength) {\n            state = 2;\n            break;\n          }\n          if (pos + len > chunk.byteLength) {\n            bufs.push(chunk.slice(pos));\n            len -= (chunk.byteLength - pos);\n            state = 2;\n            pos = chunk.byteLength; // will break while loop.\n          } else {\n            if (bufs.length > 0) {\n              const concats = new Uint8Array(bufs.reduce((p,c) => p + c.byteLength, len));\n              let p = 0;\n              for (const buf of bufs) {\n                concats.set(buf, p);\n                p += buf.byteLength;\n              }\n              concats.set(chunk.slice(pos, pos + len), p);\n              bufs = [];\n              yield concats;\n            } else {\n              yield chunk.slice(pos, pos + len);\n            }\n            pos += len;\n            state = 0;\n          }\n          break;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/async-generators/getFetchResponseBodyGenerator.ts",
    "content": "\nexport function getFetchResponseBodyGenerator(res: Response) {\n  return async function* () {\n    if (!res.body) throw new Error(\"Response body is not readable\");\n    const reader = res.body.getReader();  \n    try {\n      while (true) {\n        const { done, value } = await reader.read()\n        if (done) return\n        yield value\n      }\n    }\n    finally {\n      reader.releaseLock()\n    }\n  }\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/async-generators/produceChunkedBinaryStream.ts",
    "content": "/** Used with pipeline to preserve yielded chunks before they are send to peer.\n * Peer can then decode these chunks using consumeChunkedBinaryStream.\n * \n * Why do we need this? In order to guarantee to the consumer that every chunk is a complete\n * message of some kind, and never cut in the middle - no matter the transfer protocol, proxies\n * etc. In HTTP this can be important because a chunked message can be split into multiple chunks\n * by proxies. This function will make sure that the consumer will never see a chunk that is not\n * a complete message.\n * \n * The source iterable \n */\nexport async function* produceChunkedBinaryStream(source: AsyncIterable<Uint8Array>) {\n  const HIGH_WATER_MARK = 65535;\n  let len = 0;\n  let chunks: Uint8Array[] = [];\n\n  function* flush() {\n    const chunkLength = chunks.reduce((a, b) => a + b.length, 0);\n    if (chunkLength === 0) return;\n    const flushChunkBuffer = new ArrayBuffer(4 + chunkLength);\n    const flushChunkArray = new Uint8Array(flushChunkBuffer);\n    const dw = new DataView(flushChunkBuffer);\n    dw.setUint32(0, chunkLength, false);\n    let pos = 4;\n    for (const chunk of chunks) {\n      flushChunkArray.set(chunk, pos);\n      pos += chunk.length;\n    }\n    len = 0;\n    chunks = [];\n    yield flushChunkArray;\n  }\n\n  for await (const chunk of source) {\n    chunks.push(chunk);\n    len += chunk.length;\n    if (len > HIGH_WATER_MARK) {\n      yield* flush();\n    }\n  }\n  yield* flush();\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/change-processing/DBKeyMutation.ts",
    "content": "export type DBKeyMutation =\n  | DBKeyUpsert\n  | DBKeyUpdate\n  | DBKeyDelete;\n\nexport interface DBKeyUpsert {\n  type: \"ups\";\n  val: any;\n}\n\nexport interface DBKeyUpdate {\n  type: \"upd\";\n  mod: { [keyPath: string]: any };\n}\n\nexport interface DBKeyDelete {\n  type: \"del\";\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/change-processing/DBKeyMutationSet.ts",
    "content": "import { DBKeyMutation } from \"./DBKeyMutation.js\";\n\nexport type DBKeyMutationSet = {\n  [tableName: string]: { [key: string]: DBKeyMutation };\n};\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/change-processing/applyOperation.ts",
    "content": "import { DBKeyMutation } from \"./DBKeyMutation.js\";\nimport { DBKeyMutationSet } from \"./DBKeyMutationSet.js\";\nimport { DBOperationsSet } from \"../DBOperationsSet.js\";\nimport { DBOperation } from \"../DBOperation.js\";\nimport { setByKeyPath } from \"../utils.js\";\n\nexport function applyOperation(\n  target: DBKeyMutationSet,\n  table: string,\n  op: DBOperation\n) {\n  const tbl = target[table] || (target[table] = {});\n  const keys = op.keys.map(key => typeof key === 'string' ? key : JSON.stringify(key));\n\n  switch (op.type) {\n    case \"insert\":\n      // TODO: Don't treat insert and upsert the same?\n    case \"upsert\":\n      keys.forEach((key, idx) => {\n        tbl[key] = {\n          type: \"ups\",\n          val: op.values[idx],\n        };\n      });\n      break;\n    case \"update\":\n    case \"modify\": {\n      keys.forEach((key, idx) => {\n        const changeSpec = op.type === \"update\"\n          ? op.changeSpecs[idx]\n          : op.changeSpec;\n        const entry = tbl[key];\n        if (!entry) {\n          tbl[key] = {\n            type: \"upd\",\n            mod: changeSpec,\n          };\n        } else {\n          switch (entry.type) {\n            case \"ups\":\n              // Adjust the existing upsert with additional updates\n              for (const [propPath, value] of Object.entries(changeSpec)) {\n                setByKeyPath(entry.val, propPath, value);\n              }\n              break;\n            case \"del\":\n              // No action.\n              break;\n            case \"upd\":\n              // Adjust existing update with additional updates\n              Object.assign(entry.mod, changeSpec); // May work for deep props as well - new keys is added later, right? Does the prop order persist along TSON and all? But it will not be 100% when combined with some server code (seach for \"address.city\": \"Stockholm\" comment)\n              break;\n          }\n        }\n      });\n      break;\n    }\n    case \"delete\":\n      keys.forEach((key) => {\n        tbl[key] = {\n          type: \"del\",\n        };\n      });\n      break;\n  }\n  return target;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/change-processing/applyOperations.ts",
    "content": "import { DBOperationsSet } from \"../DBOperationsSet.js\";\nimport { applyOperation } from \"./applyOperation.js\";\nimport { DBKeyMutationSet } from \"./DBKeyMutationSet.js\";\n\nexport function applyOperations(target: DBKeyMutationSet, ops: DBOperationsSet) {\n  for (const {table, muts} of ops) {\n    for (const mut of muts) {\n      applyOperation(target, table, mut);\n    }\n  }\n}"
  },
  {
    "path": "libs/dexie-cloud-common/src/change-processing/subtractChanges.ts",
    "content": "import { setByKeyPath } from '../utils.js';\nimport { DBKeyMutationSet } from './DBKeyMutationSet.js';\n\nexport function subtractChanges(\n  target: DBKeyMutationSet, // Server change set\n  changesToSubtract: DBKeyMutationSet // additional mutations on client during syncWithServer()\n) {\n  for (const [table, mutationSet] of Object.entries(changesToSubtract)) {\n    for (const [key, mut] of Object.entries(mutationSet)) {\n      switch (mut.type) {\n        case 'ups':\n          {\n            const targetMut = target[table]?.[key];\n            if (targetMut) {\n              switch (targetMut.type) {\n                case 'ups':\n                  delete target[table][key];\n                  break;\n                case 'del':\n                  // Leave delete operation.\n                  // (Don't resurrect objects unintenionally (using tx(get, put) pattern locally))\n                  break;\n                case 'upd':\n                  delete target[table][key];\n                  break;\n              }\n            }\n          }\n          break;\n        case 'del':\n          delete target[table]?.[key];\n          break;\n        case 'upd': {\n          const targetMut = target[table]?.[key];\n          if (targetMut) {\n            switch (targetMut.type) {\n              case 'ups':\n                // Adjust the server upsert with locally updated values.\n                for (const [propPath, value] of Object.entries(mut.mod)) {\n                  setByKeyPath(targetMut.val, propPath, value);\n                }\n                break;\n              case 'del':\n                // Leave delete.\n                break;\n              case 'upd':\n                // Remove the local update props from the server update mutation.\n                for (const propPath of Object.keys(mut.mod)) {\n                  delete targetMut.mod[propPath];\n                }\n                break;\n            }\n          }\n          break;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/change-processing/toDBOperationSet.ts",
    "content": "import { DBDeleteOperation, DBOperation, DBUpdateOperation, DBUpsertOperation } from \"../DBOperation.js\";\nimport { DBOperationsSet } from \"../DBOperationsSet.js\";\nimport { DBKeyMutationSet } from \"./DBKeyMutationSet.js\";\n\n/** Convert a DBKeyMutationSet (which is an internal format capable of looking up changes per ID)\n * ...into a DBOperationsSet (which is more optimal for performing DB operations into DB (bulkAdd() etc))\n * \n * @param inSet \n * @returns DBOperationsSet representing inSet\n */\nexport function toDBOperationSet(inSet: DBKeyMutationSet, txid?: string): DBOperationsSet<string> {\n  // Convert data into a temporary map to collect mutations of same table and type\n  const map: {\n    [table: string]: { [opType: string]: { key: any, val?: any, mod?: any }[] };\n  } = {};\n  for (const [table, ops] of Object.entries(inSet)) {\n    for (const [key, op] of Object.entries(ops)) {\n      const mapEntry = map[table] || (map[table] = {});\n      const ops = mapEntry[op.type] || (mapEntry[op.type] = []);\n      ops.push({ key, ...op }); // DBKeyMutation doesn't contain key, so we need to bring it in.\n    }\n  }\n\n  // Start computing the resulting format:\n  const result: DBOperationsSet<string> = [];\n\n  for (const [table, ops] of Object.entries(map)) {\n    const resultEntry = {\n      table,\n      muts: [] as DBOperation<string>[],\n    };\n    for (const [optype, muts] of Object.entries(ops)) {\n      switch (optype) {\n        case \"ups\": {\n          const op: DBUpsertOperation<string> = {\n            type: \"upsert\",\n            keys: muts.map(mut => mut.key),\n            values: muts.map(mut => mut.val),\n            txid\n          };\n          resultEntry.muts.push(op);\n          break;\n        }\n        case \"upd\": {\n          const op: DBUpdateOperation<string> = {\n            type: \"update\",\n            keys: muts.map(mut => mut.key),\n            changeSpecs: muts.map(mut => mut.mod),\n            txid\n          };\n          resultEntry.muts.push(op);\n          break;\n        }\n        case \"del\": {\n          const op: DBDeleteOperation<string> = {\n            type: \"delete\",\n            keys: muts.map(mut => mut.key),\n            txid,\n          }\n          resultEntry.muts.push(op);\n          break;\n        }\n      }\n    }\n    result.push(resultEntry);\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/change-processing/toSyncChangeSet.ts",
    "content": "import { DBDeleteOperation, DBOperation, DBUpdateOperation, DBUpsertOperation } from \"../DBOperation.js\";\nimport { DBOperationsSet } from \"../DBOperationsSet.js\";\nimport { SyncChangeSet } from \"../SyncChangeSet.js\";\nimport { DBKeyMutationSet } from \"./DBKeyMutationSet.js\";\n\n/** Convert a DBKeyMutationSet (which is an internal format capable of looking up changes per ID)\n * ...into a SyncChangeSet (which is more optimal for performing DB operations into DB SQL UPSERT, UPDATE, DELETE)\n * \n * @param inSet \n * @returns SyncChangeSet representing inSet\n */\nexport function toSyncChangeSet(inSet: DBKeyMutationSet): SyncChangeSet {\n  // Convert data into a temporary map to collect mutations of same table and type\n  const map: {\n    [table: string]: { [opType: string]: { key: any, val?: any, mod?: any }[] };\n  } = {};\n  for (const [table, ops] of Object.entries(inSet)) {\n    for (const [key, op] of Object.entries(ops)) {\n      const mapEntry = map[table] || (map[table] = {});\n      const ops = mapEntry[op.type] || (mapEntry[op.type] = []);\n      ops.push({ key, ...op }); // DBKeyMutation doesn't contain key, so we need to bring it in.\n    }\n  }\n\n  // Start computing the resulting format:\n  const result: SyncChangeSet = {};\n\n  for (const [table, ops] of Object.entries(map)) {\n    const resultEntry: {\n      upsert?: object[];\n      update?: { [key: string]: { [keyPath: string]: any } };\n      delete?: string[];\n    } = {};\n    \n    for (const [optype, muts] of Object.entries(ops)) {\n      switch (optype) {\n        case \"ups\": {\n          resultEntry.upsert = muts.map(mut => mut.val);\n          break;\n        }\n        case \"upd\": {\n          const updateMap: { [key: string]: { [keyPath: string]: any } } = {};\n          for (const mut of muts) {\n            updateMap[mut.key] = mut.mod;\n          }\n          resultEntry.update = updateMap;\n          break;\n        }\n        case \"del\": {\n          resultEntry.delete = muts.map(mut => mut.key);\n          break;\n        }\n      }\n    }\n    \n    result[table] = resultEntry;\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/common/_global.ts",
    "content": "export const _global: any =\n  typeof globalThis !== \"undefined\" // All modern environments (node, bun, deno, browser, workers, webview etc)\n    ? globalThis\n    : typeof self !== \"undefined\" // Older browsers, workers, webview, window etc\n    ? self\n    : typeof global !== \"undefined\" // Older versions of node\n    ? global\n    : undefined; // Unsupported environment. No idea to return 'this' since we are in a module or a function scope anyway.\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/common/b64lex.ts",
    "content": "import { b64decode, b64encode } from \"./base64.js\";\n\nexport function b64LexEncode(b: Uint8Array | Buffer | ArrayBuffer): string {\n  return b64ToLex(b64encode(b));\n}\n\nexport function b64LexDecode(b64Lex: string): Uint8Array {\n  return b64decode(lexToB64(b64Lex));\n}\n\nexport function b64ToLex(base64: string): string {\n  var encoded = \"\";\n  for (var i = 0, length = base64.length; i < length; i++) {\n    encoded += ENCODE_TABLE[base64[i]];\n  }\n  return encoded;\n}\n\nexport function lexToB64(base64lex: string): string {\n  // only accept string input\n  if (typeof base64lex !== \"string\") {\n    throw new Error(\"invalid decoder input: \" + base64lex);\n  }\n\n  var base64 = \"\";\n  for (var i = 0, length = base64lex.length; i < length; i++) {\n    base64 += DECODE_TABLE[base64lex[i]];\n  }\n\n  return base64;\n}\n\nconst DECODE_TABLE = {\n  \"-\": \"=\",\n  \"0\": \"A\",\n  \"1\": \"B\",\n  \"2\": \"C\",\n  \"3\": \"D\",\n  \"4\": \"E\",\n  \"5\": \"F\",\n  \"6\": \"G\",\n  \"7\": \"H\",\n  \"8\": \"I\",\n  \"9\": \"J\",\n  A: \"K\",\n  B: \"L\",\n  C: \"M\",\n  D: \"N\",\n  E: \"O\",\n  F: \"P\",\n  G: \"Q\",\n  H: \"R\",\n  I: \"S\",\n  J: \"T\",\n  K: \"U\",\n  L: \"V\",\n  M: \"W\",\n  N: \"X\",\n  O: \"Y\",\n  P: \"Z\",\n  Q: \"a\",\n  R: \"b\",\n  S: \"c\",\n  T: \"d\",\n  U: \"e\",\n  V: \"f\",\n  W: \"g\",\n  X: \"h\",\n  Y: \"i\",\n  Z: \"j\",\n  _: \"k\",\n  a: \"l\",\n  b: \"m\",\n  c: \"n\",\n  d: \"o\",\n  e: \"p\",\n  f: \"q\",\n  g: \"r\",\n  h: \"s\",\n  i: \"t\",\n  j: \"u\",\n  k: \"v\",\n  l: \"w\",\n  m: \"x\",\n  n: \"y\",\n  o: \"z\",\n  p: \"0\",\n  q: \"1\",\n  r: \"2\",\n  s: \"3\",\n  t: \"4\",\n  u: \"5\",\n  v: \"6\",\n  w: \"7\",\n  x: \"8\",\n  y: \"9\",\n  z: \"+\",\n  \"|\": \"/\",\n};\n\nconst ENCODE_TABLE = {};\nfor (const c of Object.keys(DECODE_TABLE)) {\n  ENCODE_TABLE[DECODE_TABLE[c]] = c;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/common/base64.ts",
    "content": "const hasArrayBufferFromBase64 = \"fromBase64\" in Uint8Array; // https://github.com/tc39/proposal-arraybuffer-base64;\nconst hasArrayBufferToBase64 = \"toBase64\" in Uint8Array.prototype; // https://github.com/tc39/proposal-arraybuffer-base64;\n\nexport const b64decode: (b64: string) => Uint8Array =\n  typeof Buffer !== \"undefined\"\n    ? (base64) => Buffer.from(base64, \"base64\") // Node\n    : hasArrayBufferFromBase64\n    ? // @ts-ignore: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64\n      (base64) => Uint8Array.fromBase64(base64) // Modern javascript standard\n    : (base64) => {\n        // Legacy DOM workaround\n        const binary_string = atob(base64);\n        const len = binary_string.length;\n        const bytes = new Uint8Array(len);\n        for (var i = 0; i < len; i++) {\n          bytes[i] = binary_string.charCodeAt(i);\n        }\n        return bytes;\n      };\n\nexport const b64encode: (b: Uint8Array | Buffer | ArrayBuffer) => string =\n  typeof Buffer !== \"undefined\"\n    ? (b) => {\n        // Node\n        if (ArrayBuffer.isView(b)) {\n          return Buffer.from(b.buffer, b.byteOffset, b.byteLength).toString(\n            \"base64\"\n          );\n        } else {\n          return Buffer.from(b).toString(\"base64\");\n        }\n      }\n    : hasArrayBufferToBase64\n    ? (b) => {\n        // Uint8Array.prototype.toBase64 is available in modern browsers\n        const u8a = ArrayBuffer.isView(b) ? b : new Uint8Array(b);\n        // @ts-ignore: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64\n        return u8a.toBase64();\n      }\n    : (b) => {\n        // Legacy DOM workaround\n        const u8a = ArrayBuffer.isView(b) ? b : new Uint8Array(b);\n        const CHUNK_SIZE = 0x1000;\n        const strs: string[] = [];\n        for (let i = 0, l = u8a.length; i < l; i += CHUNK_SIZE) {\n          const chunk = u8a.subarray(i, i + CHUNK_SIZE) as Uint8Array;\n          strs.push(String.fromCharCode.apply(null, Array.from(chunk)));\n        }\n        return btoa(strs.join(\"\"));\n      };\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/common/bigint-conversion.ts",
    "content": "import { TypedArray } from \"../typings/TypedArray.js\";\nimport { b64decode, b64encode } from \"./base64.js\";\n\nconst HEX_PARSER_REGEXP = /[\\da-f]{2}/gi;\n\nexport function buf2bigint(buf: TypedArray | ArrayBuffer): bigint {\n  const bits = BigInt(8);\n  let u8a: Uint8Array;\n  if (ArrayBuffer.isView(buf)) {\n    u8a = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n  } else {\n    u8a = new Uint8Array(buf);\n  }\n\n  let ret = BigInt(0);\n  for (let i = 0; i < u8a.length; i++) {\n    const bi = BigInt(u8a[i]);\n    ret = (ret << bits) | bi;\n  }\n  return ret;\n}\n\nexport function bigint2Buf(bi: bigint): Uint8Array {\n  if (bi < 0) throw new TypeError(\"Cannot convert negative bigint to a buffer\");\n  const hex = bi.toString(16);\n  return Uint8Array.from(\n    (\n      (hex.length % 2 ? \"0\" + hex : hex).match(HEX_PARSER_REGEXP) || []\n    ).map((h) => parseInt(h, 16))\n  );\n}\n\nexport function bigint2B64(bi: bigint): string {\n  return b64encode(bigint2Buf(bi));\n}\n\nexport function b64ToBigInt(base64: string): bigint {\n  return buf2bigint(b64decode(base64));\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/entities/DBRealm.ts",
    "content": "import { DBSyncedObject } from \"./DBSyncedObject.js\";\n\nexport interface DBRealm extends DBSyncedObject {\n  name: string;\n  represents?: string;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/entities/DBRealmMember.ts",
    "content": "import { DBPermissionSet } from '../DBPermissionSet.js';\nimport { DBSyncedObject } from './DBSyncedObject.js';\n\nexport interface DBRealmMember extends DBSyncedObject {\n  id: string;\n  userId?: string;\n  email?: string;\n  name?: string;\n  invite?: boolean;\n  invitedDate?: Date; // Set by system in in processInvites\n  invitedBy?: { // Set by system in in processInvites\n    name: string;\n    email: string;\n    userId: string;\n  }\n  accepted?: Date;\n  rejected?: Date;\n  roles?: string[];\n  permissions?: DBPermissionSet;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/entities/DBRealmRole.ts",
    "content": "import { DBPermissionSet } from \"../DBPermissionSet.js\";\nimport { DBSyncedObject } from \"./DBSyncedObject.js\";\n\nexport interface DBRealmRole extends DBSyncedObject {\n  name: string;\n  permissions: DBPermissionSet;\n  description?: string;\n  displayName?: string;\n  sortOrder?: number;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/entities/DBSyncedObject.ts",
    "content": "export interface DBSyncedObject {\n  realmId: string;\n  owner: string;\n  $ts?: number;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/getDbNameFromDbUrl.ts",
    "content": "export function getDbNameFromDbUrl(\n  dbUrl: string\n) {\n  const url = new URL(dbUrl);\n  return url.pathname === \"/\"\n    ? url.hostname.split('.')[0]\n    : url.pathname.split('/')[1];\n}\n\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/index.ts",
    "content": "export * from \"./types.js\";\n\n// TSON - Typed JSON serialization (migrated from dreambase-library)\nexport * from \"./tson/index.js\";\nexport * from \"./common/base64.js\";\nexport * from \"./common/b64lex.js\";\nexport * from \"./newId.js\";\nexport * from \"./OAuthProviderInfo.js\";\nexport * from \"./AuthProvidersResponse.js\";\nexport * from \"./AuthorizationCodeTokenRequest.js\";\nexport * from \"./BaseRevisionMapEntry.js\"\nexport * from \"./SyncRequest.js\"\nexport * from \"./SyncResponse.js\"\nexport * from \"./utils.js\"\nexport * from \"./DexieCloudSchema.js\"\nexport * from \"./DBOperationsSet.js\"\nexport * from \"./DBOperation.js\"\nexport * from \"./SyncChange.js\"\nexport * from \"./validation/isValidSyncableID.js\"\nexport * from \"./change-processing/applyOperation.js\"\nexport * from \"./change-processing/applyOperations.js\"\nexport * from \"./change-processing/DBKeyMutation.js\"\nexport * from \"./change-processing/DBKeyMutationSet.js\"\nexport * from \"./change-processing/subtractChanges.js\"\nexport * from \"./change-processing/toDBOperationSet.js\"\nexport * from \"./change-processing/toSyncChangeSet.js\"\nexport * from \"./DBPermissionSet.js\";\nexport * from \"./entities/DBRealm.js\";\nexport * from \"./entities/DBRealmMember.js\";\nexport * from \"./entities/DBRealmRole.js\";\nexport * from \"./entities/DBSyncedObject.js\";\nexport * from \"./getDbNameFromDbUrl.js\";\nexport * from \"./yjs/YMessage.js\";\nexport * from \"./yjs/encoding.js\";\nexport * from \"./yjs/decoding.js\";\nexport * from \"./async-generators/asyncIterablePipeline.js\";\nexport * from \"./async-generators/consumeChunkedBinaryStream.js\";\nexport * from \"./async-generators/getFetchResponseBodyGenerator.js\";"
  },
  {
    "path": "libs/dexie-cloud-common/src/newId.ts",
    "content": "import { b64LexEncode } from \"./common/b64lex.js\";\n\nconst getRandomValues: (buf: Uint8Array) => void =\n  typeof crypto !== \"undefined\"\n    ? crypto.getRandomValues.bind(crypto)\n    : (buf: Uint8Array) => {\n        for (let i = 0; i < buf.length; ++i) {\n          buf[i] = Math.floor(Math.random() * 256);\n        }\n      };\n\nlet time = 0;\n/**\n * Generates unique ID where bytes 0-6 represents a timestampish value\n * instead of random, similary to UUID version 1 but with random istead of MAC address.\n *\n * With \"timestampish\" we mean milliseconds from 1970 approximately, as in bulk-creation\n * scenarios, milliseconds in future will be used (while creating more than 1 id per\n * millisecond)\n *\n * This is similary UUID version 1 but with random instead of Mac, and with\n * support for generating unique IDs the same millisecond.\n *\n * It's even more similar to the \"version 6\" proposal at\n * https://bradleypeabody.github.io/uuidv6/.\n *\n * Difference from \"version 6\" proposal is that we keep the clock-sequence within\n * the timestamp part to allow 9 more bits for randomness. This is at the cost of\n * knwoing how exact the time-stamp is. But since we anyway don't expect a perfect\n * time stamps as many clients may have wrong time settings, what we want is just\n * a sorted ID, still universially unique.\n *\n * Random part is totally 73 bits entropy, which basically means that a collisions would\n * be likely if 9 444 732 965 739 290 427 392 devices was generating ids during the exact same\n * millisecond.\n *\n */\nexport function newId(): string {\n  const a = new Uint8Array(18);\n  const timePart = new Uint8Array(a.buffer, 0, 6);\n  const now = Date.now(); // Will fit into 6 bytes until year 10 895.\n  if (time >= now) {\n    // User is bulk-creating objects the same millisecond.\n    // Increment the time part by one millisecond for each item.\n    // If bulk-creating 1,000,000 rows client-side in 0 seconds,\n    // the last time-stamp will be 1,000 seconds in future, which is no biggie at all.\n    // The point is to create a nice order of the generated IDs instead of\n    // using random ids.\n    ++time;\n  } else {\n    time = now;\n  }\n  timePart[0] = time / 0x10000000000;\n  timePart[1] = time / 0x100000000;\n  timePart[2] = time / 0x1000000;\n  timePart[3] = time / 0x10000;\n  timePart[4] = time / 0x100;\n  timePart[5] = time;\n  const randomPart = new Uint8Array(a.buffer, 6);\n  getRandomValues(randomPart);\n  return b64LexEncode(a);\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/FakeBlob.ts",
    "content": "export class FakeBlob {\n  constructor(public buf: ArrayBuffer, public type?: string) {}\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/FakeFile.ts",
    "content": "import { FakeBlob } from \"./FakeBlob.js\";\n\n/** This type can be used in Node in order to parse TSON with File type and stringify it down again to JSON.\n * A File is basically a Blob with a name and lastModified.\n */\nexport class FakeFile {\n  blob: FakeBlob;\n  name: string;\n  lastModified: Date;\n  constructor(blob: FakeBlob, name: string, lastModified: Date) {\n    this.blob = blob;\n    this.name = name;\n    this.lastModified = lastModified;\n  }\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/StreamingSyncProcessor.ts",
    "content": "/**\n * StreamingSyncProcessor\n *\n * Client-side processor for streaming sync responses (NDJSON format).\n * Handles incremental parsing and blob resolution to avoid memory spikes.\n *\n * BACKWARD COMPATIBILITY:\n * - Client signals streaming support via Accept header + capabilities\n * - Old servers return application/json → fallback to traditional parsing\n * - New servers return application/x-ndjson-stream → use this processor\n */\n\nimport { TSONRef, TSONRefData, replaceTSONRefs } from './TSONRef.js';\n\n// ============================================================\n// STREAM MESSAGE TYPES\n// ============================================================\n\n/** Header sent at start of streaming sync */\nexport interface StreamSyncHeader {\n  type: 'header';\n  snapshotRevision: string;\n  totalRealms: number;\n  estimatedObjects?: number;\n}\n\n/** Chunk of changes (batched objects) */\nexport interface StreamSyncChanges {\n  type: 'changes';\n  /** Table name */\n  tbl: string;\n  /** Objects to upsert */\n  objects: Array<{\n    key: string;\n    value: unknown;\n  }>;\n  /** Realm these objects belong to */\n  realmId?: string;\n}\n\n/** Marks a realm as fully sent */\nexport interface StreamSyncRealmComplete {\n  type: 'realm-complete';\n  realmId: string;\n}\n\n/** Footer sent at end of streaming sync */\nexport interface StreamSyncFooter {\n  type: 'footer';\n  serverRevision: string;\n  realms: string[];\n  inviteRealms: string[];\n  schema: Record<string, unknown>;\n  errors?: Array<{\n    name: string;\n    message: string;\n    txid?: string;\n  }>;\n}\n\n/** Union of all stream message types */\nexport type StreamSyncMessage =\n  | StreamSyncHeader\n  | StreamSyncChanges\n  | StreamSyncRealmComplete\n  | StreamSyncFooter;\n\n// ============================================================\n// PROGRESS TRACKING\n// ============================================================\n\n/**\n * Progress state for resumable streaming sync.\n */\nexport interface StreamSyncProgress {\n  /** Server revision at start of streaming sync */\n  snapshotRevision: string;\n  /** Realms fully downloaded */\n  completedRealms: string[];\n  /** Current realm being processed */\n  currentRealm?: string;\n  /** Last key written (for resume) */\n  lastKey?: {\n    table: string;\n    key: string;\n  };\n  /** Timestamp when sync started */\n  startedAt: number;\n  /** Total objects processed so far */\n  processedCount: number;\n}\n\n// ============================================================\n// BLOB RESOLUTION OPTIONS\n// ============================================================\n\nexport interface BlobResolutionOptions {\n  /** Max concurrent blob fetches */\n  concurrency: number;\n  /** Fetch function: GET /blob/:id */\n  fetchBlob: (blobId: string) => Promise<ArrayBuffer>;\n  /**\n   * Resolution strategy:\n   * - 'eager': Resolve all blobs before writing chunk\n   * - 'lazy': Write TSONRef to DB, resolve on access\n   */\n  strategy: 'eager' | 'lazy';\n}\n\n// ============================================================\n// CALLBACKS\n// ============================================================\n\nexport interface StreamingSyncCallbacks {\n  /** Called when header is received */\n  onHeader?: (header: StreamSyncHeader) => void;\n  /** Write objects to database */\n  bulkPut: (table: string, objects: Array<{ key: string; value: unknown }>) => Promise<void>;\n  /** Called when a realm is complete */\n  onRealmComplete?: (realmId: string) => void;\n  /** Called with progress updates */\n  onProgress?: (progress: StreamSyncProgress) => void;\n  /** Persist progress for resume capability */\n  saveProgress?: (progress: StreamSyncProgress) => Promise<void>;\n}\n\n// ============================================================\n// HELPER FUNCTIONS\n// ============================================================\n\n/**\n * Check if value is a TSONRef or serialized $ref format.\n */\nfunction isBlobRef(value: unknown): value is TSONRef | TSONRefData {\n  return TSONRef.isTSONRef(value) || TSONRef.isTSONRefData(value);\n}\n\n/**\n * Find all blob refs in an object tree.\n */\nexport function collectBlobRefs(obj: unknown, refs: TSONRef[] = []): TSONRef[] {\n  if (obj === null || obj === undefined) return refs;\n\n  if (TSONRef.isTSONRef(obj)) {\n    refs.push(obj);\n    return refs;\n  }\n\n  if (TSONRef.isTSONRefData(obj)) {\n    refs.push(TSONRef.fromData(obj));\n    return refs;\n  }\n\n  if (Array.isArray(obj)) {\n    for (const item of obj) {\n      collectBlobRefs(item, refs);\n    }\n    return refs;\n  }\n\n  if (typeof obj === 'object') {\n    for (const value of Object.values(obj)) {\n      collectBlobRefs(value, refs);\n    }\n  }\n\n  return refs;\n}\n\n/**\n * Resolve all blob refs in an object tree (eager strategy).\n */\nexport async function resolveAllBlobRefs(\n  obj: unknown,\n  options: BlobResolutionOptions\n): Promise<void> {\n  // Wrap fetchBlob to create a TSONRefResolver\n  const resolver = (ref: TSONRef) => options.fetchBlob(ref.ref);\n  await replaceTSONRefs(obj, resolver, options.concurrency);\n}\n\n/**\n * Check if a chunk should be skipped (already processed in previous attempt).\n */\nfunction shouldSkipChunk(\n  message: StreamSyncChanges,\n  progress: StreamSyncProgress\n): boolean {\n  // If realm is already complete, skip\n  if (message.realmId && progress.completedRealms.includes(message.realmId)) {\n    return true;\n  }\n\n  // If we have a lastKey for this table, check if chunk is before it\n  // Server must guarantee keys arrive in lexicographically sorted order,\n  // matching their logical ordering (composite keys are pre-encoded/sorted server-side)  \n  if (progress.lastKey && progress.lastKey.table === message.tbl) {\n    const lastChunkKey = message.objects[message.objects.length - 1]?.key;\n    if (lastChunkKey && lastChunkKey <= progress.lastKey.key) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\n// ============================================================\n// MAIN PROCESSOR\n// ============================================================\n\n/**\n * Process a streaming sync response.\n *\n * @param stream - ReadableStream from fetch response\n * @param callbacks - Handlers for processing messages\n * @param blobOptions - Options for blob resolution\n * @param resumeFrom - Previous progress to resume from\n * @returns Final sync result from footer\n */\nexport async function processStreamingSync(\n  stream: ReadableStream<Uint8Array>,\n  callbacks: StreamingSyncCallbacks,\n  blobOptions: BlobResolutionOptions,\n  resumeFrom?: StreamSyncProgress\n): Promise<StreamSyncFooter> {\n  const reader = stream.getReader();\n  const decoder = new TextDecoder();\n\n  let buffer = '';\n  const progress: StreamSyncProgress = resumeFrom ?? {\n    snapshotRevision: '',\n    completedRealms: [],\n    startedAt: Date.now(),\n    processedCount: 0,\n  };\n\n  let footer: StreamSyncFooter | null = null;\n  let finished = false;\n\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) {\n        finished = true;\n        break;\n      }\n\n      // Append to buffer and process complete lines\n      buffer += decoder.decode(value, { stream: true });\n      const lines = buffer.split('\\n');\n\n      // Keep incomplete line in buffer\n      buffer = lines.pop() || '';\n\n      for (const line of lines) {\n        if (!line.trim()) continue;\n\n        const message: StreamSyncMessage = JSON.parse(line);\n\n        switch (message.type) {\n          case 'header':\n            progress.snapshotRevision = message.snapshotRevision;\n            callbacks.onHeader?.(message);\n            break;\n\n          case 'changes':\n            // Skip if already processed in previous attempt\n            if (shouldSkipChunk(message, progress)) {\n              continue;\n            }\n\n            // Resolve blob refs if using eager strategy\n            if (blobOptions.strategy === 'eager') {\n              for (const obj of message.objects) {\n                await resolveAllBlobRefs(obj.value, blobOptions);\n              }\n            }\n\n            // Write to database\n            await callbacks.bulkPut(message.tbl, message.objects);\n\n            // Update progress\n            progress.processedCount += message.objects.length;\n            if (message.realmId) {\n              progress.currentRealm = message.realmId;\n            }\n            if (message.objects.length > 0) {\n              progress.lastKey = {\n                table: message.tbl,\n                key: message.objects[message.objects.length - 1].key,\n              };\n            }\n\n            // Persist progress periodically (every 100 objects)\n            if (progress.processedCount % 100 === 0) {\n              await callbacks.saveProgress?.(progress);\n              callbacks.onProgress?.(progress);\n            }\n            break;\n\n          case 'realm-complete':\n            progress.completedRealms.push(message.realmId);\n            progress.currentRealm = undefined;\n            progress.lastKey = undefined;\n            callbacks.onRealmComplete?.(message.realmId);\n            await callbacks.saveProgress?.(progress);\n            break;\n\n          case 'footer':\n            footer = message;\n            break;\n        }\n      }\n    }\n\n    // Process any remaining data in buffer\n    if (buffer.trim()) {\n      const message: StreamSyncMessage = JSON.parse(buffer);\n      if (message.type === 'footer') {\n        footer = message;\n      }\n    }\n\n    if (!footer) {\n      throw new Error('Streaming sync ended without footer');\n    }\n\n    return footer;\n  } finally {\n    if (!finished) {\n      await reader.cancel().catch(()=>{});\n    }\n    reader.releaseLock();\n  }\n}\n\n// ============================================================\n// HTTP HELPERS\n// ============================================================\n\n/**\n * Build Accept header for sync request.\n */\nexport function buildSyncAcceptHeader(supportsStreaming: boolean): string {\n  if (supportsStreaming) {\n    return 'application/x-ndjson-stream, application/json';\n  }\n  return 'application/json';\n}\n\n/**\n * Check if response is streaming format.\n */\nexport function isStreamingResponse(response: Response): boolean {\n  const contentType = response.headers.get('content-type') || '';\n  return contentType.includes('application/x-ndjson-stream');\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/TSONRef.ts",
    "content": "/**\n * TSONRef - Reference to a blob stored separately from the main data.\n *\n * When TSON parses data containing blob references, it creates TSONRef\n * instances instead of the actual binary data. The client can then\n * resolve these refs asynchronously.\n *\n * @example\n * ```typescript\n * // Configure resolver\n * TSONRef.resolver = async (ref) => {\n *   const response = await fetch(`/blob/${ref.ref}`);\n *   return response.arrayBuffer();\n * };\n *\n * // After parsing, resolve all refs in an object\n * await resolveAllRefs(data);\n * ```\n */\n\n/** Serialized format of a blob reference */\nexport interface TSONRefData {\n  /** Type marker */\n  $t: string;\n  /** Blob reference ID */\n  $ref: string;\n  /** Size in bytes */\n  $size: number;\n  /** Content-Type (for Blob type) */\n  $ct?: string;\n}\n\n/** Function type for resolving blob refs */\nexport type TSONRefResolver = (ref: TSONRef) => Promise<ArrayBuffer>;\n\n/** Symbol for type checking TSONRef instances */\nconst TSON_REF_SYMBOL = Symbol.for('TSONRef');\n\n/**\n * TSONRef represents a reference to binary data stored as a blob.\n */\nexport class TSONRef<T extends ArrayBuffer | Blob | Uint8Array = ArrayBuffer> {\n  /** Symbol for type checking */\n  static readonly TYPE_SYMBOL = TSON_REF_SYMBOL;\n\n  /** Type brand for runtime identification */\n  readonly [TSON_REF_SYMBOL] = true;\n\n  /** Global resolver function - must be configured before resolving */\n  static resolver: TSONRefResolver | null = null;\n\n  constructor(\n    /** Original TSON type: 'ArrayBuffer', 'Blob', 'Uint8Array', etc */\n    public readonly type: string,\n    /** Blob reference ID (UUID) */\n    public readonly ref: string,\n    /** Size in bytes */\n    public readonly size: number,\n    /** Content-Type (for Blob type) */\n    public readonly contentType?: string\n  ) {\n    Object.freeze(this);\n  }\n\n  /**\n   * Resolve this reference to actual data.\n   * Requires TSONRef.resolver to be configured.\n   */\n  async resolve(): Promise<T> {\n    if (!TSONRef.resolver) {\n      throw new Error(\n        'TSONRef.resolver not configured. ' +\n          'Set TSONRef.resolver to a function that fetches blobs.'\n      );\n    }\n\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const data = await TSONRef.resolver(this as any);\n    return this.reconstruct(data) as T;\n  }\n\n  /**\n   * Reconstruct the original type from ArrayBuffer.\n   * Validates byte alignment for TypedArrays that require it.\n   */\n  reconstruct(data: ArrayBuffer): ArrayBuffer | Blob | Uint8Array {\n    // Helper to validate alignment for multi-byte TypedArrays\n    const validateAlignment = (bytesPerElement: number, typeName: string) => {\n      if (data.byteLength % bytesPerElement !== 0) {\n        throw new RangeError(\n          `Buffer length ${data.byteLength} is not aligned to ${bytesPerElement} bytes for ${typeName}`\n        );\n      }\n    };\n\n    switch (this.type) {\n      case 'ArrayBuffer':\n        return data;\n\n      case 'Uint8Array':\n        return new Uint8Array(data);\n\n      case 'Blob':\n        return new Blob([data], { type: this.contentType });\n\n      // Handle other TypedArrays with alignment validation\n      case 'Int8Array':\n        return new Int8Array(data) as unknown as Uint8Array;\n      case 'Uint8ClampedArray':\n        return new Uint8ClampedArray(data) as unknown as Uint8Array;\n      case 'Int16Array':\n        validateAlignment(2, 'Int16Array');\n        return new Int16Array(data) as unknown as Uint8Array;\n      case 'Uint16Array':\n        validateAlignment(2, 'Uint16Array');\n        return new Uint16Array(data) as unknown as Uint8Array;\n      case 'Int32Array':\n        validateAlignment(4, 'Int32Array');\n        return new Int32Array(data) as unknown as Uint8Array;\n      case 'Uint32Array':\n        validateAlignment(4, 'Uint32Array');\n        return new Uint32Array(data) as unknown as Uint8Array;\n      case 'Float32Array':\n        validateAlignment(4, 'Float32Array');\n        return new Float32Array(data) as unknown as Uint8Array;\n      case 'Float64Array':\n        validateAlignment(8, 'Float64Array');\n        return new Float64Array(data) as unknown as Uint8Array;\n      case 'BigInt64Array':\n        validateAlignment(8, 'BigInt64Array');\n        return new BigInt64Array(data) as unknown as Uint8Array;\n      case 'BigUint64Array':\n        validateAlignment(8, 'BigUint64Array');\n        return new BigUint64Array(data) as unknown as Uint8Array;\n\n      default:\n        console.warn(`Unknown TSONRef type: ${this.type}, returning ArrayBuffer`);\n        return data;\n    }\n  }\n\n  /**\n   * Check if a value is a TSONRef instance.\n   */\n  static isTSONRef(value: unknown): value is TSONRef {\n    return (\n      value !== null &&\n      typeof value === 'object' &&\n      TSON_REF_SYMBOL in value &&\n      (value as Record<symbol, unknown>)[TSON_REF_SYMBOL] === true\n    );\n  }\n\n  /**\n   * Check if a value is TSONRef serialized data (has $ref).\n   */\n  static isTSONRefData(value: unknown): value is TSONRefData {\n    return (\n      value !== null &&\n      typeof value === 'object' &&\n      '$ref' in value &&\n      '$t' in value &&\n      '$size' in value\n    );\n  }\n\n  /**\n   * Create TSONRef from serialized data.\n   */\n  static fromData(data: TSONRefData): TSONRef {\n    return new TSONRef(data.$t, data.$ref, data.$size, data.$ct);\n  }\n\n  /**\n   * Serialize to JSON-compatible format.\n   */\n  toJSON(): TSONRefData {\n    const result: TSONRefData = {\n      $t: this.type,\n      $ref: this.ref,\n      $size: this.size,\n    };\n    if (this.contentType) {\n      result.$ct = this.contentType;\n    }\n    return result;\n  }\n}\n\n// ============================================================\n// UTILITY FUNCTIONS\n// ============================================================\n\n/**\n * Check if an object tree contains any TSONRef instances.\n */\nexport function hasTSONRefs(obj: unknown): boolean {\n  if (obj === null || obj === undefined) return false;\n  if (TSONRef.isTSONRef(obj) || TSONRef.isTSONRefData(obj)) return true;\n\n  if (Array.isArray(obj)) {\n    return obj.some(hasTSONRefs);\n  }\n\n  if (typeof obj === 'object') {\n    return Object.values(obj).some(hasTSONRefs);\n  }\n\n  return false;\n}\n\n/**\n * Collect all TSONRef instances from an object tree.\n */\nexport function collectTSONRefs(obj: unknown, refs: TSONRef[] = []): TSONRef[] {\n  if (obj === null || obj === undefined) return refs;\n\n  if (TSONRef.isTSONRef(obj)) {\n    refs.push(obj);\n    return refs;\n  }\n\n  if (TSONRef.isTSONRefData(obj)) {\n    refs.push(TSONRef.fromData(obj));\n    return refs;\n  }\n\n  if (Array.isArray(obj)) {\n    for (const item of obj) {\n      collectTSONRefs(item, refs);\n    }\n    return refs;\n  }\n\n  if (typeof obj === 'object') {\n    for (const value of Object.values(obj)) {\n      collectTSONRefs(value, refs);\n    }\n  }\n\n  return refs;\n}\n\n/**\n * Replace TSONRef instances with resolved data in-place.\n * \n * Note: If the root object itself is a TSONRef, it cannot be replaced in-place.\n * In that case, use the returned value instead.\n *\n * @param obj - Object tree to process\n * @param resolver - Function to fetch blob data\n * @param concurrency - Max concurrent fetches (default 5)\n * @returns The resolved value if root is a TSONRef, otherwise undefined\n */\nexport async function replaceTSONRefs(\n  obj: unknown,\n  resolver: TSONRefResolver,\n  concurrency = 5\n): Promise<ArrayBuffer | Blob | Uint8Array | undefined> {\n  const refs = collectTSONRefs(obj);\n  if (refs.length === 0) return undefined;\n\n  // Fetch all unique refs with concurrency limit\n  const resolved = new Map<string, ArrayBuffer>();\n  const uniqueRefs = [...new Map(refs.map((r) => [r.ref, r])).values()];\n\n  for (let i = 0; i < uniqueRefs.length; i += concurrency) {\n    const batch = uniqueRefs.slice(i, i + concurrency);\n    await Promise.all(\n      batch.map(async (ref) => {\n        const data = await resolver(ref);\n        resolved.set(ref.ref, data);\n      })\n    );\n  }\n\n  // Handle root-level TSONRef - cannot replace in-place, return resolved value\n  if (TSONRef.isTSONRef(obj)) {\n    const data = resolved.get(obj.ref);\n    return data ? obj.reconstruct(data) : undefined;\n  }\n  if (TSONRef.isTSONRefData(obj)) {\n    const ref = TSONRef.fromData(obj);\n    const data = resolved.get(ref.ref);\n    return data ? ref.reconstruct(data) : undefined;\n  }\n\n  // Replace refs with resolved data in nested objects\n  replaceRefsInPlace(obj, resolved);\n  return undefined;\n}\n\nfunction replaceRefsInPlace(\n  obj: unknown,\n  resolved: Map<string, ArrayBuffer>,\n  parent?: Record<string, unknown> | unknown[],\n  key?: string | number\n): void {\n  if (obj === null || obj === undefined) return;\n\n  // Handle TSONRef instance\n  if (TSONRef.isTSONRef(obj)) {\n    const data = resolved.get(obj.ref);\n    if (data && parent !== undefined && key !== undefined) {\n      (parent as Record<string | number, unknown>)[key] = obj.reconstruct(data);\n    }\n    return;\n  }\n\n  // Handle serialized $ref format\n  if (TSONRef.isTSONRefData(obj)) {\n    const ref = TSONRef.fromData(obj);\n    const data = resolved.get(ref.ref);\n    if (data && parent !== undefined && key !== undefined) {\n      (parent as Record<string | number, unknown>)[key] = ref.reconstruct(data);\n    }\n    return;\n  }\n\n  if (Array.isArray(obj)) {\n    for (let i = 0; i < obj.length; i++) {\n      replaceRefsInPlace(obj[i], resolved, obj, i);\n    }\n    return;\n  }\n\n  if (typeof obj === 'object') {\n    for (const k of Object.keys(obj)) {\n      replaceRefsInPlace(\n        (obj as Record<string, unknown>)[k],\n        resolved,\n        obj as Record<string, unknown>,\n        k\n      );\n    }\n  }\n}\n\n/**\n * Resolve all TSONRef instances in an object tree.\n * Convenience function that uses TSONRef.resolver.\n */\nexport async function resolveAllRefs(obj: unknown, concurrency = 5): Promise<void> {\n  if (!TSONRef.resolver) {\n    throw new Error('TSONRef.resolver not configured');\n  }\n  await replaceTSONRefs(obj, TSONRef.resolver, concurrency);\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/TypeDef.ts",
    "content": "import { TypeDefSet } from \"./TypeDefSet.js\";\n\nexport interface TypeDef<T = unknown, TReplaced = unknown> {\n  test?: (val: T, toStringTag: string) => boolean;\n  replace: (\n    val: T,\n    altChannel: any,\n    typeDefs: TypeDefSet\n  ) => TReplaced | (TReplaced & { $t: string });\n  revive: (val: any, altChannel: any, typeDefs: TypeDefSet) => T;\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/TypeDefSet.ts",
    "content": "import { TypeDef } from \"./TypeDef.js\";\n\nexport type TypeDefSet = {\n  string?: TypeDef<string>;\n  boolean?: TypeDef<boolean>;\n  number?: TypeDef<number>;\n  undefined?: TypeDef<undefined>;\n  bigint?: TypeDef<bigint>;\n  symbol?: TypeDef<symbol>;\n  function?: TypeDef<Function>;\n  ArrayBuffer?: TypeDef<ArrayBuffer>;\n  Date?: TypeDef<Date>;\n  Map?: TypeDef<Map<any, any>>;\n  Set?: TypeDef<Set<any>>;\n  Int8Array?: TypeDef<Int8Array>;\n  Uint8Array?: TypeDef<Uint8Array>;\n  Uint8ClampedArray?: TypeDef<Uint8ClampedArray>;\n  Int16Array?: TypeDef<Int16Array>;\n  Uint16Array?: TypeDef<Uint16Array>;\n  Int32Array?: TypeDef<Int32Array>;\n  Uint32Array?: TypeDef<Uint32Array>;\n  Float32Array?: TypeDef<Float32Array>;\n  Float64Array?: TypeDef<Float64Array>;\n  DataView?: TypeDef<DataView>;\n  BigInt64Array?: TypeDef<BigInt64Array>;\n  BigUint64Array?: TypeDef<BigUint64Array>;\n\n  // Extendable with other types, custom, DOM or node types such as Blob, Buffer etc.\n  [TypeName: string]: TypeDef<any, any> | undefined | null;\n};\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/TypesonSimplified.ts",
    "content": "import { TypeDef } from \"./TypeDef.js\";\nimport { TypeDefSet } from \"./TypeDefSet.js\";\n\nconst { toString: toStr } = {};\nfunction getToStringTag(val: any) {\n  return toStr.call(val).slice(8, -1);\n}\n\nexport function escapeDollarProps(value: any) {\n  const keys = Object.keys(value);\n  let dollarKeys: string[] | null = null;\n  for (let i = 0, l = keys.length; i < l; ++i) {\n    if (keys[i][0] === \"$\") {\n      dollarKeys = dollarKeys || [];\n      dollarKeys.push(keys[i]);\n    }\n  }\n  if (!dollarKeys) return value;\n  const clone = { ...value };\n  for (const k of dollarKeys) {\n    delete clone[k];\n  }\n  for (const k of dollarKeys) {\n    clone[\"$\" + k] = value[k];\n  }\n  return clone;\n}\n\nconst ObjectDef = {\n  replace: escapeDollarProps,\n};\n\nexport function TypesonSimplified(...typeDefsInputs: TypeDefSet[]) {\n  const typeDefs: any = typeDefsInputs.reduce(\n    (p, c) => ({ ...p, ...c }),\n    typeDefsInputs.reduce((p, c) => ({ ...c, ...p }), {})\n  );\n  const protoMap = new WeakMap<object, TypeDef | null>();\n  return {\n    stringify(value: any, alternateChannel?: any, space?: number): string {\n      const json = JSON.stringify(\n        value,\n        function (key: string) {\n          const realVal = (this as any)[key];\n          const typeDef = getTypeDef(realVal);\n          return typeDef\n            ? typeDef.replace(realVal, alternateChannel, typeDefs)\n            : realVal;\n        },\n        space\n      );\n      return json;\n    },\n\n    parse(tson: string, alternateChannel?: any) {\n      const stack: [object, string[], object][] = [];\n\n      return JSON.parse(tson, function (key, value) {\n        //\n        // Parent Part\n        //\n        const type = value?.$t;\n        if (type) {\n          const typeDef = typeDefs[type];\n          value = typeDef\n            ? typeDef.revive(value, alternateChannel, typeDefs)\n            : value;\n        }\n        let top = stack[stack.length - 1];\n        if (top && top[0] === value) {\n          // Do what the kid told us to\n          // Unescape dollar props\n          value = { ...value };\n          // Delete keys that children wanted us to delete\n          for (const k of top[1]) delete value[k];\n          // Set keys that children wanted us to set\n          for (const [k, v] of Object.entries(top[2])) {\n            value[k] = v;\n          }\n          stack.pop();\n        }\n\n        //\n        // Child part\n        //\n        if (value === undefined || (key[0] === \"$\" && key !== \"$t\")) {\n          top = stack[stack.length - 1];\n          let deletes: string[];\n          let mods: Record<string, any>;\n          if (top && top[0] === this) {\n            deletes = top[1];\n            mods = top[2] as Record<string, any>;\n          } else {\n            stack.push([this, (deletes = []), (mods = {})]);\n          }\n          if (key[0] === \"$\" && key !== \"$t\") {\n            // Unescape props (also preserves undefined if this is a combo)\n            deletes.push(key);\n            mods[key.substr(1)] = value;\n          } else {\n            // Preserve undefined\n            mods[key] = undefined;\n          }\n        }\n\n        return value;\n      });\n    },\n  };\n\n  function getTypeDef(realVal: any): TypeDef | null {\n    const type = typeof realVal;\n    switch (typeof realVal) {\n      case \"object\":\n      case \"function\": {\n        // \"object\", \"function\", null\n        if (realVal === null) return null;\n        const proto = Object.getPrototypeOf(realVal);\n        if (!proto) return ObjectDef as any;\n        let typeDef = protoMap.get(proto);\n        if (typeDef !== undefined) return typeDef; // Null counts to! So the caching of Array.prototype also counts.\n        const toStringTag = getToStringTag(realVal);\n        const entry = Object.entries(typeDefs).find(\n          ([typeName, typeDef]: [string, any]) =>\n            typeDef?.test?.(realVal, toStringTag) ?? typeName === toStringTag\n        );\n        typeDef = entry?.[1] as TypeDef | undefined;\n        if (!typeDef) {\n          typeDef = Array.isArray(realVal)\n            ? null\n            : typeof realVal === \"function\"\n            ? typeDefs.function || null\n            : (ObjectDef as any);\n        }\n        protoMap.set(proto, typeDef!);\n        return typeDef!;\n      }\n      default:\n        return typeDefs[type];\n    }\n  }\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/__tests__/TSONRef.test.ts",
    "content": "import { TSONRef, TSONRefData, collectTSONRefs, replaceTSONRefs } from '../TSONRef';\n\ndescribe('TSONRef', () => {\n  describe('constructor and properties', () => {\n    test('creates TSONRef with required properties', () => {\n      const ref = new TSONRef('Uint8Array', 'ref-123', 1024);\n      expect(ref.type).toBe('Uint8Array');\n      expect(ref.ref).toBe('ref-123');\n      expect(ref.size).toBe(1024);\n      expect(ref.contentType).toBeUndefined();\n    });\n\n    test('creates TSONRef with contentType for Blob', () => {\n      const ref = new TSONRef('Blob', 'ref-456', 2048, 'image/png');\n      expect(ref.type).toBe('Blob');\n      expect(ref.contentType).toBe('image/png');\n    });\n  });\n\n  describe('isTSONRef', () => {\n    test('returns true for TSONRef instances', () => {\n      const ref = new TSONRef('ArrayBuffer', 'ref-1', 100);\n      expect(TSONRef.isTSONRef(ref)).toBe(true);\n    });\n\n    test('returns false for plain objects', () => {\n      expect(TSONRef.isTSONRef({})).toBe(false);\n      expect(TSONRef.isTSONRef({ $ref: 'test' })).toBe(false);\n      expect(TSONRef.isTSONRef(null)).toBe(false);\n      expect(TSONRef.isTSONRef(undefined)).toBe(false);\n    });\n  });\n\n  describe('isTSONRefData', () => {\n    test('returns true for valid TSONRefData', () => {\n      const data: TSONRefData = {\n        $t: 'Uint8Array',\n        $ref: 'ref-123',\n        $size: 100,\n      };\n      expect(TSONRef.isTSONRefData(data)).toBe(true);\n    });\n\n    test('returns true for TSONRefData with contentType', () => {\n      const data: TSONRefData = {\n        $t: 'Blob',\n        $ref: 'ref-456',\n        $size: 200,\n        $ct: 'application/pdf',\n      };\n      expect(TSONRef.isTSONRefData(data)).toBe(true);\n    });\n\n    test('returns false for incomplete data', () => {\n      expect(TSONRef.isTSONRefData({ $t: 'Uint8Array' })).toBe(false);\n      expect(TSONRef.isTSONRefData({ $ref: 'ref' })).toBe(false);\n      expect(TSONRef.isTSONRefData({})).toBe(false);\n    });\n  });\n\n  describe('fromData', () => {\n    test('creates TSONRef from TSONRefData', () => {\n      const data: TSONRefData = {\n        $t: 'Float64Array',\n        $ref: 'ref-789',\n        $size: 64,\n      };\n      const ref = TSONRef.fromData(data);\n      expect(ref.type).toBe('Float64Array');\n      expect(ref.ref).toBe('ref-789');\n      expect(ref.size).toBe(64);\n    });\n  });\n\n  describe('toJSON', () => {\n    test('serializes to TSONRefData format', () => {\n      const ref = new TSONRef('Int32Array', 'ref-abc', 128);\n      const json = ref.toJSON();\n      expect(json).toEqual({\n        $t: 'Int32Array',\n        $ref: 'ref-abc',\n        $size: 128,\n      });\n    });\n\n    test('includes contentType for Blob', () => {\n      const ref = new TSONRef('Blob', 'ref-def', 256, 'text/plain');\n      const json = ref.toJSON();\n      expect(json.$ct).toBe('text/plain');\n    });\n  });\n\n  describe('reconstruct', () => {\n    test('reconstructs ArrayBuffer', () => {\n      const ref = new TSONRef('ArrayBuffer', 'ref', 4);\n      const data = new ArrayBuffer(4);\n      new Uint8Array(data).set([1, 2, 3, 4]);\n      \n      const result = ref.reconstruct(data);\n      expect(result).toBe(data); // Same reference\n    });\n\n    test('reconstructs Uint8Array', () => {\n      const ref = new TSONRef('Uint8Array', 'ref', 4);\n      const data = new ArrayBuffer(4);\n      new Uint8Array(data).set([1, 2, 3, 4]);\n      \n      const result = ref.reconstruct(data);\n      expect(result).toBeInstanceOf(Uint8Array);\n      expect(Array.from(result as Uint8Array)).toEqual([1, 2, 3, 4]);\n    });\n\n    test('reconstructs Blob with contentType', () => {\n      const ref = new TSONRef('Blob', 'ref', 4, 'application/octet-stream');\n      const data = new ArrayBuffer(4);\n      \n      const result = ref.reconstruct(data);\n      expect(result).toBeInstanceOf(Blob);\n      expect((result as Blob).type).toBe('application/octet-stream');\n      expect((result as Blob).size).toBe(4);\n    });\n\n    test('reconstructs Int16Array with alignment validation', () => {\n      const ref = new TSONRef('Int16Array', 'ref', 4);\n      const data = new ArrayBuffer(4); // 4 bytes = 2 Int16 elements, aligned\n      \n      const result = ref.reconstruct(data);\n      expect(result).toBeInstanceOf(Int16Array);\n    });\n\n    test('throws on misaligned Int16Array', () => {\n      const ref = new TSONRef('Int16Array', 'ref', 3);\n      const data = new ArrayBuffer(3); // 3 bytes is not aligned to 2\n      \n      expect(() => ref.reconstruct(data)).toThrow(RangeError);\n    });\n\n    test('throws on misaligned Int32Array', () => {\n      const ref = new TSONRef('Int32Array', 'ref', 5);\n      const data = new ArrayBuffer(5); // 5 bytes is not aligned to 4\n      \n      expect(() => ref.reconstruct(data)).toThrow(RangeError);\n    });\n\n    test('throws on misaligned Float64Array', () => {\n      const ref = new TSONRef('Float64Array', 'ref', 7);\n      const data = new ArrayBuffer(7); // 7 bytes is not aligned to 8\n      \n      expect(() => ref.reconstruct(data)).toThrow(RangeError);\n    });\n\n    test('handles unknown type gracefully', () => {\n      const ref = new TSONRef('UnknownType' as any, 'ref', 4);\n      const data = new ArrayBuffer(4);\n      \n      // Should return ArrayBuffer and log warning\n      const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();\n      const result = ref.reconstruct(data);\n      expect(result).toBe(data);\n      expect(consoleSpy).toHaveBeenCalled();\n      consoleSpy.mockRestore();\n    });\n  });\n});\n\ndescribe('collectTSONRefs', () => {\n  test('returns empty array for primitives', () => {\n    expect(collectTSONRefs(null)).toEqual([]);\n    expect(collectTSONRefs(undefined)).toEqual([]);\n    expect(collectTSONRefs(42)).toEqual([]);\n    expect(collectTSONRefs('string')).toEqual([]);\n  });\n\n  test('collects TSONRef from object', () => {\n    const ref = new TSONRef('Uint8Array', 'ref-1', 100);\n    const obj = { data: ref };\n    \n    const refs = collectTSONRefs(obj);\n    expect(refs).toHaveLength(1);\n    expect(refs[0]).toBe(ref);\n  });\n\n  test('collects TSONRefData from object', () => {\n    const data: TSONRefData = { $t: 'Uint8Array', $ref: 'ref-2', $size: 200 };\n    const obj = { data };\n    \n    const refs = collectTSONRefs(obj);\n    expect(refs).toHaveLength(1);\n    expect(refs[0].ref).toBe('ref-2');\n  });\n\n  test('collects multiple refs from nested structure', () => {\n    const ref1 = new TSONRef('Uint8Array', 'ref-1', 100);\n    const ref2 = new TSONRef('ArrayBuffer', 'ref-2', 200);\n    const obj = {\n      level1: {\n        data1: ref1,\n        level2: {\n          data2: ref2,\n        },\n      },\n    };\n    \n    const refs = collectTSONRefs(obj);\n    expect(refs).toHaveLength(2);\n  });\n\n  test('collects refs from arrays', () => {\n    const ref1 = new TSONRef('Uint8Array', 'ref-1', 100);\n    const ref2 = new TSONRef('Uint8Array', 'ref-2', 100);\n    const arr = [ref1, { nested: ref2 }];\n    \n    const refs = collectTSONRefs(arr);\n    expect(refs).toHaveLength(2);\n  });\n});\n\ndescribe('replaceTSONRefs', () => {\n  const mockResolver = jest.fn(async (ref: TSONRef) => {\n    const data = new ArrayBuffer(ref.size);\n    const view = new Uint8Array(data);\n    // Fill with pattern based on ref id\n    for (let i = 0; i < ref.size; i++) {\n      view[i] = parseInt(ref.ref.slice(-1)) + i;\n    }\n    return data;\n  });\n\n  beforeEach(() => {\n    mockResolver.mockClear();\n  });\n\n  test('replaces TSONRef in object', async () => {\n    const ref = new TSONRef('Uint8Array', 'ref-1', 4);\n    const obj: any = { data: ref };\n    \n    await replaceTSONRefs(obj, mockResolver);\n    \n    expect(obj.data).toBeInstanceOf(Uint8Array);\n    expect(mockResolver).toHaveBeenCalledTimes(1);\n  });\n\n  test('replaces TSONRefData in object', async () => {\n    const obj: any = {\n      data: { $t: 'Uint8Array', $ref: 'ref-2', $size: 4 },\n    };\n    \n    await replaceTSONRefs(obj, mockResolver);\n    \n    expect(obj.data).toBeInstanceOf(Uint8Array);\n  });\n\n  test('handles root-level TSONRef by returning value', async () => {\n    const ref = new TSONRef('Uint8Array', 'ref-3', 4);\n    \n    const result = await replaceTSONRefs(ref, mockResolver);\n    \n    expect(result).toBeInstanceOf(Uint8Array);\n  });\n\n  test('handles object with no refs', async () => {\n    const obj = { a: 1, b: 'test' };\n    \n    const result = await replaceTSONRefs(obj, mockResolver);\n    \n    expect(result).toBeUndefined();\n    expect(mockResolver).not.toHaveBeenCalled();\n  });\n\n  test('deduplicates identical refs', async () => {\n    const obj: any = {\n      data1: new TSONRef('Uint8Array', 'same-ref', 4),\n      data2: new TSONRef('Uint8Array', 'same-ref', 4),\n    };\n    \n    await replaceTSONRefs(obj, mockResolver);\n    \n    // Should only fetch once despite two refs with same id\n    expect(mockResolver).toHaveBeenCalledTimes(1);\n  });\n\n  test('respects concurrency limit', async () => {\n    let concurrent = 0;\n    let maxConcurrent = 0;\n    \n    const slowResolver = jest.fn(async (ref: TSONRef) => {\n      concurrent++;\n      maxConcurrent = Math.max(maxConcurrent, concurrent);\n      await new Promise(r => setTimeout(r, 10));\n      concurrent--;\n      return new ArrayBuffer(ref.size);\n    });\n    \n    const refs = Array.from({ length: 10 }, (_, i) => \n      new TSONRef('Uint8Array', `ref-${i}`, 4)\n    );\n    const obj: any = Object.fromEntries(refs.map((r, i) => [`data${i}`, r]));\n    \n    await replaceTSONRefs(obj, slowResolver, 3);\n    \n    expect(maxConcurrent).toBeLessThanOrEqual(3);\n    expect(slowResolver).toHaveBeenCalledTimes(10);\n  });\n});\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/__tests__/TypesonSimplified.test.ts",
    "content": "import { TypesonSimplified } from '../TypesonSimplified';\nimport { builtInTypeDefs } from '../presets/builtin';\n\ndescribe('TypesonSimplified', () => {\n  const TSON = TypesonSimplified({ ...builtInTypeDefs });\n\n  describe('primitive types', () => {\n    test('handles strings', () => {\n      const input = 'hello world';\n      const json = TSON.stringify(input);\n      expect(TSON.parse(json)).toBe(input);\n    });\n\n    test('handles plain numbers same as JSON', () => {\n      expect(TSON.stringify(88)).toBe(JSON.stringify(88));\n      expect(TSON.parse('88')).toBe(88);\n      expect(TSON.parse(TSON.stringify(42))).toBe(42);\n      expect(TSON.parse(TSON.stringify(3.14159))).toBe(3.14159);\n    });\n\n    test('handles booleans same as JSON', () => {\n      expect(TSON.stringify(false)).toBe(JSON.stringify(false));\n      expect(TSON.stringify(true)).toBe(JSON.stringify(true));\n      expect(TSON.parse('false')).toStrictEqual(false);\n      expect(TSON.parse('true')).toStrictEqual(true);\n    });\n\n    test('handles null same as JSON', () => {\n      expect(TSON.stringify(null)).toBe('null');\n      expect(TSON.parse('null')).toBeNull();\n      const objWithNullValue = { foo: null };\n      expect(TSON.stringify(objWithNullValue)).toBe(JSON.stringify(objWithNullValue));\n      expect(TSON.parse(TSON.stringify(null))).toBe(null);\n    });\n\n    test('stringifies plain objects same as JSON', () => {\n      const plainObject = { foo: \"bar\" };\n      const tson = TSON.stringify(plainObject);\n      expect(tson).toBe(JSON.stringify(plainObject));\n      expect(TSON.parse(tson)).toStrictEqual(plainObject);\n    });\n\n    test('stringifies plain arrays same as JSON', () => {\n      const plainArray = [{ foo: \"bar\" }, 5, \"dfd\"];\n      const tson = TSON.stringify(plainArray);\n      expect(tson).toBe(JSON.stringify(plainArray));\n      expect(TSON.parse(tson)).toStrictEqual(plainArray);\n    });\n\n    test('stringifies object with null value', () => {\n      const objWithNullValue = { foo: null };\n      const tson = TSON.stringify(objWithNullValue);\n      expect(tson).toBe(JSON.stringify(objWithNullValue));\n      expect(TSON.parse(tson)).toStrictEqual(objWithNullValue);\n    });\n\n    test('should not stringify undefined in objects (default behavior)', () => {\n      // Without undefined type def, undefined properties are omitted (like JSON)\n      expect(TSON.stringify({ foo: null, bar: undefined })).toBe(\n        JSON.stringify({ foo: null }),\n      );\n    });\n  });\n\n  describe('$ escaping', () => {\n    test('escapes props named $t', () => {\n      const input = { $t: \"fakeType\" };\n      const tson = TSON.stringify(input);\n      expect(tson).toBe(JSON.stringify({ $$t: \"fakeType\" }));\n      expect(TSON.parse(tson)).toStrictEqual(input);\n    });\n\n    test('escapes props starting with $', () => {\n      const tson = TSON.stringify({ $hello: \"world\" });\n      expect(tson).toBe(JSON.stringify({ $$hello: \"world\" }));\n      expect(TSON.parse(tson)).toStrictEqual({ $hello: \"world\" });\n    });\n\n    test('escapes props named $', () => {\n      const tson = TSON.stringify({ $: 3 });\n      expect(tson).toBe(JSON.stringify({ $$: 3 }));\n      expect(TSON.parse(tson)).toStrictEqual({ $: 3 });\n    });\n  });\n\n  describe('Date', () => {\n    test('round-trips Date objects', () => {\n      const date = new Date('2024-01-15T12:30:00.000Z');\n      const json = TSON.stringify(date);\n      const result = TSON.parse(json);\n      expect(result).toBeInstanceOf(Date);\n      expect(result.getTime()).toBe(date.getTime());\n    });\n\n    test('handles dates in objects', () => {\n      const obj = { created: new Date('2024-01-01'), name: 'test' };\n      const result = TSON.parse(TSON.stringify(obj));\n      expect(result.created).toBeInstanceOf(Date);\n      expect(result.name).toBe('test');\n    });\n\n    test('handles epoch date', () => {\n      const date = new Date(0);\n      const tson = TSON.stringify(date);\n      expect(tson).toBe(JSON.stringify({ $t: \"Date\", v: \"1970-01-01T00:00:00.000Z\" }));\n      expect(TSON.parse(tson)).toStrictEqual(date);\n    });\n\n    test('handles invalid Date', () => {\n      const invalidDate = new Date(NaN);\n      const tson = TSON.stringify(invalidDate);\n      expect(tson).toBe(JSON.stringify({ $t: \"Date\", v: \"NaN\" }));\n      expect(TSON.parse(tson).getTime()).toBeNaN();\n    });\n\n    test('handles Date in object with invalid Date', () => {\n      const date = new Date(0);\n      const invalidDate = new Date(NaN);\n      const plainObject = { foo: date, bar: invalidDate };\n      const tson = TSON.stringify(plainObject);\n      expect(TSON.parse(tson).foo).toStrictEqual(plainObject.foo);\n      expect(TSON.parse(tson).bar.getTime()).toBeNaN();\n    });\n  });\n\n  describe('special numbers', () => {\n    test('stringifies NaN with exact output', () => {\n      const tson = TSON.stringify(NaN);\n      expect(tson).toBe(JSON.stringify({ $t: 'number', v: 'NaN' }));\n      expect(Number.isNaN(TSON.parse(tson))).toBe(true);\n    });\n\n    test('stringifies Infinity with exact output', () => {\n      const tson = TSON.stringify(Infinity);\n      expect(tson).toBe(JSON.stringify({ $t: 'number', v: 'Infinity' }));\n      expect(TSON.parse(tson)).toBe(Infinity);\n    });\n\n    test('stringifies -Infinity with exact output', () => {\n      const tson = TSON.stringify(-Infinity);\n      expect(tson).toBe(JSON.stringify({ $t: 'number', v: '-Infinity' }));\n      expect(TSON.parse(tson)).toBe(-Infinity);\n    });\n  });\n\n  describe('BigInt', () => {\n    test('round-trips BigInt values', () => {\n      const bigint = BigInt('9007199254740993'); // Larger than MAX_SAFE_INTEGER\n      const json = TSON.stringify(bigint);\n      const result = TSON.parse(json);\n      expect(result).toBe(bigint);\n    });\n\n    test('handles negative BigInt', () => {\n      const bigint = BigInt('-123456789012345678901234567890');\n      expect(TSON.parse(TSON.stringify(bigint))).toBe(bigint);\n    });\n\n    test('handles array of BigInts including special values', () => {\n      const golem96 =\n        (BigInt(0xabcd_1234) << BigInt(64)) |\n        (BigInt(0x0000_9876) << BigInt(32)) |\n        BigInt(0x8888_7776);\n      const input = [BigInt(0), BigInt(1), BigInt(-1), BigInt(16), golem96];\n      const tson = TSON.stringify(input);\n      const back: typeof input = TSON.parse(tson);\n      expect(back[0]).toBe(BigInt(0));\n      expect(back[1]).toBe(BigInt(1));\n      expect(back[2]).toBe(BigInt(-1));\n      expect(back[3]).toBe(BigInt(16));\n      expect(back[4]).toBe(golem96);\n      expect(tson).toBe(\n        JSON.stringify([\n          { $t: 'bigint', v: '0' },\n          { $t: 'bigint', v: '1' },\n          { $t: 'bigint', v: '-1' },\n          { $t: 'bigint', v: '16' },\n          { $t: 'bigint', v: '53169852434298556854127064950' },\n        ]),\n      );\n    });\n  });\n\n  describe('Set', () => {\n    test('round-trips Set with primitives', () => {\n      const set = new Set([1, 2, 3, 'a', 'b']);\n      const result = TSON.parse(TSON.stringify(set));\n      expect(result).toBeInstanceOf(Set);\n      expect(result.size).toBe(5);\n      expect(result.has(1)).toBe(true);\n      expect(result.has('a')).toBe(true);\n    });\n\n    test('round-trips empty Set', () => {\n      const set = new Set();\n      const result = TSON.parse(TSON.stringify(set));\n      expect(result).toBeInstanceOf(Set);\n      expect(result.size).toBe(0);\n    });\n\n    test('round-trips Set with objects', () => {\n      const obj1 = { id: 1 };\n      const obj2 = { id: 2 };\n      const set = new Set([obj1, obj2]);\n      const result = TSON.parse(TSON.stringify(set));\n      expect(result).toBeInstanceOf(Set);\n      expect(result.size).toBe(2);\n      const values = Array.from(result);\n      expect(values[0]).toEqual({ id: 1 });\n      expect(values[1]).toEqual({ id: 2 });\n    });\n  });\n\n  describe('Map', () => {\n    test('round-trips Map with string keys', () => {\n      const map = new Map([\n        ['key1', 'value1'],\n        ['key2', 'value2'],\n      ]);\n      const result = TSON.parse(TSON.stringify(map));\n      expect(result).toBeInstanceOf(Map);\n      expect(result.get('key1')).toBe('value1');\n      expect(result.get('key2')).toBe('value2');\n    });\n\n    test('round-trips Map with object keys', () => {\n      const key1 = { id: 1 };\n      const key2 = { id: 2 };\n      const map = new Map([\n        [key1, 'first'],\n        [key2, 'second'],\n      ]);\n      const result = TSON.parse(TSON.stringify(map));\n      expect(result).toBeInstanceOf(Map);\n      expect(result.size).toBe(2);\n    });\n\n    test('round-trips empty Map', () => {\n      const map = new Map();\n      const result = TSON.parse(TSON.stringify(map));\n      expect(result).toBeInstanceOf(Map);\n      expect(result.size).toBe(0);\n    });\n\n    test('handles Map with nested special types', () => {\n      const m = new Map<string, { foo: Array<number> }>();\n      m.set(\"foo\", { foo: [1, 2, 3, Infinity] });\n      const tson = TSON.stringify(m);\n      expect(tson).toBe(\n        JSON.stringify({\n          $t: \"Map\",\n          v: [[\"foo\", { foo: [1, 2, 3, { $t: \"number\", v: \"Infinity\" }] }]],\n        }),\n      );\n      expect(TSON.parse(tson)).toStrictEqual(m);\n    });\n  });\n\n  describe('TypedArrays', () => {\n    test('round-trips Uint8Array', () => {\n      const arr = new Uint8Array([1, 2, 3, 255, 0]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(Uint8Array);\n      expect(Array.from(result)).toEqual([1, 2, 3, 255, 0]);\n    });\n\n    test('round-trips Int8Array', () => {\n      const arr = new Int8Array([-128, -1, 0, 1, 127]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(Int8Array);\n      expect(Array.from(result)).toEqual([-128, -1, 0, 1, 127]);\n    });\n\n    test('round-trips Int16Array', () => {\n      const arr = new Int16Array([-32768, 0, 32767]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(Int16Array);\n      expect(Array.from(result)).toEqual([-32768, 0, 32767]);\n    });\n\n    test('round-trips Uint16Array', () => {\n      const arr = new Uint16Array([0, 65535, 12345]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(Uint16Array);\n      expect(Array.from(result)).toEqual([0, 65535, 12345]);\n    });\n\n    test('round-trips Int32Array', () => {\n      const arr = new Int32Array([-2147483648, 0, 2147483647]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(Int32Array);\n      expect(Array.from(result)).toEqual([-2147483648, 0, 2147483647]);\n    });\n\n    test('round-trips Uint32Array', () => {\n      const arr = new Uint32Array([0, 4294967295]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(Uint32Array);\n      expect(Array.from(result)).toEqual([0, 4294967295]);\n    });\n\n    test('round-trips Float32Array', () => {\n      const arr = new Float32Array([1.5, -2.5, 0, 3.14159]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(Float32Array);\n      // Float32 has limited precision\n      expect(result[0]).toBeCloseTo(1.5);\n      expect(result[1]).toBeCloseTo(-2.5);\n      expect(result[2]).toBe(0);\n    });\n\n    test('round-trips Float64Array', () => {\n      const arr = new Float64Array([Math.PI, Math.E, Number.MAX_VALUE]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(Float64Array);\n      expect(result[0]).toBe(Math.PI);\n      expect(result[1]).toBe(Math.E);\n      expect(result[2]).toBe(Number.MAX_VALUE);\n    });\n\n    test('round-trips Float64Array with specific value', () => {\n      const fa = new Float64Array([19.6]);\n      const fa2 = new Float64Array([19.6]);\n      const tson = TSON.stringify(fa);\n      const back = TSON.parse(tson);\n      expect(back).toStrictEqual(fa2);\n    });\n\n    test('round-trips BigInt64Array', () => {\n      const arr = new BigInt64Array([BigInt('-9223372036854775808'), BigInt(0), BigInt('9223372036854775807')]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(BigInt64Array);\n      expect(result[0]).toBe(BigInt('-9223372036854775808'));\n      expect(result[2]).toBe(BigInt('9223372036854775807'));\n    });\n\n    test('round-trips BigUint64Array', () => {\n      const arr = new BigUint64Array([BigInt(0), BigInt('18446744073709551615')]);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(BigUint64Array);\n      expect(result[0]).toBe(BigInt(0));\n      expect(result[1]).toBe(BigInt('18446744073709551615'));\n    });\n\n    test('round-trips empty TypedArray', () => {\n      const arr = new Uint8Array(0);\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result).toBeInstanceOf(Uint8Array);\n      expect(result.length).toBe(0);\n    });\n  });\n\n  describe('ArrayBuffer', () => {\n    test('round-trips ArrayBuffer', () => {\n      const buffer = new ArrayBuffer(8);\n      const view = new Uint8Array(buffer);\n      view.set([1, 2, 3, 4, 5, 6, 7, 8]);\n\n      const result = TSON.parse(TSON.stringify(buffer));\n      expect(result).toBeInstanceOf(ArrayBuffer);\n      expect(new Uint8Array(result)).toEqual(view);\n    });\n\n    test('round-trips empty ArrayBuffer', () => {\n      const buffer = new ArrayBuffer(0);\n      const result = TSON.parse(TSON.stringify(buffer));\n      expect(result).toBeInstanceOf(ArrayBuffer);\n      expect(result.byteLength).toBe(0);\n    });\n  });\n\n  describe('complex nested structures', () => {\n    test('handles deeply nested objects', () => {\n      const obj = {\n        level1: {\n          level2: {\n            level3: {\n              value: 'deep',\n              date: new Date('2024-01-01'),\n              set: new Set([1, 2, 3]),\n            },\n          },\n        },\n      };\n      const result = TSON.parse(TSON.stringify(obj));\n      expect(result.level1.level2.level3.value).toBe('deep');\n      expect(result.level1.level2.level3.date).toBeInstanceOf(Date);\n      expect(result.level1.level2.level3.set).toBeInstanceOf(Set);\n    });\n\n    test('handles arrays with mixed types', () => {\n      const arr = [\n        1,\n        'string',\n        new Date('2024-01-01'),\n        new Set([1, 2]),\n        new Uint8Array([1, 2, 3]),\n        { nested: true },\n      ];\n      const result = TSON.parse(TSON.stringify(arr));\n      expect(result[0]).toBe(1);\n      expect(result[1]).toBe('string');\n      expect(result[2]).toBeInstanceOf(Date);\n      expect(result[3]).toBeInstanceOf(Set);\n      expect(result[4]).toBeInstanceOf(Uint8Array);\n      expect(result[5]).toEqual({ nested: true });\n    });\n\n    test('handles object with TypedArray properties', () => {\n      const obj = {\n        id: 'test',\n        data: new Uint8Array([1, 2, 3, 4]),\n        metadata: {\n          floats: new Float64Array([1.1, 2.2, 3.3]),\n        },\n      };\n      const result = TSON.parse(TSON.stringify(obj));\n      expect(result.id).toBe('test');\n      expect(result.data).toBeInstanceOf(Uint8Array);\n      expect(result.metadata.floats).toBeInstanceOf(Float64Array);\n    });\n  });\n\n  describe('TSON transparency (TSON through TSON)', () => {\n    // TSON must be 100% transparent: a POJO that happens to have a $t property\n    // matching a registered type must survive a TSON round-trip as a plain object,\n    // not be revived as that type. This is critical for blob offloading where the\n    // server stores BlobRefs as {$t: \"Uint8Array\", ref: \"...\", size: ...} and\n    // those POJOs must pass through TSON unchanged.\n\n    test('POJO with $t: \"Date\" is NOT revived as Date', () => {\n      const input = { $t: 'Date', foo: 'bar' };\n      const result = TSON.parse(TSON.stringify(input));\n      expect(result).toStrictEqual(input);\n      expect(result).not.toBeInstanceOf(Date);\n    });\n\n    test('POJO with $t: \"ArrayBuffer\" is NOT revived as ArrayBuffer', () => {\n      const input = { $t: 'ArrayBuffer', ref: 'abc123', size: 1024 };\n      const result = TSON.parse(TSON.stringify(input));\n      expect(result).toStrictEqual(input);\n      expect(result).not.toBeInstanceOf(ArrayBuffer);\n    });\n\n    test('POJO with $t: \"Uint8Array\" (BlobRef format) is NOT revived as Uint8Array', () => {\n      const input = { $t: 'Uint8Array', ref: '1:abc123', size: 4096 };\n      const result = TSON.parse(TSON.stringify(input));\n      expect(result).toStrictEqual(input);\n      expect(result).not.toBeInstanceOf(Uint8Array);\n    });\n\n    test('POJO with $t: \"Blob\" (BlobRef format) is NOT revived as Blob', () => {\n      const input = { $t: 'Blob', ref: '1:abc123', size: 8192, ct: 'image/png' };\n      const result = TSON.parse(TSON.stringify(input));\n      expect(result).toStrictEqual(input);\n    });\n\n    test('object containing BlobRef survives TSON round-trip unchanged', () => {\n      const input = {\n        id: 'doc1',\n        name: 'photo',\n        image: { $t: 'Uint8Array', ref: '1:blobid', size: 65536 },\n        $hasBlobRefs: 1,\n      };\n      const result = TSON.parse(TSON.stringify(input));\n      expect(result).toStrictEqual(input);\n      expect(result.image).not.toBeInstanceOf(Uint8Array);\n      expect(result.$hasBlobRefs).toBe(1);\n    });\n\n    test('running TSON.stringify output through TSON.parse again yields same result', () => {\n      // A real Uint8Array serialized by TSON, then passed through TSON again as a POJO\n      const arr = new Uint8Array([1, 2, 3]);\n      const tson1 = TSON.stringify(arr); // produces {\"$t\":\"Uint8Array\",\"$v\":\"AQID\"}\n      const pojo = JSON.parse(tson1);     // plain POJO: {$t: \"Uint8Array\", $v: \"AQID\"}\n      // Now this POJO goes through TSON (simulating server→client scenario)\n      const tson2 = TSON.stringify(pojo);\n      const result = TSON.parse(tson2);\n      // Must come back as the same POJO, not a Uint8Array\n      expect(result).toStrictEqual(pojo);\n      expect(result).not.toBeInstanceOf(Uint8Array);\n    });\n  });\n\n  describe('edge cases', () => {\n    test('handles Infinity and NaN in objects', () => {\n      const obj = {\n        inf: Infinity,\n        negInf: -Infinity,\n        nan: NaN,\n      };\n      const result = TSON.parse(TSON.stringify(obj));\n      expect(result.inf).toBe(Infinity);\n      expect(result.negInf).toBe(-Infinity);\n      expect(Number.isNaN(result.nan)).toBe(true);\n    });\n\n    test('handles empty objects and arrays', () => {\n      expect(TSON.parse(TSON.stringify({}))).toEqual({});\n      expect(TSON.parse(TSON.stringify([]))).toEqual([]);\n    });\n  });\n});\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/__tests__/encoding.test.ts",
    "content": "import { b64encode, b64decode } from '../../common/base64';\nimport { b64LexEncode, b64LexDecode } from '../../common/b64lex';\nimport { buf2bigint, bigint2Buf, bigint2B64, b64ToBigInt } from '../../common/bigint-conversion';\n\ndescribe('base64', () => {\n  describe('b64encode/b64decode', () => {\n    test('round-trips empty buffer', () => {\n      const input = new Uint8Array(0);\n      const encoded = b64encode(input);\n      const decoded = b64decode(encoded);\n      expect(decoded.length).toBe(0);\n    });\n\n    test('round-trips simple data', () => {\n      const input = new Uint8Array([1, 2, 3, 4, 5]);\n      const encoded = b64encode(input);\n      const decoded = b64decode(encoded);\n      expect(Array.from(decoded)).toEqual([1, 2, 3, 4, 5]);\n    });\n\n    test('round-trips all byte values', () => {\n      const input = new Uint8Array(256);\n      for (let i = 0; i < 256; i++) input[i] = i;\n      const encoded = b64encode(input);\n      const decoded = b64decode(encoded);\n      expect(Array.from(decoded)).toEqual(Array.from(input));\n    });\n\n    test('handles ArrayBuffer input', () => {\n      const buffer = new ArrayBuffer(4);\n      const view = new Uint8Array(buffer);\n      view.set([10, 20, 30, 40]);\n      const encoded = b64encode(buffer);\n      const decoded = b64decode(encoded);\n      expect(Array.from(decoded)).toEqual([10, 20, 30, 40]);\n    });\n  });\n});\n\ndescribe('b64lex', () => {\n  describe('b64LexEncode/b64LexDecode', () => {\n    test('round-trips empty buffer', () => {\n      const input = new Uint8Array(0);\n      const encoded = b64LexEncode(input);\n      const decoded = b64LexDecode(encoded);\n      expect(decoded.length).toBe(0);\n    });\n\n    test('round-trips simple data', () => {\n      const input = new Uint8Array([1, 2, 3, 4, 5]);\n      const encoded = b64LexEncode(input);\n      const decoded = b64LexDecode(encoded);\n      expect(Array.from(decoded)).toEqual([1, 2, 3, 4, 5]);\n    });\n\n    test('produces lexicographically sortable output', () => {\n      // Lower values should produce lexicographically smaller strings\n      const small = new Uint8Array([0, 0, 0, 1]);\n      const large = new Uint8Array([0, 0, 0, 2]);\n      const smallEncoded = b64LexEncode(small);\n      const largeEncoded = b64LexEncode(large);\n      expect(smallEncoded < largeEncoded).toBe(true);\n    });\n\n    test('larger buffers sort after smaller with same prefix', () => {\n      const short = new Uint8Array([1, 2, 3]);\n      const long = new Uint8Array([1, 2, 3, 4]);\n      const shortEncoded = b64LexEncode(short);\n      const longEncoded = b64LexEncode(long);\n      // With b64lex, longer should come after shorter with same prefix\n      expect(shortEncoded < longEncoded).toBe(true);\n    });\n  });\n});\n\ndescribe('bigint-conversion', () => {\n  describe('buf2bigint', () => {\n    test('converts empty buffer to 0n', () => {\n      const buf = new Uint8Array(0);\n      expect(buf2bigint(buf)).toBe(BigInt(0));\n    });\n\n    test('converts single byte', () => {\n      expect(buf2bigint(new Uint8Array([0]))).toBe(BigInt(0));\n      expect(buf2bigint(new Uint8Array([1]))).toBe(BigInt(1));\n      expect(buf2bigint(new Uint8Array([255]))).toBe(BigInt(255));\n    });\n\n    test('converts multi-byte buffer (big-endian)', () => {\n      // 0x0102 = 258\n      expect(buf2bigint(new Uint8Array([1, 2]))).toBe(BigInt(258));\n      // 0x010203 = 66051\n      expect(buf2bigint(new Uint8Array([1, 2, 3]))).toBe(BigInt(66051));\n    });\n\n    test('handles ArrayBuffer input', () => {\n      const buf = new ArrayBuffer(2);\n      new Uint8Array(buf).set([1, 2]);\n      expect(buf2bigint(buf)).toBe(BigInt(258));\n    });\n\n    test('handles TypedArray views correctly', () => {\n      // Even though it's a Uint16Array, we iterate bytes\n      const u16 = new Uint16Array([0x0201]); // Little-endian: bytes are [1, 2]\n      const result = buf2bigint(u16);\n      // Should read bytes [1, 2] → 0x0102 = 258 (big-endian interpretation)\n      expect(result).toBe(BigInt(258));\n    });\n  });\n\n  describe('bigint2Buf', () => {\n    test('converts 0n to single zero byte', () => {\n      // Note: bigint2Buf(0n) produces [0], not empty array\n      // This is because hex representation of 0 is \"0\" which becomes one byte\n      const buf = bigint2Buf(BigInt(0));\n      expect(buf.length).toBe(1);\n      expect(buf[0]).toBe(0);\n    });\n\n    test('converts small values', () => {\n      expect(Array.from(bigint2Buf(BigInt(1)))).toEqual([1]);\n      expect(Array.from(bigint2Buf(BigInt(255)))).toEqual([255]);\n      expect(Array.from(bigint2Buf(BigInt(256)))).toEqual([1, 0]);\n    });\n\n    test('converts large values', () => {\n      // 0x123456789ABCDEF0\n      const bigint = BigInt('0x123456789ABCDEF0');\n      const buf = bigint2Buf(bigint);\n      expect(buf.length).toBe(8);\n      expect(buf[0]).toBe(0x12);\n      expect(buf[7]).toBe(0xF0);\n    });\n\n    test('throws on negative BigInt', () => {\n      expect(() => bigint2Buf(BigInt(-1))).toThrow(TypeError);\n    });\n  });\n\n  describe('bigint2B64 / b64ToBigInt round-trip', () => {\n    test('round-trips zero', () => {\n      const bi = BigInt(0);\n      expect(b64ToBigInt(bigint2B64(bi))).toBe(bi);\n    });\n\n    test('round-trips small values', () => {\n      for (const val of [1, 42, 255, 256, 65535]) {\n        const bi = BigInt(val);\n        expect(b64ToBigInt(bigint2B64(bi))).toBe(bi);\n      }\n    });\n\n    test('round-trips large values', () => {\n      const bi = BigInt('123456789012345678901234567890');\n      expect(b64ToBigInt(bigint2B64(bi))).toBe(bi);\n    });\n\n    test('round-trips MAX_SAFE_INTEGER + 1', () => {\n      const bi = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1);\n      expect(b64ToBigInt(bigint2B64(bi))).toBe(bi);\n    });\n  });\n});\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/__tests__/newId.test.ts",
    "content": "import { newId } from '../../newId';\n\ndescribe('newId', () => {\n  test('generates 24-character string', () => {\n    const id = newId();\n    expect(typeof id).toBe('string');\n    expect(id.length).toBe(24);\n  });\n\n  test('generates unique IDs', () => {\n    const ids = new Set<string>();\n    for (let i = 0; i < 1000; i++) {\n      ids.add(newId());\n    }\n    expect(ids.size).toBe(1000);\n  });\n\n  test('generates sortable IDs (later IDs sort after earlier)', () => {\n    const id1 = newId();\n    // Generate many IDs to ensure time progresses or sequence increments\n    for (let i = 0; i < 100; i++) newId();\n    const id2 = newId();\n    \n    expect(id1 < id2).toBe(true);\n  });\n\n  test('bulk-created IDs are still sorted', () => {\n    const ids: string[] = [];\n    for (let i = 0; i < 100; i++) {\n      ids.push(newId());\n    }\n    \n    const sorted = [...ids].sort();\n    expect(ids).toEqual(sorted);\n  });\n\n  test('IDs contain only b64lex safe characters', () => {\n    // b64lex uses: 0-9 A-Z _ a-z and | as separator\n    const validChars = /^[0-9A-Z_a-z|]+$/;\n    for (let i = 0; i < 100; i++) {\n      const id = newId();\n      expect(id).toMatch(validChars);\n    }\n  });\n});\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/__tests__/undefined.test.ts",
    "content": "import { TypesonSimplified } from '../TypesonSimplified';\nimport { builtInTypeDefs } from '../presets/builtin';\nimport { undefinedTypeDef } from '../types/undefined';\n\ndescribe('undefined type support', () => {\n  const TSON = TypesonSimplified({ ...builtInTypeDefs, ...undefinedTypeDef });\n\n  test('should stringify undefined', () => {\n    expect(TSON.stringify({ foo: null, bar: undefined })).toBe(\n      JSON.stringify({ foo: null, bar: { $t: \"undefined\" } }),\n    );\n  });\n\n  test('should revive undefined', () => {\n    const revived = TSON.parse(TSON.stringify({ foo: null, bar: undefined }));\n    expect(Object.keys(revived)).toStrictEqual([\"foo\", \"bar\"]);\n    expect(Object.values(revived)).toStrictEqual([null, undefined]);\n  });\n\n  test('should not leak undefined properties to children', () => {\n    const ORIG = {\n      foo: null,\n      undef1: undefined,\n      $undef1: undefined,\n      bar: {\n        baz: \"x\",\n        undef: undefined,\n        $undef: undefined,\n        $$undef: undefined,\n        $Gunnar: \"2\",\n        $t: undefined,\n      },\n      undef2: undefined,\n      $undef2: undefined,\n      $t: undefined,\n    };\n    const stringified = TSON.stringify(ORIG);\n    const revived = TSON.parse(stringified);\n    expect(revived).toStrictEqual(ORIG);\n  });\n\n  test('should revive sync request with undefined values', () => {\n    const REQUEST = {\n      schema: {\n        $jobs2: {\n          markedForSync: undefined,\n          initiallySynced: true,\n          generateGlobalId: undefined,\n        },\n        $jobs: {\n          markedForSync: undefined,\n          initiallySynced: true,\n          generateGlobalId: undefined,\n        },\n      },\n    };\n    const stringified = TSON.stringify(REQUEST);\n    const revived = TSON.parse(stringified);\n    expect(revived).toStrictEqual(REQUEST);\n  });\n});\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/index.ts",
    "content": "// TSON - Typed JSON serialization\n// Migrated from dreambase-library (2026-02-10)\n\nexport * from \"./TypesonSimplified.js\";\nexport * from \"./FakeBlob.js\";\nexport * from \"./FakeFile.js\";\nexport * from \"./TypeDef.js\";\nexport * from \"./TypeDefSet.js\";\nexport * from \"./TSONRef.js\";\nexport * from \"./StreamingSyncProcessor.js\";\nexport * from \"./readBlobSync.js\";\nexport * from \"./presets/builtin.js\";\nexport * from \"./types/index.js\";\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/presets/builtin.ts",
    "content": "import { TypeDefSet } from \"../TypeDefSet.js\";\nimport { numberTypeDef } from \"../types/number.js\";\nimport { bigintTypeDef } from \"../types/bigint.js\";\nimport { dateTypeDef } from \"../types/Date.js\";\nimport { setTypeDef } from \"../types/Set.js\";\nimport { mapTypeDef } from \"../types/Map.js\";\nimport { typedArrayTypeDefs } from \"../types/TypedArray.js\";\nimport { arrayBufferTypeDef } from \"../types/ArrayBuffer.js\";\nimport { blobTypeDef } from \"../types/Blob.js\";\n\nexport const builtInTypeDefs: TypeDefSet = {\n  ...numberTypeDef,\n  ...bigintTypeDef,\n  ...dateTypeDef,\n  ...setTypeDef,\n  ...mapTypeDef,\n  ...typedArrayTypeDefs,\n  ...arrayBufferTypeDef,\n  ...blobTypeDef, // Should be moved to another preset for DOM types (or universal? since it supports node as well with FakeBlob)\n};\n\n// Keep default export for backward compatibility\nexport default builtInTypeDefs;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/readBlobSync.ts",
    "content": "export function readBlobSync(b: Blob): string {\n  const req = new XMLHttpRequest();\n  req.overrideMimeType(\"text/plain; charset=x-user-defined\");\n  const url = URL.createObjectURL(b);\n  try {\n    req.open(\"GET\", url, false); // Sync\n    req.send();\n    if (req.status !== 200 && req.status !== 0) {\n      throw new Error(\"Bad Blob access: \" + req.status);\n    }\n    return req.responseText;\n  } finally {\n    URL.revokeObjectURL(url);\n  }\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/readableStreamIterator.ts",
    "content": "export async function* readableStreamIterator(\n  stream: ReadableStream<Uint8Array>\n): AsyncIterator<Blob | null, void, number> {\n  const chunks: (Blob | null)[] = [];\n  const reader = stream.getReader();\n\n  try {\n    let chunk = 0;\n    let posInChunk = 0;\n    let pos = 0;\n    let size = 0;\n    let done = false;\n    let chunkSize = yield null;\n\n    while (!done) {\n      while (bytesLeft() < chunkSize && !done) {\n        await fill();\n      }\n      yield read(chunkSize);\n      chunkSize = yield null;\n    }\n\n    async function fill() {\n      const x = await reader.read();\n      if (x.value) {\n        chunks.push(new Blob([x.value as BlobPart]));\n        size += x.value.length;\n      }\n      if (x.done) done = true;\n    }\n\n    function bytesLeft() {\n      return size - pos;\n    }\n\n    function read(num: number): Blob {\n      // Base case: reading 0 bytes returns empty Blob\n      if (num === 0) return new Blob([]);\n      if (bytesLeft() < num) throw new Error(`Tried to read too much`);\n      \n      const currentChunk = chunks[chunk];\n      if (!currentChunk) throw new Error(`Chunk ${chunk} is null or undefined`);\n      \n      const readableAmount = currentChunk.size - posInChunk;\n      if (num < readableAmount) {\n        const part = currentChunk.slice(posInChunk, posInChunk + num);\n        posInChunk += num;\n        pos += num;\n        return new Blob([part]);\n      } else {\n        const part = currentChunk.slice(posInChunk);\n        // Release consumed chunk for GC\n        chunks[chunk] = null;\n        ++chunk;\n        pos += readableAmount;\n        posInChunk = 0;\n        return new Blob([part, read(num - readableAmount)]);\n      }\n    }\n  } finally {\n    reader.releaseLock();\n  }\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/string2ArrayBuffer.ts",
    "content": "export function string2ArrayBuffer(str: string): ArrayBuffer {\n  const array = new Uint8Array(str.length);\n  for (let i = 0; i < str.length; ++i) {\n    array[i] = str.charCodeAt(i); // & 0xff;\n  }\n  return array.buffer;\n}\n\nexport function arrayBuffer2String(buf: ArrayBuffer): string {\n  // TODO: Optimize\n  return new Uint8Array(buf).reduce(\n    (s, byte) => s + String.fromCharCode(byte),\n    \"\"\n  );\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/ArrayBuffer.ts",
    "content": "import { b64LexDecode, b64LexEncode } from \"../../common/b64lex.js\";\n\nexport const arrayBufferTypeDef = {\n  ArrayBuffer: {\n    replace: (ab: ArrayBuffer) => ({\n      $t: \"ArrayBuffer\",\n      v: b64LexEncode(ab),\n    }),\n    revive: ({ v }: { v: string }): ArrayBuffer => {\n      const ba = b64LexDecode(v);\n      const buf = ba.buffer.byteLength === ba.byteLength\n        ? ba.buffer\n        : ba.buffer.slice(ba.byteOffset, ba.byteOffset + ba.byteLength);\n      return buf as ArrayBuffer;\n    },\n  },\n};\n\nexport default arrayBufferTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/Blob.ts",
    "content": "import { b64decode, b64encode } from \"../../common/base64.js\";\nimport { FakeBlob } from \"../FakeBlob.js\";\nimport { readBlobSync } from \"../readBlobSync.js\";\nimport { string2ArrayBuffer } from \"../string2ArrayBuffer.js\";\n\nexport const blobTypeDef = {\n  Blob: {\n    test: (blob: Blob | FakeBlob, toStringTag: string) =>\n      toStringTag === \"Blob\" || blob instanceof FakeBlob,\n    replace: (blob: Blob | FakeBlob) => ({\n      $t: \"Blob\",\n      v:\n        blob instanceof FakeBlob\n          ? b64encode(blob.buf)\n          : b64encode(string2ArrayBuffer(readBlobSync(blob))),\n      type: blob.type,\n    }),\n    revive: ({ type, v }: { type: string; v: string }) => {\n      const ab = b64decode(v);\n      const buf = ab.buffer.byteLength === ab.byteLength\n        ? (ab.buffer as ArrayBuffer)\n        : (ab.buffer as ArrayBuffer).slice(ab.byteOffset, ab.byteOffset + ab.byteLength);\n      return typeof Blob !== \"undefined\"\n        ? new Blob([new Uint8Array(buf)], { type })\n        : new FakeBlob(buf, type);\n    },\n  },\n};\n\nexport default blobTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/BlobRef.ts",
    "content": "/**\n * Blob Reference Support\n *\n * Provides context types and helpers for blob offloading during TSON serialization.\n * Large binary data (>= BLOB_THRESHOLD) can be stored separately and replaced\n * with a _bt reference object: { _bt: 'Uint8Array', ref: '1:blobId', size: 1234 }\n *\n * These references are plain POJOs — TSON does not revive them.\n * Resolution is handled by blobResolveMiddleware in dexie-cloud-addon.\n */\n\n/** Threshold for blob offloading (4KB) */\nexport const BLOB_THRESHOLD = 4 * 1024;\n\n/**\n * Interface for blob storage backend.\n */\nexport interface BlobStore {\n  /** Store blob and return its ID */\n  store(data: ArrayBuffer, contentType?: string): Promise<string> | string;\n}\n\n/**\n * Context passed via alternateChannel during serialization.\n */\nexport interface BlobRefContext {\n  /** Blob storage backend */\n  blobStore?: BlobStore;\n  /** Threshold override (default: BLOB_THRESHOLD) */\n  threshold?: number;\n  /** Force inline even for large data (for old clients) */\n  forceInline?: boolean;\n  /** Maximum inline size when forceInline is true (error if exceeded) */\n  maxInlineSize?: number;\n}\n\n/**\n * Create a blob ref context for serialization.\n */\nexport function createBlobRefContext(\n  blobStore: BlobStore,\n  options?: { threshold?: number }\n): BlobRefContext {\n  return {\n    blobStore,\n    threshold: options?.threshold ?? BLOB_THRESHOLD,\n  };\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/Date.ts",
    "content": "export const dateTypeDef = {\n  Date: {\n    replace: (date: Date) => ({\n      $t: \"Date\",\n      v: isNaN(date.getTime()) ? \"NaN\" : date.toISOString(),\n    }),\n    revive: ({ v }) => new Date(v === \"NaN\" ? NaN : Date.parse(v)),\n  },\n};\n\nexport default dateTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/FakeBlob.ts",
    "content": "import { b64decode, b64encode } from \"../../common/base64.js\";\nimport { FakeBlob } from \"../FakeBlob.js\";\n\nexport const fakeBlobTypeDef = {\n  Blob: {\n    test: (blob: FakeBlob) => blob instanceof FakeBlob,\n    replace: (blob: FakeBlob) => ({\n      $t: \"Blob\",\n      v: b64encode(blob.buf),\n      type: blob.type,\n    }),\n    revive: ({ type, v }: { type: string; v: string }) => {\n      const ba = b64decode(v);\n      // Handle Node.js Buffer's shared ArrayBuffer pool\n      const buf = ba.buffer.byteLength === ba.byteLength\n        ? (ba.buffer as ArrayBuffer)\n        : (ba.buffer as ArrayBuffer).slice(ba.byteOffset, ba.byteOffset + ba.byteLength);\n      return new FakeBlob(buf, type);\n    },\n  },\n};\n\nexport default fakeBlobTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/FakeFile.ts",
    "content": "import { b64decode, b64encode } from \"../../common/base64.js\";\nimport { FakeBlob } from \"../FakeBlob.js\";\nimport { FakeFile } from \"../FakeFile.js\";\n\nexport const fakeFileTypeDef = {\n  File: {\n    test: (file: FakeFile) => file instanceof FakeFile,\n    replace: (file: FakeFile) => ({\n      $t: \"File\",\n      v: b64encode(file.blob.buf),\n      type: file.blob.type,\n      name: file.name,\n      lastModified: file.lastModified.toISOString(),\n    }),\n    revive: ({ type, v, name, lastModified }: { type: string; v: string; name: string; lastModified: string }) => {\n      const ab = b64decode(v);\n      const buf = ab.buffer.byteLength === ab.byteLength\n        ? (ab.buffer as ArrayBuffer)\n        : (ab.buffer as ArrayBuffer).slice(ab.byteOffset, ab.byteOffset + ab.byteLength);\n      const blob = new FakeBlob(buf, type);\n      return new FakeFile(blob, name, new Date(lastModified));\n    },\n  },\n};\n\nexport default fakeFileTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/File.ts",
    "content": "import { b64decode, b64encode } from \"../../common/base64.js\";\nimport { readBlobSync } from \"../readBlobSync.js\";\nimport { string2ArrayBuffer } from \"../string2ArrayBuffer.js\";\n\nexport const fileTypeDef = {\n  File: {\n    test: (file: File, toStringTag: string) => toStringTag === \"File\",\n    replace: (file: File) => ({\n      $t: \"File\",\n      v: b64encode(string2ArrayBuffer(readBlobSync(file))),\n      type: file.type,\n      name: file.name,\n      lastModified: new Date(file.lastModified).toISOString(),\n    }),\n    revive: ({ type, v, name, lastModified }: { type: string; v: string; name: string; lastModified: string }) => {\n      const ab = b64decode(v);\n      const buf = ab.buffer.byteLength === ab.byteLength\n        ? (ab.buffer as ArrayBuffer)\n        : (ab.buffer as ArrayBuffer).slice(ab.byteOffset, ab.byteOffset + ab.byteLength);\n      return new File([new Uint8Array(buf)], name, {\n        type,\n        lastModified: new Date(lastModified).getTime(),\n      });\n    },\n  },\n};\n\nexport default fileTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/Map.ts",
    "content": "export const mapTypeDef = {\n  Map: {\n    replace: (map: Map<any, any>) => ({\n      $t: \"Map\",\n      v: Array.from(map.entries()),\n    }),\n    revive: ({ v }) => new Map(v),\n  },\n};\n\nexport default mapTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/Set.ts",
    "content": "export const setTypeDef = {\n  Set: {\n    replace: (set: Set<any>) => ({\n      $t: \"Set\",\n      v: Array.from(set),\n    }),\n    revive: ({ v }) => new Set(v),\n  },\n};\n\nexport default setTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/TypedArray.ts",
    "content": "import { _global } from \"../../common/_global.js\";\nimport { TypeDef } from \"../TypeDef.js\";\n\nexport const typedArrayTypeDefs = [\n  \"Int8Array\",\n  \"Uint8Array\",\n  \"Uint8ClampedArray\",\n  \"Int16Array\",\n  \"Uint16Array\",\n  \"Int32Array\",\n  \"Uint32Array\",\n  \"Float32Array\",\n  \"Float64Array\",\n  \"DataView\",\n  \"BigInt64Array\",\n  \"BigUint64Array\",\n].reduce(\n  (specs, typeName) => ({\n    ...specs,\n    [typeName]: {\n      // Replace passes the typed array into $t, buffer so that\n      // the ArrayBuffer typedef takes care of further handling of the buffer:\n      // {$t:\"Uint8Array\",buffer:{$t:\"ArrayBuffer\",idx:0}}\n      // CHANGED ABOVE! Now shortcutting that for more sparse format of the typed arrays\n      // to contain the b64 property directly.\n      replace: (\n        a: ArrayBufferView,\n        _: any,\n        typeDefs: { ArrayBuffer: TypeDef<ArrayBuffer, { v: string }> },\n      ) => {\n        const buffer = a.buffer as ArrayBuffer;\n        const slicedBuffer = a.byteOffset === 0 && a.byteLength === buffer.byteLength\n          ? buffer\n          : buffer.slice(a.byteOffset, a.byteOffset + a.byteLength);\n        const result = {\n          $t: typeName,\n          v: typeDefs.ArrayBuffer.replace(slicedBuffer, _, typeDefs as any).v,\n        };\n        return result;\n      },\n      revive: (\n        { v }: { v: string },\n        _: any,\n        typeDefs: { ArrayBuffer: TypeDef<ArrayBuffer, { v: string }> },\n      ) => {\n        const TypedArray = _global[typeName];\n        return (\n          TypedArray &&\n          new TypedArray(typeDefs.ArrayBuffer.revive({ v }, _, typeDefs as any))\n        );\n      },\n    },\n  }),\n  {},\n);\n\nexport default typedArrayTypeDefs;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/bigint.ts",
    "content": "import { TypeDefSet } from \"../TypeDefSet.js\";\n\nexport const bigintTypeDef: TypeDefSet = {\n  bigint: {\n    replace: (realVal: bigint) => {\n      return { $t: \"bigint\", v: \"\" + realVal };\n    },\n    revive: (obj: { $t: \"bigint\"; v: string }) => BigInt(obj.v),\n  },\n};\n\nexport default bigintTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/index.ts",
    "content": "// Type definitions barrel export\nexport { arrayBufferTypeDef } from \"./ArrayBuffer.js\";\nexport { bigintTypeDef } from \"./bigint.js\";\nexport { blobTypeDef } from \"./Blob.js\";\nexport { dateTypeDef } from \"./Date.js\";\nexport { fakeBlobTypeDef } from \"./FakeBlob.js\";\nexport { fakeFileTypeDef } from \"./FakeFile.js\";\nexport { fileTypeDef } from \"./File.js\";\nexport { mapTypeDef } from \"./Map.js\";\nexport { numberTypeDef } from \"./number.js\";\nexport { setTypeDef } from \"./Set.js\";\nexport { typedArrayTypeDefs } from \"./TypedArray.js\";\nexport { undefinedTypeDef } from \"./undefined.js\";\n\n// Blob reference context (for blob offloading serialization)\nexport {\n  BLOB_THRESHOLD,\n  BlobStore,\n  BlobRefContext,\n  createBlobRefContext,\n} from \"./BlobRef.js\";\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/number.ts",
    "content": "export const numberTypeDef = {\n  number: {\n    replace: (num: number) => {\n      switch (true) {\n        case isNaN(num):\n          return { $t: \"number\", v: \"NaN\" };\n        case num === Infinity:\n          return { $t: \"number\", v: \"Infinity\" };\n        case num === -Infinity:\n          return { $t: \"number\", v: \"-Infinity\" };\n        default:\n          return num;\n      }\n    },\n    revive: ({ v }) => Number(v),\n  },\n};\n\nexport default numberTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/tson/types/undefined.ts",
    "content": "/** The undefined type is not part of builtin but can be manually added.\n * The reason for supporting undefined is if the following object should be revived correctly:\n *\n *    {foo: undefined}\n *\n * Without including this typedef, the revived object would just be {}.\n * If including this typedef, the revived object would be {foo: undefined}.\n */\n\nexport const undefinedTypeDef = {\n  undefined: {\n    replace: () => ({\n      $t: \"undefined\",\n    }),\n    revive: () => undefined,\n  },\n};\n\nexport default undefinedTypeDef;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/types.ts",
    "content": "import { AuthorizationCodeTokenRequest } from './AuthorizationCodeTokenRequest.js';\n\nexport type TokenRequest =\n  | OTPTokenRequest1\n  | OTPTokenRequest2\n  | ClientCredentialsTokenRequest\n  | RefreshTokenRequest\n  | DemoTokenRequest\n  | AuthorizationCodeTokenRequest;\n\nexport type OTPTokenRequest = OTPTokenRequest1 | OTPTokenRequest2;\nexport interface OTPTokenRequest1 {\n  grant_type: 'otp';\n  email: string;\n  scopes: string[]; // TODO use CLIENT_SCOPE type.\n}\n\nexport interface OTPTokenRequest2 {\n  grant_type: 'otp';\n  public_key?: string;\n  scopes: string[]; // TODO use CLIENT_SCOPE type.\n  otp_id: string;\n  otp: string;\n}\n\nexport interface ClientCredentialsTokenRequest {\n  grant_type: 'client_credentials';\n  client_id: string;\n  client_secret: string;\n  public_key?: string; // If a refresh token is requested. The web client calling the server-client should own corresponding private key.\n  scopes: string[]; // TODO: Move TOKEN_SCOPE type to this lib and use it instead.\n  claims?: {\n    sub: string;\n    email?: string;\n    email_verified?: string;\n    [customClaim: string]: any;\n  };\n  expires_in?: string; // timespan string compatible with https://github.com/vercel/ms\n  not_before?: string; // timespan string compatible with https://github.com/vercel/ms\n}\n\nexport interface RefreshTokenRequest {\n  grant_type: 'refresh_token';\n  scopes: string[]; // TODO use CLIENT_SCOPE type.\n  public_key?: string; // Optional. Makes it possible to renew keypair. Given signature must still be generated using the old keypair.\n  refresh_token: string;\n  time_stamp: number;\n  signing_algorithm: string; // \"RSA256\"\n  signature: string; // Base64 signature of (refresh_token + time_stamp) using the private key that corresponds to the public_key sent when retrieving the refresh_token.\n}\n\nexport interface DemoTokenRequest {\n  grant_type: 'demo';\n  scopes: ['ACCESS_DB'];\n  demo_user: string; // Email of a demo user that must have been added using the dexie cloud CLI.\n  public_key?: string;\n}\n\nexport interface TokenFinalResponse {\n  type: 'tokens';\n  claims: {\n    sub: string;\n    license?: 'ok' | 'expired' | 'deactivated';\n    [claimName: string]: any;\n  };\n  accessToken: string;\n  accessTokenExpiration: number;\n  refreshToken?: string;\n  refreshTokenExpiration?: number | null;\n  userType: 'demo' | 'eval' | 'prod' | 'client';\n  evalDaysLeft?: number;\n  userValidUntil?: number;\n  alerts?: {\n    type: 'warning' | 'info';\n    messageCode: string;\n    message: string;\n    messageParams?: { [param: string]: string };\n  }[];\n  data?: any;\n}\n\nexport interface TokenOtpSentResponse {\n  type: 'otp-sent';\n  otp_id: string;\n}\nexport interface TokenOtpDetailsResponse {\n  type: 'otp-details';\n  otp: string;\n  otp_id: string;\n  data?: any;\n}\n\nexport interface TokenErrorResponse {\n  type: 'error';\n  title: string;\n  messageCode:\n    | 'INVALID_OTP'\n    | 'INVALID_EMAIL'\n    | 'LICENSE_LIMIT_REACHED'\n    | 'GENERIC_ERROR';\n  message: string;\n  messageParams?: { [param: string]: string };\n}\n\n/** Can be returned when grant_type=\"refresh_token\" if the given time_stamp differs too much\n * from server time. Will happen if the client's clock differs too much from the server.\n * Client should then retry the token request using grant_type=\"refresh_token\" again but\n * regenerate the signature from (server_time + refresh_token).\n */\nexport interface TokenResponseInvalidTimestamp {\n  type: 'invalid-timestamp';\n  server_time: number; // Allows client to adjust its timestamp by diffing server time with client and redo refresh_token request.\n  message: string;\n}\n\nexport type TokenResponse =\n  | TokenFinalResponse\n  | TokenOtpSentResponse\n  | TokenOtpDetailsResponse\n  | TokenErrorResponse\n  | TokenResponseInvalidTimestamp;\n  \nexport interface CreateDbResponse {\n  url: string;\n  clientId: string;\n  clientSecret: string;\n}\n\nexport interface WhitelistRequest {\n  origin?: string;\n  delete?: boolean;\n}\n\nexport type WhitelistResponse = number | string[];\n\nexport type ClientsResponse = DXCClient[];\n\nexport interface DXCClient {\n  id: string;\n  email: string;\n  emailVerified: boolean;\n  scopes: string[]; // TODO: Use the CLIENT_SCOPE[] type.\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/typings/TypedArray.ts",
    "content": "export type TypedArray =\n  | Int8Array\n  | Uint8Array\n  | Uint8ClampedArray\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Float32Array\n  | Float64Array\n  | BigInt64Array\n  | BigUint64Array;\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/utils.ts",
    "content": "export function assert(b: boolean): asserts b is true {\n  if (!b) throw new Error('Assertion Failed');\n}\n\nconst _hasOwn = {}.hasOwnProperty;\nexport function hasOwn(obj, prop) {\n    return _hasOwn.call(obj, prop);\n}\n\ntype SetByKeyPathTarget =\n  | { [keyPath: string]: SetByKeyPathTarget }\n  | SetByKeyPathTarget[];\n\nexport function setByKeyPath(\n  obj: SetByKeyPathTarget,\n  keyPath: string | ArrayLike<string>,\n  value: any\n) {\n  if (!obj || keyPath === undefined) return;\n  if ('isFrozen' in Object && Object.isFrozen(obj)) return;\n  if (typeof keyPath !== 'string' && 'length' in keyPath) {\n    assert(typeof value !== 'string' && 'length' in value);\n    for (var i = 0, l = keyPath.length; i < l; ++i) {\n      setByKeyPath(obj, keyPath[i], value[i]);\n    }\n  } else {\n    var period = keyPath.indexOf('.');\n    if (period !== -1) {\n      var currentKeyPath = keyPath.substr(0, period);\n      var remainingKeyPath = keyPath.substr(period + 1);\n      if (remainingKeyPath === '')\n        if (value === undefined) {\n          if (Array.isArray(obj)) {\n            if (!isNaN(parseInt(currentKeyPath)))\n              obj.splice(parseInt(currentKeyPath), 1);\n          } else delete obj[currentKeyPath];\n          // @ts-ignore: even if currentKeyPath would be numeric string and obj would be array - it works.\n        } else obj[currentKeyPath] = value;\n      else {\n        //@ts-ignore: even if currentKeyPath would be numeric string and obj would be array - it works.\n        var innerObj = obj[currentKeyPath];\n        //@ts-ignore: even if currentKeyPath would be numeric string and obj would be array - it works.\n        if (!innerObj || !hasOwn(obj, currentKeyPath)) innerObj = (obj[currentKeyPath] = {});\n        setByKeyPath(innerObj, remainingKeyPath, value);\n      }\n    } else {\n      if (value === undefined) {\n        if (Array.isArray(obj) && !isNaN(parseInt(keyPath)))\n          // @ts-ignore: even if currentKeyPath would be numeric string and obj would be array - it works.\n          obj.splice(keyPath, 1);\n          //@ts-ignore: even if currentKeyPath would be numeric string and obj would be array - it works.\n        else delete obj[keyPath];\n        //@ts-ignore: even if currentKeyPath would be numeric string and obj would be array - it works.\n      } else obj[keyPath] = value;\n    }\n  }\n}\n\nexport const randomString = typeof self !== 'undefined' && typeof crypto !== 'undefined' ? (bytes: number, randomFill: ((buf: Uint8Array) => void)=crypto.getRandomValues.bind(crypto)) => {\n  // Web\n  const buf = new Uint8Array(bytes);\n  randomFill(buf);\n  return self.btoa(String.fromCharCode.apply(null, buf as any));\n} : typeof Buffer !== 'undefined' ? (bytes: number, randomFill:((buf: Uint8Array) => void)=simpleRandomFill) => {\n  // Node\n  const buf = Buffer.alloc(bytes);\n  randomFill(buf);\n  return buf.toString(\"base64\");\n} : ()=>{throw new Error(\"No implementation of randomString was found\");}\n\nfunction simpleRandomFill(buf: Uint8Array) {\n  for (let i=0; i<buf.length; ++i) {\n    buf[i] = Math.floor(Math.random() * 256);\n  }\n}"
  },
  {
    "path": "libs/dexie-cloud-common/src/validation/isValidSyncableID.ts",
    "content": "import { toStringTag } from \"./toStringTag.js\";\n\nconst validIDTypes = {\n  Uint8Array,\n};\n\n/** Verifies that given primary key is valid.\n * The reason we narrow validity for valid keys are twofold:\n *  1: Make sure to only support types that can be used as an object index in DBKeyMutationSet.\n *     For example, ArrayBuffer cannot be used (gives \"object ArrayBuffer\") but Uint8Array can be\n *     used (gives comma-delimited list of included bytes).\n *  2: Avoid using plain numbers and Dates as keys when they are synced, as they are not globally unique.\n *  3: Since we store the key as a VARCHAR server side in current version, try not promote types that stringifies to become very long server side.\n *\n * @param id\n * @returns\n */\nexport function isValidSyncableID(id: any) {\n  if (typeof id === \"string\") return true;\n  //if (validIDTypes[toStringTag(id)]) return true;\n  //if (Array.isArray(id)) return id.every((part) => isValidSyncableID(part));\n  if (Array.isArray(id) && id.some(key => isValidSyncableID(key)) && id.every(isValidSyncableIDPart)) return true;\n  return false;\n}\n\n\n/** Verifies that given key part is valid.\n *  1: Make sure that arrays of this types are stringified correclty and works with DBKeyMutationSet.\n *     For example, ArrayBuffer cannot be used (gives \"object ArrayBuffer\") but Uint8Array can be\n *     used (gives comma-delimited list of included bytes).\n *  2: Since we store the key as a VARCHAR server side in current version, try not promote types that stringifies to become very long server side.\n*/\nfunction isValidSyncableIDPart(part: any) {\n  return typeof part === \"string\" || typeof part === \"number\" || Array.isArray(part) && part.every(isValidSyncableIDPart);\n}\n\nexport function isValidAtID(id: any, idPrefix?: string): id is string {\n  return !idPrefix || (typeof id === \"string\" && id.startsWith(idPrefix));\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/validation/toStringTag.ts",
    "content": "const { toString } = {};\nexport function toStringTag(o: Object) {\n  return toString.call(o).slice(8, -1);\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/yjs/YMessage.ts",
    "content": "export type YMessage = YClientMessage | YServerMessage;\n\nexport type YClientMessage =\n  | YUpdateFromClientRequest\n  | YStateVector\n  | YDocumentOpen\n  | YAwarenessUpdate\n  | YDocumentClose;\n\nexport type YServerMessage =\n  | YUpdateFromClientAck\n  | YUpdateFromClientReject\n  | YUpdateFromServerMessage\n  | YAwarenessUpdate\n  | YInSyncMessage\n  | YOutdatedServerRevNotice\n  | YCompleteSyncDone\n  | YDocumentOpen; // When server want's the client to send a document open message.\n\nexport interface YUpdateFromClientRequest {\n  type: 'u-c';\n  table: string;\n  prop: string;\n  k: any;\n  u: Uint8Array;\n  i: number;\n}\n\nexport interface YDocumentOpen {\n  type: 'doc-open';\n  table: string;\n  prop: string;\n  k: any;\n  serverRev?: string; // The server revision of what client has received in last sync. User to query changes since this revision.\n  sv?: Uint8Array;\n}\n\nexport interface YStateVector {\n  type: 'sv';\n  table: string;\n  prop: string;\n  k: any;\n  sv: Uint8Array;\n}\n\nexport interface YDocumentClose {\n  type: 'doc-close';\n  table: string;\n  prop: string;\n  k: any;\n}\n\nexport interface YUpdateFromClientAck {\n  type: 'u-ack';\n  table: string;\n  prop: string;\n  i: number;\n}\n\nexport interface YUpdateFromClientReject {\n  type: 'u-reject';\n  table: string;\n  prop: string;\n  i: number;\n}\n\nexport interface YUpdateFromServerMessage {\n  type: 'u-s';\n  table: string;\n  prop: string;\n  k: any;\n  u: Uint8Array;\n  r?: string;\n}\n\nexport interface YAwarenessUpdate {\n  type: 'aware';\n  table: string;\n  prop: string;\n  k: any;\n  u: Uint8Array;\n}\n\nexport interface YInSyncMessage {\n  type: 'in-sync';\n  table: string;\n  prop: string;\n  k: any;\n}\n\nexport interface YCompleteSyncDone {\n  type: 'y-complete-sync-done';\n  yServerRev: string;\n}\n\nexport interface YOutdatedServerRevNotice {\n  type: 'outdated-server-rev';\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/yjs/decoding.ts",
    "content": "import { YMessage } from './YMessage';\nimport {\n  Decoder,\n  readAny,\n  readBigUint64,\n  readVarString,\n  readVarUint8Array,\n} from 'lib0/decoding';\n\nexport function decodeYMessage(a: Uint8Array): YMessage {\n  const decoder = new Decoder(a);\n  const type = readVarString(decoder) as YMessage['type'];\n  if (type === 'outdated-server-rev') {\n    return { type };\n  }\n  if (type === 'y-complete-sync-done') {\n    return { type, yServerRev: readVarString(decoder) };\n  }\n  const table = readVarString(decoder);\n  const prop = readVarString(decoder);\n\n  switch (type) {\n    case 'u-ack':\n    case 'u-reject':\n      return {\n        type,\n        table,\n        prop,\n        i: Number(readBigUint64(decoder)),\n      };\n    default: {\n      const k = readAny(decoder);\n      switch (type) {\n        case 'in-sync':\n          return { type, table, prop, k };\n        case 'aware':\n          return {\n            type,\n            table,\n            prop,\n            k,\n            u: readVarUint8Array(decoder),\n          };\n        case 'doc-open':\n          return {\n            type,\n            table,\n            prop,\n            k,\n            serverRev: readAny(decoder),\n            sv: readAny(decoder),\n          };\n        case 'doc-close':\n          return { type, table, prop, k };\n        case 'sv':\n          return {\n            type,\n            table,\n            prop,\n            k,\n            sv: readVarUint8Array(decoder),\n          };\n        case 'u-c':\n          return {\n            type,\n            table,\n            prop,\n            k,\n            u: readVarUint8Array(decoder),\n            i: Number(readBigUint64(decoder)),\n          };\n        case 'u-s':\n          return {\n            type,\n            table,\n            prop,\n            k,\n            u: readVarUint8Array(decoder),\n            r: (decoder.pos < decoder.arr.length && readVarString(decoder)) || undefined,\n          };\n        default:\n          throw new TypeError(`Unknown message type: ${type}`);\n        }\n    }\n  }\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/src/yjs/encoding.ts",
    "content": "import { YMessage } from './YMessage';\nimport {\n  Encoder,\n  writeVarString,\n  writeBigUint64,\n  writeAny,\n  toUint8Array,\n  writeVarUint8Array,\n} from 'lib0/encoding';\n\nexport function encodeYMessage(msg: YMessage): Uint8Array {\n  const encoder = new Encoder();\n  writeVarString(encoder, msg.type);\n  if ('table' in msg) writeVarString(encoder, msg.table);\n  if ('prop' in msg) writeVarString(encoder, msg.prop);\n\n  switch (msg.type) {\n    case 'u-ack':\n    case 'u-reject':\n      writeBigUint64(encoder, BigInt(msg.i));\n      break;\n    case 'outdated-server-rev':\n      break;\n    case 'y-complete-sync-done':\n      writeVarString(encoder, msg.yServerRev);\n      break;\n    default:\n      writeAny(encoder, msg.k);\n      switch (msg.type) {\n        case 'aware':\n          writeVarUint8Array(encoder, msg.u);\n          break;\n        case 'doc-open':\n          writeAny(encoder, msg.serverRev);\n          writeAny(encoder, msg.sv);\n          break;\n        case 'doc-close':\n          break;\n        case 'sv':\n          writeVarUint8Array(encoder, msg.sv);\n          break;\n        case 'u-c':\n          writeVarUint8Array(encoder, msg.u);\n          writeBigUint64(encoder, BigInt(msg.i));            \n          break;\n        case 'u-s':\n          writeVarUint8Array(encoder, msg.u);\n          writeVarString(encoder, msg.r || '');\n          break;\n        case 'in-sync':\n          break;\n      }\n  }\n  return toUint8Array(encoder);\n}\n"
  },
  {
    "path": "libs/dexie-cloud-common/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"es2020\", \"dom\"],\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"esnext\",\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\",\n    \"strict\": true,\n    \"noImplicitAny\": false,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"types\": [\"jest\", \"node\"]\n  },\n  \"include\": [\"./src\"]\n}"
  },
  {
    "path": "libs/dexie-react-hooks/.gitignore",
    "content": "test/dist\ndist\n"
  },
  {
    "path": "libs/dexie-react-hooks/README.md",
    "content": "# React hooks for Dexie\n\n[Live Queries](https://dexie.org/#live-queries)\n\n[Dexie React Tutorial](https://dexie.org/docs/Tutorial/React)\n\n[dexie-react-hooks](https://dexie.org/docs/libs/dexie-react-hooks)\n\n"
  },
  {
    "path": "libs/dexie-react-hooks/package.json",
    "content": "{\n  \"name\": \"dexie-react-hooks\",\n  \"version\": \"4.4.0\",\n  \"description\": \"React hooks for reactive data fetching using Dexie.js\",\n  \"main\": \"dist/dexie-react-hooks.js\",\n  \"typings\": \"dist/dexie-react-hooks.d.ts\",\n  \"scripts\": {\n    \"test\": \"just-build build-tests && just-build run-tests\",\n    \"build\": \"just-build\",\n    \"test:ltcloud\": \"cross-env LAMBDATEST=true pnpm run test:ltTunnel & sleep 10 && pnpm run test; UNIT_STATUS=$?; kill $(cat tunnel.pid); exit $UNIT_STATUS\",\n    \"test:ltTunnel\": \"node ../../test/lt-local\"\n  },\n  \"exports\": {\n    \"import\": \"./dist/dexie-react-hooks.mjs\",\n    \"require\": \"./dist/dexie-react-hooks.js\",\n    \"default\": \"./dist/dexie-react-hooks.js\"\n  },\n  \"just-build\": {\n    \"default\": [\n      \"rollup -c rollup.config.mjs\",\n      \"tsc --emitDeclarationOnly\"\n    ],\n    \"run-tests\": [\n      \"karma start test/karma.conf.js --single-run\"\n    ],\n    \"build-tests\": [\n      \"#just-build tests\",\n      \"cd test\",\n      \"webpack\"\n    ],\n    \"dexie\": [\n      \"cd ../..\",\n      \"pnpm run build\"\n    ]\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/dexie/Dexie.js.git\"\n  },\n  \"keywords\": [\n    \"react\",\n    \"hooks\",\n    \"indexeddb\",\n    \"storage\",\n    \"data-fetching\",\n    \"reactive\",\n    \"subscription\",\n    \"dexie\"\n  ],\n  \"author\": \"David Fahlander <https://github.com/dfahlander>\",\n  \"license\": \"Apache-2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/dexie/Dexie.js/issues\"\n  },\n  \"homepage\": \"https://dexie.org\",\n  \"peerDependencies\": {\n    \"dexie\": \"workspace:>=4.2.0-alpha.1 <5.0.0\",\n    \"react\": \">=16\"\n  },\n  \"devDependencies\": {\n    \"@rollup/plugin-commonjs\": \"^29.0.0\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.3\",\n    \"@rollup/plugin-typescript\": \"^12.3.0\",\n    \"@types/qunit\": \"^2.9.6\",\n    \"@types/react\": \"^19.2.2\",\n    \"@types/react-dom\": \"^19.2.1\",\n    \"dexie\": \"workspace:>=4.2.0-alpha.1 <5.0.0\",\n    \"just-build\": \"^0.9.24\",\n    \"qunit\": \"2.10.0\",\n    \"qunitjs\": \"1.23.1\",\n    \"react\": \"^19.2.3\",\n    \"react-dom\": \"^19.2.3\",\n    \"rollup\": \"^4.53.3\",\n    \"rollup-plugin-cleanup\": \"^3.2.1\",\n    \"rollup-plugin-sourcemaps\": \"^0.6.3\",\n    \"ts-loader\": \"^9.5.4\",\n    \"typescript\": \"^5.3.3\",\n    \"webpack\": \"^5.74.0\",\n    \"webpack-cli\": \"^6.0.1\",\n    \"y-dexie\": \"workspace:>=4.2.0-alpha.1 <5.0.0\",\n    \"yjs\": \"^13.6.27\"\n  }\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/rollup.config.mjs",
    "content": "//import {readFileSync} from 'fs';\n//import path from 'path';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport typescript from '@rollup/plugin-typescript';\n\n//const version = require(path.resolve(__dirname, './package.json')).version;\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\",\n];\n\nexport default {\n  input: './src/dexie-react-hooks.ts',\n  output: [{\n    file: 'dist/dexie-react-hooks.js',\n    format: 'umd',\n    globals: {dexie: \"Dexie\", react: \"React\", \"react-dom\": \"ReactDOM\"},\n    name: 'DexieReactHooks',\n    sourcemap: true,\n    exports: 'named'\n  },{\n    file: 'dist/dexie-react-hooks.mjs',\n    format: 'es',\n    sourcemap: true\n  }],\n  external: ['dexie', 'react', 'react-dom'],\n  plugins: [\n    typescript({\n      module: 'esnext',\n      target: 'es2022'\n    }),\n    nodeResolve({\n      browser: true,\n      preferBuiltins: false\n    }),\n    commonjs(),\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/dexie-react-hooks.ts",
    "content": "export * from './useLiveQuery';\nexport * from './useObservable';\nexport * from './usePermissions';\nexport * from './useDocument';\nexport * from './useSuspendingLiveQuery';\nexport * from './useSuspendingObservable';\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/index.ts",
    "content": "export * from './dexie-react-hooks';\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/types/y-dexie.d.ts",
    "content": "// Minimal ambient declarations for 'y-dexie' to satisfy type imports\n// This is a local quickfix for TypeScript ESM/CJS resolution issues.\ndeclare module 'y-dexie' {\n  // Inline minimal Y.Doc type to avoid depending on 'yjs' ambient declaration\n  export type YDoc = {\n    // minimal shape used by dexie-react-hooks\n    toJSON?: () => any;\n  };\n\n  export type DexieYProvider = {\n    doc?: YDoc | null;\n    // Partial API surface used by dexie-react-hooks\n    release?: (doc?: YDoc) => void;\n  };\n\n  export const DexieYProvider: {\n    load(doc: YDoc, options?: any): DexieYProvider;\n    for(doc: YDoc): DexieYProvider | undefined;\n    release(doc: YDoc): void;\n  };\n\n  export type YUpdateRow = any;\n  export type YSyncState = any;\n\n  export function compressYDocs(docs: unknown): unknown;\n\n  export default function yDexie(dbOrOptions?: any): any;\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/types/yjs.d.ts",
    "content": "// Minimal ambient declaration for 'yjs' used for type Doc\ndeclare module 'yjs' {\n  export type Doc = {\n    // Minimal Doc shape used by dexie-react-hooks; real API is larger\n    toJSON?: () => any;\n  };\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/useDocument.ts",
    "content": "import { Dexie } from 'dexie';\nimport React from 'react';\n\n// Using import('y-dexie') and import('yjs') to not break the build if y-dexie or yjs are not installed.\n// (these two libries are truly optional and not listed in neither peerDependencies nor optionalDependencies)\n// We want the compiler to not complain about missing imports, so we use type imports.\n// Runtime, we will detect if y-dexie is available and use it via Dexie['DexieYProvider'].\n\ntype DexieYProvider = import('y-dexie').DexieYProvider;\ntype DexieYProviderConstructor = typeof import('y-dexie').DexieYProvider;\ntype YDoc = import('yjs').Doc;\n\nconst gracePeriod = 100 // 100 ms = grace period to optimize for unload/reload scenarios\n\nconst fr = typeof FinalizationRegistry !== 'undefined' && new FinalizationRegistry((doc: YDoc) => {\n  // If coming here, react effect never ran. This is a fallback cleanup mechanism.\n  const DexieYProvider = Dexie['DexieYProvider'] as DexieYProviderConstructor;\n  if (DexieYProvider) DexieYProvider.release(doc);\n});\n\nexport function useDocument(\n  doc: YDoc | null | undefined\n): DexieYProvider | null {\n  if (!fr) throw new TypeError('FinalizationRegistry not supported.');\n  const providerRef = React.useRef<DexieYProvider | null>(null);\n  const DexieYProvider = Dexie['DexieYProvider'] as DexieYProviderConstructor;\n  if (!DexieYProvider) {\n    throw new Error('DexieYProvider is not available. Make sure `y-dexie` is installed and imported.');\n  }\n  let unregisterToken: object | undefined = undefined;\n  if (doc) {\n    if (doc !== providerRef.current?.doc) {\n      providerRef.current = DexieYProvider.load(doc, { gracePeriod });\n      unregisterToken = Object.create(null);\n      fr.register(providerRef, doc, unregisterToken);\n    }\n  } else if (providerRef.current?.doc) {\n    providerRef.current = null;\n  }\n  React.useEffect(() => {\n    if (doc) {\n      // Doc is set or changed. Unregister provider from FinalizationRegistry\n      // and instead take over from here to release the doc when component is unmounted\n      // or when doc is changed. What we're doing here is to avoid relying on FinalizationRegistry\n      // in all the normal cases and instead rely on React's lifecycle to release the doc.\n      // But there can be situations when react never calls this effect and therefore, we\n      // need to rely on FinalizationRegistry to release the doc as a fallback.\n      // We cannot wait with loading the document until the effect happens, because the doc\n      // could have been destroyed in the meantime.\n      if (unregisterToken) fr.unregister(unregisterToken);\n      let provider = DexieYProvider.for(doc);\n      if (provider) {\n        return () => {\n          DexieYProvider.release(doc);\n        }\n      } else {\n        // Maybe the doc was destroyed in the meantime.\n        // Can not happen if React and FinalizationRegistry works as we expect them to.\n        // Except if a user had called DexieYProvider.release() on the doc\n        throw new Error(`FATAL. DexieYProvider.release() has been called somewhere in application code, making us lose the document.`);\n      }\n    }\n  }, [doc, unregisterToken]);\n  return providerRef.current;\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/useLiveQuery.ts",
    "content": "import { Dexie } from 'dexie';\nimport { useObservable } from './useObservable';\n\nexport function useLiveQuery<T>(\n  querier: () => Promise<T> | T,\n  deps?: any[]\n): T | undefined;\nexport function useLiveQuery<T, TDefault>(\n  querier: () => Promise<T> | T,\n  deps: any[],\n  defaultResult: TDefault\n): T | TDefault;\nexport function useLiveQuery<T, TDefault>(\n  querier: () => Promise<T> | T,\n  deps?: any[],\n  defaultResult?: TDefault\n): T | TDefault {\n  return useObservable(\n    () => Dexie.liveQuery(querier),\n    deps || [],\n    defaultResult as TDefault\n  );\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/useObservable.ts",
    "content": "import React from 'react';\nexport interface InteropableObservable<T> {\n  subscribe(\n    onNext: (x: T) => any,\n    onError?: (error: any) => any\n  ): AnySubscription;\n  getValue?(): T; // For BehaviorSubject\n  hasValue?(): boolean; // For liveQuery observable returning false until a value is available\n}\n\nexport type AnySubscription = { unsubscribe(): any } | (() => any);\n\nexport function useObservable<T, TDefault>(\n  observable: InteropableObservable<T>\n): T | undefined;\nexport function useObservable<T, TDefault>(\n  observable: InteropableObservable<T>,\n  defaultResult: TDefault\n): T | TDefault;\nexport function useObservable<T>(\n  observableFactory: () => InteropableObservable<T>,\n  deps?: any[]\n): T | undefined;\nexport function useObservable<T, TDefault>(\n  observableFactory: () => InteropableObservable<T>,\n  deps: any[],\n  defaultResult: TDefault\n): T | TDefault;\nexport function useObservable<T, TDefault>(\n  observableFactory:\n    | InteropableObservable<T>\n    | (() => InteropableObservable<T>),\n  arg2?: any,\n  arg3?: any\n) {\n  // Resolve vars from overloading variants of this function:\n  let deps: any[];\n  let defaultResult: TDefault;\n  if (typeof observableFactory === 'function') {\n    deps = arg2 || [];\n    defaultResult = arg3;\n  } else {\n    deps = [];\n    defaultResult = arg2;\n  }\n\n  // Create a ref that keeps the state we need\n  const monitor = React.useRef({\n    hasResult: false,\n    result: defaultResult as T | TDefault,\n    error: null as any,\n  });\n  // We control when component should rerender. Make triggerUpdate\n  // as examplified on React's docs at:\n  // https://reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate\n  const [_, triggerUpdate] = React.useReducer((x) => x + 1, 0);\n\n  // Memoize the observable based on deps\n  const observable = React.useMemo(() => {\n    // Make it remember previous subscription's default value when\n    // resubscribing.\n    const observable =\n      typeof observableFactory === 'function'\n        ? observableFactory()\n        : observableFactory;\n    if (!observable || typeof observable.subscribe !== 'function') {\n      if (observableFactory === observable) {\n        throw new TypeError(\n          `Given argument to useObservable() was neither a valid observable nor a function.`\n        );\n      } else {\n        throw new TypeError(\n          `Observable factory given to useObservable() did not return a valid observable.`\n        );\n      }\n    }\n\n    if (!monitor.current.hasResult &&\n        typeof window !== 'undefined' // Don't do this in SSR\n       ) {\n      // Optimize for BehaviorSubject and other observables implementing getValue():\n      if (typeof observable.hasValue !== 'function' || observable.hasValue()) {\n        if (typeof observable.getValue === 'function') {\n          monitor.current.result = observable.getValue();\n          monitor.current.hasResult = true;\n        } else {\n          // Find out if the observable has a current value: try get it by subscribing and\n          // unsubscribing synchronously\n          const subscription = observable.subscribe((val) => {\n            monitor.current.result = val;\n            monitor.current.hasResult = true;\n          });\n          // Unsubscribe directly. We only needed any synchronous value if it was possible.\n          if (typeof subscription === 'function') {\n            subscription();\n          } else {\n            subscription.unsubscribe();\n          }\n        }\n      }\n    }\n    return observable;\n  }, deps);\n\n  // Integrate with react devtools:\n  React.useDebugValue(monitor.current.result);\n\n  // Subscribe to the observable\n  React.useEffect(() => {\n    const subscription = observable.subscribe(\n      (val) => {\n        const { current } = monitor;\n        if (current.error !== null || current.result !== val) {\n          current.error = null;\n          current.result = val;\n          current.hasResult = true;\n          triggerUpdate();\n        }\n      },\n      (err) => {\n        const { current } = monitor;\n        if (current.error !== err) {\n          current.error = err;\n          triggerUpdate();\n        }\n      }\n    );\n    return typeof subscription === 'function'\n      ? subscription // Support observables that return unsubscribe directly\n      : subscription.unsubscribe.bind(subscription);\n  }, deps);\n\n  // Throw if observable has emitted error so that\n  // an ErrorBoundrary can catch it\n  if (monitor.current.error) throw monitor.current.error;\n\n  // Return the current result\n  return monitor.current.result;\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/usePermissions.ts",
    "content": "import { Dexie } from 'dexie';\nimport { useObservable } from './useObservable';\n//import type { KeyPaths, TableProp } from 'dexie'; // Issue #1725 - not compatible with dexie@3.\n// Workaround: provide these types inline for now. When dexie 4 stable is out, we can use the types from dexie@4.\nexport type KeyPaths<T> = {\n  [P in keyof T]: \n    P extends string \n      ? T[P] extends Array<infer K>\n        ? K extends object // only drill into the array element if it's an object\n          ? P | `${P}.${number}` | `${P}.${number}.${KeyPaths<K>}` \n          : P | `${P}.${number}`\n        : T[P] extends (...args: any[]) => any // Method\n           ? never \n          : T[P] extends object \n            ? P | `${P}.${KeyPaths<T[P]>}` \n            : P \n      : never;\n}[keyof T];\nexport type TableProp<DX extends Dexie> = {\n  [K in keyof DX]: DX[K] extends {schema: any, get: any, put: any, add: any, where: any} ? K : never;\n}[keyof DX] & string;\n\n\ninterface DexieCloudEntity {\n  table(): string;\n  realmId: string;\n  owner: string;\n}\n\nexport interface PermissionChecker<T, TableName extends string> {\n  add(...tableNames: TableName[]): boolean;\n  update(...props: KeyPaths<T>[]): boolean;\n  delete(): boolean;\n}\n\nexport function usePermissions<T extends DexieCloudEntity>(\n  entity: T\n): PermissionChecker<\n  T,\n  T extends { table: () => infer TableName } ? TableName : string\n>;\nexport function usePermissions<\n  TDB extends Dexie,\n  T\n>(db: TDB, table: TableProp<TDB>, obj: T): PermissionChecker<T, TableProp<TDB>>;\nexport function usePermissions(\n  firstArg:\n    | Dexie\n    | {\n        realmId?: string;\n        owner?: string;\n        table?: () => string;\n        readonly db?: Dexie;\n      },\n  table?: string,\n  obj?: { realmId?: string; owner?: string }\n) {\n  if (!firstArg)\n    throw new TypeError(\n      `Invalid arguments to usePermissions(): undefined or null`\n    );\n  let db: Dexie;\n  if (arguments.length >= 3) {\n    if (!('transaction' in firstArg)) {\n      // Using ducktyping instead of instanceof in case there are multiple Dexie modules in app.\n      // First arg is  ensures first arg is a Dexie instance\n      throw new TypeError(\n        `Invalid arguments to usePermission(db, table, obj): 1st arg must be a Dexie instance`\n      );\n    }\n    if (typeof table !== 'string')\n      throw new TypeError(\n        `Invalid arguments to usePermission(db, table, obj): 2nd arg must be string`\n      );\n    if (!obj || typeof obj !== 'object')\n      throw new TypeError(\n        `Invalid arguments to usePermission(db, table, obj): 3rd arg must be an object`\n      );\n    db = firstArg;\n  } else {\n    if (firstArg instanceof Dexie)\n      throw new TypeError(\n        `Invalid arguments to usePermission(db, table, obj): Missing table and obj arguments.`\n      );\n\n    if (\n      typeof firstArg.table === 'function' &&\n      typeof firstArg.db === 'object'\n    ) {\n      db = firstArg.db!;\n      obj = firstArg;\n      table = firstArg.table();\n    } else {\n      throw new TypeError(\n        `Invalid arguments to usePermissions(). ` +\n          `Expected usePermissions(entity: DexieCloudEntity) or ` +\n          `usePermissions(db: Dexie, table: string, obj: DexieCloudObject)`\n      );\n    }\n  }\n  if (!('cloud' in db))\n    throw new Error(\n      `usePermissions() is only for Dexie Cloud but there's no dexie-cloud-addon active in given db.`\n    );\n  if (!('permissions' in (db as any).cloud))\n    throw new Error(`usePermissions() requires a newer version of dexie-cloud-addon. Please upgrade it.`)\n  return useObservable(\n    // @ts-ignore\n    () => db.cloud.permissions(obj, table),\n    [obj.realmId, obj.owner, table]\n  );\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/usePromise.ts",
    "content": "import * as React from 'react';\n\n/** {@link React.use} if supported, else fallback */\nconst reactUse = Reflect.get(React, 'use');\n\nexport const usePromise: <T>(promise: PromiseLike<T>) => T =\n  reactUse ?? fallbackUsePromise;\n\n/** Fallback for `React.use` with promise */\nfunction fallbackUsePromise<T>(promise: PromiseLike<T>): T {\n  const state = PROMISE_STATE_MAP.get(promise);\n\n  if (!state) {\n    PROMISE_STATE_MAP.set(promise, { status: 'pending' });\n    promise.then(\n      (value) => {\n        PROMISE_STATE_MAP.set(promise, { status: 'fulfilled', value });\n      },\n      (reason) => {\n        PROMISE_STATE_MAP.set(promise, { status: 'rejected', reason });\n      }\n    );\n    throw promise;\n  }\n\n  switch (state.status) {\n    case 'pending':\n      throw promise;\n    case 'rejected':\n      throw state.reason;\n    case 'fulfilled':\n      return state.value;\n  }\n}\n\nconst PROMISE_STATE_MAP = new WeakMap<\n  PromiseLike<any>,\n  PromiseSettledResult<any> | { status: 'pending' }\n>();\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/useSuspendingLiveQuery.ts",
    "content": "import { Dexie } from 'dexie';\nimport { useSuspendingObservable } from './useSuspendingObservable';\n\n/**\n * Observe IndexedDB data in your React component. Make the component re-render when the observed data changes.\n *\n * Suspends until first value is available.\n * \n * Cache key must be globally unique.\n */\nexport function useSuspendingLiveQuery<T>(\n  querier: () => Promise<T> | T,\n  cacheKey: React.DependencyList\n): T {\n  return useSuspendingObservable(\n    () => Dexie.liveQuery(querier),\n    ['dexie', ...cacheKey]\n  );\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/src/useSuspendingObservable.ts",
    "content": "import { Observer, Subscribable, Unsubscribable } from 'dexie';\nimport * as React from 'react';\nimport { usePromise } from './usePromise';\n\nconst observableCache = new Map<React.DependencyList, Subscribable<any>>();\nconst promiseCache = new WeakMap<Subscribable<any>, Promise<any>>();\nconst valueCache = new WeakMap<Subscribable<any>, any>();\n\nconst CLEANUP_DELAY = 3000; // Time to wait before cleaning up unused observables\n\n/**\n * Subscribes to an observable and returns the latest value.\n * Suspends until the first value is received.\n *\n * Calls with the same cache key will reuse the same observable.\n * Cache key must be globally unique.\n */\nexport function useSuspendingObservable<T>(\n  getObservable: (() => Subscribable<T>) | Subscribable<T>,\n  cacheKey: React.DependencyList\n): T {\n  let observable: Subscribable<T> | undefined;\n\n  // Try to find an existing observable for this cache key\n  for (const [key, value] of observableCache) {\n    if (\n      key.length === cacheKey.length &&\n      key.every((k, i) => Object.is(k, cacheKey[i]))\n    ) {\n      observable = value;\n      break;\n    }\n  }\n\n  // If no observable was found, create a new one\n  if (!observable) {\n    // Create a multicast observable which subscribes to source at most once.\n    const source =\n      typeof getObservable === 'function' ? getObservable() : getObservable;\n    let subscription: Unsubscribable | undefined;\n    const observers = new Set<Observer<T>>();\n    let timeout: ReturnType<typeof setTimeout> | undefined;\n    const newObservable: Subscribable<T> = {\n      subscribe: (observer) => {\n        observers.add(observer);\n        // Cancel the cleanup timer if it's running\n        if (timeout != null) {\n          clearTimeout(timeout);\n          timeout = undefined;\n        }\n        // If this is the first subscriber, subscribe to the source observable\n        if (!subscription) {\n          subscription = source.subscribe({\n            next: (val) => {\n              valueCache.set(newObservable, val);\n              // Clone observers in case the list changes during emission\n              for (const obs of new Set(observers)) obs.next?.(val);\n            },\n            error: (err) => {\n              const lastObservers = new Set(observers);\n              handleFinalize();\n              for (const obs of lastObservers) obs.error?.(err);\n            },\n            complete: () => {\n              const lastObservers = new Set(observers);\n              handleFinalize();\n              for (const obs of lastObservers) obs.complete?.();\n            },\n          });\n        }\n        // Otherwise, emit the current value to the new subscriber if any\n        else if (valueCache.has(newObservable)) {\n          observer.next?.(valueCache.get(newObservable)!);\n        }\n        // Return the unsubscriber\n        return {\n          unsubscribe: () => {\n            if (!observers.has(observer)) return;\n            observers.delete(observer);\n            // If this was the last subscriber, schedule cleanup\n            if (observers.size === 0) scheduleCleanup();\n          },\n        };\n        function handleFinalize() {\n          // Reset this observable to the initial state\n          subscription = undefined;\n          observers.clear();\n          valueCache.delete(newObservable);\n          promiseCache.delete(newObservable);\n          // Schedule cleanup in case nobody subscribes again\n          scheduleCleanup();\n        }\n        function scheduleCleanup() {\n          if (timeout != null) return; // Cleanup already scheduled\n          timeout = setTimeout(() => {\n            // Unsubscribe source if any\n            subscription?.unsubscribe();\n            subscription = undefined;\n            // Remove this observable from cache\n            for (const [key, value] of observableCache) {\n              if (value === newObservable) {\n                observableCache.delete(key);\n                break;\n              }\n            }\n          }, CLEANUP_DELAY);\n        }\n      },\n    };\n    observable = newObservable;\n    observableCache.set(cacheKey, newObservable);\n  }\n\n  // Get or initialize promise for first value\n  let promise = promiseCache.get(observable);\n  if (!promise) {\n    promise = new Promise<T>((resolve, reject) => {\n      const subscription = observable.subscribe({\n        next: (val) => {\n          resolve(val);\n          // Unsubscribe in next tick because subscription might not be assigned yet\n          queueMicrotask(() => subscription.unsubscribe());\n        },\n        error: (err) => reject(err),\n      });\n    });\n    promiseCache.set(observable, promise);\n  }\n\n  const initialValue = usePromise(promise);\n\n  const value = React.useRef<T>(initialValue);\n  const [error, setError] = React.useState<unknown>();\n  const rerender = React.useReducer((x) => x + 1, 0)[1];\n\n  // Set the value immediately on every render.\n  // This avoids waiting for effect to run.\n  value.current = valueCache.has(observable)\n    ? valueCache.get(observable)!\n    : initialValue;\n\n  // Subscribe to live updates until the source observable changes.\n  React.useEffect(() => {\n    const subscription = observable.subscribe({\n      next: (val) => {\n        if (!Object.is(val, value.current)) {\n          value.current = val;\n          rerender();\n        }\n      },\n      error: (err) => setError(err),\n    });\n    return () => subscription.unsubscribe();\n  }, [observable]);\n\n  if (error) throw error;\n  return value.current;\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/components/App.tsx",
    "content": "import React, { useState } from \"react\";\nimport { db } from \"../db\";\nimport { ErrorBoundary } from \"./ErrorBoundrary\";\nimport { ItemListComponent } from \"./ItemListComponent\";\nimport { ItemLoaderComponent } from \"./ItemLoaderComponent\";\n\nexport function App() {\n  const [currentId, setCurrentId] = useState(1);\n  return <ErrorBoundary>\n    <div id=\"list\">\n      <h2>All items</h2>\n      <ItemListComponent loadItems={async ()=> {\n        console.log(\"Liading items...\");\n        try {\n          const items = await db.items.toArray();\n          console.log(\"got items\", items);\n          return items;\n          } catch (ex) {\n          console.error(\"Error loading items\", ex);\n          throw ex;\n        }\n      }} />\n    </div>\n    <div id=\"current\">\n        <h2>Current item</h2>\n        <ErrorBoundary>\n          <ItemLoaderComponent id={currentId} loadItem={(id: number)=>db.items.get(id)} />\n        </ErrorBoundary>\n    </div>\n    <div id=\"controls\">\n      <button id=\"btnFirst\" onClick={()=>setCurrentId(1)}>First</button>\n      <button id=\"btnNext\" onClick={()=>setCurrentId(prevId => prevId + 1)}>Next</button>\n      <button id=\"btnInvalidKey\" onClick={()=>setCurrentId(NaN)}>Select invalid key</button>\n    </div>    \n  </ErrorBoundary>;\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/components/ErrorBoundrary.tsx",
    "content": "import React, { Component } from \"react\";\n\nexport class ErrorBoundary extends Component<React.PropsWithChildren<{}>, { error: any }> {\n  constructor(props: React.PropsWithChildren<{}>) {\n    super(props);\n    this.state = { error: null };\n  }\n\n  static getDerivedStateFromError(error: any) {\n    // Update state so the next render will show the fallback UI.\n    return { error };\n  }\n\n  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n    //console.error(error, errorInfo);\n  }\n\n  render() {\n    if (this.state.error) {\n      return (\n        <div id=\"errorBoundrary\">\n          <h1>Something went wrong.</h1>\n          <p>{\"\" + this.state.error}</p>\n          <button id=\"btnRetry\" onClick={()=>this.setState({error: null})}>Retry</button>\n        </div>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/components/ItemComponent.tsx",
    "content": "import React from \"react\";\nimport { Item } from \"../models/Item\";\n\ninterface Props {\n  item: Item;\n}\n\nexport function ItemComponent({ item: {id, name }}: Props) {\n  return <div className=\"item-component\" id={\"item-\" + id}>\n    <p>ID: <span className=\"id-span\">{id}</span></p>\n    <p>Name: <span className=\"name-span\">{name}</span></p>\n  </div>;\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/components/ItemListComponent.tsx",
    "content": "import React from \"react\";\nimport { useLiveQuery } from \"../../src\";\nimport { Item } from \"../models/Item\";\nimport { ItemComponent } from \"./ItemComponent\";\n\ninterface Props {\n  loadItems: () => Promise<Item[]>;\n}\n\nexport function ItemListComponent({ loadItems }: Props) {\n  const items = useLiveQuery(loadItems);\n  if (!items) return <p>Loading...</p>;\n  return (\n    <>\n      <h3>Items found:</h3>\n      <ul id=\"itemList\">\n        {items.map((item) => (\n          <ItemComponent key={item.id} item={item} />\n        ))}\n      </ul>\n    </>\n  );\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/components/ItemLoaderComponent.tsx",
    "content": "import React from \"react\";\nimport { useSuspendingLiveQuery } from \"../../src\";\nimport { Item } from \"../models/Item\";\nimport { ItemComponent } from \"./ItemComponent\";\n\nexport interface Props {\n  id: number;\n  loadItem: (id: number) => Promise<Item | undefined>;\n}\n\nexport function ItemLoaderComponent({ id, loadItem }: Props) {\n  const item = useSuspendingLiveQuery(() => loadItem(id), [\"item\", id]);\n  if (!item)\n    return (\n      <p className=\"not-found-item\">\n        <span>NOT_FOUND</span>: {id}\n      </p>\n    );\n  return <ItemComponent item={item} />;\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/db/index.ts",
    "content": "import Dexie, { Table } from \"dexie\";\nimport { Item } from \"../models/Item\";\n\nexport class TestUseLiveQueryDB extends Dexie {\n  items!: Table<Item, number>;\n\n  constructor() {\n    super(\"TestUseLiveQuery\", {cache: 'immutable'});\n    this.version(1).stores({\n      items: \"id\"\n    });\n  }\n}\n\nexport const db = new TestUseLiveQueryDB();\n\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/gh-actions.sh",
    "content": "#!/bin/bash -e\necho \"Installing dependencies for dexie-react-hooks\"\n# Build y-dexie first as it is a devDependency for dexie-react-hooks\ncd ../../../addons/y-dexie\npnpm install >/dev/null\npnpm run build\ncd -\npnpm install >/dev/null\necho \"Building and running the tests\"\npnpm test:ltcloud\n\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>dexie-react-hooks tests</title>\n  <link rel=\"stylesheet\" href=\"../node_modules/qunit/qunit/qunit.css\">\n</head>\n<body>\n    <div id=\"qunit\"></div>\n    <div id=\"qunit-fixture\"></div>\n    <script src=\"../../../node_modules/qunit/qunit/qunit.js\"></script>\n    <script src=\"dist/bundle.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/index.ts",
    "content": "import React from \"react\";\nimport * as ReactDOMClient from \"react-dom/client\";\nimport { module, test, equiv, assert } from \"qunit\";\nimport { db } from \"./db\";\nimport { App } from \"./components/App\";\nimport { waitTilEqual } from \"./utils/waitTilEqual\";\nimport { waitTilOk } from \"./utils/waitTilOk\";\n\nconst div = document.createElement('div');\n\ndocument.body.insertAdjacentHTML('beforeend', `<div style=\"margin-top: 300px;\"></div>`);\ndocument.body.appendChild(div);\n// Use React 18 createRoot API\nconst root = ReactDOMClient.createRoot(div);\nroot.render(React.createElement(App));\n\nmodule('useLiveQuery', {\n  async beforeEach(assert) {\n    const done = assert.async();\n    try {\n      console.log(\"Clearing database\");\n      await db.items.clear();\n      console.log(\"Successfully cleared database\");\n    } finally {\n      done();\n    }\n  },\n});\n\ntest(\"List component is reacting to changes\", async ()=>{\n  await waitTilEqual(\n    () => div.querySelector('ul#itemList')?.textContent,\n    '',\n    'The list should be empty'\n  );\n  // Add items:\n  console.log(\"Putting items\");\n  await db.items.bulkPut([{\n    id: 1,\n    name: \"Hello\",\n  }, {\n    id: 2,\n    name: \"World\"\n  }]);\n  console.log(\"after bulkPut\");\n  await waitTilEqual(\n    ()=>div.querySelector(\"ul#itemList\")?.textContent,\n    \"ID: 1Name: HelloID: 2Name: World\",\n    \"The list should be populated\"\n  );\n\n  // Remove an item:\n  db.items.delete(2);\n  await waitTilEqual(\n    ()=>div.querySelector(\"ul#itemList\")?.textContent,\n    \"ID: 1Name: Hello\",\n    \"The second item should have been removed\"\n  );\n\n  // Update an item:\n  await db.items.update(1, {name: \"Hola\"});\n  await waitTilEqual(\n    ()=>div.querySelector(\"ul#itemList\")?.textContent,\n    \"ID: 1Name: Hola\",\n    \"The first item should have been updated\");\n});\n\ntest(\"ItemLoaderComponent is reacting to changes\", async ()=>{\n  const divCurrent = div.querySelector(\"div#current\")!;\n  // Initial value when loaded - should be \"not found\"\n  waitTilEqual(()=>{\n    let pNotFoundItem = divCurrent.querySelector(\"p.not-found-item\");  \n    return pNotFoundItem?.textContent;\n  }, \"NOT_FOUND: 1\", \"Before we add anything - the component should say NOT_FOUND: 1\");\n  // Add items:\n  await db.items.put({\n    id: 1,\n    name: \"Foo\",\n  });\n  await waitTilEqual(\n    ()=>divCurrent.querySelector(\"div#item-1\")?.textContent,\n    \"ID: 1Name: Foo\",\n    \"Current item should have been rendered\");\n\n  // Update it:\n  await db.items.update(1, {name: \"Bar\"});\n  await waitTilEqual(\n    ()=>divCurrent.querySelector(\"div#item-1\")?.textContent,\n    \"ID: 1Name: Bar\",\n    \"Current item should have been updated\");\n\n  // Remove it:\n  db.items.delete(1);\n  await waitTilOk(()=>{\n    const current = divCurrent.querySelector(\"div#item-1\");\n    const pNotFoundItem = divCurrent.querySelector(\"p.not-found-item\");\n    return !current\n  }, \"Item 1 should not be in the DOM tree anymore\");\n  assert.equal(divCurrent.querySelector(\"p.not-found-item\")?.textContent, \"NOT_FOUND: 1\", \"After deleting it - the component should say NOT_FOUND: 1\");\n});\n\ntest(\"Clicking next button will update the currently viewed item\", async ()=>{\n  const divCurrent = div.querySelector(\"div#current\")!;\n  // Add items:\n  await db.items.bulkPut([{\n    id: 1,\n    name: \"Hello\",\n  }, {\n    id: 2,\n    name: \"World\"\n  }]);\n\n  await waitTilEqual(()=>divCurrent.textContent, \"Current itemID: 1Name: Hello\", \"We are now vieweing item 1\");\n\n  // Click next button and verify we are then viewing item 2\n  const btnNext = div.querySelector(\"#btnNext\");\n  // Click button:\n  (btnNext as HTMLElement).click();\n  await waitTilEqual(()=>divCurrent.textContent, \"Current itemID: 2Name: World\", \"We are now vieweing item 2\");\n\n  // Go back to first:\n  const btnFirst = div.querySelector(\"#btnFirst\");\n  (btnFirst as HTMLElement).click();\n  await waitTilEqual(()=>divCurrent.textContent, \"Current itemID: 1Name: Hello\", \"We are now viewing item 1 again\");\n\n  // Update item 2 while it's not rendered but still in promise cache:\n  await db.items.update(2, {name: \"Earth\"});\n  // Go to item 2 again:\n  (btnNext as HTMLElement).click();\n  await waitTilEqual(()=>divCurrent.textContent, \"Current itemID: 2Name: Earth\", \"We are now viewing updated item 2\");\n});\n\ntest(\"Selecting invalid key trigger the err-boundrary\", async ()=>{\n  // Stop test from failing because we trigger an error!\n  QUnit[\"onUnhandledRejection\"] = ()=>true;\n  QUnit[\"onUncaughtException\"] = ()=>true; // Needed in latest version of qunit when \"global failure: \" is preventing test from succeeding.\n  QUnit[\"onError\"] = ()=>true;\n  //QUnit.config.current.ignoreGlobalErrors = true;\n  /*\n  self.onunhandledrejection = (ev)=>{\n    ev.preventDefault();\n    ev.stopPropagation();\n    return false;\n  };*/\n  //console.error = ()=>{};\n  \n  \n\n  // Add some data:\n  await db.items.bulkPut([{\n    id: 1,\n    name: \"Hello\",\n  }, {\n    id: 2,\n    name: \"World\"\n  }]);\n\n  // Wait for both parts to update...\n  await waitTilOk(()=>{\n    const list = div.querySelector(\"ul#itemList\")?.textContent;\n    return list === \"ID: 1Name: HelloID: 2Name: World\";\n  }, \"We have a inital setup with two items in the list: 'Hello' and 'World'\");\n\n  // Now click the invalid key and wait for a render:\n  // Click a bad button:\n  (div.querySelector(\"#btnInvalidKey\") as HTMLElement).click();\n  await waitTilOk(()=>/Something went wrong/.test(div.innerText), \"The error boundrary should be shown\");\n  \n  // Restore the gui from the error state:\n  (div.querySelector(\"#btnFirst\") as HTMLElement).click();\n  (div.querySelector(\"#btnRetry\") as HTMLElement).click();\n  await waitTilEqual(\n    () => div.querySelector(\"div#current\")?.textContent,\n    \"Current itemID: 1Name: Hello\", \"We should be back to viewing item 1\"\n  );\n});\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/karma.conf.js",
    "content": "// Include common configuration\nconst {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../../test/karma.common');\n\nmodule.exports = function (config) {\n  const cfg = getKarmaConfig({}, {\n    basePath: '../../..',\n    files: [\n      // Load babel polyfill\n      'test/babel-polyfill/polyfill.min.js',\n      // Load qunitjs 1.23.1 manually\n      'node_modules/qunitjs/qunit/qunit.js',\n      // karma-qunit adapter\n      'node_modules/karma-qunit/lib/adapter.js',\n      // karma environment setup\n      'test/karma-env.js',\n      // Test bundle\n      'libs/dexie-react-hooks/test/dist/bundle.js'\n    ]\n  });\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/models/Item.ts",
    "content": "export interface Item {\n  id: number;\n  name: string;\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"declaration\": false,\n    \"declarationMap\": false,\n    \"target\": \"es2020\",\n    \"rootDir\": \"..\",\n    \"types\": [\"qunit\"],\n    \"outDir\": \"dist\",\n  },\n  \"include\": [\"../src\", \".\"]\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/utils/BinarySemaphore.ts",
    "content": "export class BinarySemaphore {\n  constructor() {\n    this.init();\n  }\n  post: () => void;\n  then: (onSuccess: (val: any) => void, onError: (err: any) => void) => void;\n  reset() {\n    this.post();\n    this.init();\n  }\n  private init() {\n    const promise = new Promise<void>((resolve) => {\n      this.post = resolve as () => void;\n    });\n    this.then = promise.then.bind(promise);\n  }\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/utils/closest.ts",
    "content": "export function closest(node: Node, selector: string) {\n  const element = node.nodeType === 1 ? node as Element : node.parentElement;\n  return element?.closest(selector);\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/utils/sleep.ts",
    "content": "export function sleep(ms: number, signal?: AbortSignal) {\n  return new Promise((resolve, reject) => {\n    const timeout = setTimeout(resolve, ms);\n    if (signal) {\n      signal.addEventListener('abort', () => {\n        clearTimeout(timeout);\n        reject(new DOMException('Aborted', 'AbortError'));\n      });\n    }\n  });\n}"
  },
  {
    "path": "libs/dexie-react-hooks/test/utils/timeout.ts",
    "content": "export function timeout(ms): AbortSignal {\n  const controller = new AbortController();\n  setTimeout(() => controller.abort(), ms);\n  return controller.signal;\n}"
  },
  {
    "path": "libs/dexie-react-hooks/test/utils/waitTilEqual.ts",
    "content": "import { assert } from 'qunit';\n\nexport function waitTilEqual(\n  getActual: () => any,\n  expected: any,\n  description: string,\n  timeout = 2000\n) {\n  return new Promise((resolve, reject) => {\n    const start = Date.now();\n    const interval = setInterval(() => {\n      const actual = getActual();\n      if (actual === expected) {\n        clearInterval(interval);\n        assert.equal(actual, expected, description);\n        resolve(true);\n      } else if (Date.now() - start > timeout) {\n        clearInterval(interval);\n        assert.equal(actual, expected, description);\n        resolve(false);\n      }\n    }, 10);\n  });\n}\n\n\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/utils/waitTilOk.ts",
    "content": "import { assert } from 'qunit';\n\nexport function waitTilOk(\n  evaluateCondition: () => boolean,\n  description: string,\n  timeout = 2000\n) {\n  return new Promise((resolve, reject) => {\n    const start = Date.now();\n    const interval = setInterval(() => {\n      if (evaluateCondition()) {\n        clearInterval(interval);\n        assert.ok(true, description);\n        resolve(true);\n      } else if (Date.now() - start > timeout) {\n        clearInterval(interval);\n        assert.ok(false, description);\n        resolve(false);\n      }\n    }, 10);\n  });\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/test/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  entry: './index.ts',\n  devtool: 'inline-source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.tsx?$/,\n        use: 'ts-loader',\n        exclude: /node_modules/,\n      },\n    ],\n  },\n  resolve: {\n    extensions: [ '.tsx', '.ts', '.js' ],\n  },\n  output: {\n    filename: 'bundle.js',\n    path: path.resolve(__dirname, 'dist'),\n  },\n  optimization: {\n    minimize: false\n  },\n  externals: {\n    qunit: \"QUnit\",\n  }\n};"
  },
  {
    "path": "libs/dexie-react-hooks/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"es2023\", \"dom\", \"dom.iterable\"],\n    \"moduleResolution\": \"node16\",\n    \"importHelpers\": false,\n    \"jsx\": \"react\",\n    \"target\": \"es2022\",\n    \"module\": \"node16\",\n    \"noImplicitAny\": false,\n    \"strictNullChecks\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\",\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "libs/dexie-react-hooks/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  entry: './src/index.ts',\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.tsx?$/,\n        use: 'ts-loader',\n        exclude: /node_modules/,\n      },\n    ],\n  },\n  resolve: {\n    extensions: [ '.tsx', '.ts', '.js' ],\n  },\n  optimization: {\n    //minimize: false // to see a not-minimized output.\n  },\n  output: {\n    library: 'DexieReactHooks',\n    libraryTarget: 'umd',\n    filename: 'dexie-react-hooks.js',\n    path: path.resolve(__dirname, 'dist'),\n  },\n  externals: {\n    react: {\n      commonjs: 'react',\n      commonjs2: 'react',\n      amd: 'react',\n      root: 'React'\n    },\n    \"react-dom\": {\n      commonjs: 'react-dom',\n      commonjs2: 'react-dom',\n      amd: 'react-dom',\n      root: 'ReactDOM'\n    },\n    dexie: {\n      commonjs: 'dexie',\n      commonjs2: 'dexie',\n      amd: 'dexie',\n      root: 'Dexie'\n    }\n  }\n};\n"
  },
  {
    "path": "libs/dexie-svelte-query/.gitignore",
    "content": "# Output\n/.svelte-kit\n/dist\n\n"
  },
  {
    "path": "libs/dexie-svelte-query/.npmrc",
    "content": "engine-strict=true\n"
  },
  {
    "path": "libs/dexie-svelte-query/README.md",
    "content": "# Dexie.js Svelte Query\n\nSvelte reactive state query for fetching using Dexie.js\n\n## Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { stateQuery } from 'dexie-svelte-query';\n\timport { db } from './db';\n\n\tlet minAge = $state(18);\n\tlet maxAge = $state(65);\n\n\tconst friendsQuery = stateQuery(\n\t\t() => db.friends.where('age').between(minAge, maxAge).toArray(),\n\t\t() => [minAge, maxAge]\n\t);\n\tlet friends = $derived(friendsQuery.current);\n</script>\n\n<div>\n\t<h2>Friends</h2>\n\t<ul>\n\t\t{#each friends ?? [] as friend (friend.id)}\n\t\t\t<li>{friend.name}, {friend.age}</li>\n\t\t{/each}\n\t\t</ul>\n</div>\n```\n"
  },
  {
    "path": "libs/dexie-svelte-query/package.json",
    "content": "{\n\t\"name\": \"dexie-svelte-query\",\n\t\"version\": \"1.0.0\",\n\t\"description\": \"Svelte reactive state query for fetching using Dexie.js\",\n\t\"author\": \"Oliver Dowling (https://github.com/oliverdowling)\",\n\t\"scripts\": {\n\t\t\"build\": \"pnpm run package\",\n\t\t\"package\": \"svelte-package && publint\",\n\t\t\"prepublishOnly\": \"pnpm run package\",\n\t\t\"check\": \"svelte-check --tsconfig ./tsconfig.json\",\n\t\t\"check:watch\": \"svelte-check --tsconfig ./tsconfig.json --watch\"\n\t},\n\t\"files\": [\n\t\t\"dist\",\n\t\t\"!dist/**/*.test.*\",\n\t\t\"!dist/**/*.spec.*\"\n\t],\n\t\"svelte\": \"./dist/index.js\",\n\t\"types\": \"./dist/index.d.ts\",\n\t\"type\": \"module\",\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"types\": \"./dist/index.d.ts\",\n\t\t\t\"svelte\": \"./dist/index.js\"\n\t\t}\n\t},\n\t\"peerDependencies\": {\n\t\t\"dexie\": \"^3.2 || ^4.0.1\",\n\t\t\"svelte\": \"^5.0.0\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@sveltejs/package\": \"^2.3.7\",\n\t\t\"dexie\": \"^4.0.10\",\n\t\t\"publint\": \"^0.3.0\",\n\t\t\"svelte\": \"^5.17.1\",\n\t\t\"svelte-check\": \"^4.1.3\",\n\t\t\"typescript\": \"^5.7.3\"\n\t}\n}\n"
  },
  {
    "path": "libs/dexie-svelte-query/src/lib/index.ts",
    "content": "export * from \"./stateQuery.svelte.js\";\n"
  },
  {
    "path": "libs/dexie-svelte-query/src/lib/stateQuery.svelte.ts",
    "content": "import { liveQuery } from \"dexie\";\n\nexport function stateQuery<T>(querier: () => T | Promise<T>, dependencies?: () => unknown[]) {\n\tconst query = $state<{ current?: T; isLoading: boolean; error?: any }>({\n\t\tcurrent: undefined,\n\t\tisLoading: true,\n\t\terror: undefined,\n\t});\n\t$effect(() => {\n\t\tdependencies?.();\n\t\treturn liveQuery(querier).subscribe(\n\t\t\t(value) => {\n\t\t\t\tquery.error = undefined;\n\t\t\t\tif (value !== undefined) {\n\t\t\t\t\tquery.current = value;\n\t\t\t\t\tquery.isLoading = false;\n\t\t\t\t} else {\n\t\t\t\t\tquery.isLoading = true;\n\t\t\t\t}\n\t\t\t},\n\t\t\t(error) => {\n\t\t\t\tquery.error = error;\n\t\t\t\tquery.isLoading = false;\n\t\t\t}\n\t\t).unsubscribe;\n\t});\n\treturn query;\n}\n"
  },
  {
    "path": "libs/dexie-svelte-query/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"allowJs\": true,\n\t\t\"checkJs\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"sourceMap\": true,\n\t\t\"strict\": true,\n\t\t\"module\": \"NodeNext\",\n\t\t\"moduleResolution\": \"NodeNext\"\n\t},\n\t\"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"dexie\",\n  \"version\": \"4.4.0\",\n  \"description\": \"A Minimalistic Wrapper for IndexedDB\",\n  \"main\": \"dist/dexie.js\",\n  \"module\": \"dist/dexie.mjs\",\n  \"jsnext:main\": \"dist/dexie.mjs\",\n  \"exports\": {\n    \".\": {\n      \"production\": {\n        \"module\": \"./import-wrapper-prod.mjs\",\n        \"import\": \"./import-wrapper-prod.mjs\",\n        \"require\": \"./dist/dexie.min.js\",\n        \"default\": \"./dist/dexie.min.js\"\n      },\n      \"development\": {\n        \"module\": \"./import-wrapper.mjs\",\n        \"import\": \"./import-wrapper.mjs\",\n        \"require\": \"./dist/dexie.js\",\n        \"default\": \"./dist/dexie.js\"\n      },\n      \"default\": {\n        \"module\": \"./import-wrapper.mjs\",\n        \"import\": \"./import-wrapper.mjs\",\n        \"require\": \"./dist/dexie.js\",\n        \"default\": \"./dist/dexie.js\"\n      }\n    },\n    \"./package.json\": \"./package.json\",\n    \"./dist/*\": \"./dist/*\"\n  },\n  \"typings\": \"dist/dexie.d.ts\",\n  \"jspm\": {\n    \"format\": \"cjs\",\n    \"ignore\": [\n      \"src/\"\n    ]\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dexie/Dexie.js.git\"\n  },\n  \"keywords\": [\n    \"indexeddb\",\n    \"browser\",\n    \"database\"\n  ],\n  \"author\": \"David Fahlander <https://github.com/dfahlander>\",\n  \"contributors\": [\n    \"Christopher Hunt <https://github.com/chrahunt>\",\n    \"Nikolas Poniros <https://github.com/nponiros>\",\n    \"Anders Ekdahl <https://github.com/andersekdahl>\",\n    \"Yury Solovyov <https://github.com/YurySolovyov>\",\n    \"Martin Diphoorn <https://github.com/martindiphoorn>\"\n  ],\n  \"license\": \"Apache-2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/dexie/Dexie.js/issues\"\n  },\n  \"scripts\": {\n    \"build\": \"just-build\",\n    \"watch\": \"just-build --watch\",\n    \"clean\": \"rm -rf tools/tmp && rm dist/*.js && rm dist/*.map && rm dist/*.ts && rm dist/*.mjs\",\n    \"test\": \"pnpm run build && pnpm run test:typings && pnpm run test:unit\",\n    \"test:unit\": \"karma start test/karma.conf.js --single-run\",\n    \"test:typings\": \"tsc -p test/typings-test/\",\n    \"test:debug\": \"karma start test/karma.conf.js --log-level debug\",\n    \"test:ltcloud\": \"cross-env LAMBDATEST=true pnpm run test:ltTunnel & sleep 10 && pnpm run test:unit; UNIT_STATUS=$?; kill $(cat tunnel.pid); exit $UNIT_STATUS\",\n    \"test:ltTunnel\": \"node test/lt-local\",\n    \"prepack\": \"pnpm run build\"\n  },\n  \"just-build\": {\n    \"default\": [\n      \"# Build all targets (es5, es6 and test) and minify the default es5 UMD module\",\n      \"just-build release test\"\n    ],\n    \"dexie\": [\n      \"# Build dist/dexie.js, dist/dexie.mjs and dist/dexie.d.ts\",\n      \"cd src\",\n      \"tsc [--watch 'Watching for file changes']\",\n      \"tsc --target es2021 --outdir ../tools/tmp/modern/src/\",\n      \"rollup -c ../tools/build-configs/rollup.config.mjs\",\n      \"rollup -c ../tools/build-configs/rollup.umd.config.mjs\",\n      \"rollup -c ../tools/build-configs/rollup.modern.config.mjs\",\n      \"node ../tools/replaceVersionAndDate.js ../dist/dexie.js\",\n      \"node ../tools/replaceVersionAndDate.js ../dist/dexie.mjs\",\n      \"node ../tools/replaceVersionAndDate.js ../dist/modern/dexie.mjs\",\n      \"dts-bundle-generator --inline-declare-global --inline-declare-externals --no-check -o ../dist/dexie.d.ts public/index.d.ts\",\n      \"node ../tools/fix-dts-duplicates.js ../dist/dexie.d.ts\",\n      \"node ../tools/prepend.js ../dist/dexie.d.ts ../tools/build-configs/banner.txt\",\n      \"node ../tools/replaceVersionAndDate.js ../dist/dexie.d.ts\"\n    ],\n    \"release\": [\n      \"# Build ES5 umd module as well as the es6 module.\",\n      \"just-build dexie\",\n      \"node tools/replaceVersionAndDate.js dist/dexie.d.ts\",\n      \"# Minify the default ES5 UMD module\",\n      \"cd dist\",\n      \"uglifyjs dexie.js -m -c negate_iife=0 -o dexie.min.js --source-map url=dexie.min.js.map\",\n      \"# Minify modern bundle\",\n      \"cd modern\",\n      \"terser --comments false --compress --mangle --module --source-map url=dexie.min.mjs.map -o dexie.min.mjs -- dexie.mjs\"\n    ],\n    \"dev\": [\n      \"# Build ES5 module and the tests\",\n      \"just-build dexie test\"\n    ],\n    \"gzip\": [\n      \"# Optionally gzip to find the size of the minified & gzipped version\",\n      \"gzip dist/dexie.min.js -k -f -9\"\n    ],\n    \"test\": [\n      \"# Build the test suite.\",\n      \"cd test\",\n      \"tsc [--watch 'Watching for file changes']\",\n      \"rollup -c ../tools/build-configs/rollup.tests.config.mjs\"\n    ]\n  },\n  \"homepage\": \"https://dexie.org\",\n  \"devDependencies\": {\n    \"@lambdatest/node-tunnel\": \"^4.0.7\",\n    \"@rollup/plugin-commonjs\": \"^29.0.0\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.3\",\n    \"cross-env\": \"^10.1.0\",\n    \"dts-bundle-generator\": \"^9.5.1\",\n    \"just-build\": \"^0.9.24\",\n    \"karma\": \"^6.1.1\",\n    \"karma-chrome-launcher\": \"^3.1.0\",\n    \"karma-firefox-launcher\": \"^2.1.0\",\n    \"karma-mocha-reporter\": \"^2.2.5\",\n    \"karma-qunit\": \"^4.1.1\",\n    \"karma-webdriver-launcher\": \"^1.0.8\",\n    \"qunit\": \"2.10.0\",\n    \"qunitjs\": \"1.23.1\",\n    \"rollup\": \"^4.53.3\",\n    \"rollup-plugin-cleanup\": \"^3.2.1\",\n    \"rollup-plugin-sourcemaps\": \"^0.6.3\",\n    \"rxjs\": \"^7.8.2\",\n    \"safari-14-idb-fix\": \"^3.0.0\",\n    \"serve-static\": \"^2.2.0\",\n    \"sorted-json\": \"^0.3.0\",\n    \"terser\": \"^5.3.1\",\n    \"tslib\": \"^2.1.0\",\n    \"typescript\": \"^5.6.3\",\n    \"uglify-js\": \"^3.9.2\",\n    \"yjs\": \"^13.6.27\"\n  },\n  \"pnpm\": {\n    \"onlyBuiltDependencies\": [\n      \"core-js-bundle\",\n      \"esbuild\",\n      \"unrs-resolver\",\n      \"wd\"\n    ]\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  # The \"dexie\" package in the root\n  - '.'\n  # all packages in direct subdirs of addons/\n  - 'addons/*'\n  # all packages in direct subdirs of libs/\n  - 'libs/*'\n  # Dexie cloud todo app\n  - 'samples/dexie-cloud-todo-app'"
  },
  {
    "path": "samples/angular/.gitignore",
    "content": "node_modules/\ndist/\n.angular/\n*.log\n"
  },
  {
    "path": "samples/angular/README.md",
    "content": "# Dexie.js + Angular Example\n\nA modern Angular Todo app demonstrating Dexie.js with:\n\n- **Angular 21** with standalone components\n- **Zoneless change detection** (default in Angular 21+, no zone.js required)\n- **Signal-based reactivity** using `toSignal()` from `@angular/core/rxjs-interop`\n- **New control flow syntax** (`@for`, `@if`, `@empty`)\n- **Signal inputs** (`input.required<T>()`)\n- **Dexie's `liveQuery()`** for reactive database queries\n- **EntityTable** for typed table access\n\n## Quick Start\n\n```bash\nnpm install\nnpm start\n```\n\nThen open http://localhost:4200\n\n## Why Zoneless?\n\nAngular 21 defaults to zoneless change detection, which:\n- **Improves performance** - no unnecessary change detection cycles\n- **Reduces bundle size** - zone.js adds ~13KB gzipped\n- **Works with native async/await** - no need to transpile down to ES2015\n- **Pairs perfectly with signals** - change detection is triggered by signal updates\n\nThis example uses signals throughout, making it an ideal fit for zoneless mode.\n\n## Key Patterns\n\n### Database Setup (db.ts)\n\nThis is a simplified example. See `src/app/db.ts` for the full schema with TodoList and TodoItem tables.\n\n```typescript\nimport Dexie, { type EntityTable } from 'dexie';\n\ninterface TodoItem {\n  id: number;\n  title: string;\n  done: boolean;\n}\n\nconst db = new Dexie('MyApp') as Dexie & {\n  todoItems: EntityTable<TodoItem, 'id'>;\n};\n\ndb.version(1).stores({\n  todoItems: '++id',\n});\n\nexport { db };\n```\n\n### Using liveQuery with Angular Signals\n\n```typescript\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { from } from 'rxjs';\nimport { liveQuery } from 'dexie';\n\n// In your component:\nitems = toSignal(\n  from(liveQuery(() => db.todoItems.toArray())),\n  { initialValue: [] }\n);\n```\n\nThe `liveQuery()` function returns an observable that emits whenever the underlying data changes. Combined with `toSignal()`, you get automatic UI updates when database records change.\n\n### Template with New Control Flow\n\n```html\n@for (item of items(); track item.id) {\n  <div>{{ item.title }}</div>\n} @empty {\n  <p>No items yet</p>\n}\n```\n\n### Atomic Operations with Transactions\n\n```typescript\n// Delete list and all its items atomically\nawait db.transaction('rw', db.todoItems, db.todoLists, async () => {\n  await db.todoItems.where({ todoListId: listId }).delete();\n  await db.todoLists.delete(listId);\n});\n```\n\n## Live Demo\n\n[Open in StackBlitz](https://stackblitz.com/github/dexie/Dexie.js/tree/master/samples/angular)\n\n## Learn More\n\n- [Dexie.js Documentation](https://dexie.org/docs)\n- [Angular Tutorial on dexie.org](https://dexie.org/docs/Tutorial/Angular)\n- [liveQuery() Documentation](https://dexie.org/docs/liveQuery())\n- [Angular Zoneless Guide](https://angular.dev/guide/zoneless)\n"
  },
  {
    "path": "samples/angular/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"dexie-angular-example\": {\n      \"projectType\": \"application\",\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:application\",\n          \"options\": {\n            \"outputPath\": \"dist/dexie-angular-example\",\n            \"index\": \"src/index.html\",\n            \"browser\": \"src/main.ts\",\n            \"tsConfig\": \"tsconfig.json\",\n            \"assets\": [],\n            \"styles\": [],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"500kB\",\n                  \"maximumError\": \"1MB\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"optimization\": false,\n              \"extractLicenses\": false,\n              \"sourceMap\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"buildTarget\": \"dexie-angular-example:build:production\"\n            },\n            \"development\": {\n              \"buildTarget\": \"dexie-angular-example:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "samples/angular/package.json",
    "content": "{\n  \"name\": \"dexie-angular-example\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\"\n  },\n  \"dependencies\": {\n    \"@angular/animations\": \"^21.0.0\",\n    \"@angular/common\": \"^21.0.0\",\n    \"@angular/compiler\": \"^21.0.0\",\n    \"@angular/core\": \"^21.0.0\",\n    \"@angular/forms\": \"^21.0.0\",\n    \"@angular/platform-browser\": \"^21.0.0\",\n    \"@angular/platform-browser-dynamic\": \"^21.0.0\",\n    \"dexie\": \"^4.0.0\",\n    \"rxjs\": \"~7.8.0\",\n    \"tslib\": \"^2.6.0\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"^21.0.0\",\n    \"@angular/cli\": \"^21.0.0\",\n    \"@angular/compiler-cli\": \"^21.0.0\",\n    \"typescript\": \"~5.9.0\"\n  }\n}\n"
  },
  {
    "path": "samples/angular/src/app/app.component.ts",
    "content": "// app.component.ts - Main app component (standalone, zoneless)\nimport { Component } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { from } from 'rxjs';\nimport { liveQuery } from 'dexie';\nimport { db, TodoList } from './db';\nimport { ItemListComponent } from './item-list.component';\n\n@Component({\n  selector: 'app-root',\n  standalone: true,\n  imports: [FormsModule, ItemListComponent],\n  template: `\n    <main>\n      <h1>Dexie.js + Angular Todo App</h1>\n\n      @for (list of todoLists(); track list.id) {\n        <app-item-list [todoList]=\"list\" />\n      } @empty {\n        <p class=\"empty-state\">No lists yet. Add one below!</p>\n      }\n\n      <form (ngSubmit)=\"addNewList()\">\n        <input\n          type=\"text\"\n          [(ngModel)]=\"newListName\"\n          name=\"listName\"\n          placeholder=\"New list name...\"\n        />\n        <button type=\"submit\">Add List</button>\n      </form>\n    </main>\n  `,\n  styles: [`\n    main {\n      max-width: 600px;\n      margin: 2rem auto;\n      padding: 1rem;\n      font-family: system-ui, sans-serif;\n    }\n    .empty-state {\n      color: #666;\n      font-style: italic;\n      margin: 1rem 0;\n    }\n    form {\n      margin-top: 2rem;\n      display: flex;\n      gap: 0.5rem;\n    }\n    input {\n      flex: 1;\n      padding: 0.5rem;\n      border: 1px solid #ccc;\n      border-radius: 4px;\n    }\n    button {\n      padding: 0.5rem 1rem;\n      background: #4f46e5;\n      color: white;\n      border: none;\n      border-radius: 4px;\n      cursor: pointer;\n    }\n    button:hover {\n      background: #4338ca;\n    }\n  `],\n})\nexport class AppComponent {\n  newListName = '';\n\n  // Convert Dexie's liveQuery observable to Angular signal\n  todoLists = toSignal(\n    from(liveQuery(() => db.todoLists.toArray())),\n    { initialValue: [] as TodoList[] }\n  );\n\n  async addNewList() {\n    const title = this.newListName.trim();\n    if (!title) return;\n    await db.todoLists.add({ title });\n    this.newListName = '';\n  }\n}\n"
  },
  {
    "path": "samples/angular/src/app/db.ts",
    "content": "// db.ts - Database definition with typed tables\nimport Dexie, { type EntityTable } from 'dexie';\n\n// Entity interfaces\nexport interface TodoList {\n  id: number;\n  title: string;\n}\n\nexport interface TodoItem {\n  id: number;\n  todoListId: number;\n  title: string;\n  done: boolean;\n}\n\n// Database class with typed tables\nconst db = new Dexie('AngularTodoApp') as Dexie & {\n  todoLists: EntityTable<TodoList, 'id'>;\n  todoItems: EntityTable<TodoItem, 'id'>;\n};\n\ndb.version(1).stores({\n  todoLists: '++id',\n  todoItems: '++id, todoListId',\n});\n\n// Populate with sample data on first run\ndb.on('populate', async () => {\n  const todoListId = await db.todoLists.add({\n    title: 'To Do Today',\n  });\n  await db.todoItems.bulkAdd([\n    { todoListId, title: 'Feed the birds', done: false },\n    { todoListId, title: 'Watch a movie', done: false },\n    { todoListId, title: 'Have some sleep', done: false },\n  ]);\n});\n\nexport { db };\n"
  },
  {
    "path": "samples/angular/src/app/item-list.component.ts",
    "content": "// item-list.component.ts - Todo list component (standalone, zoneless)\nimport { Component, computed, input } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { from } from 'rxjs';\nimport { liveQuery } from 'dexie';\nimport { db, TodoItem, TodoList } from './db';\n\n@Component({\n  selector: 'app-item-list',\n  standalone: true,\n  imports: [FormsModule],\n  template: `\n    <section class=\"todo-list\">\n      <h2>{{ todoList().title }}</h2>\n\n      <ul>\n        @for (item of items(); track item.id) {\n          <li [class.done]=\"item.done\">\n            <label>\n              <input\n                type=\"checkbox\"\n                [checked]=\"item.done\"\n                (change)=\"toggleItem(item)\"\n              />\n              {{ item.title }}\n            </label>\n            <button class=\"delete\" (click)=\"deleteItem(item.id)\">×</button>\n          </li>\n        } @empty {\n          <li class=\"empty\">No items yet</li>\n        }\n      </ul>\n\n      <form (ngSubmit)=\"addItem()\">\n        <input\n          type=\"text\"\n          [(ngModel)]=\"newItemTitle\"\n          name=\"itemTitle\"\n          placeholder=\"Add new item...\"\n        />\n        <button type=\"submit\">Add</button>\n      </form>\n\n      <button class=\"delete-list\" (click)=\"deleteList()\">\n        Delete List\n      </button>\n    </section>\n  `,\n  styles: [`\n    .todo-list {\n      background: #f9fafb;\n      border-radius: 8px;\n      padding: 1rem;\n      margin-bottom: 1rem;\n    }\n    h2 {\n      margin: 0 0 1rem;\n      font-size: 1.25rem;\n    }\n    ul {\n      list-style: none;\n      padding: 0;\n      margin: 0 0 1rem;\n    }\n    li {\n      display: flex;\n      align-items: center;\n      padding: 0.5rem;\n      border-bottom: 1px solid #e5e7eb;\n    }\n    li.done label {\n      text-decoration: line-through;\n      color: #9ca3af;\n    }\n    li.empty {\n      color: #9ca3af;\n      font-style: italic;\n    }\n    label {\n      flex: 1;\n      display: flex;\n      align-items: center;\n      gap: 0.5rem;\n      cursor: pointer;\n    }\n    form {\n      display: flex;\n      gap: 0.5rem;\n    }\n    input[type=\"text\"] {\n      flex: 1;\n      padding: 0.5rem;\n      border: 1px solid #d1d5db;\n      border-radius: 4px;\n    }\n    button {\n      padding: 0.5rem 1rem;\n      background: #4f46e5;\n      color: white;\n      border: none;\n      border-radius: 4px;\n      cursor: pointer;\n    }\n    button:hover {\n      background: #4338ca;\n    }\n    .delete {\n      background: transparent;\n      color: #ef4444;\n      padding: 0.25rem 0.5rem;\n    }\n    .delete:hover {\n      background: #fee2e2;\n    }\n    .delete-list {\n      margin-top: 1rem;\n      background: #ef4444;\n      width: 100%;\n    }\n    .delete-list:hover {\n      background: #dc2626;\n    }\n  `],\n})\nexport class ItemListComponent {\n  // Signal input (Angular 17+)\n  todoList = input.required<TodoList>();\n\n  newItemTitle = '';\n\n  // Computed signal that depends on todoList input\n  private todoListId = computed(() => this.todoList().id);\n\n  // LiveQuery as signal - updates when database changes\n  // Note: liveQuery tracks Dexie reads, not signal changes.\n  // This works because @for track ensures each component instance has a fixed todoListId.\n  items = toSignal(\n    from(liveQuery(() => \n      db.todoItems.where({ todoListId: this.todoListId() }).toArray()\n    )),\n    { initialValue: [] as TodoItem[] }\n  );\n\n  async addItem() {\n    const title = this.newItemTitle.trim();\n    if (!title) return;\n    await db.todoItems.add({\n      todoListId: this.todoListId(),\n      title,\n      done: false,\n    });\n    this.newItemTitle = '';\n  }\n\n  async toggleItem(item: TodoItem) {\n    await db.todoItems.update(item.id, { done: !item.done });\n  }\n\n  async deleteItem(id: number) {\n    await db.todoItems.delete(id);\n  }\n\n  async deleteList() {\n    // Delete all items in this list, then the list itself (atomically)\n    await db.transaction('rw', db.todoItems, db.todoLists, async () => {\n      await db.todoItems.where({ todoListId: this.todoListId() }).delete();\n      await db.todoLists.delete(this.todoListId());\n    });\n  }\n}\n"
  },
  {
    "path": "samples/angular/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Dexie.js + Angular Todo App</title>\n    <base href=\"/\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      * { box-sizing: border-box; }\n      body { margin: 0; }\n    </style>\n  </head>\n  <body>\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "samples/angular/src/main.ts",
    "content": "// main.ts - Application bootstrap (standalone, zoneless)\nimport { bootstrapApplication } from '@angular/platform-browser';\nimport { AppComponent } from './app/app.component';\n\n// Angular 21+ uses zoneless change detection by default\n// Change detection is triggered by signals, template events, and Angular APIs\nbootstrapApplication(AppComponent).catch((err) => console.error(err));\n"
  },
  {
    "path": "samples/angular/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"outDir\": \"./dist/out-tsc\",\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"esModuleInterop\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"bundler\",\n    \"importHelpers\": true,\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"lib\": [\"ES2022\", \"dom\"],\n    \"skipLibCheck\": true\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n/dummytests/\n\n# dexie-cloud\ndexie-cloud.json\ndexie-cloud.key\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/README.md",
    "content": "# Dexie Cloud ToDo app\n\nThis project is built with [Vite](https://vitejs.dev/) and uses TypeScript and React.\n\n## How to use the sample\n\n**[You can open a deployed version of this PWA at https://dexie.github.io/Dexie.js/dexie-cloud-todo-app](https://dexie.github.io/Dexie.js/dexie-cloud-todo-app)**\n\nIf you just want to see the app in action, navigate to the [pre-built published version](https://dexie.github.io/Dexie.js/dexie-cloud-todo-app/).\n\nIf you want to build and play with it locally, follow these steps:\n\n1. `npx dexie-cloud create` or `npx dexie-cloud connect <existing db>`\n2. `npm install`\n3. `./configure-app.sh`\n4. `npm run dev` (or `npm start`)\n\nThe steps above will:\n\n1. Create a new database in the cloud (or connect to existing)\n2. Install dependencies\n3. Import demo-users to your database and create a .env.local file that connects the ToDo app to your database.\n4. Build and start the application in local dev-mode with Vite's fast HMR (Hot Module Replacement).\n\n## Activating Service Worker\n\nService worker is automatically enabled in production builds thanks to vite-plugin-pwa. In development mode, the service worker is disabled by default for easier debugging.\n\nThe easiest way to test the full PWA experience is to deploy the app:\n\n1. `npm run build && npm run preview` (local production preview). The production build is previewed on port 3001\n2. Or deploy with: `npm run deploy` (will publish the app to your gh-pages branch of your Dexie.js fork)\n3. `npx dexie-cloud whitelist https://your-github-username.github.io` (replace `your-github-username`)\n4. Voila: Go to https://your-github-username.github.io/Dexie.js/dexie-cloud-todo-app/ from your browser. This is a full installable PWA that you can add to your start screen on a mobile phone.\n\nDexie Cloud works both with and without a service worker but there are some benefits of activating the service worker:\n\n* Your app becomes a real installable PWA that people can add to their start screen on mobile or desktop.\n* If you do a change while being offline and close the app, it will be synced once you get online, no matter if you're still in the app or not.\n* If your PWA is installed via Chrome (or any browser that supports the periodicSync event), the app will periodically sync with the server also when you aren't using it, making sure that next time the app is started, it already has the fresh data from server.\n\n## Disabling Service Worker\n\nTo disable Dexie Cloud from using its service worker (for syncing data):\n* Remove `tryUseServiceWorker: true` from `db.cloud.configure()` in [src/db/TodoDB.ts](https://github.com/dfahlander/Dexie.js/blob/master/samples/dexie-cloud-todo-app/src/db/TodoDB.ts)\n\nTo disable the application from using service worker (for caching resources):\n* Remove the `serviceWorkerRegistration.register();` call from [src/index.tsx](https://github.com/dfahlander/Dexie.js/blob/master/samples/dexie-cloud-todo-app/src/index.tsx)\n* Or set `registerType: 'disabled'` in the VitePWA plugin configuration in `vite.config.ts`\n\n## Available Scripts\n\nIn the project directory, you can run:\n\n### `./configure-app.sh`\n\nConfigure this app to use your created database.\nThis command will create the file .env.local and configure it against the DB URL in dexie-cloud.json.\nYou can equally well set the environment variable VITE_DBURL manually to the URL of your\nDexie Cloud database.\n\n### `npm run dev` or `npm start`\n\nRuns the app in development mode using Vite.\\\nOpen [http://localhost:3000](http://localhost:3000) to view it in the browser.\n\nThe page will reload automatically when you make edits thanks to Vite's Hot Module Replacement (HMR).\\\nYou will also see any lint errors in the console.\n\n### `npm test`\n\nRuns the test suite using Vitest once and exits.\\\nPerfect for CI/CD or quick test verification.\n\n### `npm run test:watch`\n\nRuns the test suite in watch mode using Vitest.\\\nTests will re-run automatically when files change during development.\n\n### `npm run test:ui`\n\nRuns the test suite with Vitest's UI interface for a better testing experience.\n\n### `npm run build`\n\nBuilds the app for production to the `build` folder.\\\nIt correctly bundles React in production mode and optimizes the build for the best performance using Vite.\n\nThe build is minified and includes content hashing for optimal caching.\\\nYour app is ready to be deployed!\n\n### `npm run preview`\n\nServes the production build locally for testing.\\\nThis allows you to test the built app before deployment, including the service worker functionality.\n\n### `npm run deploy`\n\nDeploys the built app to gh-pages branch of this github repository under the folder /dexie-cloud-todo-app/.\n\n## Learn More\n\nYou can learn more about Vite in the [Vite documentation](https://vitejs.dev/).\n\nTo learn React, check out the [React documentation](https://reactjs.org/).\n\nTo learn about Dexie Cloud, check out the [Dexie Cloud documentation](https://dexie.org/cloud/docs/).\n\n## Configuration and Customization\n\nUnlike Create React App's \"eject\" operation, Vite provides full configuration flexibility without ejecting:\n\n- **Build configuration**: Modify `vite.config.ts` to customize the build process\n- **TypeScript settings**: Update `tsconfig.json` for TypeScript configuration  \n- **PWA settings**: Configure PWA behavior in the VitePWA plugin settings\n- **Testing**: Customize test setup in `vitest.config.ts`\n- **Plugins**: Add any Vite plugin for additional functionality\n\nAll configuration files are accessible and can be modified at any time without losing upgrade paths or tooling support.\n\n## Migration from Create React App\n\nThis project has been migrated from Create React App to Vite for improved development experience and build performance. Key changes include:\n\n- **Faster development server** with Vite's Hot Module Replacement (HMR)\n- **Faster builds** using esbuild and Rollup\n- **Built-in TypeScript support** without additional configuration\n- **PWA support** via vite-plugin-pwa instead of Workbox\n- **Environment variables** now use `VITE_` prefix instead of `REACT_APP_`\n- **Modern build output** optimized for modern browsers\n\n### Public URL Configuration\n\nUnlike Create React App's `%PUBLIC_URL%` template syntax, Vite handles public paths through the `base` configuration in `vite.config.ts`. \n\n- **Default**: Absolute paths (`/assets/...`) for root domain deployment\n- **Subdirectory deployment**: Set `PUBLIC_URL` environment variable\n  ```bash\n  # For deployment in subdirectory\n  PUBLIC_URL=/my-app npm run build\n  \n  # For relative paths (e.g., GitHub Pages without custom domain)\n  PUBLIC_URL=. npm run build\n  ```\n\nThe `PUBLIC_URL` environment variable is automatically converted to Vite's `base` option during build.\n\nThe application functionality remains exactly the same, but with better performance and developer experience.\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"tailwind.config.js\",\n    \"css\": \"src/index.css\",\n    \"baseColor\": \"slate\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"src/components\",\n    \"utils\": \"src/lib/utils\"\n  }\n}"
  },
  {
    "path": "samples/dexie-cloud-todo-app/configure-app.sh",
    "content": "#!/bin/bash -e\nif [ ! -f \"./dexie-cloud.json\" ]; then\n    echo \"Please run:\"\n    echo \"  npx dexie-cloud create\"\n    echo \"or: \"\n    echo \"  npx dexie-cloud connect <DB-URL>\"\n    echo \"...to create a database in the cloud\"\n    echo \"Then retry this script!\"\n    exit 1;\nfi\n\necho \"Importing demo users...\"\nnpx dexie-cloud import src/data/demoUsers.json\n\necho \"Importing roles...\"\nnpx dexie-cloud import src/data/roles.json\n\necho \"Whitelisting origin: http://localhost:3000 (for npm run dev)\"\nnpx dexie-cloud whitelist http://localhost:3000\n\necho \"Whitelisting origin: http://localhost:3001 (for npm run build && npm run preview)\"\nnpx dexie-cloud whitelist http://localhost:3001\n\nDB_URL=$(node -p \"require('./dexie-cloud.json').dbUrl\")\necho \"\"\necho \"Configuring .env.local: VITE_DBURL=$DB_URL\"\necho \"VITE_DBURL=$DB_URL\" > .env.local\necho \"\"\necho \"Application is now configured!\"\necho \"Use 'npm install' if you haven't done so already.\"\necho \"Use 'npm run dev' or 'npm start' to start the app.\"\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <meta\n      name=\"description\"\n      content=\"Dexie Cloud example application built with Vite\"\n    />\n    <link rel=\"apple-touch-icon\" href=\"/dexie-icon-192x192.png\" />\n    <title>Dexie Cloud ToDo App</title>\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/package.json",
    "content": "{\n  \"name\": \"dexie-cloud-todo-app\",\n  \"version\": \"2.0.2\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"dependencies\": {\n    \"dexie\": \"^4.3.0\",\n    \"dexie-cloud-addon\": \"^4.3.3\",\n    \"dexie-react-hooks\": \"^4.2.0\",\n    \"lucide-react\": \"^0.554.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"react-use\": \"^17.6.0\",\n    \"typescript\": \"^5.9.3\",\n    \"workbox-core\": \"^7.4.0\",\n    \"workbox-expiration\": \"^7.4.0\",\n    \"workbox-precaching\": \"^7.4.0\",\n    \"workbox-routing\": \"^7.4.0\",\n    \"workbox-strategies\": \"^7.4.0\",\n    \"workbox-window\": \"^7.4.0\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"start\": \"vite\",\n    \"build\": \"vite build\",\n    \"build:check\": \"tsc && vite build\",\n    \"preview\": \"vite preview\",\n    \"predeploy\": \"npm run build\",\n    \"deploy\": \"gh-pages -d build -e dexie-cloud-todo-app\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest\",\n    \"test:ui\": \"vitest --ui\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/typography\": \"^0.5.19\",\n    \"@testing-library/jest-dom\": \"^6.9.1\",\n    \"@testing-library/react\": \"^16.3.0\",\n    \"@testing-library/user-event\": \"^14.4.3\",\n    \"@types/node\": \"^20.19.25\",\n    \"@types/react\": \"^18.3.27\",\n    \"@types/react-dom\": \"^18.3.7\",\n    \"@vitejs/plugin-react\": \"^4.3.0\",\n    \"@vitest/ui\": \"^4.0.12\",\n    \"autoprefixer\": \"^10.4.22\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"gh-pages\": \"^6.3.0\",\n    \"jsdom\": \"^25.0.1\",\n    \"postcss\": \"^8.5.6\",\n    \"tailwind-merge\": \"^3.4.0\",\n    \"tailwindcss\": \"^3.4.18\",\n    \"tailwindcss-animate\": \"^1.0.7\",\n    \"vite\": \"^6.4.1\",\n    \"vite-plugin-pwa\": \"^1.1.0\",\n    \"vitest\": \"^4.0.12\",\n    \"workbox-build\": \"^7.4.0\"\n  },\n  \"overrides\": {\n    \"magic-string\": \"^0.30.21\",\n    \"sourcemap-codec\": \"npm:@jridgewell/sourcemap-codec@^1.5.0\",\n    \"source-map\": \"^0.7.4\"\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/postcss.config.js",
    "content": "import tailwindcss from 'tailwindcss'\nimport autoprefixer from 'autoprefixer'\n\nexport default {\n  plugins: [\n    tailwindcss,\n    autoprefixer,\n  ],\n}"
  },
  {
    "path": "samples/dexie-cloud-todo-app/public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/App.test.tsx",
    "content": "import React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport { describe, it, expect } from 'vitest';\nimport App from './App';\n\ndescribe('App', () => {\n  it('renders todo app without crashing', () => {\n    render(<App />);\n    // Test that the navbar and app title renders\n    const element = screen.getByText('Dexie Cloud ToDo App');\n    expect(element).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/App.tsx",
    "content": "import { TodoLists } from './components/TodoLists';\nimport { AddTodoList } from './components/AddTodoList';\nimport { ResetDatabaseButton } from './components/ResetDatabaseButton';\nimport { NavBar } from './components/navbar/NavBar';\nimport { Invites } from './components/access-control/Invites';\nimport { useObservable } from 'dexie-react-hooks';\nimport { db } from './db';\nimport { type UserLogin } from 'dexie-cloud-addon';\nimport { Button } from './components/ui/button';\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from './components/ui/card';\n\nfunction App() {\n  return (\n    <div className=\"min-h-screen bg-background\">\n      <NavBar />\n      <main className=\"pt-16\">\n        <div className=\"max-w-4xl mx-auto px-4 pb-2\">\n          <LicenseAdvertiseExample />\n          <Invites />\n        </div>\n        <TodoLists />\n        <div className=\"max-w-4xl mx-auto px-4 py-6\">\n          <div className=\"flex flex-col sm:flex-row gap-4\">\n            <AddTodoList />\n            <ResetDatabaseButton />\n          </div>\n        </div>\n      </main>\n    </div>\n  );\n}\n\nfunction LicenseAdvertiseExample() {\n  const currentUser = useObservable(db.cloud.currentUser);\n  if (!currentUser) return null; // Information about current user not available yet.\n  const { license } = currentUser;\n  if (!license) return null; // No license information available yet.\n\n  if (\n    license.status === 'ok' &&\n    license.validUntil === undefined &&\n    license.evalDaysLeft === undefined\n  ) {\n    // No license limits. Don't show any warning.\n    return null;\n  }\n\n  if (license.status !== 'ok') {\n    // User license has expired or is invalid.\n    return (\n      <Card className=\"mb-6 border-destructive\">\n        <CardHeader>\n          <CardTitle className=\"text-destructive\">License Required</CardTitle>\n        </CardHeader>\n        <CardContent className=\"space-y-4\">\n          <CardDescription>\n            No valid license. You are in offline mode until a valid license is purchased.\n          </CardDescription>\n          <div className=\"space-y-2\">\n            <Button asChild>\n              <a href=\"https://example.com\">Purchase License</a>\n            </Button>\n            <Button \n              variant=\"destructive\" \n              onClick={(ev) => {\n                ev.preventDefault();\n                ev.stopPropagation();\n                deleteUserAccount(currentUser);\n              }}\n            >\n              Delete Account & All Data\n            </Button>\n          </div>\n        </CardContent>\n      </Card>\n    );\n  }\n\n  let licenseExpiresInDays = license.evalDaysLeft; // Might be undefined.\n  if (licenseExpiresInDays === undefined) {\n    // Using Absolute expiration date on eval or production license\n    const validUntil = license.validUntil?.getTime() ?? Infinity;\n    licenseExpiresInDays = Math.round(\n      (validUntil! - Date.now()) / (24 * 60 * 60 * 1000)\n    );\n  }\n\n  if (licenseExpiresInDays < 7) {\n    return (\n      <Card className=\"mb-6 border-yellow-500\">\n        <CardHeader>\n          <CardTitle className=\"text-yellow-600\">License Expiring Soon</CardTitle>\n        </CardHeader>\n        <CardContent className=\"space-y-4\">\n          <CardDescription>\n            License expires in {licenseExpiresInDays} days.\n          </CardDescription>\n          <Button asChild>\n            <a href=\"https://example.com\">Purchase Production License</a>\n          </Button>\n        </CardContent>\n      </Card>\n    );\n  }\n\n  // Enough time left. Don't show any warning.\n  return null;\n}\n\nasync function deleteUserAccount(user: UserLogin) {\n  if (!user?.userId) return; // Safety check\n  const confirmed = confirm(`Are you sure you want to delete your user completely along all stored data for ${user.userId}? Private data will be deleted. Shared data will not be deleted. This action cannot be undone.`);\n  if (!confirmed) return;\n  await fetch(`${db.cloud.options?.databaseUrl}/users/${user.userId}`, {\n    method: 'DELETE',\n    headers: {\n      Authorization: `Bearer ${user.accessToken}`,\n    },\n  });\n  await db.cloud.logout({force: true});\n}\n\nexport default App;\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/AddTodoItem.tsx",
    "content": "import { useState } from 'react';\nimport { db } from '../db';\nimport { TodoItem } from '../db/TodoItem';\nimport { TodoList } from '../db/TodoList';\nimport { Plus } from 'lucide-react';\nimport { Input } from './ui/input';\nimport { Button } from './ui/button';\n\ninterface Props {\n  todoList: TodoList;\n  autoFocus?: boolean;\n}\n\nexport function AddTodoItem({ todoList, autoFocus }: Props) {\n  const [title, setTitle] = useState('');\n\n  const handleSubmit = async (ev: React.FormEvent) => {\n    ev.preventDefault();\n    if (title.trim()) {\n      await db.todoItems.add({\n        todoListId: todoList.id,\n        realmId: todoList.realmId,\n        title: title.trim(),\n      });\n      setTitle('');\n    }\n  };\n\n  return (\n    <form onSubmit={handleSubmit} className=\"flex gap-2\">\n      <div className=\"flex-1\">\n        <Input\n          type=\"text\"\n          placeholder=\"Add todo item ...\"\n          value={title}\n          autoFocus={autoFocus}\n          onChange={(ev) => setTitle(ev.target.value)}\n        />\n      </div>\n      <Button \n        type=\"submit\" \n        size=\"icon\"\n        disabled={!title.trim()}\n        title=\"Add item\"\n      >\n        <Plus className=\"h-4 w-4\" />\n      </Button>\n    </form>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/AddTodoList.tsx",
    "content": "import { List, Plus } from \"lucide-react\";\nimport { useLiveQuery } from \"dexie-react-hooks\";\nimport { useState } from \"react\";\nimport { db } from \"../db\";\nimport { Button } from \"./ui/button\";\nimport { Input } from \"./ui/input\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"./ui/card\";\n\nexport function AddTodoList() {\n  const [isActive, setIsActive] = useState(false);\n  const [title, setTitle] = useState(\"\");\n  const hasAnyList = useLiveQuery(async () => {\n    const listCount = await db.todoLists.count();\n    return listCount > 0;\n  });\n\n  const handleSubmit = () => {\n    if (title.trim()) {\n      db.todoLists.add({ title: title.trim() });\n      setTitle(\"\");\n      setIsActive(false);\n    }\n  };\n\n  return !isActive ? (\n    <Button \n      onClick={() => setIsActive(true)}\n      size=\"lg\"\n      variant=\"outline\"\n      className=\"w-full sm:w-auto flex items-center gap-2 border-slate-300 text-slate-700 hover:bg-slate-50\"\n    >\n      <List className=\"h-4 w-4\" />\n      {hasAnyList ? `Add another list` : `Create ToDo List`}\n    </Button>\n  ) : (\n    <Card className=\"w-full sm:max-w-md\">\n      <CardHeader>\n        <CardTitle className=\"flex items-center gap-2\">\n          <Plus className=\"h-5 w-5\" />\n          New Todo List\n        </CardTitle>\n        <CardDescription>Give your list a name to get started</CardDescription>\n      </CardHeader>\n      <CardContent className=\"space-y-4\">\n        <Input\n          type=\"text\"\n          autoFocus\n          placeholder=\"Name of list...\"\n          value={title}\n          onChange={(ev) => setTitle(ev.target.value)}\n          onKeyUp={(ev) => {\n            if (ev.key === \"Enter\") {\n              handleSubmit();\n            } else if (ev.key === \"Escape\") {\n              setIsActive(false);\n              setTitle(\"\");\n            }\n          }}\n        />\n        <div className=\"flex gap-2\">\n          <Button onClick={handleSubmit} disabled={!title.trim()}>\n            Create List\n          </Button>\n          <Button \n            variant=\"outline\" \n            onClick={() => {\n              setIsActive(false);\n              setTitle(\"\");\n            }}\n          >\n            Cancel\n          </Button>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/ResetDatabaseButton.tsx",
    "content": "import { RotateCcw } from 'lucide-react';\nimport { db } from '../db';\nimport { Button } from './ui/button';\n\nexport function ResetDatabaseButton() {\n  const handleReset = async () => {\n    const confirmed = confirm(\n      'Are you sure you want to reset the database? This will delete all your local data and reload the app.'\n    );\n    \n    if (confirmed) {\n      await db.delete();\n      location.reload(); // Reload the page to reset application state hard.\n    }\n  };\n\n  return (\n    <Button\n      variant=\"outline\"\n      size=\"lg\"\n      onClick={handleReset}\n      className=\"w-full sm:w-auto flex items-center gap-2 border-red-300 text-red-700 bg-red-50 hover:bg-red-100 hover:border-red-400\"\n    >\n      <RotateCcw className=\"h-4 w-4\" />\n      Factory reset client\n    </Button>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/TodoItemView.tsx",
    "content": "import { useEffect, useState } from 'react';\nimport { db } from '../db';\nimport { TodoItem } from '../db/TodoItem';\nimport { Trash2 } from 'lucide-react';\nimport { usePermissions } from 'dexie-react-hooks';\nimport { Button } from './ui/button';\nimport { CheckedSign } from './ui/CheckedSign';\nimport { cn } from '../lib/utils';\nimport { handleError } from '../helpers/handleError';\n\ninterface Props {\n  item: TodoItem;\n}\n\nexport function TodoItemView({ item }: Props) {\n  const can = usePermissions(db, 'todoItems', item);\n  const [isHovering, setIsHovering] = useState(false);\n  const [showTrash, setShowTrash] = useState(false);\n  const [isEditing, setIsEditing] = useState(false);\n  \n  const handleToggle = (checked: boolean) => {\n    db.todoItems.update(item.id, {\n      done: checked,\n    });\n  };\n\n  const handleDelete = async () => {\n    await db.todoItems.delete(item.id!);\n  };\n\n  const handleStartEdit = () => {\n    if (can.update('title')) {\n      setIsEditing(true);\n    }\n  };\n\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === 'Enter') {\n      e.preventDefault();\n      setIsEditing(false);\n    } else if (e.key === 'Escape') {\n      e.preventDefault();\n      setIsEditing(false);\n    }\n  };\n\n  useEffect(() => {\n    // On hover, show trash icon only if user can delete. After 2 seconds of hover, start fading out.\n    let timer: NodeJS.Timeout;\n    \n    if (isHovering && can.delete()) {\n      setShowTrash(true);\n      // After 2 seconds of hover, start fading out\n      timer = setTimeout(() => {\n        setShowTrash(false);\n      }, 2000);\n    } else {\n      setShowTrash(false);\n    }\n    \n    return () => {\n      if (timer) clearTimeout(timer);\n    };\n  }, [isHovering, can]);\n\n  const showTrashOnClick = (e: React.MouseEvent) => {\n    // Show trash on any click within the item (for mobile users)\n    if (can.delete()) {\n      setShowTrash(true);\n      // Reset hover state to trigger useEffect timer\n      setIsHovering(false);\n      setTimeout(() => setIsHovering(true), 10);\n    }\n  };\n\n  return (\n    <div \n      className={cn(\n        \"group flex items-center gap-3 py-3 px-4 border-b border-blue-200/60 last:border-b-0 transition-colors\",\n        item.done \n          ? \"bg-blue-50 dark:bg-blue-900/20\" \n          : \"bg-background hover:bg-muted/30\"\n      )}\n      onMouseEnter={() => setIsHovering(true)}\n      onMouseLeave={() => setIsHovering(false)}\n      onClick={showTrashOnClick}\n    >\n      {/* Custom Checkbox */}\n      <label className=\"relative flex items-center cursor-pointer\">\n        <input\n          type=\"checkbox\"\n          disabled={!can.update('done')}\n          checked={!!item.done}\n          onChange={(ev) => handleToggle(ev.target.checked)}\n          className=\"sr-only\"\n        />\n        <div className={cn(\n          \"w-6 h-6 border-2 rounded flex items-center justify-center transition-all duration-200\",\n          item.done \n            ? \"bg-blue-500 border-blue-500 text-white shadow-sm\" \n            : \"border-gray-300 hover:border-blue-400 bg-white\",\n          !can.update('done') && \"opacity-50 cursor-not-allowed\"\n        )}>\n          {item.done && (\n            <CheckedSign />\n          )}\n        </div>\n      </label>\n      \n      {/* Editable Title */}\n      <div className=\"flex-1\">\n        {isEditing ? (\n          <input\n            type=\"text\"\n            defaultValue={item.title}\n            onChange={handleError(\n                (ev) => db.todoItems.update(item.id, { title: ev.target.value })\n            )}\n            onKeyDown={handleKeyDown}\n            onBlur={() => setIsEditing(false)}\n            autoFocus\n            className={cn(\n              \"w-full text-sm leading-relaxed bg-transparent border-none outline-none focus:ring-1 focus:ring-blue-500 rounded px-1 py-0.5\",\n              item.done ? \"text-foreground/80\" : \"text-foreground\"\n            )}\n          />\n        ) : (\n          <div \n            className={cn(\n              \"text-sm leading-relaxed cursor-pointer hover:bg-muted/30 rounded px-1 py-0.5 transition-colors\",\n              item.done ? \"text-foreground/80\" : \"text-foreground\",\n              !can.update('title') && \"cursor-default hover:bg-transparent\"\n            )}\n            onClick={handleStartEdit}\n            title={can.update('title') ? \"Click to edit\" : undefined}\n          >\n            {item.title}\n          </div>\n        )}\n      </div>\n      \n      <Button\n        variant=\"ghost\"\n        size=\"icon\"\n        onClick={handleDelete}\n        title=\"Delete item\"\n        className={cn(\n          \"h-8 w-8 text-muted-foreground hover:text-destructive transition-all duration-300\",\n          showTrash \n            ? \"opacity-100 translate-x-0\" \n            : \"opacity-0 translate-x-2 pointer-events-none\"\n        )}\n      >\n        <Trash2 className=\"h-4 w-4\" />\n      </Button>\n    </div>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/TodoListView.tsx",
    "content": "import { useState } from 'react';\nimport { useLiveQuery, usePermissions } from 'dexie-react-hooks';\nimport { TodoList } from '../db/TodoList';\nimport { db } from '../db';\nimport { TodoItemView } from './TodoItemView';\nimport { AddTodoItem } from './AddTodoItem';\nimport { Share2, Trash2 } from 'lucide-react';\nimport { SharingForm } from './access-control/SharingForm';\nimport { usePersistedOpenState } from '../helpers/usePersistedOpenState';\nimport { Button } from './ui/button';\nimport { handleError } from '../helpers/handleError';\nimport { cn } from '../lib/utils';\n\ninterface Props {\n  todoList: TodoList;\n  autoFocus?: boolean;\n}\n\nexport function TodoListView({ todoList, autoFocus }: Props) {\n  const [isEditingTitle, setIsEditingTitle] = useState(false);\n  const items = useLiveQuery(\n    () => db.todoItems\n      .where({ todoListId: todoList.id })\n      .reverse() // Show newest items first\n      .toArray(),\n    [todoList.id]\n  );\n  const can = usePermissions(todoList);\n  const [showInviteForm, setShowInviteForm] = usePersistedOpenState('sharing-menu', todoList.id, false);\n\n  if (!items) return null;\n\n  const handleDelete = async () => {\n    const confirmed = confirm(`Are you sure you want to delete \"${todoList.title}\" and all its items?`);\n    if (confirmed) {\n      await todoList.deleteList();\n    }\n  };\n\n  const handleStartTitleEdit = () => {\n    if (can.update('title')) {\n      setIsEditingTitle(true);\n    }\n  };\n\n  const handleTitleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === 'Enter') {\n      e.preventDefault();\n      setIsEditingTitle(false);\n    } else if (e.key === 'Escape') {\n      e.preventDefault();\n      setIsEditingTitle(false);\n    }\n  };\n\n  return (\n    <div className=\"border-b border-border bg-background\">\n      {/* Header */}\n      <div className=\"flex items-center justify-between px-4 py-4 border-b border-blue-300/70 bg-blue-500 dark:bg-blue-600\">\n        {/* Editable Title */}\n        <div className=\"flex-1 mr-4\">\n          {isEditingTitle ? (\n            <input\n              type=\"text\"\n              defaultValue={todoList.title}\n              onChange={handleError(\n                (ev) => db.todoLists.update(todoList.id, { title: ev.target.value })\n              )}\n              onKeyDown={handleTitleKeyDown}\n              onBlur={() => setIsEditingTitle(false)}\n              autoFocus\n              className={cn(\n                \"text-lg font-semibold bg-transparent border-none outline-none focus:ring-1 focus:ring-white/50 rounded px-2 py-1 text-white placeholder-white/70 w-full\"\n              )}\n            />\n          ) : (\n            <h2\n              className={cn(\n                \"text-lg font-semibold text-white cursor-pointer hover:bg-white/10 rounded px-2 py-1 transition-colors\",\n                !can.update('title') && \"cursor-default hover:bg-transparent\"\n              )}\n              onClick={handleStartTitleEdit}\n              title={can.update('title') ? \"Click to edit list name\" : undefined}\n            >\n              {todoList.title}\n            </h2>\n          )}\n        </div>\n        <div className=\"flex items-center gap-2\">\n\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            onClick={() => setShowInviteForm(!showInviteForm)}\n            title=\"Share list\"\n            className={cn(\n              \"text-white hover:bg-blue-600 transition-all duration-200\",\n              showInviteForm && \"bg-blue-600/50\"\n            )}\n          >\n            <Share2 className={cn(\n              \"h-4 w-4 transition-transform duration-200\",\n              showInviteForm && \"rotate-180\"\n            )} />\n          </Button>\n\n          {can.delete() && (\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              onClick={handleDelete}\n              title=\"Delete list\"\n              className=\"text-white hover:bg-blue-600\"\n            >\n              <Trash2 className=\"h-4 w-4\" />\n            </Button>\n          )}\n        </div>\n      </div>\n\n      {/* Sharing Form */}\n      <div\n        className={cn(\n          \"overflow-hidden transition-all duration-300 ease-in-out\",\n          showInviteForm\n            ? \"max-h-[600px] opacity-100\"\n            : \"max-h-0 opacity-0\"\n        )}\n      >\n        <div className=\"px-4 py-4 bg-blue-50/70 dark:bg-blue-900/15 border-b border-blue-200/60\">\n          <SharingForm todoList={todoList} />\n        </div>\n      </div>\n\n      {/* Todo Items */}\n      <div className=\"px-0 py-0\">\n        {can.add('todoItems') && (\n          <div className=\"px-4 py-3 border-b border-blue-200/60 bg-background\">\n            <AddTodoItem todoList={todoList} autoFocus={autoFocus} />\n          </div>\n        )}\n        {items.map((item) => (\n          <TodoItemView key={item.id} item={item} />\n        ))}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/TodoLists.tsx",
    "content": "import { useLiveQuery } from \"dexie-react-hooks\";\nimport { db } from \"../db\";\nimport { TodoListView } from \"./TodoListView\";\n\nexport function TodoLists() {\n  const lists = useLiveQuery(\n    () => db.todoLists\n      .reverse() // Show newest lists first\n      .toArray()\n  );\n  if (!lists) return null;\n\n  return (\n    <div className=\"w-full\">\n      {lists.map((list, i) => (\n        <TodoListView key={list.id} todoList={list} autoFocus={i === 0} />\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/access-control/EditMember.tsx",
    "content": "import { User, Crown } from 'lucide-react';\nimport { DBRealmMember } from 'dexie-cloud-addon';\nimport { useObservable, usePermissions } from 'dexie-react-hooks';\nimport { db, TodoList } from '../../db';\nimport { EditMemberAccess } from './EditMemberAccess';\n\ninterface Props {\n  member: DBRealmMember;\n  todoList: TodoList;\n}\n\nexport function EditMember({ member, todoList }: Props) {\n  const can = usePermissions(db, 'members', member);\n  const globalRoles = useObservable(db.cloud.roles);\n  const roleName = member.roles?.[0];\n  const role = roleName ? globalRoles?.[roleName] : null;\n\n  const memberAccess =\n    member.userId === todoList.owner ? 'owner' : roleName || 'readonly';\n\n  const memberAccessDisplayName =\n    memberAccess === 'owner' ? 'Owner' : role?.displayName || memberAccess;\n\n  const getIcon = () => {\n    if (memberAccess === 'owner') return <Crown className=\"h-4 w-4\" />;\n    return <User className=\"h-4 w-4\" />;\n  };\n\n  return (\n    <div className=\"flex items-center gap-2 px-2 py-1 bg-gray-50 rounded border border-gray-200 min-w-[100px]\">\n      {getIcon()}\n      {can.update('roles') ? (\n        <EditMemberAccess\n          todoList={todoList}\n          member={member}\n          access={memberAccess}\n        />\n      ) : (\n        <span className=\"text-sm font-medium\">{memberAccessDisplayName}</span>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/access-control/EditMemberAccess.tsx",
    "content": "import { DBRealmMember } from 'dexie-cloud-addon';\nimport { useObservable } from 'dexie-react-hooks';\nimport { db, TodoList } from '../../db';\n\ninterface Props {\n  todoList: TodoList;\n  member: DBRealmMember;\n  access: string;\n}\n\nexport function EditMemberAccess({ todoList, member, access }: Props) {\n  const roles = useObservable(db.cloud.roles) || {};\n  return (\n    <select\n      disabled={\n        todoList.owner === member.userId &&\n        member.userId === db.cloud.currentUserId\n      }\n      style={{ border: 0 }}\n      value={access}\n      onChange={(ev) => changeAccess(todoList, member, access, ev.target.value)}\n    >\n      <option value=\"owner\" disabled={!member.userId}>\n        Owner\n      </option>\n      {Object.entries(roles).map(([roleName, role]) => (\n        <option key={roleName} value={roleName}>\n          {role.displayName}\n        </option>\n      ))}\n      {!roles[access] && access !== 'owner' && <option key={access}>(unknown)</option>}\n    </select>\n  );\n}\n\nasync function changeAccess(\n  todoList: TodoList,\n  member: DBRealmMember,\n  existingAccess: string,\n  newAccess: string\n) {\n  if (newAccess === 'owner') {\n    //\n    // Assigning ownership\n    //\n    if (!member.userId) {\n      throw new Error(\n        `Cannot give ownership to user before invite is accepted.`\n      );\n    }\n    if (existingAccess === 'owner') {\n      // Already owner - no change\n      return;\n    } else {\n      // Change ownership to this member\n      return todoList.changeOwner(member.userId);\n    }\n  } else {\n    //\n    // Assigning role\n    //\n    if (member.userId === todoList.owner) {\n      // The user wants to change the current owner to get another role.\n      // Assume user wants to deassign the ownership first to someone else.\n      // Since somebody has to be owner, transfer ownership to the actor (current user).\n      await todoList.changeOwner(db.cloud.currentUserId);\n    }\n    await todoList.changeMemberRole(member, newAccess);\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/access-control/Invites.tsx",
    "content": "import { Trash2, Users } from 'lucide-react';\nimport { useObservable } from 'react-use';\nimport { db } from '../../db';\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';\nimport { Button } from '../ui/button';\n\nexport function Invites() {\n  const invites = useObservable(db.cloud.invites, []);\n  if (invites.length === 0) return null;\n  \n  return (\n    <Card>\n      <CardHeader>\n        <CardTitle className=\"flex items-center gap-2\">\n          <Users className=\"h-5 w-5\" />\n          You've got invited!\n        </CardTitle>\n        <CardDescription>\n          You have {invites.length} pending invitation{invites.length > 1 ? 's' : ''}\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"space-y-4\">\n          {invites.map((invite) => (\n            <div key={invite.id} className=\"flex items-center justify-between p-4 border rounded-lg\">\n              <div className=\"flex-1\">\n                <div className=\"font-medium\">{invite.realm?.name}</div>\n                <div className=\"text-sm text-muted-foreground\">\n                  Invited by {invite.invitedBy?.name}\n                </div>\n              </div>\n              <div className=\"flex items-center gap-2\">\n                <Button\n                  size=\"sm\"\n                  onClick={() => invite.accept()}\n                  disabled={!!invite.accepted}\n                  variant={invite.accepted ? \"secondary\" : \"default\"}\n                >\n                  {invite.accepted ? 'Accepted' : 'Accept'}\n                </Button>\n                <Button\n                  size=\"sm\"\n                  variant=\"outline\"\n                  onClick={() => invite.reject()}\n                  disabled={!!invite.rejected}\n                >\n                  {invite.rejected ? 'Rejected' : 'Reject'}\n                </Button>\n                <Button\n                  size=\"sm\"\n                  variant=\"ghost\"\n                  onClick={() => db.members.delete(invite.id!)}\n                  title=\"Remove invite\"\n                >\n                  <Trash2 className=\"h-4 w-4\" />\n                </Button>\n              </div>\n            </div>\n          ))}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/access-control/SharingForm.tsx",
    "content": "import { useState } from 'react';\nimport { TodoList } from '../../db/TodoList';\nimport { useLiveQuery, usePermissions } from 'dexie-react-hooks';\nimport { db } from '../../db';\nimport { DBRealmMember } from 'dexie-cloud-addon';\nimport { Trash2, Mail, UserPlus } from 'lucide-react';\nimport { EditMember } from './EditMember';\nimport { Button } from '../ui/button';\nimport { Input } from '../ui/input';\nimport demoUsersJson from '../../data/demoUsers.json';\n\ninterface Props {\n  todoList: TodoList;\n}\n\nexport function SharingForm({ todoList }: Props) {\n  const [name, setName] = useState('');\n  const [email, setEmail] = useState('');\n  const members = useLiveQuery(async () => {\n    const result = await db.members\n      .where({ realmId: todoList.realmId })\n      .toArray();\n    return result;\n  }, [todoList.realmId]);\n  const [manualInviteOpen, setManualInviteOpen] = useState(false);\n\n  const can = usePermissions(todoList);\n\n  return (\n    <div className=\"space-y-3\">\n      {members && members.length > 0 && (\n        <div>\n          <h4 className=\"text-sm font-medium text-foreground mb-2\">Shared with:</h4>\n          <div className=\"space-y-1\">\n            {members?.map((member) => (\n              <MemberRow key={member.id} {...{ todoList, member }} />\n            ))}\n          </div>\n        </div>\n      )}\n      \n      <div className=\"border-t border-border pt-3\">\n        {can.add('members') && (\n          <div className=\"space-y-3\">\n            <h4 className=\"text-sm font-medium text-foreground flex items-center gap-2\">\n              <UserPlus className=\"h-4 w-4\" />\n              Invite someone?\n            </h4>\n            \n            <div className=\"space-y-1\">\n              {Object.keys(demoUsersJson.demoUsers)\n                .filter((demoUser) => demoUser !== db.cloud.currentUserId)\n                .map((demoUser) => (\n                  <div key={demoUser} className=\"flex items-center justify-between py-2 px-3 bg-white rounded border border-blue-100\">\n                    <span className=\"text-sm text-foreground\">{demoUser}</span>\n                    <Button\n                      size=\"sm\"\n                      variant=\"outline\"\n                      onClick={() => {\n                        setManualInviteOpen(false);\n                        todoList.shareWith(demoUser, demoUser, true, ['doer']);\n                      }}\n                      className=\"h-7 px-3 text-xs border-blue-200 text-blue-600 hover:bg-blue-50\"\n                    >\n                      Invite\n                    </Button>\n                  </div>\n                ))}\n            </div>\n            \n            {manualInviteOpen ? (\n              <form\n                onSubmit={(ev) => {\n                  ev.preventDefault();\n                  setManualInviteOpen(false);\n                  todoList.shareWith(name, email, true, ['doer']);\n                  setName('');\n                  setEmail('');\n                }}\n                className=\"space-y-3 p-4 bg-muted/50 rounded-lg\"\n              >\n                <Input\n                  type=\"email\"\n                  placeholder=\"Email address\"\n                  value={email}\n                  onChange={(ev) => setEmail(ev.target.value)}\n                />\n                <Input\n                  type=\"text\"\n                  placeholder=\"Name\"\n                  value={name}\n                  onChange={(ev) => setName(ev.target.value)}\n                />\n                <div className=\"flex gap-2\">\n                  <Button \n                    type=\"submit\" \n                    disabled={!/@/.test(email)}\n                    size=\"sm\"\n                    className=\"flex items-center gap-2\"\n                  >\n                    <Mail className=\"h-4 w-4\" />\n                    Send invite\n                  </Button>\n                  <Button \n                    type=\"button\" \n                    variant=\"outline\" \n                    size=\"sm\"\n                    onClick={() => setManualInviteOpen(false)}\n                  >\n                    Cancel\n                  </Button>\n                </div>\n              </form>\n            ) : (\n              <div className=\"mt-2\">\n                <Button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  onClick={() => setManualInviteOpen(true)}\n                  className=\"flex items-center gap-2 h-8 px-3 text-xs border-blue-200 text-blue-600 hover:bg-blue-50\"\n                >\n                  <Mail className=\"h-4 w-4\" />\n                  Invite by email address\n                </Button>\n              </div>\n            )}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n\nfunction MemberRow({\n  member,\n  todoList,\n}: {\n  member: DBRealmMember;\n  todoList: TodoList;\n}) {\n  const can = usePermissions(db, 'members', member);\n  const isMe = member.userId === db.cloud.currentUserId;\n  const isOwner = member.userId === todoList.owner;\n  let memberText = member.name || member.email || member.userId;\n  if (isMe) memberText += ' (me)';\n\n  const handleUnshare = async (member: DBRealmMember) => {\n    await todoList.unshareWith(member);\n    const numOtherPeople = await db.members\n          .where({ realmId: todoList.realmId })\n          .filter((m) => m.userId !== db.cloud.currentUserId)\n          .count();\n    if (numOtherPeople === 0) {\n      // If you removed the last other person, you are now the sole owner.\n      // Remove all other members (there should be none) and make the list\n      // private again.\n      await todoList.makePrivate();\n    }\n  }\n  \n\n  return (\n    <div className={`flex items-center justify-between py-2 px-3 bg-white rounded border border-blue-100 ${\n      member.accepted ? '' : 'opacity-50'\n    }`}>\n      <div className=\"flex-1\">\n        <span className=\"text-sm text-foreground font-medium\">{memberText}</span>\n        {!member.rejected && !member.accepted && (\n          <span className=\"text-xs text-muted-foreground italic ml-2\">Pending invite</span>\n        )}\n        {member.rejected && (\n          <span className=\"text-xs text-muted-foreground italic ml-2\">Rejected</span>\n        )}\n      </div>\n      \n      <div className=\"flex items-center gap-2 min-w-[140px] justify-end\">\n        <EditMember member={member} todoList={todoList} />\n        \n        <div className=\"w-8 flex justify-center ml-1\">\n          {can.delete() && !isOwner ? (\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              onClick={() => handleUnshare(member)}\n              className=\"text-red-500 hover:text-red-700 hover:bg-red-50 h-8 w-8 p-0\"\n            >\n              <Trash2 className=\"h-4 w-4\" />\n            </Button>\n          ) : (\n            !isOwner &&\n            member.userId === db.cloud.currentUserId &&\n            ((member.accepted?.getTime() || 0) >\n            (member.rejected?.getTime() || 0) ? (\n              <Button \n                variant=\"outline\" \n                size=\"sm\" \n                onClick={() => todoList.leaveList()}\n                className=\"h-7 px-3 text-xs border-red-300 text-red-600 hover:bg-red-50 hover:border-red-400 min-w-[50px]\"\n              >\n                Leave\n              </Button>\n            ) : (\n              <Button\n                variant=\"default\"\n                size=\"sm\"\n                onClick={() =>\n                  db.members.update(member.id!, {\n                    accepted: new Date(),\n                    rejected: undefined,\n                  })\n                }\n                className=\"h-7 px-3 text-xs bg-blue-500 hover:bg-blue-600\"\n              >\n                Accept\n              </Button>\n            ))\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/navbar/NavBar.tsx",
    "content": "import { useState } from 'react';\nimport { Menu, ChevronDown, User, LogOut } from 'lucide-react';\nimport { useObservable } from 'react-use';\nimport { db } from '../../db';\nimport { SyncStatusIcon } from './SyncStatusIcon';\nimport { Button } from '../ui/button';\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '../ui/dropdown-menu';\nimport demoUsersJson from '../../data/demoUsers.json';\nimport { handleError } from '../../helpers/handleError';\nimport { logout } from '../../db/logout';\n\nexport function NavBar() {\n  const currentUser = useObservable(db.cloud.currentUser);\n  const [isMenuOpen, setIsMenuOpen] = useState(false);\n\n  return (\n    <nav className=\"fixed top-0 left-0 right-0 z-50 bg-background border-b border-border\">\n      <div className=\"mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\">\n        <div className=\"flex h-16 items-center justify-between\">\n          {/* Logo/Brand */}\n          <div className=\"flex items-center\">\n            <h1 className=\"text-xl font-semibold text-foreground\">\n              Dexie Cloud ToDo App\n            </h1>\n          </div>\n\n          {/* Desktop Menu */}\n          <div className=\"hidden md:flex items-center space-x-4\">\n            <SyncStatusIcon className=\"h-5 w-5\" />\n            {currentUser?.isLoggedIn ? (\n              <DropdownMenu>\n                <DropdownMenuTrigger>\n                  <Button variant=\"ghost\" className=\"flex items-center gap-2\">\n                    <User className=\"h-4 w-4\" />\n                    {currentUser.name}\n                    <ChevronDown className=\"h-4 w-4\" />\n                  </Button>\n                </DropdownMenuTrigger>\n                <DropdownMenuContent>\n                  <DropdownMenuItem onClick={() => logout()}>\n                    <LogOut className=\"mr-2 h-4 w-4\" />\n                    Logout\n                  </DropdownMenuItem>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            ) : (\n              <DropdownMenu>\n                <DropdownMenuTrigger>\n                  <Button variant=\"outline\" className=\"flex items-center gap-2\">\n                    Sign in or create account\n                    <ChevronDown className=\"h-4 w-4\" />\n                  </Button>\n                </DropdownMenuTrigger>\n                <DropdownMenuContent>\n                  <DropdownMenuLabel>Sign in a demo user</DropdownMenuLabel>\n                  {Object.keys(demoUsersJson.demoUsers).map((email) => (\n                    <DropdownMenuItem\n                      key={email}\n                      onClick={handleError(() =>\n                        db.cloud.login({ grant_type: 'demo', email })\n                      )}\n                    >\n                      {email}\n                    </DropdownMenuItem>\n                  ))}\n                  <DropdownMenuSeparator />\n                  <DropdownMenuLabel>Sign in real user</DropdownMenuLabel>\n                  <DropdownMenuItem\n                    onClick={handleError(() => db.cloud.login())}\n                  >\n                    Sign in / sign up yourself\n                  </DropdownMenuItem>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            )}\n          </div>\n\n          {/* Mobile Menu Button */}\n          <div className=\"md:hidden flex items-center space-x-2\">\n            <SyncStatusIcon className=\"h-5 w-5\" />\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              onClick={() => setIsMenuOpen(!isMenuOpen)}\n            >\n              <Menu className=\"h-5 w-5\" />\n            </Button>\n          </div>\n        </div>\n\n        {/* Mobile Menu */}\n        {isMenuOpen && (\n          <div className=\"md:hidden border-t border-border\">\n            <div className=\"px-2 pt-2 pb-3 space-y-1\">\n              {currentUser?.isLoggedIn ? (\n                <div className=\"space-y-1\">\n                  <div className=\"px-3 py-2 text-sm text-muted-foreground\">\n                    Signed in as {currentUser.name}\n                  </div>\n                  <Button\n                    variant=\"ghost\"\n                    className=\"w-full justify-start\"\n                    onClick={() => {\n                      logout();\n                      setIsMenuOpen(false);\n                    }}\n                  >\n                    <LogOut className=\"mr-2 h-4 w-4\" />\n                    Logout\n                  </Button>\n                </div>\n              ) : (\n                <div className=\"space-y-1\">\n                  <div className=\"px-3 py-2 text-sm font-medium text-muted-foreground\">\n                    Sign in a demo user\n                  </div>\n                  {Object.keys(demoUsersJson.demoUsers).map((email) => (\n                    <Button\n                      key={email}\n                      variant=\"ghost\"\n                      className=\"w-full justify-start\"\n                      onClick={handleError(async () => {\n                        await db.cloud.login({ grant_type: 'demo', email });\n                        setIsMenuOpen(false);\n                      })}\n                    >\n                      {email}\n                    </Button>\n                  ))}\n                  <div className=\"border-t border-border my-2\"></div>\n                  <div className=\"px-3 py-2 text-sm font-medium text-muted-foreground\">\n                    Sign in real user\n                  </div>\n                  <Button\n                    variant=\"outline\"\n                    className=\"w-full justify-start\"\n                    onClick={handleError(async () => {\n                      await db.cloud.login();\n                      setIsMenuOpen(false);\n                    })}\n                  >\n                    Sign in / sign up yourself\n                  </Button>\n                </div>\n              )}\n            </div>\n          </div>\n        )}\n      </div>\n    </nav>\n  );\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/navbar/SyncStatusIcon.tsx",
    "content": "import { \n  Cloud, \n  CloudMoon,\n  Wifi, \n  WifiOff, \n  AlertCircle, \n  Loader2 \n} from 'lucide-react';\nimport { useObservable } from 'react-use';\nimport { db } from '../../db';\nimport { cn } from '../../lib/utils';\n\ninterface Props {\n  className?: string;\n}\n\nexport function SyncStatusIcon({ className }: Props) {\n  const syncStatus = useObservable(db.cloud.syncState);\n  \n  const iconClassName = cn(\"text-muted-foreground\", className);\n  \n  switch (syncStatus?.status) {\n    case 'not-started':\n      return (\n        <div title=\"Sync not started\">\n          <Cloud className={iconClassName} />\n        </div>\n      );\n    case 'connecting':\n      return (\n        <div title=\"Connecting to cloud...\">\n          <Loader2 className={cn(iconClassName, \"animate-spin\")} />\n        </div>\n      );\n    case 'connected':\n      return (\n        <div title=\"Connected and syncing\">\n          <Wifi className={cn(iconClassName, \"text-green-500\")} />\n        </div>\n      );\n    case 'disconnected':\n      // Use CloudMoon to suggest \"sleeping/dormant\" rather than \"off\"\n      return (\n        <div title=\"Connection sleeping - will reconnect when active\">\n          <CloudMoon className={cn(iconClassName, \"text-blue-400\")} />\n        </div>\n      );\n    case 'offline':\n      return (\n        <div title={syncStatus.error ? String(syncStatus.error) : \"Offline\"}>\n          <WifiOff className={cn(iconClassName, \"text-orange-500\")} />\n        </div>\n      );\n    case 'error':\n      return (\n        <div title={syncStatus.error ? String(syncStatus.error) : \"Connection error\"}>\n          <AlertCircle className={cn(iconClassName, \"text-red-500\")} />\n        </div>\n      );\n    default:\n      return (\n        <div title=\"Sync status unknown\">\n          <Cloud className={iconClassName} />\n        </div>\n      );\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/ui/CheckedSign.tsx",
    "content": "import { cn } from '../../lib/utils';\n\ninterface CheckedSignProps {\n  className?: string;\n}\n\nexport function CheckedSign({ className }: CheckedSignProps) {\n  return (\n    <svg className={cn(\"w-4 h-4 fill-current\", className)} viewBox=\"0 0 20 20\">\n      <path d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\"/>\n    </svg>\n  );\n}"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/ui/button.tsx",
    "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90\",\n        outline:\n          \"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground\",\n        secondary:\n          \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2\",\n        sm: \"h-8 rounded-md px-3 text-xs\",\n        lg: \"h-10 rounded-md px-8\",\n        icon: \"h-9 w-9\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    return (\n      <button\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/ui/card.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Card = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      \"rounded-xl border bg-card text-card-foreground shadow\",\n      className\n    )}\n    {...props}\n  />\n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"flex flex-col space-y-1.5 p-6\", className)} {...props} />\n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n  <h3\n    ref={ref}\n    className={cn(\"font-semibold leading-none tracking-tight\", className)}\n    {...props}\n  />\n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n  <p\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex items-center p-6 pt-0\", className)}\n    {...props}\n  />\n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/ui/checkbox.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"../../lib/utils\"\n\nexport interface CheckboxProps\n  extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(\n  ({ className, ...props }, ref) => {\n    return (\n      <input\n        type=\"checkbox\"\n        className={cn(\n          \"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nCheckbox.displayName = \"Checkbox\"\n\nexport { Checkbox }"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/ui/dropdown-menu.tsx",
    "content": "import * as React from \"react\"\nimport { cn } from \"../../lib/utils\"\n\nconst DropdownMenu = ({ children, ...props }: React.ComponentProps<\"div\">) => {\n  const [isOpen, setIsOpen] = React.useState(false)\n  \n  return (\n    <div className=\"relative inline-block text-left\" {...props}>\n      {React.Children.map(children, (child) => {\n        if (React.isValidElement(child)) {\n          if (child.type === DropdownMenuTrigger) {\n            return React.cloneElement(child as React.ReactElement<any>, {\n              onClick: () => setIsOpen(!isOpen),\n              'aria-expanded': isOpen,\n            })\n          }\n          if (child.type === DropdownMenuContent) {\n            return React.cloneElement(child as React.ReactElement<any>, {\n              isOpen,\n              onClose: () => setIsOpen(false),\n            })\n          }\n        }\n        return child\n      })}\n    </div>\n  )\n}\n\nconst DropdownMenuTrigger = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<\"button\">\n>(({ className, children, ...props }, ref) => (\n  <button\n    ref={ref}\n    className={cn(\n      \"inline-flex w-full justify-center gap-x-1.5 rounded-md bg-background px-3 py-2 text-sm font-semibold text-foreground shadow-sm ring-1 ring-inset ring-border hover:bg-accent\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </button>\n))\nDropdownMenuTrigger.displayName = \"DropdownMenuTrigger\"\n\nconst DropdownMenuContent = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<\"div\"> & { \n    align?: \"start\" | \"end\"\n    isOpen?: boolean\n    onClose?: () => void\n  }\n>(({ className, align = \"end\", isOpen, onClose, children, ...props }, ref) => {\n  React.useEffect(() => {\n    const handleClickOutside = (event: MouseEvent) => {\n      if (ref && 'current' in ref && ref.current && !ref.current.contains(event.target as Node)) {\n        onClose?.()\n      }\n    }\n\n    if (isOpen) {\n      document.addEventListener('mousedown', handleClickOutside)\n      return () => document.removeEventListener('mousedown', handleClickOutside)\n    }\n  }, [isOpen, onClose, ref])\n\n  if (!isOpen) return null\n\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        \"absolute z-10 mt-2 w-56 origin-top-right rounded-md bg-popover shadow-lg ring-1 ring-border ring-opacity-5 focus:outline-none\",\n        align === \"end\" ? \"right-0\" : \"left-0\",\n        className\n      )}\n      {...props}\n    >\n      {React.Children.map(children, (child) => {\n        if (React.isValidElement(child) && child.type === DropdownMenuItem) {\n          return React.cloneElement(child as React.ReactElement<any>, {\n            onClick: (e: React.MouseEvent) => {\n              child.props.onClick?.(e)\n              onClose?.()\n            }\n          })\n        }\n        return child\n      })}\n    </div>\n  )\n})\nDropdownMenuContent.displayName = \"DropdownMenuContent\"\n\nconst DropdownMenuItem = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<\"button\">\n>(({ className, ...props }, ref) => (\n  <button\n    ref={ref}\n    className={cn(\n      \"block w-full px-4 py-2 text-left text-sm text-popover-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuItem.displayName = \"DropdownMenuItem\"\n\nconst DropdownMenuSeparator = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<\"div\">\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"my-1 h-px bg-border\", className)}\n    {...props}\n  />\n))\nDropdownMenuSeparator.displayName = \"DropdownMenuSeparator\"\n\nconst DropdownMenuLabel = React.forwardRef<\n  HTMLDivElement,\n  React.ComponentProps<\"div\">\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"px-4 py-2 text-xs font-semibold text-muted-foreground\", className)}\n    {...props}\n  />\n))\nDropdownMenuLabel.displayName = \"DropdownMenuLabel\"\n\nexport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuLabel,\n}"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/components/ui/input.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"../../lib/utils\"\n\nexport interface InputProps\n  extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n  ({ className, type, ...props }, ref) => {\n    return (\n      <input\n        type={type}\n        className={cn(\n          \"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nInput.displayName = \"Input\"\n\nexport { Input }"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/data/demoUsers.json",
    "content": "{\n  \"demoUsers\": {\n    \"alice@demo.local\": {},\n    \"bob@demo.local\": {},\n    \"foo@demo.local\": {},\n    \"bar@demo.local\": {}\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/data/roles.json",
    "content": "{\n  \"roles\": {\n    \"manager\": {\n      \"displayName\": \"Manager\",\n      \"description\": \"Members with this role gains full permissions within the realm pointed out by the member entry\",\n      \"sortOrder\": 1,\n      \"permissions\": { \"manage\": \"*\" }\n    },\n    \"doer\": {\n      \"displayName\": \"Doer\",\n      \"description\": \"Members with this role can add todoItems, manage own todoItems and mark other todoItems as done or undone\",\n      \"sortOrder\": 2,\n      \"permissions\": {\n        \"add\": [\"todoItems\"],\n        \"update\": {\n          \"todoItems\": [\"done\"]\n        }\n      }\n    },\n    \"readonly\": {\n      \"displayName\": \"ReadOnly\",\n      \"description\": \"Members with this role have no permissions to change any data\",\n      \"sortOrder\": 3,\n      \"permissions\": {}\n    }\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/db/TodoDB.ts",
    "content": "import Dexie, { Table } from 'dexie';\nimport dexieCloud, { DexieCloudTable } from 'dexie-cloud-addon';\nimport { TodoItem } from './TodoItem';\nimport { TodoList } from './TodoList';\n\nexport class TodoDB extends Dexie {\n  todoLists!: DexieCloudTable<TodoList, 'id'>;\n  todoItems!: DexieCloudTable<TodoItem, 'id'>;\n  openCloseStates!: Table<boolean, [string, string]>;\n\n  constructor() {\n    super('TodoDBCloud2', {\n      addons: [dexieCloud],\n      cache: \"immutable\"\n    });\n\n    this.version(15).stores({\n      todoLists: `@id, [realmId+id]`,\n      todoItems: `@id, realmId, [todoListId+realmId]`,\n      openCloseStates: `` // Set of open ids (persisted local state only)\n    });\n    this.todoLists.mapToClass(TodoList);\n\n    // Configure cloud:\n    //\n    // See docs: https://dexie.org/cloud/docs/db.cloud.configure()\n    //\n    this.cloud.configure({\n      unsyncedTables: ['openCloseStates'], // See also unsyncedProperties\n      databaseUrl: import.meta.env.VITE_DBURL!,\n      tryUseServiceWorker: true,\n      requireAuth: false,\n    });\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/db/TodoItem.ts",
    "content": "export interface TodoItem {\n  id: string;\n  realmId: string;\n  todoListId: string;\n  title: string;\n  owner: string;\n  done?: boolean;\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/db/TodoList.ts",
    "content": "import { Entity } from 'dexie';\nimport type { TodoDB } from './TodoDB';\nimport { DBRealmMember, getTiedRealmId } from 'dexie-cloud-addon';\n\n/** Since there are some actions associated with\n * this entity (shareWith(), unshareWith() etc) we encapsulate all\n * sync-consistent logic in these class methods.\n *\n * We could equally well have declared TodoList as an interface\n * and write helper functions on the side.\n *\n * The Entity base class tells dexie to inject db as a prop this.db.\n * This is to avoid recursive dependencies when you need to access\n * db from within a method.\n */\nexport class TodoList extends Entity<TodoDB> {\n  //\n  // Persisted Properties\n  //\n\n  id!: string;\n  realmId!: string;\n  owner!: string;\n  title!: string;\n\n  //\n  // Methods\n  //\n\n  isSharable() {\n    return this.realmId === getTiedRealmId(this.id);\n  }\n\n  /** Ensures the todoList is in a sharable state\n   * If the list is already sharable, does nothing.\n   * If the list is not sharable, it will be moved\n   * to a new realm with a deterministic id based on\n   * the todo-list id.\n   * \n   * The operation is sync-consistent and will work correctly\n   * whether the todo-list is shared or not while the operation\n   * is performed offline.\n   * \n   * If another client of the same user has added new todo-items\n   * to the list while offline, those items will be\n   * moved to the new realm as well.\n   */\n  async makeSharable() {\n    // Compute a deterministic realmId tied to this todoList:\n    const realmId = getTiedRealmId(this.id);\n\n    const db = this.db; // Entity<T> provides this.db - avoids cyclic deps.\n    await db.transaction('rw', [db.todoLists, db.todoItems, db.realms], () => {\n      // Make sure a realm exists (using a deterministic id based on the id of the\n      // todo-list)\n      // We use Table.upsert() instead of add(), put() here:\n      //   In case same user does this on two offline devices, we don't\n      //   want one of the actions to fail (which would be the case if using add())\n      //   and we don't want to overwrite existing props like owner (which\n      //   would be the case if using put())\n      db.realms.upsert(realmId, {\n        name: this.title,\n        represents: 'a to-do list',\n      });\n\n      // Move the todoList into the new realm\n      db.todoLists.update(this.id!, { realmId: realmId });\n\n      // Move all todo items into the new realm consistently\n      // (modify() is consistent across sync peers)\n      db.todoItems\n        .where({ todoListId: this.id })\n        .modify({ realmId: realmId });\n    });\n    return realmId;\n  }\n\n  /** Moves the list to the private realm and removes all members\n   * \n   * The operation is sync-consistent and will work correctly\n   * whether the todo-list is shared or not while the operation\n   * is performed offline.\n   * \n   * The operation may succeed offline but fail later during sync\n   * if someone else has removed our access in the meantime.\n   * If that happens, the server will roll-back the operation and\n   * restore the local data to mirror the server state.\n   */\n  async makePrivate() {\n    const tiedRealmId = getTiedRealmId(this.id);\n    const db = this.db;\n    await db.transaction(\n      'rw',\n      [db.todoLists, db.todoItems, db.members, db.realms],\n      async () => {\n        // Move todoItems out of the realm in a sync-consistent operation:\n        await db.todoItems\n          .where({\n            realmId: tiedRealmId,\n            todoListId: this.id,\n          })\n          .modify({\n            realmId: db.cloud.currentUserId,\n            owner: db.cloud.currentUserId,\n          });\n\n        // Move the todoList back into your private realm:\n        await db.todoLists.update(this.id, {\n          realmId: this.db.cloud.currentUserId,\n          owner: this.db.cloud.currentUserId,\n        });\n\n        // Remove all access (Collection.delete() is a sync-consistent operation)\n        await db.members.where('realmId').equals(tiedRealmId).delete();\n        // Delete tied realm\n        await db.realms.delete(tiedRealmId);\n      }\n    );\n  }\n\n  /** Share the todo list with a new person.\n   * \n   * This will create an invite for the person\n   * to accept.\n   * \n   * The operation is sync-consistent and will work correctly\n   * whether the todo-list is shared or not while the operation\n   * is performed offline.\n   * \n   * If the list is not already shared, it will be made\n   * shared (moved to a new realm with deterministic id).\n   * \n   * @param name Name of the person to share with\n   * @param email Email of the person to share with\n   * @param sendEmail Whether to send an email invite or not\n   * @param roles Roles to assign the new member (e.g. ['readonly'] or ['manager'])\n   */\n  async shareWith(\n    name: string,\n    email: string,\n    sendEmail: boolean,\n    roles: string[]\n  ) {\n    const { db } = this;\n    await db.transaction(\n      'rw',\n      [\n        db.members,   // Used in this method\n        db.todoLists, // Used in makeSharable()\n        db.todoItems, // Used in makeSharable()\n        db.realms     // Used in makeSharable()\n      ],\n      async () => {\n        // Ensure todoList is sharable (in a realm with deterministic id)\n        // ( idempotent operation )\n        const realmId = await this.makeSharable(); // sub transaction\n\n        // Add given name and email as a member with full permissions\n        await db.members.add({\n          realmId,\n          name,\n          email,\n          invite: sendEmail,\n          roles,\n        });\n      }\n    );\n  }\n\n  /** Remove access to the list for given member\n   * \n   * This is a sync-consistent operation. The server\n   * will reject the operation if the deletion results in the realm\n   * not having any member representing the owner (realm.owner).\n   * \n   * If this happens, the server will roll-back the operation and\n   * restore the local data to mirror the server state (i.e. the\n   * given member will become added locally again).\n   * \n   * @param member \n   */\n  async unshareWith(member: DBRealmMember) {\n    await this.db.members.delete(member.id);\n  }\n\n  /** Remove access to the list for the current user.\n   * \n   * This is a sync-consistent operation. The server\n   * will reject the operation if the user is the owner of the list.\n   * If this happens, the server will roll-back the operation and\n   * restore the local data to mirror the server state (i.e. the\n   * user will still have access and be owner).\n   */\n  async leaveList() {\n    // Delete own member entry --> you will then no longer have access\n    // to the shared list.\n    const { db } = this;\n    await db.members\n      .where({\n        realmId: getTiedRealmId(this.id),\n        userId: db.cloud.currentUserId,\n      })\n      .delete();\n  }\n\n  /** Delete the todo list including all its related entities (todo-items,\n   * realm and memberships)\n   * \n   * The operation is sync-consistent and will work correctly\n   * whether the todo-list is shared or not while the operation\n   * is performed offline.\n   */\n  async deleteList() {\n    const { db } = this;\n    await db.transaction(\n      'rw',\n      [db.todoLists, db.todoItems, db.members, db.realms],\n      () => {\n        // Delete todo items on the tied realmId in case it's shared\n        db.todoItems\n          .where({\n            todoListId: this.id,\n            realmId: getTiedRealmId(this.id)\n          })\n          .delete();\n\n        // Delete todo items on the private realmId in case it's unshared\n        db.todoItems\n          .where({\n            todoListId: this.id,\n            realmId: db.cloud.currentUserId,\n          })\n          .delete();\n\n        // Delete the list\n        db.todoLists.delete(this.id!);\n\n        // Delete any tied realm and related access.\n        // If it wasn't shared, this is a no-op but do\n        // it anyway to make this operation consistent\n        // in case it was shared by other offline\n        // client and then syncs.\n        // No need to delete members - they will be deleted\n        // automatically when the realm is deleted.\n        const tiedRealmId = getTiedRealmId(this.id);\n        db.realms.delete(tiedRealmId);\n      }\n    );\n  }\n\n  /** Change ownership of the todo list to given userId.\n   * \n   * The operation is sync-consistent and will work correctly\n   * whether the todo-list is shared or not while the operation\n   * is performed offline.\n   * \n   * The operation may succeed offline but fail later during sync\n   * if someone else has removed access for userId in the meantime.\n   * If that happens, the server will roll-back the operation and\n   * restore the local data to mirror the server state.\n   *\n   * @param userId UserID of the new owner\n   */\n  async changeOwner(userId: string) {\n    const { db } = this;\n    const realmId = getTiedRealmId(this.id);\n\n    if (!userId)\n      throw new Error(\n        `Cannot give ownership to user before invite is accepted.`\n      );\n\n    return db.transaction('rw', db.todoLists, db.members, db.realms, () => {\n      // Before changing owner, give full permissions to the old owner:\n      db.members\n        .where({ realmId, userId: this.owner })\n        .modify({ roles: ['manager'] });\n      // Change owner of all members in the realm:\n      db.members.where({ realmId }).modify({ owner: userId });\n      // Change owner of the todo list:\n      db.todoLists.where({ realmId, id: this.id }).modify({ owner: userId });\n      // Change owner of realm:\n      db.realms.update(realmId, { owner: userId });\n    });\n  }\n\n  /** Change role for given member\n   * \n   * @param member \n   * @param role \n   */\n  async changeMemberRole(member: DBRealmMember, role: string) {\n    await this.db.members.update(member.id, {\n      permissions: {},\n      roles: [role]\n    });\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/db/db.ts",
    "content": "import { TodoDB } from './TodoDB';\n\nexport const db = new TodoDB();\n\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/db/index.ts",
    "content": "export * from './db';\nexport * from './TodoDB';\nexport * from './TodoItem';\nexport * from './TodoList';\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/db/logout.ts",
    "content": "import { db } from \"./db\";\n\nexport function logout() {\n  return db.cloud.logout().catch(e => {\n    console.log(\"Logout cancelled\", e);\n  });\n}"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/helpers/handleError.ts",
    "content": "export function handleError<TArgs extends any[], TResult>(\n  fn: (...args: TArgs) => Promise<TResult>\n) {\n  return (...args: TArgs) =>\n    fn(...args).catch((error) => {\n      if (error?.name === 'AbortError') {\n        // AbortErrors are normal. It means a user has willingly cancelled a dialog\n        // or aborted a transaction. Give a lower log level.\n        console.debug(error);\n      } else {\n        console.error(error);\n      }\n    });\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/helpers/simplify-debugging.ts",
    "content": "import Dexie from \"dexie\";\n\n//\n// Put Dexie on window.\n// (makes it possible to use Dexie in devtools console)\n//\n// Enables an easy way of seeing which version is used and inspect the database:\n//\n//  > Dexie.semVer\n// \"3.2.0-beta-2\"\n//  > Dexie.Cloud.version\n//  \"1.0.0-beta.6\"\n//  > await Dexie.getDatabaseNames()\n//  (2) [\"TodoDBCloud\", \"TodoDBCloud-z0lesejpr\"]\n//  > db = await new Dexie(\"TodoDBCloud-z0lesejpr\").open()\n//  > db.tables\n//  (14) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]\n//  > await db.table('todoItems').toArray()\n//  (8) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]\n\n//@ts-ignore\nwindow.Dexie = Dexie; \nDexie.debug = true;\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/helpers/usePersistedOpenState.ts",
    "content": "import { useLiveQuery } from 'dexie-react-hooks';\nimport { db } from '../db';\n\nexport function usePersistedOpenState(\n  namespace: string,\n  id: string,\n  defaultOpen = false\n) {\n  const isOpen =\n    useLiveQuery(\n      () => db.openCloseStates.get([namespace, id]),\n      [namespace, id],\n      defaultOpen\n    ) ?? defaultOpen;\n  const setIsOpen = (isOpen: boolean) =>\n    db.openCloseStates.put(isOpen, [namespace, id]);\n  return [isOpen, setIsOpen] as const;\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/index.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 222.2 84% 4.9%;\n    --card: 0 0% 100%;\n    --card-foreground: 222.2 84% 4.9%;\n    --popover: 0 0% 100%;\n    --popover-foreground: 222.2 84% 4.9%;\n    --primary: 221.2 83.2% 53.3%;\n    --primary-foreground: 210 40% 98%;\n    --secondary: 210 40% 96%;\n    --secondary-foreground: 222.2 84% 4.9%;\n    --muted: 210 40% 96%;\n    --muted-foreground: 215.4 16.3% 46.9%;\n    --accent: 210 40% 96%;\n    --accent-foreground: 222.2 84% 4.9%;\n    --destructive: 0 84.2% 60.2%;\n    --destructive-foreground: 210 40% 98%;\n    --border: 214.3 31.8% 91.4%;\n    --input: 214.3 31.8% 91.4%;\n    --ring: 221.2 83.2% 53.3%;\n    --radius: 0.5rem;\n  }\n\n  .dark {\n    --background: 222.2 84% 4.9%;\n    --foreground: 210 40% 98%;\n    --card: 222.2 84% 4.9%;\n    --card-foreground: 210 40% 98%;\n    --popover: 222.2 84% 4.9%;\n    --popover-foreground: 210 40% 98%;\n    --primary: 217.2 91.2% 59.8%;\n    --primary-foreground: 222.2 84% 4.9%;\n    --secondary: 217.2 32.6% 17.5%;\n    --secondary-foreground: 210 40% 98%;\n    --muted: 217.2 32.6% 17.5%;\n    --muted-foreground: 215 20.2% 65.1%;\n    --accent: 217.2 32.6% 17.5%;\n    --accent-foreground: 210 40% 98%;\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 210 40% 98%;\n    --border: 217.2 32.6% 17.5%;\n    --input: 217.2 32.6% 17.5%;\n    --ring: 224.3 76.3% 94.1%;\n  }\n}\n\n@layer base {\n  * {\n    border-color: hsl(var(--border));\n  }\n  \n  body {\n    background-color: hsl(var(--background));\n    color: hsl(var(--foreground));\n    font-feature-settings: \"rlig\" 1, \"calt\" 1;\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/index.tsx",
    "content": "import React from 'react';\nimport { createRoot } from 'react-dom/client';\nimport './index.css';\nimport App from './App';\nimport * as serviceWorkerRegistration from './serviceWorkerRegistration';\nimport './helpers/simplify-debugging';\n\nconst container = document.getElementById('root');\nconst root = createRoot(container!);\n\nroot.render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>\n);\n\n// Register service worker for PWA capabilities\nserviceWorkerRegistration.register();\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/lib/utils.ts",
    "content": "import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/serviceWorkerRegistration.ts",
    "content": "// This file integrates with vite-plugin-pwa for service worker functionality\n// vite-plugin-pwa automatically generates the service worker based on the config\n\ntype Config = {\n  onSuccess?: (registration: ServiceWorkerRegistration) => void;\n  onUpdate?: (registration: ServiceWorkerRegistration) => void;\n  onOfflineReady?: () => void;\n  onNeedRefresh?: () => void;\n};\n\nexport function register(config?: Config) {\n  // Only register in production and if service worker is supported\n  if (import.meta.env.PROD && 'serviceWorker' in navigator) {\n    window.addEventListener('load', async () => {\n      try {\n        // Use Vite's BASE_URL to support deployment to subpaths\n        let baseUrl = import.meta.env.BASE_URL;\n        // Ensure baseUrl ends with /\n        if (!baseUrl.endsWith('/')) baseUrl += '/';\n        \n        const registration = await navigator.serviceWorker.register(`${baseUrl}sw.js`, {\n          scope: baseUrl\n        });\n        \n        console.log(`🔧 Service Worker: Registration successful`, registration);\n\n        registration.addEventListener('updatefound', () => {\n          const newWorker = registration.installing;\n          if (!newWorker) return;\n\n          newWorker.addEventListener('statechange', () => {\n            if (newWorker.state === 'installed') {\n              if (navigator.serviceWorker.controller) {\n                console.log('New content is available and will be used when all tabs for this page are closed.');\n                config?.onUpdate?.(registration);\n                config?.onNeedRefresh?.();\n              } else {\n                console.log('App ready to work offline.');\n                config?.onSuccess?.(registration);\n                config?.onOfflineReady?.();\n              }\n            }\n          });\n        });\n\n      } catch (error) {\n        console.error('🚨 Service worker registration failed:', error);\n        if (error instanceof Error) {\n          console.error('🚨 Error details:', {\n            name: error.name,\n            message: error.message,\n            stack: error.stack\n          });\n        }\n      }\n    });\n  } else {\n    if (!import.meta.env.PROD) {\n      console.log('🚫 Service Worker: Disabled in development mode for easier debugging');\n    } else if (!('serviceWorker' in navigator)) {\n      console.log('🚫 Service Worker: Not supported in this browser');\n    }\n  }\n  \n  return () => {};\n}\n\nexport function unregister() {\n  if ('serviceWorker' in navigator) {\n    navigator.serviceWorker.ready\n      .then((registration) => {\n        registration.unregister();\n      })\n      .catch((error) => {\n        console.error(error.message);\n      });\n  }\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/setupTests.ts",
    "content": "// jest-dom adds custom vitest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).toHaveTextContent(/react/i)\n// learn more: https://github.com/testing-library/jest-dom\nimport '@testing-library/jest-dom';\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/sw.ts",
    "content": "/// <reference lib=\"webworker\" />\n\n/* Minimal Service Worker for caching and background sync with Dexie Cloud\n * \n * This service worker does two main things:\n * 1. Enables Dexie Cloud background sync by importing the Dexie Cloud service worker addon.\n * 2. Pre-caches and routes assets using Workbox for offline capabilities and faster load times.\n * \n * The service worker is set up to log its build date for easier debugging of deployed versions.\n * It also uses `skipWaiting()` during installation to activate the new service worker immediately.\n * In a production app, consider allowing users to control updates instead.\n * \n * Note: This file is part of a sample application and may be modified to fit specific needs.\n * \n * See vite-config.ts for the Vite PWA plugin configuration that generates the final service worker.\n */\n\nconsole.log(`🚀 Service Worker starting (built at ${process.env.BUILD_DATE})`);\n\n// Enable Dexie Cloud background sync:\nimport 'dexie-cloud-addon/service-worker';\n\n// Pre-caching and routing of assets:\nimport { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching';\n\ndeclare const self: ServiceWorkerGlobalScope; // typings for service worker\n\n//\n// Precache all assets\n//\nprecacheAndRoute(self.__WB_MANIFEST);\ncleanupOutdatedCaches();\n\n// Optional: Use skipWaiting for immediate updates during development\n// In production apps, consider letting users control updates instead\nself.addEventListener('install', (event) => {\n  self.skipWaiting();\n});\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n/// <reference types=\"vite-plugin-pwa/client\" />\n/// <reference types=\"vitest/globals\" />\n/// <reference types=\"@testing-library/jest-dom\" />\n\ninterface ImportMetaEnv {\n  readonly VITE_DBURL: string\n  readonly VITE_BUILD_TIME: string\n  // Add more env variables here as needed\n}\n\ninterface ImportMeta {\n  readonly env: ImportMetaEnv\n}"
  },
  {
    "path": "samples/dexie-cloud-todo-app/tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\nexport default {\n  darkMode: [\"class\"],\n  content: [\n    './pages/**/*.{ts,tsx}',\n    './components/**/*.{ts,tsx}',\n    './app/**/*.{ts,tsx}',\n    './src/**/*.{ts,tsx}',\n  ],\n  prefix: \"\",\n  theme: {\n    container: {\n      center: true,\n      padding: \"2rem\",\n      screens: {\n        \"2xl\": \"1400px\",\n      },\n    },\n    extend: {\n      colors: {\n        border: \"hsl(var(--border))\",\n        input: \"hsl(var(--input))\",\n        ring: \"hsl(var(--ring))\",\n        background: \"hsl(var(--background))\",\n        foreground: \"hsl(var(--foreground))\",\n        primary: {\n          DEFAULT: \"hsl(var(--primary))\",\n          foreground: \"hsl(var(--primary-foreground))\",\n        },\n        secondary: {\n          DEFAULT: \"hsl(var(--secondary))\",\n          foreground: \"hsl(var(--secondary-foreground))\",\n        },\n        destructive: {\n          DEFAULT: \"hsl(var(--destructive))\",\n          foreground: \"hsl(var(--destructive-foreground))\",\n        },\n        muted: {\n          DEFAULT: \"hsl(var(--muted))\",\n          foreground: \"hsl(var(--muted-foreground))\",\n        },\n        accent: {\n          DEFAULT: \"hsl(var(--accent))\",\n          foreground: \"hsl(var(--accent-foreground))\",\n        },\n        popover: {\n          DEFAULT: \"hsl(var(--popover))\",\n          foreground: \"hsl(var(--popover-foreground))\",\n        },\n        card: {\n          DEFAULT: \"hsl(var(--card))\",\n          foreground: \"hsl(var(--card-foreground))\",\n        },\n      },\n      borderRadius: {\n        lg: \"var(--radius)\",\n        md: \"calc(var(--radius) - 2px)\",\n        sm: \"calc(var(--radius) - 4px)\",\n      },\n      keyframes: {\n        \"accordion-down\": {\n          from: { height: \"0\" },\n          to: { height: \"var(--radix-accordion-content-height)\" },\n        },\n        \"accordion-up\": {\n          from: { height: \"var(--radix-accordion-content-height)\" },\n          to: { height: \"0\" },\n        },\n      },\n      animation: {\n        \"accordion-down\": \"accordion-down 0.2s ease-out\",\n        \"accordion-up\": \"accordion-up 0.2s ease-out\",\n      },\n    },\n  },\n  plugins: [require(\"tailwindcss-animate\")],\n}"
  },
  {
    "path": "samples/dexie-cloud-todo-app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"noFallthroughCasesInSwitch\": true,\n\n    /* Additional for app */\n    \"allowJs\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"forceConsistentCasingInFileNames\": true,\n\n    /* Vite types */\n    \"types\": [\"vite/client\", \"vitest/globals\"]\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "samples/dexie-cloud-todo-app/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}"
  },
  {
    "path": "samples/dexie-cloud-todo-app/vite.config.ts",
    "content": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\nimport { VitePWA } from 'vite-plugin-pwa'\n//import path from 'path'\n\n/*\n * TODO: Customize this starter template for your own Dexie Cloud PWA\n * \n * Before deploying your own app to production, make sure to:\n * 1. Update package.json name, description, and version\n * 2. Replace app icons (see manifest.icons below)\n * 3. Add real app screenshots (see manifest.screenshots below)\n * 4. Update app name, description, and theme colors in manifest\n * 5. Configure your Dexie Cloud database URL in src/db/db.ts\n * 6. Update service worker caching strategy if needed (src/sw.ts)\n * 7. Test PWA functionality on mobile and desktop\n * 8. Set up proper error logging and analytics\n */\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ command }) => {\n  const base = process.env.PUBLIC_URL ?? './'\n  \n  return {\n    base,\n    define: {\n      // (replaces process.env.BUILD_DATE with actual build timestamp - for logging)\n      \"process.env.BUILD_DATE\": JSON.stringify(new Date().toISOString()),\n    },\n    resolve: {\n      alias: {\n        // Unmark below row temporarily while developing dexie/dexie-cloud-addon/dexie-react-hooks/dexie-cloud-todo-app together:\n        // It will point dexie directly to ESM build to avoid import-wrapper.mjs issues with workspace links\n        //'dexie': path.resolve(__dirname, '../../dist/dexie.mjs')\n      }\n    },\n    build: {\n      outDir: 'build',\n      sourcemap: true,\n      chunkSizeWarningLimit: 1000,\n    },\n    server: {\n      port: 3000,\n      open: true\n    },\n    preview: {\n      port: 3001\n    },\n    plugins: [\n      react(),\n      VitePWA({\n        injectRegister: false, // We handle registration manually\n        strategies: 'injectManifest',\n        srcDir: 'src',\n        filename: 'sw.ts',\n        manifest: {\n          // TODO: Customize your PWA manifest for production\n          // 1. Change app name and description to match your app\n          name: 'Dexie Cloud ToDo Sample App',\n          short_name: 'DexieCloudToDo',\n          description: 'A todo app demonstrating Dexie Cloud with Vite',\n          \n          // TODO: Update theme colors to match your brand\n          // (For complete manifest properties reference, see:\n          //  https://developer.mozilla.org/en-US/docs/Web/Manifest)\n          theme_color: '#000000',\n          background_color: '#ffffff',\n          display: 'standalone',\n          start_url: './',\n          \n          // TODO: Replace with your own app icons\n          // Create icons in sizes: 64x64, 192x192, 512x512\n          // Place them in public/ folder and update paths below\n          icons: [\n            {\n              src: base + 'dexie-icon-64x64.gif',\n              sizes: '64x64',\n              type: 'image/gif'\n            },\n            {\n              src: base + 'dexie-icon-192x192.png',\n              sizes: '192x192',\n              type: 'image/png'\n            },\n            {\n              src: base + 'dexie-icon-512x512.png',\n              sizes: '512x512',\n              type: 'image/png'\n            }\n          ],\n          \n          // TODO: Replace with actual app screenshots for better install UX\n          // Desktop: 1280x800 or similar wide format\n          // Mobile: 320x640 or similar narrow format\n          screenshots: [\n            {\n              src: base + 'dexie-icon-512x512.png', // TODO: Replace with desktop screenshot\n              sizes: '512x512',\n              type: 'image/png',\n              form_factor: 'wide',\n              label: 'Dexie Cloud ToDo App - Desktop View' // TODO: Update label\n            },\n            {\n              src: base + 'dexie-icon-512x512.png', // TODO: Replace with mobile screenshot\n              sizes: '512x512',\n              type: 'image/png',\n              form_factor: 'narrow',\n              label: 'Dexie Cloud ToDo App - Mobile View' // TODO: Update label\n            }\n          ]\n        }\n      })\n    ],\n  }\n})"
  },
  {
    "path": "samples/dexie-cloud-todo-app/vitest.config.ts",
    "content": "/// <reference types=\"vitest\" />\nimport { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig({\n  plugins: [react()],\n  test: {\n    environment: 'jsdom',\n    globals: true,\n    setupFiles: ['./src/setupTests.ts'],\n  },\n})"
  },
  {
    "path": "samples/full-text-search/FullTextSearch.js",
    "content": "﻿    ///<reference path=\"../dist/dexie.js\" />\n\n    /*\n        This example is a simple implementation of full-text search based on multi-valued indexes and Dexie hooks.\n        NOTE: Multi-valued indexes are only supported in Opera, Firefox and Chrome. Does not work with IE so far.\n        To see an example that works with IE, see FullTextSearch2.js.\n    */\n\n    var db = new Dexie(\"FullTextSample\");\n\n    db.version(1).stores({emails: \"++id,subject,from,*to,*cc,*bcc,message,*messageWords\"});\n\n    // Add hooks that will index \"message\" for full-text search:\n    db.emails.hook(\"creating\", function (primKey, obj, trans) {\n        if (typeof obj.message == 'string') obj.messageWords = getAllWords(obj.message);\n    });\n    db.emails.hook(\"updating\", function (mods, primKey, obj, trans) {\n        if (mods.hasOwnProperty(\"message\")) {\n            // \"message\" property is being updated\n            if (typeof mods.message == 'string')\n                // \"message\" property was updated to another valid value. Re-index messageWords:\n                return { messageWords: getAllWords(mods.message) };\n            else\n                // \"message\" property was deleted (typeof mods.message === 'undefined') or changed to an unknown type. Remove indexes:\n                return { messageWords: [] };\n        }\n\n    });\n    function getAllWords(text) {\n        /// <param name=\"text\" type=\"String\"></param>\n        var allWordsIncludingDups = text.split(' ');\n        var wordSet = allWordsIncludingDups.reduce(function (prev, current) {\n            prev[current] = true;\n            return prev;\n        }, {});\n        return Object.keys(wordSet);\n    }\n\n    // Open database to allow application code using it.\n    db.open();\n\n\n    //\n    // Application code:\n    //\n\n    db.transaction('rw', db.emails, function () {\n        // Add an email:\n        db.emails.add({\n            subject: \"Testing full-text search\",\n            from: \"david@abc.com\",\n            to: [\"test@abc.com\"],\n            message: \"Here is my very long message that I want to write\"\n        });\n\n        // Search for emails:\n        db.emails.where(\"messageWords\").startsWithIgnoreCase(\"v\").distinct().toArray(function (a) {\n            alert(\"Found \" + a.length + \" emails containing a word starting with 'v'\");\n        });\n    }).catch(function (e) {\n        alert(e.stack || e);\n    });\n\n"
  },
  {
    "path": "samples/liveQuery/liveQuery.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <script type=\"importmap\">\n      {\n        \"imports\": {\n          \"dexie\": \"https://unpkg.com/dexie@^3.2/dist/modern/dexie.min.mjs\"\n        }\n      }\n    </script>\n    <script type=\"module\">\n      import Dexie, { liveQuery } from 'dexie';\n\n      const db = new Dexie('MyDatabase');\n      db.version(1).stores({\n        friends: '++id, name, age',\n      });\n\n      const friendsObservable = liveQuery(() =>\n        db.friends.where('age').between(50, 75).toArray()\n      );\n\n      const subscription = friendsObservable.subscribe({\n        next: (result) => console.log('Got result:', JSON.stringify(result)),\n        error: (error) => console.error(error),\n      });\n\n      const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n\n      await sleep(1000);\n\n      console.log('1. Adding friend');\n      const friendId = await db.friends.add({ name: 'Magdalena', age: 54 });\n      await sleep(1000);\n\n      console.log('2. Changing age to 99');\n      await db.friends.update(friendId, { age: 99 });\n      await sleep(1000);\n\n      console.log('3. Changing age to 55');\n      await db.friends.update(friendId, { age: 55 });\n      await sleep(1000);\n\n      console.log(\"4. Setting property 'foo' to 'bar'\");\n      await db.friends.update(friendId, { foo: 'bar' });\n      await sleep(1000);\n\n      console.log('5. Deleting friend');\n      await db.friends.delete(friendId);\n\n      subscription.unsubscribe();\n    </script>\n  </head>\n  <body>\n    <h1>Dexie liveQuery() playground</h1>\n    <p>Please open your console to see anything happen...</p>\n    <p>Mac: Press CMD+ALT+I</p>\n    <p>Windows: Press F12</p>\n  </body>\n</html>\n"
  },
  {
    "path": "samples/open-existing-db/dump-databases.html",
    "content": "﻿<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n    <title>Database Dump Sample</title>\n    <style>\n        textarea\n        {\n            width: 100%;\n            height: 1000px;\n        }\n    </style>\n    <script src=\"https://unpkg.com/dexie/dist/dexie.js\"></script>\n    <script>\n        //\n        // PREPARATION\n        //\n\n        // To make the sample more visible - replace window.console with one that shows the content on page\n        function Console() {\n            this.textarea = document.createElement('textarea');\n            this.log = function (txt, type) {\n                if (type) this.textarea.value += type + \" \";\n                this.textarea.value += txt + \"\\n\";\n            }\n            this.error = function (txt) {\n                this.log(txt, \"ERROR!\");\n            }\n        }\n        window.console = new Console();\n        document.addEventListener('DOMContentLoaded', function () {\n            document.getElementById('consoleArea').appendChild(console.textarea);\n        });\n\n    </script>\n    <script>\n        //\n        // SAMPLE CODE\n        //\n        // This sample shows how to iterate all databases at current origin and dump the version and schemas of each database.\n        // If you run this sample on Chrome or Opera it will also be able to dump databases not created by Dexie. On IE and FF,\n        // there is no native API to list existing databases, but Dexie is still capable of dumping the version and schema in \n        // case you know the database names.\n        //\n        // The console output will be displayed in the form of javascript code, as how the database would be defined with Dexie.\n        //\n        // The code could be extended to also dump the objects from the database (see comment in code). Another posibility that would be\n        // doable is to monitor the database live. That is possible if using the Dexie.Observable addon but it will only be capable\n        // of monitoring other databases used with the Dexie.Observable addon applied. It would be possible to create a dynamic\n        // view of the contents in DB that would automatically update the page directly as soon as an object is added, changed or\n        // deleted.\n        //\n        // It is also possible to get notified when a database is added, deleted or upgraded. This is actually possible without\n        // the Dexie.Observable addon. To get notified when databases are added or deleted, just listen to the window.onstorage\n        // event and when the key \"Dexie.DatabaseNames\" changes. To listen to upgrades, just listen to Dexie event \n        // db.on('versionchange'). The default behavior of that event is to close the database and reload the page. To override\n        // this, return false from your subscriber function and instead do db.close(); db.open(); Then the database will reopen\n        // to the new schema.\n\n        console.log(\"Dumping Databases\");\n        console.log(\"=================\");\n        Dexie.getDatabaseNames(function (databaseNames) {\n            if (databaseNames.length === 0) {\n                // No databases at this origin as we know of.\n                console.log(\"Could not find databases on current origin.\");\n                console.log(\"Was your database created without using Dexie? Try the [Add database] button above!\");\n            } else {\n                // At least one database to dump\n                dump(databaseNames);\n            }\n\n            function dump(databaseNames) {\n                if (databaseNames.length > 0) {\n                    var db = new Dexie(databaseNames[0]);\n                    // Now, open database without specifying any version. This will make the database open any existing database and read its schema automatically.\n                    db.open().then(function () {\n                        console.log(\"var db = new Dexie('\" + db.name + \"');\");\n                        console.log(\"db.version(\" + db.verno + \").stores({\");\n                        db.tables.forEach(function (table, i) {\n                            var primKeyAndIndexes = [table.schema.primKey].concat(table.schema.indexes);\n                            var schemaSyntax = primKeyAndIndexes.map(function (index) { return index.src; }).join(',');\n                            console.log(\"    \" + table.name + \": \" + \"'\" + schemaSyntax + \"'\" + (i < db.tables.length - 1 ? \",\" : \"\"));\n                            // Note: We could also dump the objects here if we'd like to:\n                            //  table.each(function (object) {\n                            //      console.log(JSON.stringify(object));\n                            //  });\n                        });\n                        console.log(\"});\\n\");\n                    }).finally(function () {\n                        db.close();\n                        dump(databaseNames.slice(1));\n                    });;\n                } else {\n                    console.log(\"Finished dumping databases\");\n                    console.log(\"==========================\");\n                    console.log(\"Hint: Is your DB not listed? Try using the [Add database] button above!\");\n                }\n            }\n        });\n\n        function addDatabase(ev) {\n            const dbname = prompt(\"Enter name of a database on this origin (\" + location.host + \")\");\n            if (!dbname) return; // User cancelled dialog\n            console.log(\"Trying to open \" + dbname);\n            return new Dexie(dbname, {addons: []}).open().then(function (db) {\n                console.log(\"Database \" + dbname + \" found. Adding it to __dbnames db\");\n                db.close();\n            }).then(function () {\n                return new Dexie (\"__dbnames\", {addons: []}).open();\n            }).then(function (db) {\n                return db.table(\"dbnames\").put({name: dbname}).then(function() {\n                    db.close();\n                });\n            }).then(function(){\n                console.log(\"Succcessfully added \" + dbname + \". Please reload page to dump it!\");\n            }).catch(function (error) {\n                console.error(error);\n            });\n        }\n    </script>\n</head>\n<body>\n    <div class=\"buttons\">\n        <button onclick=\"addDatabase(event);\">Add database</button>\n    </div>\n    <div id=\"consoleArea\"></div>\n</body>\n</html>\n"
  },
  {
    "path": "samples/react/README.md",
    "content": "See [stackblitz example](https://stackblitz.com/edit/dexie-todo-list?file=components/TodoListView.tsx)\n\nSee also [dexie.org/docs/Tutorial/React](https://dexie.org/docs/Tutorial/React)\n"
  },
  {
    "path": "samples/remote-sync/ajax/AjaxSyncProtocol.js",
    "content": "﻿/// <reference path=\"../../dist/dexie.js\" />\n/// <reference path=\"../../addons/Dexie.Syncable/dist/dexie-syncable.js\" />\n/// <reference path=\"../../addons/Dexie.Syncable/Dexie.Syncable.SyncProtocolAPI.js\" />\n/// <reference path=\"includes/jquery-2.1.0.js\" />\n\nDexie.Syncable.registerSyncProtocol(\"sample_protocol\", {\n\n    sync: function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n        /// <param name=\"context\" type=\"IPersistedContext\"></param>\n        /// <param name=\"url\" type=\"String\"></param>\n        /// <param name=\"changes\" type=\"Array\" elementType=\"IDatabaseChange\"></param>\n        /// <param name=\"applyRemoteChanges\" value=\"function (changes, lastRevision, partial, clear) {}\"></param>\n        /// <param name=\"onSuccess\" value=\"function (continuation) {}\"></param>\n        /// <param name=\"onError\" value=\"function (error, again) {}\"></param>\n\n        var POLL_INTERVAL = 10000; // Poll every 10th second\n\n        // In this example, the server expects the following JSON format of the request:\n        //  {\n        //      [clientIdentity: unique value representing the client identity. If omitted, server will return a new client identity in its response that we should apply in next sync call.]\n        //      baseRevision: baseRevision,\n        //      partial: partial,\n        //      changes: changes,\n        //      syncedRevision: syncedRevision\n        //  }\n        //  To keep the sample simple, we assume the server has the exact same specification of how changes are structured.\n        //  In real world, you would have to pre-process the changes array to fit the server specification.\n        //  However, this example shows how to deal with ajax to fulfil the API.\n        var request = {\n            clientIdentity: context.clientIdentity || null,\n            baseRevision: baseRevision,\n            partial: partial,\n            changes: changes,\n            syncedRevision: syncedRevision\n        };\n\n        // Send the request:\n        $.ajax(url, {\n            type: 'post',\n            contentType: 'application/json', // Make sure we set the correct content-type header as some servers expect this\n            dataType: 'json',\n            data: JSON.stringify(request),\n            error: function (xhr, textStatus) {\n                // Network down, server unreachable or other failure. Try again in POLL_INTERVAL seconds.\n                onError(textStatus, POLL_INTERVAL);\n            },\n            success: function (data) {\n                // In this example, the server response has the following format:\n                //{\n                //    success: true / false,\n                //    errorMessage: \"\",\n                //    changes: changes,\n                //    currentRevision: revisionOfLastChange,\n                //    needsResync: false, // Flag telling that server doesn't have given syncedRevision or of other reason wants client to resync. ATTENTION: this flag is currently ignored by Dexie.Syncable\n                //    partial: true / false, // The server sent only a part of the changes it has for us. On next resync it will send more based on the clientIdentity\n                //    [clientIdentity: unique value representing the client identity. Only provided if we did not supply a valid clientIdentity in the request.]\n                //}\n                if (!data.success) {\n                    onError (data.errorMessage, Infinity); // Infinity = Don't try again. We would continue getting this error.\n                } else {\n                    if ('clientIdentity' in data) {\n                        context.clientIdentity = data.clientIdentity;\n                        // Make sure we save the clientIdentity sent by the server before we try to resync.\n                        // If saving fails we wouldn't be able to do a partial synchronization\n                        context.save()\n                            .then(() => {\n                            // Since we got success, we also know that server accepted our changes:\n                            onChangesAccepted();\n                            // Convert the response format to the Dexie.Syncable.Remote.SyncProtocolAPI specification:\n                            applyRemoteChanges (data.changes, data.currentRevision, data.partial, data.needsResync);\n                            onSuccess({ again: POLL_INTERVAL });\n                            })\n                            .catch((e) => {\n                                // We didn't manage to save the clientIdentity stop synchronization\n                                onError(e, Infinity);\n                            });\n                    } else {\n                        // Since we got success, we also know that server accepted our changes:\n                        onChangesAccepted();\n                        // Convert the response format to the Dexie.Syncable.Remote.SyncProtocolAPI specification:\n                        applyRemoteChanges (data.changes, data.currentRevision, data.partial, data.needsResync);\n                        onSuccess({ again: POLL_INTERVAL });\n                    }\n                }\n            }\n        });\n    }\n});\n"
  },
  {
    "path": "samples/remote-sync/ajax/jquery-2.1.0.js",
    "content": "/*!\n * jQuery JavaScript Library v2.1.0\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-01-23T21:10Z\n */\n\n(function( global, factory ) {\n\n\tif ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n\t\t// For CommonJS and CommonJS-like environments where a proper window is present,\n\t\t// execute the factory and get jQuery\n\t\t// For environments that do not inherently posses a window with a document\n\t\t// (such as Node.js), expose a jQuery-making factory as module.exports\n\t\t// This accentuates the need for the creation of a real window\n\t\t// e.g. var jQuery = require(\"jquery\")(window);\n\t\t// See ticket #14549 for more info\n\t\tmodule.exports = global.document ?\n\t\t\tfactory( global, true ) :\n\t\t\tfunction( w ) {\n\t\t\t\tif ( !w.document ) {\n\t\t\t\t\tthrow new Error( \"jQuery requires a window with a document\" );\n\t\t\t\t}\n\t\t\t\treturn factory( w );\n\t\t\t};\n\t} else {\n\t\tfactory( global );\n\t}\n\n// Pass this if window is not defined yet\n}(typeof window !== \"undefined\" ? window : this, function( window, noGlobal ) {\n\n// Can't do this because several apps including ASP.NET trace\n// the stack via arguments.caller.callee and Firefox dies if\n// you try to trace through \"use strict\" call chains. (#13335)\n// Support: Firefox 18+\n//\n\nvar arr = [];\n\nvar slice = arr.slice;\n\nvar concat = arr.concat;\n\nvar push = arr.push;\n\nvar indexOf = arr.indexOf;\n\nvar class2type = {};\n\nvar toString = class2type.toString;\n\nvar hasOwn = class2type.hasOwnProperty;\n\nvar trim = \"\".trim;\n\nvar support = {};\n\n\n\nvar\n\t// Use the correct document accordingly with window argument (sandbox)\n\tdocument = window.document,\n\n\tversion = \"2.1.0\",\n\n\t// Define a local copy of jQuery\n\tjQuery = function( selector, context ) {\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\n\t\t// Need init if jQuery is called (just allow error to be thrown if not included)\n\t\treturn new jQuery.fn.init( selector, context );\n\t},\n\n\t// Matches dashed string for camelizing\n\trmsPrefix = /^-ms-/,\n\trdashAlpha = /-([\\da-z])/gi,\n\n\t// Used by jQuery.camelCase as callback to replace()\n\tfcamelCase = function( all, letter ) {\n\t\treturn letter.toUpperCase();\n\t};\n\njQuery.fn = jQuery.prototype = {\n\t// The current version of jQuery being used\n\tjquery: version,\n\n\tconstructor: jQuery,\n\n\t// Start with an empty selector\n\tselector: \"\",\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\ttoArray: function() {\n\t\treturn slice.call( this );\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function( num ) {\n\t\treturn num != null ?\n\n\t\t\t// Return a 'clean' array\n\t\t\t( num < 0 ? this[ num + this.length ] : this[ num ] ) :\n\n\t\t\t// Return just the object\n\t\t\tslice.call( this );\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function( elems ) {\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject = this;\n\t\tret.context = this.context;\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\t// (You can seed the arguments with an array of args, but this is\n\t// only used internally.)\n\teach: function( callback, args ) {\n\t\treturn jQuery.each( this, callback, args );\n\t},\n\n\tmap: function( callback ) {\n\t\treturn this.pushStack( jQuery.map(this, function( elem, i ) {\n\t\t\treturn callback.call( elem, i, elem );\n\t\t}));\n\t},\n\n\tslice: function() {\n\t\treturn this.pushStack( slice.apply( this, arguments ) );\n\t},\n\n\tfirst: function() {\n\t\treturn this.eq( 0 );\n\t},\n\n\tlast: function() {\n\t\treturn this.eq( -1 );\n\t},\n\n\teq: function( i ) {\n\t\tvar len = this.length,\n\t\t\tj = +i + ( i < 0 ? len : 0 );\n\t\treturn this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );\n\t},\n\n\tend: function() {\n\t\treturn this.prevObject || this.constructor(null);\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array's method, not like a jQuery method.\n\tpush: push,\n\tsort: arr.sort,\n\tsplice: arr.splice\n};\n\njQuery.extend = jQuery.fn.extend = function() {\n\tvar options, name, src, copy, copyIsArray, clone,\n\t\ttarget = arguments[0] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\n\t\t// skip the boolean and the target\n\t\ttarget = arguments[ i ] || {};\n\t\ti++;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !jQuery.isFunction(target) ) {\n\t\ttarget = {};\n\t}\n\n\t// extend jQuery itself if only one argument is passed\n\tif ( i === length ) {\n\t\ttarget = this;\n\t\ti--;\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\t\t// Only deal with non-null/undefined values\n\t\tif ( (options = arguments[ i ]) != null ) {\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {\n\t\t\t\t\tif ( copyIsArray ) {\n\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\tclone = src && jQuery.isArray(src) ? src : [];\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src && jQuery.isPlainObject(src) ? src : {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend({\n\t// Unique for each copy of jQuery on the page\n\texpando: \"jQuery\" + ( version + Math.random() ).replace( /\\D/g, \"\" ),\n\n\t// Assume jQuery is ready without the ready module\n\tisReady: true,\n\n\terror: function( msg ) {\n\t\tthrow new Error( msg );\n\t},\n\n\tnoop: function() {},\n\n\t// See test/unit/core.js for details concerning isFunction.\n\t// Since version 1.3, DOM methods and functions like alert\n\t// aren't supported. They return false on IE (#2968).\n\tisFunction: function( obj ) {\n\t\treturn jQuery.type(obj) === \"function\";\n\t},\n\n\tisArray: Array.isArray,\n\n\tisWindow: function( obj ) {\n\t\treturn obj != null && obj === obj.window;\n\t},\n\n\tisNumeric: function( obj ) {\n\t\t// parseFloat NaNs numeric-cast false positives (null|true|false|\"\")\n\t\t// ...but misinterprets leading-number strings, particularly hex literals (\"0x...\")\n\t\t// subtraction forces infinities to NaN\n\t\treturn obj - parseFloat( obj ) >= 0;\n\t},\n\n\tisPlainObject: function( obj ) {\n\t\t// Not plain objects:\n\t\t// - Any object or value whose internal [[Class]] property is not \"[object Object]\"\n\t\t// - DOM nodes\n\t\t// - window\n\t\tif ( jQuery.type( obj ) !== \"object\" || obj.nodeType || jQuery.isWindow( obj ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Support: Firefox <20\n\t\t// The try/catch suppresses exceptions thrown when attempting to access\n\t\t// the \"constructor\" property of certain host objects, ie. |window.location|\n\t\t// https://bugzilla.mozilla.org/show_bug.cgi?id=814622\n\t\ttry {\n\t\t\tif ( obj.constructor &&\n\t\t\t\t\t!hasOwn.call( obj.constructor.prototype, \"isPrototypeOf\" ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} catch ( e ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// If the function hasn't returned already, we're confident that\n\t\t// |obj| is a plain object, created by {} or constructed with new Object\n\t\treturn true;\n\t},\n\n\tisEmptyObject: function( obj ) {\n\t\tvar name;\n\t\tfor ( name in obj ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\ttype: function( obj ) {\n\t\tif ( obj == null ) {\n\t\t\treturn obj + \"\";\n\t\t}\n\t\t// Support: Android < 4.0, iOS < 6 (functionish RegExp)\n\t\treturn typeof obj === \"object\" || typeof obj === \"function\" ?\n\t\t\tclass2type[ toString.call(obj) ] || \"object\" :\n\t\t\ttypeof obj;\n\t},\n\n\t// Evaluates a script in a global context\n\tglobalEval: function( code ) {\n\t\tvar script,\n\t\t\tindirect = eval;\n\n\t\tcode = jQuery.trim( code );\n\n\t\tif ( code ) {\n\t\t\t// If the code includes a valid, prologue position\n\t\t\t// strict mode pragma, execute code by injecting a\n\t\t\t// script tag into the document.\n\t\t\tif ( code.indexOf(\"use strict\") === 1 ) {\n\t\t\t\tscript = document.createElement(\"script\");\n\t\t\t\tscript.text = code;\n\t\t\t\tdocument.head.appendChild( script ).parentNode.removeChild( script );\n\t\t\t} else {\n\t\t\t// Otherwise, avoid the DOM node creation, insertion\n\t\t\t// and removal by using an indirect global eval\n\t\t\t\tindirect( code );\n\t\t\t}\n\t\t}\n\t},\n\n\t// Convert dashed to camelCase; used by the css and data modules\n\t// Microsoft forgot to hump their vendor prefix (#9572)\n\tcamelCase: function( string ) {\n\t\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\n\t},\n\n\tnodeName: function( elem, name ) {\n\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n\t},\n\n\t// args is for internal usage only\n\teach: function( obj, callback, args ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = obj.length,\n\t\t\tisArray = isArraylike( obj );\n\n\t\tif ( args ) {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// A special, fast, case for the most common use of each\n\t\t} else {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\ttrim: function( text ) {\n\t\treturn text == null ? \"\" : trim.call( text );\n\t},\n\n\t// results is for internal usage only\n\tmakeArray: function( arr, results ) {\n\t\tvar ret = results || [];\n\n\t\tif ( arr != null ) {\n\t\t\tif ( isArraylike( Object(arr) ) ) {\n\t\t\t\tjQuery.merge( ret,\n\t\t\t\t\ttypeof arr === \"string\" ?\n\t\t\t\t\t[ arr ] : arr\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpush.call( ret, arr );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function( elem, arr, i ) {\n\t\treturn arr == null ? -1 : indexOf.call( arr, elem, i );\n\t},\n\n\tmerge: function( first, second ) {\n\t\tvar len = +second.length,\n\t\t\tj = 0,\n\t\t\ti = first.length;\n\n\t\tfor ( ; j < len; j++ ) {\n\t\t\tfirst[ i++ ] = second[ j ];\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function( elems, callback, invert ) {\n\t\tvar callbackInverse,\n\t\t\tmatches = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tcallbackExpect = !invert;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor ( ; i < length; i++ ) {\n\t\t\tcallbackInverse = !callback( elems[ i ], i );\n\t\t\tif ( callbackInverse !== callbackExpect ) {\n\t\t\t\tmatches.push( elems[ i ] );\n\t\t\t}\n\t\t}\n\n\t\treturn matches;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function( elems, callback, arg ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tisArray = isArraylike( elems ),\n\t\t\tret = [];\n\n\t\t// Go through the array, translating each of the items to their new values\n\t\tif ( isArray ) {\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t} else {\n\t\t\tfor ( i in elems ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn concat.apply( [], ret );\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// Bind a function to a context, optionally partially applying any\n\t// arguments.\n\tproxy: function( fn, context ) {\n\t\tvar tmp, args, proxy;\n\n\t\tif ( typeof context === \"string\" ) {\n\t\t\ttmp = fn[ context ];\n\t\t\tcontext = fn;\n\t\t\tfn = tmp;\n\t\t}\n\n\t\t// Quick check to determine if target is callable, in the spec\n\t\t// this throws a TypeError, but we will just return undefined.\n\t\tif ( !jQuery.isFunction( fn ) ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Simulated bind\n\t\targs = slice.call( arguments, 2 );\n\t\tproxy = function() {\n\t\t\treturn fn.apply( context || this, args.concat( slice.call( arguments ) ) );\n\t\t};\n\n\t\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\t\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n\t\treturn proxy;\n\t},\n\n\tnow: Date.now,\n\n\t// jQuery.support is not used in Core but other projects attach their\n\t// properties to it so it needs to exist.\n\tsupport: support\n});\n\n// Populate the class2type map\njQuery.each(\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \"), function(i, name) {\n\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n});\n\nfunction isArraylike( obj ) {\n\tvar length = obj.length,\n\t\ttype = jQuery.type( obj );\n\n\tif ( type === \"function\" || jQuery.isWindow( obj ) ) {\n\t\treturn false;\n\t}\n\n\tif ( obj.nodeType === 1 && length ) {\n\t\treturn true;\n\t}\n\n\treturn type === \"array\" || length === 0 ||\n\t\ttypeof length === \"number\" && length > 0 && ( length - 1 ) in obj;\n}\nvar Sizzle =\n/*!\n * Sizzle CSS Selector Engine v1.10.16\n * http://sizzlejs.com/\n *\n * Copyright 2013 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-01-13\n */\n(function( window ) {\n\nvar i,\n\tsupport,\n\tExpr,\n\tgetText,\n\tisXML,\n\tcompile,\n\toutermostContext,\n\tsortInput,\n\thasDuplicate,\n\n\t// Local document vars\n\tsetDocument,\n\tdocument,\n\tdocElem,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\trbuggyMatches,\n\tmatches,\n\tcontains,\n\n\t// Instance-specific data\n\texpando = \"sizzle\" + -(new Date()),\n\tpreferredDoc = window.document,\n\tdirruns = 0,\n\tdone = 0,\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\tsortOrder = function( a, b ) {\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t}\n\t\treturn 0;\n\t},\n\n\t// General-purpose constants\n\tstrundefined = typeof undefined,\n\tMAX_NEGATIVE = 1 << 31,\n\n\t// Instance methods\n\thasOwn = ({}).hasOwnProperty,\n\tarr = [],\n\tpop = arr.pop,\n\tpush_native = arr.push,\n\tpush = arr.push,\n\tslice = arr.slice,\n\t// Use a stripped-down indexOf if we can't use a native one\n\tindexOf = arr.indexOf || function( elem ) {\n\t\tvar i = 0,\n\t\t\tlen = this.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( this[i] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",\n\n\t// Regular expressions\n\n\t// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\t// http://www.w3.org/TR/css3-syntax/#characters\n\tcharacterEncoding = \"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",\n\n\t// Loosely modeled on CSS identifier characters\n\t// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors\n\t// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\n\tidentifier = characterEncoding.replace( \"w\", \"w#\" ),\n\n\t// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + characterEncoding + \")\" + whitespace +\n\t\t\"*(?:([*^$|!~]?=)\" + whitespace + \"*(?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|(\" + identifier + \")|)|)\" + whitespace + \"*\\\\]\",\n\n\t// Prefer arguments quoted,\n\t//   then not containing pseudos/brackets,\n\t//   then attribute selectors/non-parenthetical expressions,\n\t//   then anything else\n\t// These preferences are here to reduce the number of selectors\n\t//   needing tokenize in the PSEUDO preFilter\n\tpseudos = \":(\" + characterEncoding + \")(?:\\\\(((['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes.replace( 3, 8 ) + \")*)|.*)\\\\)|)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace + \"*\" ),\n\n\trattributeQuotes = new RegExp( \"=\" + whitespace + \"*([^\\\\]'\\\"]*?)\" + whitespace + \"*\\\\]\", \"g\" ),\n\n\trpseudo = new RegExp( pseudos ),\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + characterEncoding + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + characterEncoding + \")\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + characterEncoding.replace( \"w\", \"w*\" ) + \")\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" + whitespace +\n\t\t\t\"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" + whitespace +\n\t\t\t\"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace + \"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" +\n\t\t\twhitespace + \"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\n\t},\n\n\trinputs = /^(?:input|select|textarea|button)$/i,\n\trheader = /^h\\d$/i,\n\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trsibling = /[+~]/,\n\trescape = /'|\\\\/g,\n\n\t// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape = new RegExp( \"\\\\\\\\([\\\\da-f]{1,6}\" + whitespace + \"?|(\" + whitespace + \")|.)\", \"ig\" ),\n\tfunescape = function( _, escaped, escapedWhitespace ) {\n\t\tvar high = \"0x\" + escaped - 0x10000;\n\t\t// NaN means non-codepoint\n\t\t// Support: Firefox\n\t\t// Workaround erroneous numeric interpretation of +\"0x\"\n\t\treturn high !== high || escapedWhitespace ?\n\t\t\tescaped :\n\t\t\thigh < 0 ?\n\t\t\t\t// BMP codepoint\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\n\t\t\t\t// Supplemental Plane codepoint (surrogate pair)\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\n\t};\n\n// Optimize for push.apply( _, NodeList )\ntry {\n\tpush.apply(\n\t\t(arr = slice.call( preferredDoc.childNodes )),\n\t\tpreferredDoc.childNodes\n\t);\n\t// Support: Android<4.0\n\t// Detect silently failing push.apply\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch ( e ) {\n\tpush = { apply: arr.length ?\n\n\t\t// Leverage slice if possible\n\t\tfunction( target, els ) {\n\t\t\tpush_native.apply( target, slice.call(els) );\n\t\t} :\n\n\t\t// Support: IE<9\n\t\t// Otherwise append directly\n\t\tfunction( target, els ) {\n\t\t\tvar j = target.length,\n\t\t\t\ti = 0;\n\t\t\t// Can't trust NodeList.length\n\t\t\twhile ( (target[j++] = els[i++]) ) {}\n\t\t\ttarget.length = j - 1;\n\t\t}\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tvar match, elem, m, nodeType,\n\t\t// QSA vars\n\t\ti, groups, old, nid, newContext, newSelector;\n\n\tif ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\n\tcontext = context || document;\n\tresults = results || [];\n\n\tif ( !selector || typeof selector !== \"string\" ) {\n\t\treturn results;\n\t}\n\n\tif ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {\n\t\treturn [];\n\t}\n\n\tif ( documentIsHTML && !seed ) {\n\n\t\t// Shortcuts\n\t\tif ( (match = rquickExpr.exec( selector )) ) {\n\t\t\t// Speed-up: Sizzle(\"#ID\")\n\t\t\tif ( (m = match[1]) ) {\n\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\telem = context.getElementById( m );\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document (jQuery #6963)\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE, Opera, and Webkit return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Context is not a document\n\t\t\t\t\tif ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&\n\t\t\t\t\t\tcontains( context, elem ) && elem.id === m ) {\n\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Speed-up: Sizzle(\"TAG\")\n\t\t\t} else if ( match[2] ) {\n\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\n\t\t\t\treturn results;\n\n\t\t\t// Speed-up: Sizzle(\".CLASS\")\n\t\t\t} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {\n\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\n\t\t\t\treturn results;\n\t\t\t}\n\t\t}\n\n\t\t// QSA path\n\t\tif ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {\n\t\t\tnid = old = expando;\n\t\t\tnewContext = context;\n\t\t\tnewSelector = nodeType === 9 && selector;\n\n\t\t\t// qSA works strangely on Element-rooted queries\n\t\t\t// We can work around this by specifying an extra ID on the root\n\t\t\t// and working up from there (Thanks to Andrew Dupont for the technique)\n\t\t\t// IE 8 doesn't work on object elements\n\t\t\tif ( nodeType === 1 && context.nodeName.toLowerCase() !== \"object\" ) {\n\t\t\t\tgroups = tokenize( selector );\n\n\t\t\t\tif ( (old = context.getAttribute(\"id\")) ) {\n\t\t\t\t\tnid = old.replace( rescape, \"\\\\$&\" );\n\t\t\t\t} else {\n\t\t\t\t\tcontext.setAttribute( \"id\", nid );\n\t\t\t\t}\n\t\t\t\tnid = \"[id='\" + nid + \"'] \";\n\n\t\t\t\ti = groups.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tgroups[i] = nid + toSelector( groups[i] );\n\t\t\t\t}\n\t\t\t\tnewContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;\n\t\t\t\tnewSelector = groups.join(\",\");\n\t\t\t}\n\n\t\t\tif ( newSelector ) {\n\t\t\t\ttry {\n\t\t\t\t\tpush.apply( results,\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch(qsaError) {\n\t\t\t\t} finally {\n\t\t\t\t\tif ( !old ) {\n\t\t\t\t\t\tcontext.removeAttribute(\"id\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {Function(string, Object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache() {\n\tvar keys = [];\n\n\tfunction cache( key, value ) {\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n\t\tif ( keys.push( key + \" \" ) > Expr.cacheLength ) {\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn (cache[ key + \" \" ] = value);\n\t}\n\treturn cache;\n}\n\n/**\n * Mark a function for special use by Sizzle\n * @param {Function} fn The function to mark\n */\nfunction markFunction( fn ) {\n\tfn[ expando ] = true;\n\treturn fn;\n}\n\n/**\n * Support testing using an element\n * @param {Function} fn Passed the created div and expects a boolean result\n */\nfunction assert( fn ) {\n\tvar div = document.createElement(\"div\");\n\n\ttry {\n\t\treturn !!fn( div );\n\t} catch (e) {\n\t\treturn false;\n\t} finally {\n\t\t// Remove from its parent by default\n\t\tif ( div.parentNode ) {\n\t\t\tdiv.parentNode.removeChild( div );\n\t\t}\n\t\t// release memory in IE\n\t\tdiv = null;\n\t}\n}\n\n/**\n * Adds the same handler for all of the specified attrs\n * @param {String} attrs Pipe-separated list of attributes\n * @param {Function} handler The method that will be applied\n */\nfunction addHandle( attrs, handler ) {\n\tvar arr = attrs.split(\"|\"),\n\t\ti = attrs.length;\n\n\twhile ( i-- ) {\n\t\tExpr.attrHandle[ arr[i] ] = handler;\n\t}\n}\n\n/**\n * Checks document order of two siblings\n * @param {Element} a\n * @param {Element} b\n * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\n */\nfunction siblingCheck( a, b ) {\n\tvar cur = b && a,\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n\t\t\t( ~b.sourceIndex || MAX_NEGATIVE ) -\n\t\t\t( ~a.sourceIndex || MAX_NEGATIVE );\n\n\t// Use IE sourceIndex if available on both nodes\n\tif ( diff ) {\n\t\treturn diff;\n\t}\n\n\t// Check if b follows a\n\tif ( cur ) {\n\t\twhile ( (cur = cur.nextSibling) ) {\n\t\t\tif ( cur === b ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn a ? 1 : -1;\n}\n\n/**\n * Returns a function to use in pseudos for input types\n * @param {String} type\n */\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for buttons\n * @param {String} type\n */\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn (name === \"input\" || name === \"button\") && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for positionals\n * @param {Function} fn\n */\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction(function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction(function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ (j = matchIndexes[i]) ] ) {\n\t\t\t\t\tseed[j] = !(matches[j] = seed[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n/**\n * Checks a node for validity as a Sizzle context\n * @param {Element|Object=} context\n * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value\n */\nfunction testContext( context ) {\n\treturn context && typeof context.getElementsByTagName !== strundefined && context;\n}\n\n// Expose support vars for convenience\nsupport = Sizzle.support = {};\n\n/**\n * Detects XML nodes\n * @param {Element|Object} elem An element or a document\n * @returns {Boolean} True iff elem is a non-HTML XML node\n */\nisXML = Sizzle.isXML = function( elem ) {\n\t// documentElement is verified for cases where it doesn't yet exist\n\t// (such as loading iframes in IE - #4833)\n\tvar documentElement = elem && (elem.ownerDocument || elem).documentElement;\n\treturn documentElement ? documentElement.nodeName !== \"HTML\" : false;\n};\n\n/**\n * Sets document-related variables once based on the current document\n * @param {Element|Object} [doc] An element or document object to use to set the document\n * @returns {Object} Returns the current document\n */\nsetDocument = Sizzle.setDocument = function( node ) {\n\tvar hasCompare,\n\t\tdoc = node ? node.ownerDocument || node : preferredDoc,\n\t\tparent = doc.defaultView;\n\n\t// If no document and documentElement is available, return\n\tif ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {\n\t\treturn document;\n\t}\n\n\t// Set our document\n\tdocument = doc;\n\tdocElem = doc.documentElement;\n\n\t// Support tests\n\tdocumentIsHTML = !isXML( doc );\n\n\t// Support: IE>8\n\t// If iframe document is assigned to \"document\" variable and if iframe has been reloaded,\n\t// IE will throw \"permission denied\" error when accessing \"document\" variable, see jQuery #13936\n\t// IE6-8 do not support the defaultView property so parent will be undefined\n\tif ( parent && parent !== parent.top ) {\n\t\t// IE11 does not have attachEvent, so all must suffer\n\t\tif ( parent.addEventListener ) {\n\t\t\tparent.addEventListener( \"unload\", function() {\n\t\t\t\tsetDocument();\n\t\t\t}, false );\n\t\t} else if ( parent.attachEvent ) {\n\t\t\tparent.attachEvent( \"onunload\", function() {\n\t\t\t\tsetDocument();\n\t\t\t});\n\t\t}\n\t}\n\n\t/* Attributes\n\t---------------------------------------------------------------------- */\n\n\t// Support: IE<8\n\t// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)\n\tsupport.attributes = assert(function( div ) {\n\t\tdiv.className = \"i\";\n\t\treturn !div.getAttribute(\"className\");\n\t});\n\n\t/* getElement(s)By*\n\t---------------------------------------------------------------------- */\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tsupport.getElementsByTagName = assert(function( div ) {\n\t\tdiv.appendChild( doc.createComment(\"\") );\n\t\treturn !div.getElementsByTagName(\"*\").length;\n\t});\n\n\t// Check if getElementsByClassName can be trusted\n\tsupport.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) {\n\t\tdiv.innerHTML = \"<div class='a'></div><div class='a i'></div>\";\n\n\t\t// Support: Safari<4\n\t\t// Catch class over-caching\n\t\tdiv.firstChild.className = \"i\";\n\t\t// Support: Opera<10\n\t\t// Catch gEBCN failure to find non-leading classes\n\t\treturn div.getElementsByClassName(\"i\").length === 2;\n\t});\n\n\t// Support: IE<10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don't pick up programatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById = assert(function( div ) {\n\t\tdocElem.appendChild( div ).id = expando;\n\t\treturn !doc.getElementsByName || !doc.getElementsByName( expando ).length;\n\t});\n\n\t// ID find and filter\n\tif ( support.getById ) {\n\t\tExpr.find[\"ID\"] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== strundefined && documentIsHTML ) {\n\t\t\t\tvar m = context.getElementById( id );\n\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\treturn m && m.parentNode ? [m] : [];\n\t\t\t}\n\t\t};\n\t\tExpr.filter[\"ID\"] = function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.getAttribute(\"id\") === attrId;\n\t\t\t};\n\t\t};\n\t} else {\n\t\t// Support: IE6/7\n\t\t// getElementById is not reliable as a find shortcut\n\t\tdelete Expr.find[\"ID\"];\n\n\t\tExpr.filter[\"ID\"] =  function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode(\"id\");\n\t\t\t\treturn node && node.value === attrId;\n\t\t\t};\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find[\"TAG\"] = support.getElementsByTagName ?\n\t\tfunction( tag, context ) {\n\t\t\tif ( typeof context.getElementsByTagName !== strundefined ) {\n\t\t\t\treturn context.getElementsByTagName( tag );\n\t\t\t}\n\t\t} :\n\t\tfunction( tag, context ) {\n\t\t\tvar elem,\n\t\t\t\ttmp = [],\n\t\t\t\ti = 0,\n\t\t\t\tresults = context.getElementsByTagName( tag );\n\n\t\t\t// Filter out possible comments\n\t\t\tif ( tag === \"*\" ) {\n\t\t\t\twhile ( (elem = results[i++]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t\treturn results;\n\t\t};\n\n\t// Class\n\tExpr.find[\"CLASS\"] = support.getElementsByClassName && function( className, context ) {\n\t\tif ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {\n\t\t\treturn context.getElementsByClassName( className );\n\t\t}\n\t};\n\n\t/* QSA/matchesSelector\n\t---------------------------------------------------------------------- */\n\n\t// QSA and matchesSelector support\n\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\trbuggyMatches = [];\n\n\t// qSa(:focus) reports false when true (Chrome 21)\n\t// We allow this because of a bug in IE8/9 that throws an error\n\t// whenever `document.activeElement` is accessed on an iframe\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\n\t// See http://bugs.jquery.com/ticket/13378\n\trbuggyQSA = [];\n\n\tif ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert(function( div ) {\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explicitly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// http://bugs.jquery.com/ticket/12359\n\t\t\tdiv.innerHTML = \"<select t=''><option selected=''></option></select>\";\n\n\t\t\t// Support: IE8, Opera 10-12\n\t\t\t// Nothing should be selected when empty strings follow ^= or $= or *=\n\t\t\tif ( div.querySelectorAll(\"[t^='']\").length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !div.querySelectorAll(\"[selected]\").length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":checked\").length ) {\n\t\t\t\trbuggyQSA.push(\":checked\");\n\t\t\t}\n\t\t});\n\n\t\tassert(function( div ) {\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type and name attributes are restricted during .innerHTML assignment\n\t\t\tvar input = doc.createElement(\"input\");\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tdiv.appendChild( input ).setAttribute( \"name\", \"D\" );\n\n\t\t\t// Support: IE8\n\t\t\t// Enforce case-sensitivity of name attribute\n\t\t\tif ( div.querySelectorAll(\"[name=d]\").length ) {\n\t\t\t\trbuggyQSA.push( \"name\" + whitespace + \"*[*^$|!~]?=\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":enabled\").length ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\tdiv.querySelectorAll(\"*,:x\");\n\t\t\trbuggyQSA.push(\",.*:\");\n\t\t});\n\t}\n\n\tif ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector) )) ) {\n\n\t\tassert(function( div ) {\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( div, \"div\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\tmatches.call( div, \"[s!='']:x\" );\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t});\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join(\"|\") );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join(\"|\") );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\thasCompare = rnative.test( docElem.compareDocumentPosition );\n\n\t// Element contains another\n\t// Purposefully does not implement inclusive descendent\n\t// As in, an element does not contain itself\n\tcontains = hasCompare || rnative.test( docElem.contains ) ?\n\t\tfunction( a, b ) {\n\t\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\n\t\t\t\tbup = b && b.parentNode;\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\n\t\t\t\tadown.contains ?\n\t\t\t\t\tadown.contains( bup ) :\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\n\t\t\t));\n\t\t} :\n\t\tfunction( a, b ) {\n\t\t\tif ( b ) {\n\t\t\t\twhile ( (b = b.parentNode) ) {\n\t\t\t\t\tif ( b === a ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t/* Sorting\n\t---------------------------------------------------------------------- */\n\n\t// Document order sorting\n\tsortOrder = hasCompare ?\n\tfunction( a, b ) {\n\n\t\t// Flag for duplicate removal\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Sort on method existence if only one input has compareDocumentPosition\n\t\tvar compare = !a.compareDocumentPosition - !b.compareDocumentPosition;\n\t\tif ( compare ) {\n\t\t\treturn compare;\n\t\t}\n\n\t\t// Calculate position if both inputs belong to the same document\n\t\tcompare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?\n\t\t\ta.compareDocumentPosition( b ) :\n\n\t\t\t// Otherwise we know they are disconnected\n\t\t\t1;\n\n\t\t// Disconnected nodes\n\t\tif ( compare & 1 ||\n\t\t\t(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {\n\n\t\t\t// Choose the first element that is related to our preferred document\n\t\t\tif ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// Maintain original order\n\t\t\treturn sortInput ?\n\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\n\t\t\t\t0;\n\t\t}\n\n\t\treturn compare & 4 ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\t\t// Exit early if the nodes are identical\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tap = [ a ],\n\t\t\tbp = [ b ];\n\n\t\t// Parentless nodes are either documents or disconnected\n\t\tif ( !aup || !bup ) {\n\t\t\treturn a === doc ? -1 :\n\t\t\t\tb === doc ? 1 :\n\t\t\t\taup ? -1 :\n\t\t\t\tbup ? 1 :\n\t\t\t\tsortInput ?\n\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\n\t\t\t\t0;\n\n\t\t// If the nodes are siblings, we can do a quick check\n\t\t} else if ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\t\t}\n\n\t\t// Otherwise we need full lists of their ancestors for comparison\n\t\tcur = a;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tap.unshift( cur );\n\t\t}\n\t\tcur = b;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tbp.unshift( cur );\n\t\t}\n\n\t\t// Walk down the tree looking for a discrepancy\n\t\twhile ( ap[i] === bp[i] ) {\n\t\t\ti++;\n\t\t}\n\n\t\treturn i ?\n\t\t\t// Do a sibling check if the nodes have a common ancestor\n\t\t\tsiblingCheck( ap[i], bp[i] ) :\n\n\t\t\t// Otherwise nodes in our document sort first\n\t\t\tap[i] === preferredDoc ? -1 :\n\t\t\tbp[i] === preferredDoc ? 1 :\n\t\t\t0;\n\t};\n\n\treturn doc;\n};\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\t// Make sure that attribute selectors are quoted\n\texpr = expr.replace( rattributeQuotes, \"='$1']\" );\n\n\tif ( support.matchesSelector && documentIsHTML &&\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\n\n\t\ttry {\n\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\tif ( ret || support.disconnectedMatch ||\n\t\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t\t// fragment in IE 9\n\t\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch(e) {}\n\t}\n\n\treturn Sizzle( expr, document, null, [elem] ).length > 0;\n};\n\nSizzle.contains = function( context, elem ) {\n\t// Set document vars if needed\n\tif ( ( context.ownerDocument || context ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\treturn contains( context, elem );\n};\n\nSizzle.attr = function( elem, name ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\n\t\tval = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\n\t\t\tfn( elem, name, !documentIsHTML ) :\n\t\t\tundefined;\n\n\treturn val !== undefined ?\n\t\tval :\n\t\tsupport.attributes || !documentIsHTML ?\n\t\t\telem.getAttribute( name ) :\n\t\t\t(val = elem.getAttributeNode(name)) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\tnull;\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\n/**\n * Document sorting and removing duplicates\n * @param {ArrayLike} results\n */\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\tj = 0,\n\t\ti = 0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\thasDuplicate = !support.detectDuplicates;\n\tsortInput = !support.sortStable && results.slice( 0 );\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\twhile ( (elem = results[i++]) ) {\n\t\t\tif ( elem === results[ i ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\t// Clear input after sorting to release objects\n\t// See https://github.com/jquery/sizzle/pull/225\n\tsortInput = null;\n\n\treturn results;\n};\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( !nodeType ) {\n\t\t// If no nodeType, this is expected to be an array\n\t\twhile ( (node = elem[i++]) ) {\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\t\t// Use textContent for elements\n\t\t// innerText usage removed for consistency of new lines (jQuery #11153)\n\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\treturn elem.textContent;\n\t\t} else {\n\t\t\t// Traverse its children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tret += getText( elem );\n\t\t\t}\n\t\t}\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\treturn elem.nodeValue;\n\t}\n\t// Do not include comment or processing instruction nodes\n\n\treturn ret;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[1] = match[1].replace( runescape, funescape );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[3] = ( match[4] || match[5] || \"\" ).replace( runescape, funescape );\n\n\t\t\tif ( match[2] === \"~=\" ) {\n\t\t\t\tmatch[3] = \" \" + match[3] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 what (child|of-type)\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t5 sign of xn-component\n\t\t\t\t6 x of xn-component\n\t\t\t\t7 sign of y-component\n\t\t\t\t8 y of y-component\n\t\t\t*/\n\t\t\tmatch[1] = match[1].toLowerCase();\n\n\t\t\tif ( match[1].slice( 0, 3 ) === \"nth\" ) {\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif ( !match[3] ) {\n\t\t\t\t\tSizzle.error( match[0] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === \"even\" || match[3] === \"odd\" ) );\n\t\t\t\tmatch[5] = +( ( match[7] + match[8] ) || match[3] === \"odd\" );\n\n\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[3] ) {\n\t\t\t\tSizzle.error( match[0] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[5] && match[2];\n\n\t\t\tif ( matchExpr[\"CHILD\"].test( match[0] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif ( match[3] && match[4] !== undefined ) {\n\t\t\t\tmatch[2] = match[4];\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t(excess = tokenize( unquoted, true )) &&\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t(excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length) ) {\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[0] = match[0].slice( 0, excess );\n\t\t\t\tmatch[2] = unquoted.slice( 0, excess );\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\t\"TAG\": function( nodeNameSelector ) {\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn nodeNameSelector === \"*\" ?\n\t\t\t\tfunction() { return true; } :\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t(pattern = new RegExp( \"(^|\" + whitespace + \")\" + className + \"(\" + whitespace + \"|$)\" )) &&\n\t\t\t\tclassCache( className, function( elem ) {\n\t\t\t\t\treturn pattern.test( typeof elem.className === \"string\" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute(\"class\") || \"\" );\n\t\t\t\t});\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, what, argument, first, last ) {\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\n\t\t\t\tofType = what === \"of-type\";\n\n\t\t\treturn first === 1 && last === 0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tvar cache, outerCache, node, diff, nodeIndex, start,\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n\t\t\t\t\t\tparent = elem.parentNode,\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml && !ofType;\n\n\t\t\t\t\tif ( parent ) {\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif ( simple ) {\n\t\t\t\t\t\t\twhile ( dir ) {\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\twhile ( (node = node[ dir ]) ) {\n\t\t\t\t\t\t\t\t\tif ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif ( forward && useCache ) {\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\t\t\t\t\t\t\touterCache = parent[ expando ] || (parent[ expando ] = {});\n\t\t\t\t\t\t\tcache = outerCache[ type ] || [];\n\t\t\t\t\t\t\tnodeIndex = cache[0] === dirruns && cache[1];\n\t\t\t\t\t\t\tdiff = cache[0] === dirruns && cache[2];\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\n\t\t\t\t\t\t\t\t\touterCache[ type ] = [ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {\n\t\t\t\t\t\t\tdiff = cache[1];\n\n\t\t\t\t\t\t// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\tif ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {\n\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\tif ( useCache ) {\n\t\t\t\t\t\t\t\t\t\t(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif ( node === elem ) {\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -= last;\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction(function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf.call( seed, matched[i] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[i] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\t\t// Potentially complex pseudos\n\t\t\"not\": markFunction(function( selector ) {\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction(function( seed, matches, context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = unmatched[i]) ) {\n\t\t\t\t\t\t\tseed[i] = !(matches[i] = elem);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}) :\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tinput[0] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t}),\n\n\t\t\"has\": markFunction(function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t}),\n\n\t\t\"contains\": markFunction(function( text ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t}),\n\n\t\t// \"Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element's language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by \"-\".\n\t\t// The matching of C against the element's language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name.\"\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\n\t\t\"lang\": markFunction( function( lang ) {\n\t\t\t// lang value must be a valid identifier\n\t\t\tif ( !ridentifier.test(lang || \"\") ) {\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\n\t\t\t}\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn function( elem ) {\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif ( (elemLang = documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute(\"xml:lang\") || elem.getAttribute(\"lang\")) ) {\n\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\n\t\t\t\t\t}\n\t\t\t\t} while ( (elem = elem.parentNode) && elem.nodeType === 1 );\n\t\t\t\treturn false;\n\t\t\t};\n\t\t}),\n\n\t\t// Miscellaneous\n\t\t\"target\": function( elem ) {\n\t\t\tvar hash = window.location && window.location.hash;\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\n\t\t},\n\n\t\t\"root\": function( elem ) {\n\t\t\treturn elem === docElem;\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\treturn elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);\n\t\t},\n\n\t\t// Boolean properties\n\t\t\"enabled\": function( elem ) {\n\t\t\treturn elem.disabled === false;\n\t\t},\n\n\t\t\"disabled\": function( elem ) {\n\t\t\treturn elem.disabled === true;\n\t\t},\n\n\t\t\"checked\": function( elem ) {\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn (nodeName === \"input\" && !!elem.checked) || (nodeName === \"option\" && !!elem.selected);\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t// Contents\n\t\t\"empty\": function( elem ) {\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),\n\t\t\t//   but not by others (comment: 8; processing instruction: 7; etc.)\n\t\t\t// nodeType < 6 works because attributes (2) do not appear as children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tif ( elem.nodeType < 6 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[\"empty\"]( elem );\n\t\t},\n\n\t\t// Element/input types\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar attr;\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\telem.type === \"text\" &&\n\n\t\t\t\t// Support: IE<8\n\t\t\t\t// New HTML5 attribute values (e.g., \"search\") appear with elem.type === \"text\"\n\t\t\t\t( (attr = elem.getAttribute(\"type\")) == null || attr.toLowerCase() === \"text\" );\n\t\t},\n\n\t\t// Position-in-collection\n\t\t\"first\": createPositionalPseudo(function() {\n\t\t\treturn [ 0 ];\n\t\t}),\n\n\t\t\"last\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t}),\n\n\t\t\"eq\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t}),\n\n\t\t\"even\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"odd\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 1;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"lt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"gt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t})\n\t}\n};\n\nExpr.pseudos[\"nth\"] = Expr.pseudos[\"eq\"];\n\n// Add button/input type pseudos\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\n\tExpr.pseudos[ i ] = createInputPseudo( i );\n}\nfor ( i in { submit: true, reset: true } ) {\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\n}\n\n// Easy API for creating new setFilters\nfunction setFilters() {}\nsetFilters.prototype = Expr.filters = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\nfunction tokenize( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || (match = rcomma.exec( soFar )) ) {\n\t\t\tif ( match ) {\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[0].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( (tokens = []) );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( (match = rcombinators.exec( soFar )) ) {\n\t\t\tmatched = match.shift();\n\t\t\ttokens.push({\n\t\t\t\tvalue: matched,\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[0].replace( rtrim, \" \" )\n\t\t\t});\n\t\t\tsoFar = soFar.slice( matched.length );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\n\t\t\t\t(match = preFilters[ type ]( match ))) ) {\n\t\t\t\tmatched = match.shift();\n\t\t\t\ttokens.push({\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t});\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n}\n\nfunction toSelector( tokens ) {\n\tvar i = 0,\n\t\tlen = tokens.length,\n\t\tselector = \"\";\n\tfor ( ; i < len; i++ ) {\n\t\tselector += tokens[i].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tcheckNonElements = base && dir === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar oldCache, outerCache,\n\t\t\t\tnewCache = [ dirruns, doneName ];\n\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\n\t\t\tif ( xml ) {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\touterCache = elem[ expando ] || (elem[ expando ] = {});\n\t\t\t\t\t\tif ( (oldCache = outerCache[ dir ]) &&\n\t\t\t\t\t\t\toldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {\n\n\t\t\t\t\t\t\t// Assign to newCache so results back-propagate to previous elements\n\t\t\t\t\t\t\treturn (newCache[ 2 ] = oldCache[ 2 ]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Reuse newcache so results back-propagate to previous elements\n\t\t\t\t\t\t\touterCache[ dir ] = newCache;\n\n\t\t\t\t\t\t\t// A match means we're done; a fail means we have to keep checking\n\t\t\t\t\t\t\tif ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[i]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[0];\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (elem = unmatched[i]) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction(function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts( selector || \"*\", context.nodeType ? [ context ] : context, [] ),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( (elem = temp[i]) ) {\n\t\t\t\t\tmatcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = matcherOut[i]) ) {\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( (matcherIn[i] = elem) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, (matcherOut = []), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( (elem = matcherOut[i]) &&\n\t\t\t\t\t\t(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {\n\n\t\t\t\t\t\tseed[temp] = !(results[temp] = elem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[0].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[\" \"],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf.call( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\treturn ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t(checkContext = context).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (matcher = Expr.relative[ tokens[i].type ]) ) {\n\t\t\tmatchers = [ addCombinator(elementMatcher( matchers ), matcher) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[j].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && toSelector(\n\t\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\t\ttokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" })\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( (tokens = tokens.slice( j )) ),\n\t\t\t\t\tj < len && toSelector( tokens )\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\tvar bySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, outermost ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\tsetMatched = [],\n\t\t\t\tcontextBackup = outermostContext,\n\t\t\t\t// We must always have either seed elements or outermost context\n\t\t\t\telems = seed || byElement && Expr.find[\"TAG\"]( \"*\", outermost ),\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),\n\t\t\t\tlen = elems.length;\n\n\t\t\tif ( outermost ) {\n\t\t\t\toutermostContext = context !== document && context;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Keep `i` a string if there are no elements so `matchedCount` will be \"00\" below\n\t\t\t// Support: IE<9, Safari\n\t\t\t// Tolerate NodeList properties (IE: \"length\"; Safari: <number>) matching elements by id\n\t\t\tfor ( ; i !== len && (elem = elems[i]) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (matcher = elementMatchers[j++]) ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( (elem = !matcher && elem) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\tmatchedCount += i;\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (matcher = setMatchers[j++]) ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !(unmatched[i] || setMatched[i]) ) {\n\t\t\t\t\t\t\t\tsetMatched[i] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ selector + \" \" ];\n\n\tif ( !cached ) {\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !group ) {\n\t\t\tgroup = tokenize( selector );\n\t\t}\n\t\ti = group.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( group[i] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );\n\t}\n\treturn cached;\n};\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[i], results );\n\t}\n\treturn results;\n}\n\nfunction select( selector, context, results, seed ) {\n\tvar i, tokens, token, type, find,\n\t\tmatch = tokenize( selector );\n\n\tif ( !seed ) {\n\t\t// Try to minimize operations if there is only one group\n\t\tif ( match.length === 1 ) {\n\n\t\t\t// Take a shortcut and set the context if the root selector is an ID\n\t\t\ttokens = match[0] = match[0].slice( 0 );\n\t\t\tif ( tokens.length > 2 && (token = tokens[0]).type === \"ID\" &&\n\t\t\t\t\tsupport.getById && context.nodeType === 9 && documentIsHTML &&\n\t\t\t\t\tExpr.relative[ tokens[1].type ] ) {\n\n\t\t\t\tcontext = ( Expr.find[\"ID\"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];\n\t\t\t\tif ( !context ) {\n\t\t\t\t\treturn results;\n\t\t\t\t}\n\t\t\t\tselector = selector.slice( tokens.shift().value.length );\n\t\t\t}\n\n\t\t\t// Fetch a seed set for right-to-left matching\n\t\t\ti = matchExpr[\"needsContext\"].test( selector ) ? 0 : tokens.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\ttoken = tokens[i];\n\n\t\t\t\t// Abort if we hit a combinator\n\t\t\t\tif ( Expr.relative[ (type = token.type) ] ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif ( (find = Expr.find[ type ]) ) {\n\t\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\t\tif ( (seed = find(\n\t\t\t\t\t\ttoken.matches[0].replace( runescape, funescape ),\n\t\t\t\t\t\trsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context\n\t\t\t\t\t)) ) {\n\n\t\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\t\tselector = seed.length && toSelector( tokens );\n\t\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\t\tpush.apply( results, seed );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\tcompile( selector, match )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\trsibling.test( selector ) && testContext( context.parentNode ) || context\n\t);\n\treturn results;\n}\n\n// One-time assignments\n\n// Sort stability\nsupport.sortStable = expando.split(\"\").sort( sortOrder ).join(\"\") === expando;\n\n// Support: Chrome<14\n// Always assume duplicates if they aren't passed to the comparison function\nsupport.detectDuplicates = !!hasDuplicate;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n// Detached nodes confoundingly follow *each other*\nsupport.sortDetached = assert(function( div1 ) {\n\t// Should return 1, but returns 4 (following)\n\treturn div1.compareDocumentPosition( document.createElement(\"div\") ) & 1;\n});\n\n// Support: IE<8\n// Prevent attribute/property \"interpolation\"\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !assert(function( div ) {\n\tdiv.innerHTML = \"<a href='#'></a>\";\n\treturn div.firstChild.getAttribute(\"href\") === \"#\" ;\n}) ) {\n\taddHandle( \"type|href|height|width\", function( elem, name, isXML ) {\n\t\tif ( !isXML ) {\n\t\t\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use defaultValue in place of getAttribute(\"value\")\nif ( !support.attributes || !assert(function( div ) {\n\tdiv.innerHTML = \"<input/>\";\n\tdiv.firstChild.setAttribute( \"value\", \"\" );\n\treturn div.firstChild.getAttribute( \"value\" ) === \"\";\n}) ) {\n\taddHandle( \"value\", function( elem, name, isXML ) {\n\t\tif ( !isXML && elem.nodeName.toLowerCase() === \"input\" ) {\n\t\t\treturn elem.defaultValue;\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use getAttributeNode to fetch booleans when getAttribute lies\nif ( !assert(function( div ) {\n\treturn div.getAttribute(\"disabled\") == null;\n}) ) {\n\taddHandle( booleans, function( elem, name, isXML ) {\n\t\tvar val;\n\t\tif ( !isXML ) {\n\t\t\treturn elem[ name ] === true ? name.toLowerCase() :\n\t\t\t\t\t(val = elem.getAttributeNode( name )) && val.specified ?\n\t\t\t\t\tval.value :\n\t\t\t\tnull;\n\t\t}\n\t});\n}\n\nreturn Sizzle;\n\n})( window );\n\n\n\njQuery.find = Sizzle;\njQuery.expr = Sizzle.selectors;\njQuery.expr[\":\"] = jQuery.expr.pseudos;\njQuery.unique = Sizzle.uniqueSort;\njQuery.text = Sizzle.getText;\njQuery.isXMLDoc = Sizzle.isXML;\njQuery.contains = Sizzle.contains;\n\n\n\nvar rneedsContext = jQuery.expr.match.needsContext;\n\nvar rsingleTag = (/^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/);\n\n\n\nvar risSimple = /^.[^:#\\[\\.,]*$/;\n\n// Implement the identical functionality for filter and not\nfunction winnow( elements, qualifier, not ) {\n\tif ( jQuery.isFunction( qualifier ) ) {\n\t\treturn jQuery.grep( elements, function( elem, i ) {\n\t\t\t/* jshint -W018 */\n\t\t\treturn !!qualifier.call( elem, i, elem ) !== not;\n\t\t});\n\n\t}\n\n\tif ( qualifier.nodeType ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( elem === qualifier ) !== not;\n\t\t});\n\n\t}\n\n\tif ( typeof qualifier === \"string\" ) {\n\t\tif ( risSimple.test( qualifier ) ) {\n\t\t\treturn jQuery.filter( qualifier, elements, not );\n\t\t}\n\n\t\tqualifier = jQuery.filter( qualifier, elements );\n\t}\n\n\treturn jQuery.grep( elements, function( elem ) {\n\t\treturn ( indexOf.call( qualifier, elem ) >= 0 ) !== not;\n\t});\n}\n\njQuery.filter = function( expr, elems, not ) {\n\tvar elem = elems[ 0 ];\n\n\tif ( not ) {\n\t\texpr = \":not(\" + expr + \")\";\n\t}\n\n\treturn elems.length === 1 && elem.nodeType === 1 ?\n\t\tjQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :\n\t\tjQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {\n\t\t\treturn elem.nodeType === 1;\n\t\t}));\n};\n\njQuery.fn.extend({\n\tfind: function( selector ) {\n\t\tvar i,\n\t\t\tlen = this.length,\n\t\t\tret = [],\n\t\t\tself = this;\n\n\t\tif ( typeof selector !== \"string\" ) {\n\t\t\treturn this.pushStack( jQuery( selector ).filter(function() {\n\t\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}) );\n\t\t}\n\n\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\tjQuery.find( selector, self[ i ], ret );\n\t\t}\n\n\t\t// Needed because $( selector, context ) becomes $( context ).find( selector )\n\t\tret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );\n\t\tret.selector = this.selector ? this.selector + \" \" + selector : selector;\n\t\treturn ret;\n\t},\n\tfilter: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], false) );\n\t},\n\tnot: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], true) );\n\t},\n\tis: function( selector ) {\n\t\treturn !!winnow(\n\t\t\tthis,\n\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n\t\t\ttypeof selector === \"string\" && rneedsContext.test( selector ) ?\n\t\t\t\tjQuery( selector ) :\n\t\t\t\tselector || [],\n\t\t\tfalse\n\t\t).length;\n\t}\n});\n\n\n// Initialize a jQuery object\n\n\n// A central reference to the root jQuery(document)\nvar rootjQuery,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\n\t// Strict HTML recognition (#11290: must start with <)\n\trquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,\n\n\tinit = jQuery.fn.init = function( selector, context ) {\n\t\tvar match, elem;\n\n\t\t// HANDLE: $(\"\"), $(null), $(undefined), $(false)\n\t\tif ( !selector ) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Handle HTML strings\n\t\tif ( typeof selector === \"string\" ) {\n\t\t\tif ( selector[0] === \"<\" && selector[ selector.length - 1 ] === \">\" && selector.length >= 3 ) {\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch = [ null, selector, null ];\n\n\t\t\t} else {\n\t\t\t\tmatch = rquickExpr.exec( selector );\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif ( match && (match[1] || !context) ) {\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif ( match[1] ) {\n\t\t\t\t\tcontext = context instanceof jQuery ? context[0] : context;\n\n\t\t\t\t\t// scripts is true for back-compat\n\t\t\t\t\t// Intentionally let the error be thrown if parseHTML is not present\n\t\t\t\t\tjQuery.merge( this, jQuery.parseHTML(\n\t\t\t\t\t\tmatch[1],\n\t\t\t\t\t\tcontext && context.nodeType ? context.ownerDocument || context : document,\n\t\t\t\t\t\ttrue\n\t\t\t\t\t) );\n\n\t\t\t\t\t// HANDLE: $(html, props)\n\t\t\t\t\tif ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {\n\t\t\t\t\t\tfor ( match in context ) {\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\n\t\t\t\t\t\t\tif ( jQuery.isFunction( this[ match ] ) ) {\n\t\t\t\t\t\t\t\tthis[ match ]( context[ match ] );\n\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.attr( match, context[ match ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this;\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t} else {\n\t\t\t\t\telem = document.getElementById( match[2] );\n\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Inject the element directly into the jQuery object\n\t\t\t\t\t\tthis.length = 1;\n\t\t\t\t\t\tthis[0] = elem;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.context = document;\n\t\t\t\t\tthis.selector = selector;\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t} else if ( !context || context.jquery ) {\n\t\t\t\treturn ( context || rootjQuery ).find( selector );\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t} else {\n\t\t\t\treturn this.constructor( context ).find( selector );\n\t\t\t}\n\n\t\t// HANDLE: $(DOMElement)\n\t\t} else if ( selector.nodeType ) {\n\t\t\tthis.context = this[0] = selector;\n\t\t\tthis.length = 1;\n\t\t\treturn this;\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t} else if ( jQuery.isFunction( selector ) ) {\n\t\t\treturn typeof rootjQuery.ready !== \"undefined\" ?\n\t\t\t\trootjQuery.ready( selector ) :\n\t\t\t\t// Execute immediately if ready is not present\n\t\t\t\tselector( jQuery );\n\t\t}\n\n\t\tif ( selector.selector !== undefined ) {\n\t\t\tthis.selector = selector.selector;\n\t\t\tthis.context = selector.context;\n\t\t}\n\n\t\treturn jQuery.makeArray( selector, this );\n\t};\n\n// Give the init function the jQuery prototype for later instantiation\ninit.prototype = jQuery.fn;\n\n// Initialize central reference\nrootjQuery = jQuery( document );\n\n\nvar rparentsprev = /^(?:parents|prev(?:Until|All))/,\n\t// methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique = {\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.extend({\n\tdir: function( elem, dir, until ) {\n\t\tvar matched = [],\n\t\t\ttruncate = until !== undefined;\n\n\t\twhile ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\tif ( truncate && jQuery( elem ).is( until ) ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tmatched.push( elem );\n\t\t\t}\n\t\t}\n\t\treturn matched;\n\t},\n\n\tsibling: function( n, elem ) {\n\t\tvar matched = [];\n\n\t\tfor ( ; n; n = n.nextSibling ) {\n\t\t\tif ( n.nodeType === 1 && n !== elem ) {\n\t\t\t\tmatched.push( n );\n\t\t\t}\n\t\t}\n\n\t\treturn matched;\n\t}\n});\n\njQuery.fn.extend({\n\thas: function( target ) {\n\t\tvar targets = jQuery( target, this ),\n\t\t\tl = targets.length;\n\n\t\treturn this.filter(function() {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tif ( jQuery.contains( this, targets[i] ) ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\tclosest: function( selectors, context ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tmatched = [],\n\t\t\tpos = rneedsContext.test( selectors ) || typeof selectors !== \"string\" ?\n\t\t\t\tjQuery( selectors, context || this.context ) :\n\t\t\t\t0;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tfor ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {\n\t\t\t\t// Always skip document fragments\n\t\t\t\tif ( cur.nodeType < 11 && (pos ?\n\t\t\t\t\tpos.index(cur) > -1 :\n\n\t\t\t\t\t// Don't pass non-elements to Sizzle\n\t\t\t\t\tcur.nodeType === 1 &&\n\t\t\t\t\t\tjQuery.find.matchesSelector(cur, selectors)) ) {\n\n\t\t\t\t\tmatched.push( cur );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );\n\t},\n\n\t// Determine the position of an element within\n\t// the matched set of elements\n\tindex: function( elem ) {\n\n\t\t// No argument, return index in parent\n\t\tif ( !elem ) {\n\t\t\treturn ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;\n\t\t}\n\n\t\t// index in selector\n\t\tif ( typeof elem === \"string\" ) {\n\t\t\treturn indexOf.call( jQuery( elem ), this[ 0 ] );\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn indexOf.call( this,\n\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[ 0 ] : elem\n\t\t);\n\t},\n\n\tadd: function( selector, context ) {\n\t\treturn this.pushStack(\n\t\t\tjQuery.unique(\n\t\t\t\tjQuery.merge( this.get(), jQuery( selector, context ) )\n\t\t\t)\n\t\t);\n\t},\n\n\taddBack: function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter(selector)\n\t\t);\n\t}\n});\n\nfunction sibling( cur, dir ) {\n\twhile ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}\n\treturn cur;\n}\n\njQuery.each({\n\tparent: function( elem ) {\n\t\tvar parent = elem.parentNode;\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\n\t},\n\tparents: function( elem ) {\n\t\treturn jQuery.dir( elem, \"parentNode\" );\n\t},\n\tparentsUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"parentNode\", until );\n\t},\n\tnext: function( elem ) {\n\t\treturn sibling( elem, \"nextSibling\" );\n\t},\n\tprev: function( elem ) {\n\t\treturn sibling( elem, \"previousSibling\" );\n\t},\n\tnextAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\" );\n\t},\n\tprevAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\" );\n\t},\n\tnextUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\", until );\n\t},\n\tprevUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\", until );\n\t},\n\tsiblings: function( elem ) {\n\t\treturn jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );\n\t},\n\tchildren: function( elem ) {\n\t\treturn jQuery.sibling( elem.firstChild );\n\t},\n\tcontents: function( elem ) {\n\t\treturn elem.contentDocument || jQuery.merge( [], elem.childNodes );\n\t}\n}, function( name, fn ) {\n\tjQuery.fn[ name ] = function( until, selector ) {\n\t\tvar matched = jQuery.map( this, fn, until );\n\n\t\tif ( name.slice( -5 ) !== \"Until\" ) {\n\t\t\tselector = until;\n\t\t}\n\n\t\tif ( selector && typeof selector === \"string\" ) {\n\t\t\tmatched = jQuery.filter( selector, matched );\n\t\t}\n\n\t\tif ( this.length > 1 ) {\n\t\t\t// Remove duplicates\n\t\t\tif ( !guaranteedUnique[ name ] ) {\n\t\t\t\tjQuery.unique( matched );\n\t\t\t}\n\n\t\t\t// Reverse order for parents* and prev-derivatives\n\t\t\tif ( rparentsprev.test( name ) ) {\n\t\t\t\tmatched.reverse();\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched );\n\t};\n});\nvar rnotwhite = (/\\S+/g);\n\n\n\n// String to Object options format cache\nvar optionsCache = {};\n\n// Convert String-formatted options into Object-formatted ones and store in cache\nfunction createOptions( options ) {\n\tvar object = optionsCache[ options ] = {};\n\tjQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {\n\t\tobject[ flag ] = true;\n\t});\n\treturn object;\n}\n\n/*\n * Create a callback list using the following parameters:\n *\n *\toptions: an optional list of space-separated options that will change how\n *\t\t\tthe callback list behaves or a more traditional option object\n *\n * By default a callback list will act like an event callback list and can be\n * \"fired\" multiple times.\n *\n * Possible options:\n *\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n *\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n *\t\t\t\t\tvalues (like a Deferred)\n *\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n *\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\n *\n */\njQuery.Callbacks = function( options ) {\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions = typeof options === \"string\" ?\n\t\t( optionsCache[ options ] || createOptions( options ) ) :\n\t\tjQuery.extend( {}, options );\n\n\tvar // Last fire value (for non-forgettable lists)\n\t\tmemory,\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\t\t// Flag to know if list is currently firing\n\t\tfiring,\n\t\t// First callback to fire (used internally by add and fireWith)\n\t\tfiringStart,\n\t\t// End of the loop when firing\n\t\tfiringLength,\n\t\t// Index of currently firing callback (modified by remove if needed)\n\t\tfiringIndex,\n\t\t// Actual callback list\n\t\tlist = [],\n\t\t// Stack of fire calls for repeatable lists\n\t\tstack = !options.once && [],\n\t\t// Fire callbacks\n\t\tfire = function( data ) {\n\t\t\tmemory = options.memory && data;\n\t\t\tfired = true;\n\t\t\tfiringIndex = firingStart || 0;\n\t\t\tfiringStart = 0;\n\t\t\tfiringLength = list.length;\n\t\t\tfiring = true;\n\t\t\tfor ( ; list && firingIndex < firingLength; firingIndex++ ) {\n\t\t\t\tif ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {\n\t\t\t\t\tmemory = false; // To prevent further calls using add\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiring = false;\n\t\t\tif ( list ) {\n\t\t\t\tif ( stack ) {\n\t\t\t\t\tif ( stack.length ) {\n\t\t\t\t\t\tfire( stack.shift() );\n\t\t\t\t\t}\n\t\t\t\t} else if ( memory ) {\n\t\t\t\t\tlist = [];\n\t\t\t\t} else {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// Actual Callbacks object\n\t\tself = {\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\t// First, we save the current length\n\t\t\t\t\tvar start = list.length;\n\t\t\t\t\t(function add( args ) {\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\n\t\t\t\t\t\t\tvar type = jQuery.type( arg );\n\t\t\t\t\t\t\tif ( type === \"function\" ) {\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\n\t\t\t\t\t\t\t\t\tlist.push( arg );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if ( arg && arg.length && type !== \"string\" ) {\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd( arg );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t})( arguments );\n\t\t\t\t\t// Do we need to add the callbacks to the\n\t\t\t\t\t// current firing batch?\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tfiringLength = list.length;\n\t\t\t\t\t// With memory, if we're not firing then\n\t\t\t\t\t// we should call right away\n\t\t\t\t\t} else if ( memory ) {\n\t\t\t\t\t\tfiringStart = start;\n\t\t\t\t\t\tfire( memory );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\tjQuery.each( arguments, function( _, arg ) {\n\t\t\t\t\t\tvar index;\n\t\t\t\t\t\twhile ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\n\t\t\t\t\t\t\tlist.splice( index, 1 );\n\t\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\t\t\tif ( index <= firingLength ) {\n\t\t\t\t\t\t\t\t\tfiringLength--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif ( index <= firingIndex ) {\n\t\t\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Check if a given callback is in the list.\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\n\t\t\thas: function( fn ) {\n\t\t\t\treturn fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );\n\t\t\t},\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function() {\n\t\t\t\tlist = [];\n\t\t\t\tfiringLength = 0;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Have the list do nothing anymore\n\t\t\tdisable: function() {\n\t\t\t\tlist = stack = memory = undefined;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it disabled?\n\t\t\tdisabled: function() {\n\t\t\t\treturn !list;\n\t\t\t},\n\t\t\t// Lock the list in its current state\n\t\t\tlock: function() {\n\t\t\t\tstack = undefined;\n\t\t\t\tif ( !memory ) {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it locked?\n\t\t\tlocked: function() {\n\t\t\t\treturn !stack;\n\t\t\t},\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function( context, args ) {\n\t\t\t\tif ( list && ( !fired || stack ) ) {\n\t\t\t\t\targs = args || [];\n\t\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tstack.push( args );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfire( args );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function() {\n\t\t\t\tself.fireWith( this, arguments );\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function() {\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\n\n\njQuery.extend({\n\n\tDeferred: function( func ) {\n\t\tvar tuples = [\n\t\t\t\t// action, add listener, listener list, final state\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks(\"once memory\"), \"resolved\" ],\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks(\"once memory\"), \"rejected\" ],\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks(\"memory\") ]\n\t\t\t],\n\t\t\tstate = \"pending\",\n\t\t\tpromise = {\n\t\t\t\tstate: function() {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function() {\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\tthen: function( /* fnDone, fnFail, fnProgress */ ) {\n\t\t\t\t\tvar fns = arguments;\n\t\t\t\t\treturn jQuery.Deferred(function( newDefer ) {\n\t\t\t\t\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\t\t\t\t\tvar fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];\n\t\t\t\t\t\t\t// deferred[ done | fail | progress ] for forwarding actions to newDefer\n\t\t\t\t\t\t\tdeferred[ tuple[1] ](function() {\n\t\t\t\t\t\t\t\tvar returned = fn && fn.apply( this, arguments );\n\t\t\t\t\t\t\t\tif ( returned && jQuery.isFunction( returned.promise ) ) {\n\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\n\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject )\n\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tnewDefer[ tuple[ 0 ] + \"With\" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfns = null;\n\t\t\t\t\t}).promise();\n\t\t\t\t},\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function( obj ) {\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred = {};\n\n\t\t// Keep pipe for back-compat\n\t\tpromise.pipe = promise.then;\n\n\t\t// Add list-specific methods\n\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\tvar list = tuple[ 2 ],\n\t\t\t\tstateString = tuple[ 3 ];\n\n\t\t\t// promise[ done | fail | progress ] = list.add\n\t\t\tpromise[ tuple[1] ] = list.add;\n\n\t\t\t// Handle state\n\t\t\tif ( stateString ) {\n\t\t\t\tlist.add(function() {\n\t\t\t\t\t// state = [ resolved | rejected ]\n\t\t\t\t\tstate = stateString;\n\n\t\t\t\t// [ reject_list | resolve_list ].disable; progress_list.lock\n\t\t\t\t}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );\n\t\t\t}\n\n\t\t\t// deferred[ resolve | reject | notify ]\n\t\t\tdeferred[ tuple[0] ] = function() {\n\t\t\t\tdeferred[ tuple[0] + \"With\" ]( this === deferred ? promise : this, arguments );\n\t\t\t\treturn this;\n\t\t\t};\n\t\t\tdeferred[ tuple[0] + \"With\" ] = list.fireWith;\n\t\t});\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise( deferred );\n\n\t\t// Call given func if any\n\t\tif ( func ) {\n\t\t\tfunc.call( deferred, deferred );\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function( subordinate /* , ..., subordinateN */ ) {\n\t\tvar i = 0,\n\t\t\tresolveValues = slice.call( arguments ),\n\t\t\tlength = resolveValues.length,\n\n\t\t\t// the count of uncompleted subordinates\n\t\t\tremaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,\n\n\t\t\t// the master Deferred. If resolveValues consist of only a single Deferred, just use that.\n\t\t\tdeferred = remaining === 1 ? subordinate : jQuery.Deferred(),\n\n\t\t\t// Update function for both resolve and progress values\n\t\t\tupdateFunc = function( i, contexts, values ) {\n\t\t\t\treturn function( value ) {\n\t\t\t\t\tcontexts[ i ] = this;\n\t\t\t\t\tvalues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;\n\t\t\t\t\tif ( values === progressValues ) {\n\t\t\t\t\t\tdeferred.notifyWith( contexts, values );\n\t\t\t\t\t} else if ( !( --remaining ) ) {\n\t\t\t\t\t\tdeferred.resolveWith( contexts, values );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\t\t\tprogressValues, progressContexts, resolveContexts;\n\n\t\t// add listeners to Deferred subordinates; treat others as resolved\n\t\tif ( length > 1 ) {\n\t\t\tprogressValues = new Array( length );\n\t\t\tprogressContexts = new Array( length );\n\t\t\tresolveContexts = new Array( length );\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {\n\t\t\t\t\tresolveValues[ i ].promise()\n\t\t\t\t\t\t.done( updateFunc( i, resolveContexts, resolveValues ) )\n\t\t\t\t\t\t.fail( deferred.reject )\n\t\t\t\t\t\t.progress( updateFunc( i, progressContexts, progressValues ) );\n\t\t\t\t} else {\n\t\t\t\t\t--remaining;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// if we're not waiting on anything, resolve the master\n\t\tif ( !remaining ) {\n\t\t\tdeferred.resolveWith( resolveContexts, resolveValues );\n\t\t}\n\n\t\treturn deferred.promise();\n\t}\n});\n\n\n// The deferred used on DOM ready\nvar readyList;\n\njQuery.fn.ready = function( fn ) {\n\t// Add the callback\n\tjQuery.ready.promise().done( fn );\n\n\treturn this;\n};\n\njQuery.extend({\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See #6781\n\treadyWait: 1,\n\n\t// Hold (or release) the ready event\n\tholdReady: function( hold ) {\n\t\tif ( hold ) {\n\t\t\tjQuery.readyWait++;\n\t\t} else {\n\t\t\tjQuery.ready( true );\n\t\t}\n\t},\n\n\t// Handle when the DOM is ready\n\tready: function( wait ) {\n\n\t\t// Abort if there are pending holds or we're already ready\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady = true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith( document, [ jQuery ] );\n\n\t\t// Trigger any bound ready events\n\t\tif ( jQuery.fn.trigger ) {\n\t\t\tjQuery( document ).trigger(\"ready\").off(\"ready\");\n\t\t}\n\t}\n});\n\n/**\n * The ready event handler and self cleanup method\n */\nfunction completed() {\n\tdocument.removeEventListener( \"DOMContentLoaded\", completed, false );\n\twindow.removeEventListener( \"load\", completed, false );\n\tjQuery.ready();\n}\n\njQuery.ready.promise = function( obj ) {\n\tif ( !readyList ) {\n\n\t\treadyList = jQuery.Deferred();\n\n\t\t// Catch cases where $(document).ready() is called after the browser event has already occurred.\n\t\t// we once tried to use readyState \"interactive\" here, but it caused issues like the one\n\t\t// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n\t\tif ( document.readyState === \"complete\" ) {\n\t\t\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\t\t\tsetTimeout( jQuery.ready );\n\n\t\t} else {\n\n\t\t\t// Use the handy event callback\n\t\t\tdocument.addEventListener( \"DOMContentLoaded\", completed, false );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.addEventListener( \"load\", completed, false );\n\t\t}\n\t}\n\treturn readyList.promise( obj );\n};\n\n// Kick off the DOM ready check even if the user does not\njQuery.ready.promise();\n\n\n\n\n// Multifunctional method to get and set values of a collection\n// The value/s can optionally be executed if it's a function\nvar access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {\n\tvar i = 0,\n\t\tlen = elems.length,\n\t\tbulk = key == null;\n\n\t// Sets many values\n\tif ( jQuery.type( key ) === \"object\" ) {\n\t\tchainable = true;\n\t\tfor ( i in key ) {\n\t\t\tjQuery.access( elems, fn, i, key[i], true, emptyGet, raw );\n\t\t}\n\n\t// Sets one value\n\t} else if ( value !== undefined ) {\n\t\tchainable = true;\n\n\t\tif ( !jQuery.isFunction( value ) ) {\n\t\t\traw = true;\n\t\t}\n\n\t\tif ( bulk ) {\n\t\t\t// Bulk operations run against the entire set\n\t\t\tif ( raw ) {\n\t\t\t\tfn.call( elems, value );\n\t\t\t\tfn = null;\n\n\t\t\t// ...except when executing function values\n\t\t\t} else {\n\t\t\t\tbulk = fn;\n\t\t\t\tfn = function( elem, key, value ) {\n\t\t\t\t\treturn bulk.call( jQuery( elem ), value );\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif ( fn ) {\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\tfn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn chainable ?\n\t\telems :\n\n\t\t// Gets\n\t\tbulk ?\n\t\t\tfn.call( elems ) :\n\t\t\tlen ? fn( elems[0], key ) : emptyGet;\n};\n\n\n/**\n * Determines whether an object can have data\n */\njQuery.acceptData = function( owner ) {\n\t// Accepts only:\n\t//  - Node\n\t//    - Node.ELEMENT_NODE\n\t//    - Node.DOCUMENT_NODE\n\t//  - Object\n\t//    - Any\n\t/* jshint -W018 */\n\treturn owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );\n};\n\n\nfunction Data() {\n\t// Support: Android < 4,\n\t// Old WebKit does not have Object.preventExtensions/freeze method,\n\t// return new empty object instead with no [[set]] accessor\n\tObject.defineProperty( this.cache = {}, 0, {\n\t\tget: function() {\n\t\t\treturn {};\n\t\t}\n\t});\n\n\tthis.expando = jQuery.expando + Math.random();\n}\n\nData.uid = 1;\nData.accepts = jQuery.acceptData;\n\nData.prototype = {\n\tkey: function( owner ) {\n\t\t// We can accept data for non-element nodes in modern browsers,\n\t\t// but we should not, see #8335.\n\t\t// Always return the key for a frozen object.\n\t\tif ( !Data.accepts( owner ) ) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar descriptor = {},\n\t\t\t// Check if the owner object already has a cache key\n\t\t\tunlock = owner[ this.expando ];\n\n\t\t// If not, create one\n\t\tif ( !unlock ) {\n\t\t\tunlock = Data.uid++;\n\n\t\t\t// Secure it in a non-enumerable, non-writable property\n\t\t\ttry {\n\t\t\t\tdescriptor[ this.expando ] = { value: unlock };\n\t\t\t\tObject.defineProperties( owner, descriptor );\n\n\t\t\t// Support: Android < 4\n\t\t\t// Fallback to a less secure definition\n\t\t\t} catch ( e ) {\n\t\t\t\tdescriptor[ this.expando ] = unlock;\n\t\t\t\tjQuery.extend( owner, descriptor );\n\t\t\t}\n\t\t}\n\n\t\t// Ensure the cache object\n\t\tif ( !this.cache[ unlock ] ) {\n\t\t\tthis.cache[ unlock ] = {};\n\t\t}\n\n\t\treturn unlock;\n\t},\n\tset: function( owner, data, value ) {\n\t\tvar prop,\n\t\t\t// There may be an unlock assigned to this node,\n\t\t\t// if there is no entry for this \"owner\", create one inline\n\t\t\t// and set the unlock as though an owner entry had always existed\n\t\t\tunlock = this.key( owner ),\n\t\t\tcache = this.cache[ unlock ];\n\n\t\t// Handle: [ owner, key, value ] args\n\t\tif ( typeof data === \"string\" ) {\n\t\t\tcache[ data ] = value;\n\n\t\t// Handle: [ owner, { properties } ] args\n\t\t} else {\n\t\t\t// Fresh assignments by object are shallow copied\n\t\t\tif ( jQuery.isEmptyObject( cache ) ) {\n\t\t\t\tjQuery.extend( this.cache[ unlock ], data );\n\t\t\t// Otherwise, copy the properties one-by-one to the cache object\n\t\t\t} else {\n\t\t\t\tfor ( prop in data ) {\n\t\t\t\t\tcache[ prop ] = data[ prop ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn cache;\n\t},\n\tget: function( owner, key ) {\n\t\t// Either a valid cache is found, or will be created.\n\t\t// New caches will be created and the unlock returned,\n\t\t// allowing direct access to the newly created\n\t\t// empty data object. A valid owner object must be provided.\n\t\tvar cache = this.cache[ this.key( owner ) ];\n\n\t\treturn key === undefined ?\n\t\t\tcache : cache[ key ];\n\t},\n\taccess: function( owner, key, value ) {\n\t\tvar stored;\n\t\t// In cases where either:\n\t\t//\n\t\t//   1. No key was specified\n\t\t//   2. A string key was specified, but no value provided\n\t\t//\n\t\t// Take the \"read\" path and allow the get method to determine\n\t\t// which value to return, respectively either:\n\t\t//\n\t\t//   1. The entire cache object\n\t\t//   2. The data stored at the key\n\t\t//\n\t\tif ( key === undefined ||\n\t\t\t\t((key && typeof key === \"string\") && value === undefined) ) {\n\n\t\t\tstored = this.get( owner, key );\n\n\t\t\treturn stored !== undefined ?\n\t\t\t\tstored : this.get( owner, jQuery.camelCase(key) );\n\t\t}\n\n\t\t// [*]When the key is not a string, or both a key and value\n\t\t// are specified, set or extend (existing objects) with either:\n\t\t//\n\t\t//   1. An object of properties\n\t\t//   2. A key and value\n\t\t//\n\t\tthis.set( owner, key, value );\n\n\t\t// Since the \"set\" path can have two possible entry points\n\t\t// return the expected data based on which path was taken[*]\n\t\treturn value !== undefined ? value : key;\n\t},\n\tremove: function( owner, key ) {\n\t\tvar i, name, camel,\n\t\t\tunlock = this.key( owner ),\n\t\t\tcache = this.cache[ unlock ];\n\n\t\tif ( key === undefined ) {\n\t\t\tthis.cache[ unlock ] = {};\n\n\t\t} else {\n\t\t\t// Support array or space separated string of keys\n\t\t\tif ( jQuery.isArray( key ) ) {\n\t\t\t\t// If \"name\" is an array of keys...\n\t\t\t\t// When data is initially created, via (\"key\", \"val\") signature,\n\t\t\t\t// keys will be converted to camelCase.\n\t\t\t\t// Since there is no way to tell _how_ a key was added, remove\n\t\t\t\t// both plain key and camelCase key. #12786\n\t\t\t\t// This will only penalize the array argument path.\n\t\t\t\tname = key.concat( key.map( jQuery.camelCase ) );\n\t\t\t} else {\n\t\t\t\tcamel = jQuery.camelCase( key );\n\t\t\t\t// Try the string as a key before any manipulation\n\t\t\t\tif ( key in cache ) {\n\t\t\t\t\tname = [ key, camel ];\n\t\t\t\t} else {\n\t\t\t\t\t// If a key with the spaces exists, use it.\n\t\t\t\t\t// Otherwise, create an array by matching non-whitespace\n\t\t\t\t\tname = camel;\n\t\t\t\t\tname = name in cache ?\n\t\t\t\t\t\t[ name ] : ( name.match( rnotwhite ) || [] );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ti = name.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tdelete cache[ name[ i ] ];\n\t\t\t}\n\t\t}\n\t},\n\thasData: function( owner ) {\n\t\treturn !jQuery.isEmptyObject(\n\t\t\tthis.cache[ owner[ this.expando ] ] || {}\n\t\t);\n\t},\n\tdiscard: function( owner ) {\n\t\tif ( owner[ this.expando ] ) {\n\t\t\tdelete this.cache[ owner[ this.expando ] ];\n\t\t}\n\t}\n};\nvar data_priv = new Data();\n\nvar data_user = new Data();\n\n\n\n/*\n\tImplementation Summary\n\n\t1. Enforce API surface and semantic compatibility with 1.9.x branch\n\t2. Improve the module's maintainability by reducing the storage\n\t\tpaths to a single mechanism.\n\t3. Use the same single mechanism to support \"private\" and \"user\" data.\n\t4. _Never_ expose \"private\" data to user code (TODO: Drop _data, _removeData)\n\t5. Avoid exposing implementation details on user objects (eg. expando properties)\n\t6. Provide a clear path for implementation upgrade to WeakMap in 2014\n*/\nvar rbrace = /^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,\n\trmultiDash = /([A-Z])/g;\n\nfunction dataAttr( elem, key, data ) {\n\tvar name;\n\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif ( data === undefined && elem.nodeType === 1 ) {\n\t\tname = \"data-\" + key.replace( rmultiDash, \"-$1\" ).toLowerCase();\n\t\tdata = elem.getAttribute( name );\n\n\t\tif ( typeof data === \"string\" ) {\n\t\t\ttry {\n\t\t\t\tdata = data === \"true\" ? true :\n\t\t\t\t\tdata === \"false\" ? false :\n\t\t\t\t\tdata === \"null\" ? null :\n\t\t\t\t\t// Only convert to a number if it doesn't change the string\n\t\t\t\t\t+data + \"\" === data ? +data :\n\t\t\t\t\trbrace.test( data ) ? jQuery.parseJSON( data ) :\n\t\t\t\t\tdata;\n\t\t\t} catch( e ) {}\n\n\t\t\t// Make sure we set the data so it isn't changed later\n\t\t\tdata_user.set( elem, key, data );\n\t\t} else {\n\t\t\tdata = undefined;\n\t\t}\n\t}\n\treturn data;\n}\n\njQuery.extend({\n\thasData: function( elem ) {\n\t\treturn data_user.hasData( elem ) || data_priv.hasData( elem );\n\t},\n\n\tdata: function( elem, name, data ) {\n\t\treturn data_user.access( elem, name, data );\n\t},\n\n\tremoveData: function( elem, name ) {\n\t\tdata_user.remove( elem, name );\n\t},\n\n\t// TODO: Now that all calls to _data and _removeData have been replaced\n\t// with direct calls to data_priv methods, these can be deprecated.\n\t_data: function( elem, name, data ) {\n\t\treturn data_priv.access( elem, name, data );\n\t},\n\n\t_removeData: function( elem, name ) {\n\t\tdata_priv.remove( elem, name );\n\t}\n});\n\njQuery.fn.extend({\n\tdata: function( key, value ) {\n\t\tvar i, name, data,\n\t\t\telem = this[ 0 ],\n\t\t\tattrs = elem && elem.attributes;\n\n\t\t// Gets all values\n\t\tif ( key === undefined ) {\n\t\t\tif ( this.length ) {\n\t\t\t\tdata = data_user.get( elem );\n\n\t\t\t\tif ( elem.nodeType === 1 && !data_priv.get( elem, \"hasDataAttrs\" ) ) {\n\t\t\t\t\ti = attrs.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tname = attrs[ i ].name;\n\n\t\t\t\t\t\tif ( name.indexOf( \"data-\" ) === 0 ) {\n\t\t\t\t\t\t\tname = jQuery.camelCase( name.slice(5) );\n\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdata_priv.set( elem, \"hasDataAttrs\", true );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif ( typeof key === \"object\" ) {\n\t\t\treturn this.each(function() {\n\t\t\t\tdata_user.set( this, key );\n\t\t\t});\n\t\t}\n\n\t\treturn access( this, function( value ) {\n\t\t\tvar data,\n\t\t\t\tcamelKey = jQuery.camelCase( key );\n\n\t\t\t// The calling jQuery object (element matches) is not empty\n\t\t\t// (and therefore has an element appears at this[ 0 ]) and the\n\t\t\t// `value` parameter was not undefined. An empty jQuery object\n\t\t\t// will result in `undefined` for elem = this[ 0 ] which will\n\t\t\t// throw an exception if an attempt to read a data cache is made.\n\t\t\tif ( elem && value === undefined ) {\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// with the key as-is\n\t\t\t\tdata = data_user.get( elem, key );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// with the key camelized\n\t\t\t\tdata = data_user.get( elem, camelKey );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to \"discover\" the data in\n\t\t\t\t// HTML5 custom data-* attrs\n\t\t\t\tdata = dataAttr( elem, camelKey, undefined );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// We tried really hard, but the data doesn't exist.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the data...\n\t\t\tthis.each(function() {\n\t\t\t\t// First, attempt to store a copy or reference of any\n\t\t\t\t// data that might've been store with a camelCased key.\n\t\t\t\tvar data = data_user.get( this, camelKey );\n\n\t\t\t\t// For HTML5 data-* attribute interop, we have to\n\t\t\t\t// store property names with dashes in a camelCase form.\n\t\t\t\t// This might not apply to all properties...*\n\t\t\t\tdata_user.set( this, camelKey, value );\n\n\t\t\t\t// *... In the case of properties that might _actually_\n\t\t\t\t// have dashes, we need to also store a copy of that\n\t\t\t\t// unchanged property.\n\t\t\t\tif ( key.indexOf(\"-\") !== -1 && data !== undefined ) {\n\t\t\t\t\tdata_user.set( this, key, value );\n\t\t\t\t}\n\t\t\t});\n\t\t}, null, value, arguments.length > 1, null, true );\n\t},\n\n\tremoveData: function( key ) {\n\t\treturn this.each(function() {\n\t\t\tdata_user.remove( this, key );\n\t\t});\n\t}\n});\n\n\njQuery.extend({\n\tqueue: function( elem, type, data ) {\n\t\tvar queue;\n\n\t\tif ( elem ) {\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\n\t\t\tqueue = data_priv.get( elem, type );\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif ( data ) {\n\t\t\t\tif ( !queue || jQuery.isArray( data ) ) {\n\t\t\t\t\tqueue = data_priv.access( elem, type, jQuery.makeArray(data) );\n\t\t\t\t} else {\n\t\t\t\t\tqueue.push( data );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue || [];\n\t\t}\n\t},\n\n\tdequeue: function( elem, type ) {\n\t\ttype = type || \"fx\";\n\n\t\tvar queue = jQuery.queue( elem, type ),\n\t\t\tstartLength = queue.length,\n\t\t\tfn = queue.shift(),\n\t\t\thooks = jQuery._queueHooks( elem, type ),\n\t\t\tnext = function() {\n\t\t\t\tjQuery.dequeue( elem, type );\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif ( fn === \"inprogress\" ) {\n\t\t\tfn = queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\tif ( fn ) {\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif ( type === \"fx\" ) {\n\t\t\t\tqueue.unshift( \"inprogress\" );\n\t\t\t}\n\n\t\t\t// clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call( elem, next, hooks );\n\t\t}\n\n\t\tif ( !startLength && hooks ) {\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// not intended for public consumption - generates a queueHooks object, or returns the current one\n\t_queueHooks: function( elem, type ) {\n\t\tvar key = type + \"queueHooks\";\n\t\treturn data_priv.get( elem, key ) || data_priv.access( elem, key, {\n\t\t\tempty: jQuery.Callbacks(\"once memory\").add(function() {\n\t\t\t\tdata_priv.remove( elem, [ type + \"queue\", key ] );\n\t\t\t})\n\t\t});\n\t}\n});\n\njQuery.fn.extend({\n\tqueue: function( type, data ) {\n\t\tvar setter = 2;\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tdata = type;\n\t\t\ttype = \"fx\";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif ( arguments.length < setter ) {\n\t\t\treturn jQuery.queue( this[0], type );\n\t\t}\n\n\t\treturn data === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function() {\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\n\n\t\t\t\t// ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks( this, type );\n\n\t\t\t\tif ( type === \"fx\" && queue[0] !== \"inprogress\" ) {\n\t\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t\t}\n\t\t\t});\n\t},\n\tdequeue: function( type ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.dequeue( this, type );\n\t\t});\n\t},\n\tclearQueue: function( type ) {\n\t\treturn this.queue( type || \"fx\", [] );\n\t},\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function( type, obj ) {\n\t\tvar tmp,\n\t\t\tcount = 1,\n\t\t\tdefer = jQuery.Deferred(),\n\t\t\telements = this,\n\t\t\ti = this.length,\n\t\t\tresolve = function() {\n\t\t\t\tif ( !( --count ) ) {\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tobj = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\ttype = type || \"fx\";\n\n\t\twhile ( i-- ) {\n\t\t\ttmp = data_priv.get( elements[ i ], type + \"queueHooks\" );\n\t\t\tif ( tmp && tmp.empty ) {\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add( resolve );\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise( obj );\n\t}\n});\nvar pnum = (/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/).source;\n\nvar cssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ];\n\nvar isHidden = function( elem, el ) {\n\t\t// isHidden might be called from jQuery#filter function;\n\t\t// in that case, element will be second argument\n\t\telem = el || elem;\n\t\treturn jQuery.css( elem, \"display\" ) === \"none\" || !jQuery.contains( elem.ownerDocument, elem );\n\t};\n\nvar rcheckableType = (/^(?:checkbox|radio)$/i);\n\n\n\n(function() {\n\tvar fragment = document.createDocumentFragment(),\n\t\tdiv = fragment.appendChild( document.createElement( \"div\" ) );\n\n\t// #11217 - WebKit loses check when the name is after the checked attribute\n\tdiv.innerHTML = \"<input type='radio' checked='checked' name='t'/>\";\n\n\t// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3\n\t// old WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Make sure textarea (and checkbox) defaultValue is properly cloned\n\t// Support: IE9-IE11+\n\tdiv.innerHTML = \"<textarea>x</textarea>\";\n\tsupport.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;\n})();\nvar strundefined = typeof undefined;\n\n\n\nsupport.focusinBubbles = \"onfocusin\" in window;\n\n\nvar\n\trkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|contextmenu)|click/,\n\trfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\trtypenamespace = /^([^.]*)(?:\\.(.+)|)$/;\n\nfunction returnTrue() {\n\treturn true;\n}\n\nfunction returnFalse() {\n\treturn false;\n}\n\nfunction safeActiveElement() {\n\ttry {\n\t\treturn document.activeElement;\n\t} catch ( err ) { }\n}\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tglobal: {},\n\n\tadd: function( elem, types, handler, data, selector ) {\n\n\t\tvar handleObjIn, eventHandle, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = data_priv.get( elem );\n\n\t\t// Don't attach events to noData or text/comment nodes (but allow plain objects)\n\t\tif ( !elemData ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tif ( !(events = elemData.events) ) {\n\t\t\tevents = elemData.events = {};\n\t\t}\n\t\tif ( !(eventHandle = elemData.handle) ) {\n\t\t\teventHandle = elemData.handle = function( e ) {\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?\n\t\t\t\t\tjQuery.event.dispatch.apply( elem, arguments ) : undefined;\n\t\t\t};\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif ( !type ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend({\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join(\".\")\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\tif ( !(handlers = events[ type ]) ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener if the special events handler returns false\n\t\t\t\tif ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle, false );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\n\t\tvar j, origCount, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = data_priv.hasData( elem ) && data_priv.get( elem );\n\n\t\tif ( !elemData || !(events = elemData.events) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\t\t\thandlers = events[ type ] || [];\n\t\t\ttmp = tmp[2] && new RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" );\n\n\t\t\t// Remove matching events\n\t\t\torigCount = j = handlers.length;\n\t\t\twhile ( j-- ) {\n\t\t\t\thandleObj = handlers[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\n\t\t\t\t\t( !selector || selector === handleObj.selector || selector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\thandlers.splice( j, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( origCount && !handlers.length ) {\n\t\t\t\tif ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdelete elemData.handle;\n\t\t\tdata_priv.remove( elem, \"events\" );\n\t\t}\n\t},\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\n\t\tvar i, cur, tmp, bubbleType, ontype, handle, special,\n\t\t\teventPath = [ elem || document ],\n\t\t\ttype = hasOwn.call( event, \"type\" ) ? event.type : event,\n\t\t\tnamespaces = hasOwn.call( event, \"namespace\" ) ? event.namespace.split(\".\") : [];\n\n\t\tcur = tmp = elem = elem || document;\n\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf(\".\") >= 0 ) {\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split(\".\");\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype = type.indexOf(\":\") < 0 && \"on\" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent = event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\n\t\tevent.namespace = namespaces.join(\".\");\n\t\tevent.namespace_re = event.namespace ?\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" ) :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data == null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray( data, [ event ] );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n\t\tif ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push( cur );\n\t\t\t\ttmp = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( tmp === (elem.ownerDocument || document) ) {\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti = 0;\n\t\twhile ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {\n\n\t\t\tevent.type = i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType || type;\n\n\t\t\t// jQuery handler\n\t\t\thandle = ( data_priv.get( cur, \"events\" ) || {} )[ event.type ] && data_priv.get( cur, \"handle\" );\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && handle.apply && jQuery.acceptData( cur ) ) {\n\t\t\t\tevent.result = handle.apply( cur, data );\n\t\t\t\tif ( event.result === false ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&\n\t\t\t\tjQuery.acceptData( elem ) ) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name name as the event.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\n\t\t\t\tif ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp = elem[ ontype ];\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\t\t\t\t\telem[ type ]();\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\tdispatch: function( event ) {\n\n\t\t// Make a writable jQuery.Event from the native event object\n\t\tevent = jQuery.event.fix( event );\n\n\t\tvar i, j, ret, matched, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\targs = slice.call( arguments ),\n\t\t\thandlers = ( data_priv.get( this, \"events\" ) || {} )[ event.type ] || [],\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[0] = event;\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti = 0;\n\t\twhile ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tj = 0;\n\t\t\twhile ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {\n\n\t\t\t\t// Triggered event must either 1) have no namespace, or\n\t\t\t\t// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).\n\t\t\t\tif ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.handleObj = handleObj;\n\t\t\t\t\tevent.data = handleObj.data;\n\n\t\t\t\t\tret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )\n\t\t\t\t\t\t\t.apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tif ( (event.result = ret) === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function( event, handlers ) {\n\t\tvar i, matches, sel, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\tcur = event.target;\n\n\t\t// Find delegate handlers\n\t\t// Black-hole SVG <use> instance trees (#13180)\n\t\t// Avoid non-left-click bubbling in Firefox (#3861)\n\t\tif ( delegateCount && cur.nodeType && (!event.button || event.type !== \"click\") ) {\n\n\t\t\tfor ( ; cur !== this; cur = cur.parentNode || this ) {\n\n\t\t\t\t// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)\n\t\t\t\tif ( cur.disabled !== true || event.type !== \"click\" ) {\n\t\t\t\t\tmatches = [];\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\n\t\t\t\t\t\thandleObj = handlers[ i ];\n\n\t\t\t\t\t\t// Don't conflict with Object.prototype properties (#13203)\n\t\t\t\t\t\tsel = handleObj.selector + \" \";\n\n\t\t\t\t\t\tif ( matches[ sel ] === undefined ) {\n\t\t\t\t\t\t\tmatches[ sel ] = handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) >= 0 :\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( matches[ sel ] ) {\n\t\t\t\t\t\t\tmatches.push( handleObj );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( matches.length ) {\n\t\t\t\t\t\thandlerQueue.push({ elem: cur, handlers: matches });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tif ( delegateCount < handlers.length ) {\n\t\t\thandlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });\n\t\t}\n\n\t\treturn handlerQueue;\n\t},\n\n\t// Includes some event props shared by KeyEvent and MouseEvent\n\tprops: \"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),\n\n\tfixHooks: {},\n\n\tkeyHooks: {\n\t\tprops: \"char charCode key keyCode\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\n\t\t\t// Add which for key events\n\t\t\tif ( event.which == null ) {\n\t\t\t\tevent.which = original.charCode != null ? original.charCode : original.keyCode;\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tmouseHooks: {\n\t\tprops: \"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\t\t\tvar eventDoc, doc, body,\n\t\t\t\tbutton = original.button;\n\n\t\t\t// Calculate pageX/Y if missing and clientX/Y available\n\t\t\tif ( event.pageX == null && original.clientX != null ) {\n\t\t\t\teventDoc = event.target.ownerDocument || document;\n\t\t\t\tdoc = eventDoc.documentElement;\n\t\t\t\tbody = eventDoc.body;\n\n\t\t\t\tevent.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );\n\t\t\t\tevent.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );\n\t\t\t}\n\n\t\t\t// Add which for click: 1 === left; 2 === middle; 3 === right\n\t\t\t// Note: button is not normalized, so don't use it\n\t\t\tif ( !event.which && button !== undefined ) {\n\t\t\t\tevent.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tfix: function( event ) {\n\t\tif ( event[ jQuery.expando ] ) {\n\t\t\treturn event;\n\t\t}\n\n\t\t// Create a writable copy of the event object and normalize some properties\n\t\tvar i, prop, copy,\n\t\t\ttype = event.type,\n\t\t\toriginalEvent = event,\n\t\t\tfixHook = this.fixHooks[ type ];\n\n\t\tif ( !fixHook ) {\n\t\t\tthis.fixHooks[ type ] = fixHook =\n\t\t\t\trmouseEvent.test( type ) ? this.mouseHooks :\n\t\t\t\trkeyEvent.test( type ) ? this.keyHooks :\n\t\t\t\t{};\n\t\t}\n\t\tcopy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;\n\n\t\tevent = new jQuery.Event( originalEvent );\n\n\t\ti = copy.length;\n\t\twhile ( i-- ) {\n\t\t\tprop = copy[ i ];\n\t\t\tevent[ prop ] = originalEvent[ prop ];\n\t\t}\n\n\t\t// Support: Cordova 2.5 (WebKit) (#13255)\n\t\t// All events should have a target; Cordova deviceready doesn't\n\t\tif ( !event.target ) {\n\t\t\tevent.target = document;\n\t\t}\n\n\t\t// Support: Safari 6.0+, Chrome < 28\n\t\t// Target should not be a text node (#504, #13143)\n\t\tif ( event.target.nodeType === 3 ) {\n\t\t\tevent.target = event.target.parentNode;\n\t\t}\n\n\t\treturn fixHook.filter ? fixHook.filter( event, originalEvent ) : event;\n\t},\n\n\tspecial: {\n\t\tload: {\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\t\tfocus: {\n\t\t\t// Fire native event if possible so blur/focus sequence is correct\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this !== safeActiveElement() && this.focus ) {\n\t\t\t\t\tthis.focus();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusin\"\n\t\t},\n\t\tblur: {\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this === safeActiveElement() && this.blur ) {\n\t\t\t\t\tthis.blur();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusout\"\n\t\t},\n\t\tclick: {\n\t\t\t// For checkbox, fire native event so checked state will be right\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this.type === \"checkbox\" && this.click && jQuery.nodeName( this, \"input\" ) ) {\n\t\t\t\t\tthis.click();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// For cross-browser consistency, don't fire native .click() on links\n\t\t\t_default: function( event ) {\n\t\t\t\treturn jQuery.nodeName( event.target, \"a\" );\n\t\t\t}\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tpostDispatch: function( event ) {\n\n\t\t\t\t// Support: Firefox 20+\n\t\t\t\t// Firefox doesn't alert if the returnValue field is not set.\n\t\t\t\tif ( event.result !== undefined ) {\n\t\t\t\t\tevent.originalEvent.returnValue = event.result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tsimulate: function( type, elem, event, bubble ) {\n\t\t// Piggyback on a donor event to simulate a different one.\n\t\t// Fake originalEvent to avoid donor's stopPropagation, but if the\n\t\t// simulated event prevents default then we do the same on the donor.\n\t\tvar e = jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{\n\t\t\t\ttype: type,\n\t\t\t\tisSimulated: true,\n\t\t\t\toriginalEvent: {}\n\t\t\t}\n\t\t);\n\t\tif ( bubble ) {\n\t\t\tjQuery.event.trigger( e, null, elem );\n\t\t} else {\n\t\t\tjQuery.event.dispatch.call( elem, e );\n\t\t}\n\t\tif ( e.isDefaultPrevented() ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t}\n};\n\njQuery.removeEvent = function( elem, type, handle ) {\n\tif ( elem.removeEventListener ) {\n\t\telem.removeEventListener( type, handle, false );\n\t}\n};\n\njQuery.Event = function( src, props ) {\n\t// Allow instantiation without the 'new' keyword\n\tif ( !(this instanceof jQuery.Event) ) {\n\t\treturn new jQuery.Event( src, props );\n\t}\n\n\t// Event object\n\tif ( src && src.type ) {\n\t\tthis.originalEvent = src;\n\t\tthis.type = src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented = src.defaultPrevented ||\n\t\t\t\t// Support: Android < 4.0\n\t\t\t\tsrc.defaultPrevented === undefined &&\n\t\t\t\tsrc.getPreventDefault && src.getPreventDefault() ?\n\t\t\treturnTrue :\n\t\t\treturnFalse;\n\n\t// Event type\n\t} else {\n\t\tthis.type = src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif ( props ) {\n\t\tjQuery.extend( this, props );\n\t}\n\n\t// Create a timestamp if incoming event doesn't have one\n\tthis.timeStamp = src && src.timeStamp || jQuery.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ] = true;\n};\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype = {\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse,\n\n\tpreventDefault: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isDefaultPrevented = returnTrue;\n\n\t\tif ( e && e.preventDefault ) {\n\t\t\te.preventDefault();\n\t\t}\n\t},\n\tstopPropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isPropagationStopped = returnTrue;\n\n\t\tif ( e && e.stopPropagation ) {\n\t\t\te.stopPropagation();\n\t\t}\n\t},\n\tstopImmediatePropagation: function() {\n\t\tthis.isImmediatePropagationStopped = returnTrue;\n\t\tthis.stopPropagation();\n\t}\n};\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\n// Support: Chrome 15+\njQuery.each({\n\tmouseenter: \"mouseover\",\n\tmouseleave: \"mouseout\"\n}, function( orig, fix ) {\n\tjQuery.event.special[ orig ] = {\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function( event ) {\n\t\t\tvar ret,\n\t\t\t\ttarget = this,\n\t\t\t\trelated = event.relatedTarget,\n\t\t\t\thandleObj = event.handleObj;\n\n\t\t\t// For mousenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif ( !related || (related !== target && !jQuery.contains( target, related )) ) {\n\t\t\t\tevent.type = handleObj.origType;\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\n\t\t\t\tevent.type = fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n});\n\n// Create \"bubbling\" focus and blur events\n// Support: Firefox, Chrome, Safari\nif ( !support.focusinBubbles ) {\n\tjQuery.each({ focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\n\n\t\t// Attach a single capturing handler on the document while someone wants focusin/focusout\n\t\tvar handler = function( event ) {\n\t\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );\n\t\t\t};\n\n\t\tjQuery.event.special[ fix ] = {\n\t\t\tsetup: function() {\n\t\t\t\tvar doc = this.ownerDocument || this,\n\t\t\t\t\tattaches = data_priv.access( doc, fix );\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.addEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t\tdata_priv.access( doc, fix, ( attaches || 0 ) + 1 );\n\t\t\t},\n\t\t\tteardown: function() {\n\t\t\t\tvar doc = this.ownerDocument || this,\n\t\t\t\t\tattaches = data_priv.access( doc, fix ) - 1;\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.removeEventListener( orig, handler, true );\n\t\t\t\t\tdata_priv.remove( doc, fix );\n\n\t\t\t\t} else {\n\t\t\t\t\tdata_priv.access( doc, fix, attaches );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n}\n\njQuery.fn.extend({\n\n\ton: function( types, selector, data, fn, /*INTERNAL*/ one ) {\n\t\tvar origFn, type;\n\n\t\t// Types can be a map of types/handlers\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-Object, selector, data )\n\t\t\tif ( typeof selector !== \"string\" ) {\n\t\t\t\t// ( types-Object, data )\n\t\t\t\tdata = data || selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.on( type, selector, data, types[ type ], one );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( data == null && fn == null ) {\n\t\t\t// ( types, fn )\n\t\t\tfn = selector;\n\t\t\tdata = selector = undefined;\n\t\t} else if ( fn == null ) {\n\t\t\tif ( typeof selector === \"string\" ) {\n\t\t\t\t// ( types, selector, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = undefined;\n\t\t\t} else {\n\t\t\t\t// ( types, data, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t} else if ( !fn ) {\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( one === 1 ) {\n\t\t\torigFn = fn;\n\t\t\tfn = function( event ) {\n\t\t\t\t// Can use an empty set, since event contains the info\n\t\t\t\tjQuery().off( event );\n\t\t\t\treturn origFn.apply( this, arguments );\n\t\t\t};\n\t\t\t// Use same guid so caller can remove using origFn\n\t\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.add( this, types, fn, data, selector );\n\t\t});\n\t},\n\tone: function( types, selector, data, fn ) {\n\t\treturn this.on( types, selector, data, fn, 1 );\n\t},\n\toff: function( types, selector, fn ) {\n\t\tvar handleObj, type;\n\t\tif ( types && types.preventDefault && types.handleObj ) {\n\t\t\t// ( event )  dispatched jQuery.Event\n\t\t\thandleObj = types.handleObj;\n\t\t\tjQuery( types.delegateTarget ).off(\n\t\t\t\thandleObj.namespace ? handleObj.origType + \".\" + handleObj.namespace : handleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-object [, selector] )\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.off( type, selector, types[ type ] );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif ( selector === false || typeof selector === \"function\" ) {\n\t\t\t// ( types [, fn] )\n\t\t\tfn = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.remove( this, types, fn, selector );\n\t\t});\n\t},\n\n\ttrigger: function( type, data ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.trigger( type, data, this );\n\t\t});\n\t},\n\ttriggerHandler: function( type, data ) {\n\t\tvar elem = this[0];\n\t\tif ( elem ) {\n\t\t\treturn jQuery.event.trigger( type, data, elem, true );\n\t\t}\n\t}\n});\n\n\nvar\n\trxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,\n\trtagName = /<([\\w:]+)/,\n\trhtml = /<|&#?\\w+;/,\n\trnoInnerhtml = /<(?:script|style|link)/i,\n\t// checked=\"checked\" or checked\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\trscriptType = /^$|\\/(?:java|ecma)script/i,\n\trscriptTypeMasked = /^true\\/(.*)/,\n\trcleanScript = /^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,\n\n\t// We have to close these tags to support XHTML (#13200)\n\twrapMap = {\n\n\t\t// Support: IE 9\n\t\toption: [ 1, \"<select multiple='multiple'>\", \"</select>\" ],\n\n\t\tthead: [ 1, \"<table>\", \"</table>\" ],\n\t\tcol: [ 2, \"<table><colgroup>\", \"</colgroup></table>\" ],\n\t\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n\t\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\n\t\t_default: [ 0, \"\", \"\" ]\n\t};\n\n// Support: IE 9\nwrapMap.optgroup = wrapMap.option;\n\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\n// Support: 1.x compatibility\n// Manipulating tables requires a tbody\nfunction manipulationTarget( elem, content ) {\n\treturn jQuery.nodeName( elem, \"table\" ) &&\n\t\tjQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, \"tr\" ) ?\n\n\t\telem.getElementsByTagName(\"tbody\")[0] ||\n\t\t\telem.appendChild( elem.ownerDocument.createElement(\"tbody\") ) :\n\t\telem;\n}\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\nfunction disableScript( elem ) {\n\telem.type = (elem.getAttribute(\"type\") !== null) + \"/\" + elem.type;\n\treturn elem;\n}\nfunction restoreScript( elem ) {\n\tvar match = rscriptTypeMasked.exec( elem.type );\n\n\tif ( match ) {\n\t\telem.type = match[ 1 ];\n\t} else {\n\t\telem.removeAttribute(\"type\");\n\t}\n\n\treturn elem;\n}\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval( elems, refElements ) {\n\tvar i = 0,\n\t\tl = elems.length;\n\n\tfor ( ; i < l; i++ ) {\n\t\tdata_priv.set(\n\t\t\telems[ i ], \"globalEval\", !refElements || data_priv.get( refElements[ i ], \"globalEval\" )\n\t\t);\n\t}\n}\n\nfunction cloneCopyEvent( src, dest ) {\n\tvar i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;\n\n\tif ( dest.nodeType !== 1 ) {\n\t\treturn;\n\t}\n\n\t// 1. Copy private data: events, handlers, etc.\n\tif ( data_priv.hasData( src ) ) {\n\t\tpdataOld = data_priv.access( src );\n\t\tpdataCur = data_priv.set( dest, pdataOld );\n\t\tevents = pdataOld.events;\n\n\t\tif ( events ) {\n\t\t\tdelete pdataCur.handle;\n\t\t\tpdataCur.events = {};\n\n\t\t\tfor ( type in events ) {\n\t\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\n\t\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Copy user data\n\tif ( data_user.hasData( src ) ) {\n\t\tudataOld = data_user.access( src );\n\t\tudataCur = jQuery.extend( {}, udataOld );\n\n\t\tdata_user.set( dest, udataCur );\n\t}\n}\n\nfunction getAll( context, tag ) {\n\tvar ret = context.getElementsByTagName ? context.getElementsByTagName( tag || \"*\" ) :\n\t\t\tcontext.querySelectorAll ? context.querySelectorAll( tag || \"*\" ) :\n\t\t\t[];\n\n\treturn tag === undefined || tag && jQuery.nodeName( context, tag ) ?\n\t\tjQuery.merge( [ context ], ret ) :\n\t\tret;\n}\n\n// Support: IE >= 9\nfunction fixInput( src, dest ) {\n\tvar nodeName = dest.nodeName.toLowerCase();\n\n\t// Fails to persist the checked state of a cloned checkbox or radio button.\n\tif ( nodeName === \"input\" && rcheckableType.test( src.type ) ) {\n\t\tdest.checked = src.checked;\n\n\t// Fails to return the selected option to the default selected state when cloning options\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\n\t\tdest.defaultValue = src.defaultValue;\n\t}\n}\n\njQuery.extend({\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\n\t\tvar i, l, srcElements, destElements,\n\t\t\tclone = elem.cloneNode( true ),\n\t\t\tinPage = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t// Support: IE >= 9\n\t\t// Fix Cloning issues\n\t\tif ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&\n\t\t\t\t!jQuery.isXMLDoc( elem ) ) {\n\n\t\t\t// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2\n\t\t\tdestElements = getAll( clone );\n\t\t\tsrcElements = getAll( elem );\n\n\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\tfixInput( srcElements[ i ], destElements[ i ] );\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif ( dataAndEvents ) {\n\t\t\tif ( deepDataAndEvents ) {\n\t\t\t\tsrcElements = srcElements || getAll( elem );\n\t\t\t\tdestElements = destElements || getAll( clone );\n\n\t\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\t\tcloneCopyEvent( srcElements[ i ], destElements[ i ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcloneCopyEvent( elem, clone );\n\t\t\t}\n\t\t}\n\n\t\t// Preserve script evaluation history\n\t\tdestElements = getAll( clone, \"script\" );\n\t\tif ( destElements.length > 0 ) {\n\t\t\tsetGlobalEval( destElements, !inPage && getAll( elem, \"script\" ) );\n\t\t}\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tbuildFragment: function( elems, context, scripts, selection ) {\n\t\tvar elem, tmp, tag, wrap, contains, j,\n\t\t\tfragment = context.createDocumentFragment(),\n\t\t\tnodes = [],\n\t\t\ti = 0,\n\t\t\tl = elems.length;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\telem = elems[ i ];\n\n\t\t\tif ( elem || elem === 0 ) {\n\n\t\t\t\t// Add nodes directly\n\t\t\t\tif ( jQuery.type( elem ) === \"object\" ) {\n\t\t\t\t\t// Support: QtWebKit\n\t\t\t\t\t// jQuery.merge because push.apply(_, arraylike) throws\n\t\t\t\t\tjQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );\n\n\t\t\t\t// Convert non-html into a text node\n\t\t\t\t} else if ( !rhtml.test( elem ) ) {\n\t\t\t\t\tnodes.push( context.createTextNode( elem ) );\n\n\t\t\t\t// Convert html into DOM nodes\n\t\t\t\t} else {\n\t\t\t\t\ttmp = tmp || fragment.appendChild( context.createElement(\"div\") );\n\n\t\t\t\t\t// Deserialize a standard representation\n\t\t\t\t\ttag = ( rtagName.exec( elem ) || [ \"\", \"\" ] )[ 1 ].toLowerCase();\n\t\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\n\t\t\t\t\ttmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, \"<$1></$2>\" ) + wrap[ 2 ];\n\n\t\t\t\t\t// Descend through wrappers to the right content\n\t\t\t\t\tj = wrap[ 0 ];\n\t\t\t\t\twhile ( j-- ) {\n\t\t\t\t\t\ttmp = tmp.lastChild;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Support: QtWebKit\n\t\t\t\t\t// jQuery.merge because push.apply(_, arraylike) throws\n\t\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\n\n\t\t\t\t\t// Remember the top-level container\n\t\t\t\t\ttmp = fragment.firstChild;\n\n\t\t\t\t\t// Fixes #12346\n\t\t\t\t\t// Support: Webkit, IE\n\t\t\t\t\ttmp.textContent = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Remove wrapper from fragment\n\t\tfragment.textContent = \"\";\n\n\t\ti = 0;\n\t\twhile ( (elem = nodes[ i++ ]) ) {\n\n\t\t\t// #4087 - If origin and destination elements are the same, and this is\n\t\t\t// that element, do not do anything\n\t\t\tif ( selection && jQuery.inArray( elem, selection ) !== -1 ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcontains = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t\t// Append to fragment\n\t\t\ttmp = getAll( fragment.appendChild( elem ), \"script\" );\n\n\t\t\t// Preserve script evaluation history\n\t\t\tif ( contains ) {\n\t\t\t\tsetGlobalEval( tmp );\n\t\t\t}\n\n\t\t\t// Capture executables\n\t\t\tif ( scripts ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (elem = tmp[ j++ ]) ) {\n\t\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\n\t\t\t\t\t\tscripts.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn fragment;\n\t},\n\n\tcleanData: function( elems ) {\n\t\tvar data, elem, events, type, key, j,\n\t\t\tspecial = jQuery.event.special,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[ i ]) !== undefined; i++ ) {\n\t\t\tif ( jQuery.acceptData( elem ) ) {\n\t\t\t\tkey = elem[ data_priv.expando ];\n\n\t\t\t\tif ( key && (data = data_priv.cache[ key ]) ) {\n\t\t\t\t\tevents = Object.keys( data.events || {} );\n\t\t\t\t\tif ( events.length ) {\n\t\t\t\t\t\tfor ( j = 0; (type = events[j]) !== undefined; j++ ) {\n\t\t\t\t\t\t\tif ( special[ type ] ) {\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( data_priv.cache[ key ] ) {\n\t\t\t\t\t\t// Discard any remaining `private` data\n\t\t\t\t\t\tdelete data_priv.cache[ key ];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Discard any remaining `user` data\n\t\t\tdelete data_user.cache[ elem[ data_user.expando ] ];\n\t\t}\n\t}\n});\n\njQuery.fn.extend({\n\ttext: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\treturn value === undefined ?\n\t\t\t\tjQuery.text( this ) :\n\t\t\t\tthis.empty().each(function() {\n\t\t\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\t\t\tthis.textContent = value;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t}, null, value, arguments.length );\n\t},\n\n\tappend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.appendChild( elem );\n\t\t\t}\n\t\t});\n\t},\n\n\tprepend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.insertBefore( elem, target.firstChild );\n\t\t\t}\n\t\t});\n\t},\n\n\tbefore: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\n\t\t\t}\n\t\t});\n\t},\n\n\tafter: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\n\t\t\t}\n\t\t});\n\t},\n\n\tremove: function( selector, keepData /* Internal Use Only */ ) {\n\t\tvar elem,\n\t\t\telems = selector ? jQuery.filter( selector, this ) : this,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\t\t\tif ( !keepData && elem.nodeType === 1 ) {\n\t\t\t\tjQuery.cleanData( getAll( elem ) );\n\t\t\t}\n\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\tif ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\t\t\tsetGlobalEval( getAll( elem, \"script\" ) );\n\t\t\t\t}\n\t\t\t\telem.parentNode.removeChild( elem );\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tempty: function() {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = this[i]) != null; i++ ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\n\t\t\t\t// Prevent memory leaks\n\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\n\t\t\t\t// Remove any remaining nodes\n\t\t\t\telem.textContent = \"\";\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n\t\treturn this.map(function() {\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\n\t\t});\n\t},\n\n\thtml: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\tvar elem = this[ 0 ] || {},\n\t\t\t\ti = 0,\n\t\t\t\tl = this.length;\n\n\t\t\tif ( value === undefined && elem.nodeType === 1 ) {\n\t\t\t\treturn elem.innerHTML;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [ \"\", \"\" ] )[ 1 ].toLowerCase() ] ) {\n\n\t\t\t\tvalue = value.replace( rxhtmlTag, \"<$1></$2>\" );\n\n\t\t\t\ttry {\n\t\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\t\telem = this[ i ] || {};\n\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t\t\t\t\telem.innerHTML = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem = 0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch( e ) {}\n\t\t\t}\n\n\t\t\tif ( elem ) {\n\t\t\t\tthis.empty().append( value );\n\t\t\t}\n\t\t}, null, value, arguments.length );\n\t},\n\n\treplaceWith: function() {\n\t\tvar arg = arguments[ 0 ];\n\n\t\t// Make the changes, replacing each context element with the new content\n\t\tthis.domManip( arguments, function( elem ) {\n\t\t\targ = this.parentNode;\n\n\t\t\tjQuery.cleanData( getAll( this ) );\n\n\t\t\tif ( arg ) {\n\t\t\t\targ.replaceChild( elem, this );\n\t\t\t}\n\t\t});\n\n\t\t// Force removal if there was no new content (e.g., from empty arguments)\n\t\treturn arg && (arg.length || arg.nodeType) ? this : this.remove();\n\t},\n\n\tdetach: function( selector ) {\n\t\treturn this.remove( selector, true );\n\t},\n\n\tdomManip: function( args, callback ) {\n\n\t\t// Flatten any nested arrays\n\t\targs = concat.apply( [], args );\n\n\t\tvar fragment, first, scripts, hasScripts, node, doc,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tset = this,\n\t\t\tiNoClone = l - 1,\n\t\t\tvalue = args[ 0 ],\n\t\t\tisFunction = jQuery.isFunction( value );\n\n\t\t// We can't cloneNode fragments that contain checked, in WebKit\n\t\tif ( isFunction ||\n\t\t\t\t( l > 1 && typeof value === \"string\" &&\n\t\t\t\t\t!support.checkClone && rchecked.test( value ) ) ) {\n\t\t\treturn this.each(function( index ) {\n\t\t\t\tvar self = set.eq( index );\n\t\t\t\tif ( isFunction ) {\n\t\t\t\t\targs[ 0 ] = value.call( this, index, self.html() );\n\t\t\t\t}\n\t\t\t\tself.domManip( args, callback );\n\t\t\t});\n\t\t}\n\n\t\tif ( l ) {\n\t\t\tfragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );\n\t\t\tfirst = fragment.firstChild;\n\n\t\t\tif ( fragment.childNodes.length === 1 ) {\n\t\t\t\tfragment = first;\n\t\t\t}\n\n\t\t\tif ( first ) {\n\t\t\t\tscripts = jQuery.map( getAll( fragment, \"script\" ), disableScript );\n\t\t\t\thasScripts = scripts.length;\n\n\t\t\t\t// Use the original fragment for the last item instead of the first because it can end up\n\t\t\t\t// being emptied incorrectly in certain situations (#8070).\n\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\tnode = fragment;\n\n\t\t\t\t\tif ( i !== iNoClone ) {\n\t\t\t\t\t\tnode = jQuery.clone( node, true, true );\n\n\t\t\t\t\t\t// Keep references to cloned scripts for later restoration\n\t\t\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\t\t\t// Support: QtWebKit\n\t\t\t\t\t\t\t// jQuery.merge because push.apply(_, arraylike) throws\n\t\t\t\t\t\t\tjQuery.merge( scripts, getAll( node, \"script\" ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tcallback.call( this[ i ], node, i );\n\t\t\t\t}\n\n\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\tdoc = scripts[ scripts.length - 1 ].ownerDocument;\n\n\t\t\t\t\t// Reenable scripts\n\t\t\t\t\tjQuery.map( scripts, restoreScript );\n\n\t\t\t\t\t// Evaluate executable scripts on first document insertion\n\t\t\t\t\tfor ( i = 0; i < hasScripts; i++ ) {\n\t\t\t\t\t\tnode = scripts[ i ];\n\t\t\t\t\t\tif ( rscriptType.test( node.type || \"\" ) &&\n\t\t\t\t\t\t\t!data_priv.access( node, \"globalEval\" ) && jQuery.contains( doc, node ) ) {\n\n\t\t\t\t\t\t\tif ( node.src ) {\n\t\t\t\t\t\t\t\t// Optional AJAX dependency, but won't run scripts if not present\n\t\t\t\t\t\t\t\tif ( jQuery._evalUrl ) {\n\t\t\t\t\t\t\t\t\tjQuery._evalUrl( node.src );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.globalEval( node.textContent.replace( rcleanScript, \"\" ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n});\n\njQuery.each({\n\tappendTo: \"append\",\n\tprependTo: \"prepend\",\n\tinsertBefore: \"before\",\n\tinsertAfter: \"after\",\n\treplaceAll: \"replaceWith\"\n}, function( name, original ) {\n\tjQuery.fn[ name ] = function( selector ) {\n\t\tvar elems,\n\t\t\tret = [],\n\t\t\tinsert = jQuery( selector ),\n\t\t\tlast = insert.length - 1,\n\t\t\ti = 0;\n\n\t\tfor ( ; i <= last; i++ ) {\n\t\t\telems = i === last ? this : this.clone( true );\n\t\t\tjQuery( insert[ i ] )[ original ]( elems );\n\n\t\t\t// Support: QtWebKit\n\t\t\t// .get() because push.apply(_, arraylike) throws\n\t\t\tpush.apply( ret, elems.get() );\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n});\n\n\nvar iframe,\n\telemdisplay = {};\n\n/**\n * Retrieve the actual display of a element\n * @param {String} name nodeName of the element\n * @param {Object} doc Document object\n */\n// Called only from within defaultDisplay\nfunction actualDisplay( name, doc ) {\n\tvar elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),\n\n\t\t// getDefaultComputedStyle might be reliably used only on attached element\n\t\tdisplay = window.getDefaultComputedStyle ?\n\n\t\t\t// Use of this method is a temporary fix (more like optmization) until something better comes along,\n\t\t\t// since it was removed from specification and supported only in FF\n\t\t\twindow.getDefaultComputedStyle( elem[ 0 ] ).display : jQuery.css( elem[ 0 ], \"display\" );\n\n\t// We don't have any data stored on the element,\n\t// so use \"detach\" method as fast way to get rid of the element\n\telem.detach();\n\n\treturn display;\n}\n\n/**\n * Try to determine the default display value of an element\n * @param {String} nodeName\n */\nfunction defaultDisplay( nodeName ) {\n\tvar doc = document,\n\t\tdisplay = elemdisplay[ nodeName ];\n\n\tif ( !display ) {\n\t\tdisplay = actualDisplay( nodeName, doc );\n\n\t\t// If the simple way fails, read from inside an iframe\n\t\tif ( display === \"none\" || !display ) {\n\n\t\t\t// Use the already-created iframe if possible\n\t\t\tiframe = (iframe || jQuery( \"<iframe frameborder='0' width='0' height='0'/>\" )).appendTo( doc.documentElement );\n\n\t\t\t// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse\n\t\t\tdoc = iframe[ 0 ].contentDocument;\n\n\t\t\t// Support: IE\n\t\t\tdoc.write();\n\t\t\tdoc.close();\n\n\t\t\tdisplay = actualDisplay( nodeName, doc );\n\t\t\tiframe.detach();\n\t\t}\n\n\t\t// Store the correct default display\n\t\telemdisplay[ nodeName ] = display;\n\t}\n\n\treturn display;\n}\nvar rmargin = (/^margin/);\n\nvar rnumnonpx = new RegExp( \"^(\" + pnum + \")(?!px)[a-z%]+$\", \"i\" );\n\nvar getStyles = function( elem ) {\n\t\treturn elem.ownerDocument.defaultView.getComputedStyle( elem, null );\n\t};\n\n\n\nfunction curCSS( elem, name, computed ) {\n\tvar width, minWidth, maxWidth, ret,\n\t\tstyle = elem.style;\n\n\tcomputed = computed || getStyles( elem );\n\n\t// Support: IE9\n\t// getPropertyValue is only needed for .css('filter') in IE9, see #12537\n\tif ( computed ) {\n\t\tret = computed.getPropertyValue( name ) || computed[ name ];\n\t}\n\n\tif ( computed ) {\n\n\t\tif ( ret === \"\" && !jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\tret = jQuery.style( elem, name );\n\t\t}\n\n\t\t// Support: iOS < 6\n\t\t// A tribute to the \"awesome hack by Dean Edwards\"\n\t\t// iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels\n\t\t// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values\n\t\tif ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {\n\n\t\t\t// Remember the original values\n\t\t\twidth = style.width;\n\t\t\tminWidth = style.minWidth;\n\t\t\tmaxWidth = style.maxWidth;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\n\t\t\tret = computed.width;\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.width = width;\n\t\t\tstyle.minWidth = minWidth;\n\t\t\tstyle.maxWidth = maxWidth;\n\t\t}\n\t}\n\n\treturn ret !== undefined ?\n\t\t// Support: IE\n\t\t// IE returns zIndex value as an integer.\n\t\tret + \"\" :\n\t\tret;\n}\n\n\nfunction addGetHookIf( conditionFn, hookFn ) {\n\t// Define the hook, we'll check on the first run if it's really needed.\n\treturn {\n\t\tget: function() {\n\t\t\tif ( conditionFn() ) {\n\t\t\t\t// Hook not needed (or it's not possible to use it due to missing dependency),\n\t\t\t\t// remove it.\n\t\t\t\t// Since there are no other hooks for marginRight, remove the whole object.\n\t\t\t\tdelete this.get;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Hook needed; redefine it so that the support test is not executed again.\n\n\t\t\treturn (this.get = hookFn).apply( this, arguments );\n\t\t}\n\t};\n}\n\n\n(function() {\n\tvar pixelPositionVal, boxSizingReliableVal,\n\t\t// Support: Firefox, Android 2.3 (Prefixed box-sizing versions).\n\t\tdivReset = \"padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;\" +\n\t\t\t\"-moz-box-sizing:content-box;box-sizing:content-box\",\n\t\tdocElem = document.documentElement,\n\t\tcontainer = document.createElement( \"div\" ),\n\t\tdiv = document.createElement( \"div\" );\n\n\tdiv.style.backgroundClip = \"content-box\";\n\tdiv.cloneNode( true ).style.backgroundClip = \"\";\n\tsupport.clearCloneStyle = div.style.backgroundClip === \"content-box\";\n\n\tcontainer.style.cssText = \"border:0;width:0;height:0;position:absolute;top:0;left:-9999px;\" +\n\t\t\"margin-top:1px\";\n\tcontainer.appendChild( div );\n\n\t// Executing both pixelPosition & boxSizingReliable tests require only one layout\n\t// so they're executed at the same time to save the second computation.\n\tfunction computePixelPositionAndBoxSizingReliable() {\n\t\t// Support: Firefox, Android 2.3 (Prefixed box-sizing versions).\n\t\tdiv.style.cssText = \"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;\" +\n\t\t\t\"box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;\" +\n\t\t\t\"position:absolute;top:1%\";\n\t\tdocElem.appendChild( container );\n\n\t\tvar divStyle = window.getComputedStyle( div, null );\n\t\tpixelPositionVal = divStyle.top !== \"1%\";\n\t\tboxSizingReliableVal = divStyle.width === \"4px\";\n\n\t\tdocElem.removeChild( container );\n\t}\n\n\t// Use window.getComputedStyle because jsdom on node.js will break without it.\n\tif ( window.getComputedStyle ) {\n\t\tjQuery.extend(support, {\n\t\t\tpixelPosition: function() {\n\t\t\t\t// This test is executed only once but we still do memoizing\n\t\t\t\t// since we can use the boxSizingReliable pre-computing.\n\t\t\t\t// No need to check if the test was already performed, though.\n\t\t\t\tcomputePixelPositionAndBoxSizingReliable();\n\t\t\t\treturn pixelPositionVal;\n\t\t\t},\n\t\t\tboxSizingReliable: function() {\n\t\t\t\tif ( boxSizingReliableVal == null ) {\n\t\t\t\t\tcomputePixelPositionAndBoxSizingReliable();\n\t\t\t\t}\n\t\t\t\treturn boxSizingReliableVal;\n\t\t\t},\n\t\t\treliableMarginRight: function() {\n\t\t\t\t// Support: Android 2.3\n\t\t\t\t// Check if div with explicit width and no margin-right incorrectly\n\t\t\t\t// gets computed margin-right based on width of container. (#3333)\n\t\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\t\t// This support function is only executed once so no memoizing is needed.\n\t\t\t\tvar ret,\n\t\t\t\t\tmarginDiv = div.appendChild( document.createElement( \"div\" ) );\n\t\t\t\tmarginDiv.style.cssText = div.style.cssText = divReset;\n\t\t\t\tmarginDiv.style.marginRight = marginDiv.style.width = \"0\";\n\t\t\t\tdiv.style.width = \"1px\";\n\t\t\t\tdocElem.appendChild( container );\n\n\t\t\t\tret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight );\n\n\t\t\t\tdocElem.removeChild( container );\n\n\t\t\t\t// Clean up the div for other support tests.\n\t\t\t\tdiv.innerHTML = \"\";\n\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t});\n\t}\n})();\n\n\n// A method for quickly swapping in/out CSS properties to get correct calculations.\njQuery.swap = function( elem, options, callback, args ) {\n\tvar ret, name,\n\t\told = {};\n\n\t// Remember the old values, and insert the new ones\n\tfor ( name in options ) {\n\t\told[ name ] = elem.style[ name ];\n\t\telem.style[ name ] = options[ name ];\n\t}\n\n\tret = callback.apply( elem, args || [] );\n\n\t// Revert the old values\n\tfor ( name in options ) {\n\t\telem.style[ name ] = old[ name ];\n\t}\n\n\treturn ret;\n};\n\n\nvar\n\t// swappable if display is none or starts with table except \"table\", \"table-cell\", or \"table-caption\"\n\t// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\n\trnumsplit = new RegExp( \"^(\" + pnum + \")(.*)$\", \"i\" ),\n\trrelNum = new RegExp( \"^([+-])=(\" + pnum + \")\", \"i\" ),\n\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n\tcssNormalTransform = {\n\t\tletterSpacing: 0,\n\t\tfontWeight: 400\n\t},\n\n\tcssPrefixes = [ \"Webkit\", \"O\", \"Moz\", \"ms\" ];\n\n// return a css property mapped to a potentially vendor prefixed property\nfunction vendorPropName( style, name ) {\n\n\t// shortcut for names that are not vendor prefixed\n\tif ( name in style ) {\n\t\treturn name;\n\t}\n\n\t// check for vendor prefixed names\n\tvar capName = name[0].toUpperCase() + name.slice(1),\n\t\torigName = name,\n\t\ti = cssPrefixes.length;\n\n\twhile ( i-- ) {\n\t\tname = cssPrefixes[ i ] + capName;\n\t\tif ( name in style ) {\n\t\t\treturn name;\n\t\t}\n\t}\n\n\treturn origName;\n}\n\nfunction setPositiveNumber( elem, value, subtract ) {\n\tvar matches = rnumsplit.exec( value );\n\treturn matches ?\n\t\t// Guard against undefined \"subtract\", e.g., when used as in cssHooks\n\t\tMath.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || \"px\" ) :\n\t\tvalue;\n}\n\nfunction augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {\n\tvar i = extra === ( isBorderBox ? \"border\" : \"content\" ) ?\n\t\t// If we already have the right measurement, avoid augmentation\n\t\t4 :\n\t\t// Otherwise initialize for horizontal or vertical properties\n\t\tname === \"width\" ? 1 : 0,\n\n\t\tval = 0;\n\n\tfor ( ; i < 4; i += 2 ) {\n\t\t// both box models exclude margin, so add it if we want it\n\t\tif ( extra === \"margin\" ) {\n\t\t\tval += jQuery.css( elem, extra + cssExpand[ i ], true, styles );\n\t\t}\n\n\t\tif ( isBorderBox ) {\n\t\t\t// border-box includes padding, so remove it if we want content\n\t\t\tif ( extra === \"content\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\t\t\t}\n\n\t\t\t// at this point, extra isn't border nor margin, so remove border\n\t\t\tif ( extra !== \"margin\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t} else {\n\t\t\t// at this point, extra isn't content, so add padding\n\t\t\tval += jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\n\t\t\t// at this point, extra isn't content nor padding, so add border\n\t\t\tif ( extra !== \"padding\" ) {\n\t\t\t\tval += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn val;\n}\n\nfunction getWidthOrHeight( elem, name, extra ) {\n\n\t// Start with offset property, which is equivalent to the border-box value\n\tvar valueIsBorderBox = true,\n\t\tval = name === \"width\" ? elem.offsetWidth : elem.offsetHeight,\n\t\tstyles = getStyles( elem ),\n\t\tisBorderBox = jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\";\n\n\t// some non-html elements return undefined for offsetWidth, so check for null/undefined\n\t// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285\n\t// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668\n\tif ( val <= 0 || val == null ) {\n\t\t// Fall back to computed then uncomputed css if necessary\n\t\tval = curCSS( elem, name, styles );\n\t\tif ( val < 0 || val == null ) {\n\t\t\tval = elem.style[ name ];\n\t\t}\n\n\t\t// Computed unit is not pixels. Stop here and return.\n\t\tif ( rnumnonpx.test(val) ) {\n\t\t\treturn val;\n\t\t}\n\n\t\t// we need the check for style in case a browser which returns unreliable values\n\t\t// for getComputedStyle silently falls back to the reliable elem.style\n\t\tvalueIsBorderBox = isBorderBox &&\n\t\t\t( support.boxSizingReliable() || val === elem.style[ name ] );\n\n\t\t// Normalize \"\", auto, and prepare for extra\n\t\tval = parseFloat( val ) || 0;\n\t}\n\n\t// use the active box-sizing model to add/subtract irrelevant styles\n\treturn ( val +\n\t\taugmentWidthOrHeight(\n\t\t\telem,\n\t\t\tname,\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\n\t\t\tvalueIsBorderBox,\n\t\t\tstyles\n\t\t)\n\t) + \"px\";\n}\n\nfunction showHide( elements, show ) {\n\tvar display, elem, hidden,\n\t\tvalues = [],\n\t\tindex = 0,\n\t\tlength = elements.length;\n\n\tfor ( ; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tvalues[ index ] = data_priv.get( elem, \"olddisplay\" );\n\t\tdisplay = elem.style.display;\n\t\tif ( show ) {\n\t\t\t// Reset the inline display of this element to learn if it is\n\t\t\t// being hidden by cascaded rules or not\n\t\t\tif ( !values[ index ] && display === \"none\" ) {\n\t\t\t\telem.style.display = \"\";\n\t\t\t}\n\n\t\t\t// Set elements which have been overridden with display: none\n\t\t\t// in a stylesheet to whatever the default browser style is\n\t\t\t// for such an element\n\t\t\tif ( elem.style.display === \"\" && isHidden( elem ) ) {\n\t\t\t\tvalues[ index ] = data_priv.access( elem, \"olddisplay\", defaultDisplay(elem.nodeName) );\n\t\t\t}\n\t\t} else {\n\n\t\t\tif ( !values[ index ] ) {\n\t\t\t\thidden = isHidden( elem );\n\n\t\t\t\tif ( display && display !== \"none\" || !hidden ) {\n\t\t\t\t\tdata_priv.set( elem, \"olddisplay\", hidden ? display : jQuery.css(elem, \"display\") );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of most of the elements in a second loop\n\t// to avoid the constant reflow\n\tfor ( index = 0; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( !show || elem.style.display === \"none\" || elem.style.display === \"\" ) {\n\t\t\telem.style.display = show ? values[ index ] || \"\" : \"none\";\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.extend({\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Don't automatically add \"px\" to these possibly-unitless properties\n\tcssNumber: {\n\t\t\"columnCount\": true,\n\t\t\"fillOpacity\": true,\n\t\t\"fontWeight\": true,\n\t\t\"lineHeight\": true,\n\t\t\"opacity\": true,\n\t\t\"order\": true,\n\t\t\"orphans\": true,\n\t\t\"widows\": true,\n\t\t\"zIndex\": true,\n\t\t\"zoom\": true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {\n\t\t// normalize float css property\n\t\t\"float\": \"cssFloat\"\n\t},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function( elem, name, value, extra ) {\n\t\t// Don't set styles on text and comment nodes\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we're working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName = jQuery.camelCase( name ),\n\t\t\tstyle = elem.style;\n\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// Check if we're setting a value\n\t\tif ( value !== undefined ) {\n\t\t\ttype = typeof value;\n\n\t\t\t// convert relative number strings (+= or -=) to relative numbers. #7345\n\t\t\tif ( type === \"string\" && (ret = rrelNum.exec( value )) ) {\n\t\t\t\tvalue = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );\n\t\t\t\t// Fixes bug #9237\n\t\t\t\ttype = \"number\";\n\t\t\t}\n\n\t\t\t// Make sure that null and NaN values aren't set. See: #7116\n\t\t\tif ( value == null || value !== value ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number was passed in, add 'px' to the (except for certain CSS properties)\n\t\t\tif ( type === \"number\" && !jQuery.cssNumber[ origName ] ) {\n\t\t\t\tvalue += \"px\";\n\t\t\t}\n\n\t\t\t// Fixes #8908, it can be done more correctly by specifying setters in cssHooks,\n\t\t\t// but it would mean to define eight (for every problematic property) identical functions\n\t\t\tif ( !support.clearCloneStyle && value === \"\" && name.indexOf( \"background\" ) === 0 ) {\n\t\t\t\tstyle[ name ] = \"inherit\";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif ( !hooks || !(\"set\" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {\n\t\t\t\t// Support: Chrome, Safari\n\t\t\t\t// Setting style to blank string required to delete \"style: x !important;\"\n\t\t\t\tstyle[ name ] = \"\";\n\t\t\t\tstyle[ name ] = value;\n\t\t\t}\n\n\t\t} else {\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function( elem, name, extra, styles ) {\n\t\tvar val, num, hooks,\n\t\t\torigName = jQuery.camelCase( name );\n\n\t\t// Make sure that we're working with the right name\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif ( hooks && \"get\" in hooks ) {\n\t\t\tval = hooks.get( elem, true, extra );\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif ( val === undefined ) {\n\t\t\tval = curCSS( elem, name, styles );\n\t\t}\n\n\t\t//convert \"normal\" to computed value\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\n\t\t\tval = cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Return, converting to number if forced or a qualifier was provided and val looks numeric\n\t\tif ( extra === \"\" || extra ) {\n\t\t\tnum = parseFloat( val );\n\t\t\treturn extra === true || jQuery.isNumeric( num ) ? num || 0 : val;\n\t\t}\n\t\treturn val;\n\t}\n});\n\njQuery.each([ \"height\", \"width\" ], function( i, name ) {\n\tjQuery.cssHooks[ name ] = {\n\t\tget: function( elem, computed, extra ) {\n\t\t\tif ( computed ) {\n\t\t\t\t// certain elements can have dimension info if we invisibly show them\n\t\t\t\t// however, it must have a current display style that would benefit from this\n\t\t\t\treturn elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, \"display\" ) ) ?\n\t\t\t\t\tjQuery.swap( elem, cssShow, function() {\n\t\t\t\t\t\treturn getWidthOrHeight( elem, name, extra );\n\t\t\t\t\t}) :\n\t\t\t\t\tgetWidthOrHeight( elem, name, extra );\n\t\t\t}\n\t\t},\n\n\t\tset: function( elem, value, extra ) {\n\t\t\tvar styles = extra && getStyles( elem );\n\t\t\treturn setPositiveNumber( elem, value, extra ?\n\t\t\t\taugmentWidthOrHeight(\n\t\t\t\t\telem,\n\t\t\t\t\tname,\n\t\t\t\t\textra,\n\t\t\t\t\tjQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\t\t\t\tstyles\n\t\t\t\t) : 0\n\t\t\t);\n\t\t}\n\t};\n});\n\n// Support: Android 2.3\njQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,\n\tfunction( elem, computed ) {\n\t\tif ( computed ) {\n\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\t// Work around by temporarily setting element display to inline-block\n\t\t\treturn jQuery.swap( elem, { \"display\": \"inline-block\" },\n\t\t\t\tcurCSS, [ elem, \"marginRight\" ] );\n\t\t}\n\t}\n);\n\n// These hooks are used by animate to expand properties\njQuery.each({\n\tmargin: \"\",\n\tpadding: \"\",\n\tborder: \"Width\"\n}, function( prefix, suffix ) {\n\tjQuery.cssHooks[ prefix + suffix ] = {\n\t\texpand: function( value ) {\n\t\t\tvar i = 0,\n\t\t\t\texpanded = {},\n\n\t\t\t\t// assumes a single number if not a string\n\t\t\t\tparts = typeof value === \"string\" ? value.split(\" \") : [ value ];\n\n\t\t\tfor ( ; i < 4; i++ ) {\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif ( !rmargin.test( prefix ) ) {\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n\t}\n});\n\njQuery.fn.extend({\n\tcss: function( name, value ) {\n\t\treturn access( this, function( elem, name, value ) {\n\t\t\tvar styles, len,\n\t\t\t\tmap = {},\n\t\t\t\ti = 0;\n\n\t\t\tif ( jQuery.isArray( name ) ) {\n\t\t\t\tstyles = getStyles( elem );\n\t\t\t\tlen = name.length;\n\n\t\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t\tmap[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );\n\t\t\t\t}\n\n\t\t\t\treturn map;\n\t\t\t}\n\n\t\t\treturn value !== undefined ?\n\t\t\t\tjQuery.style( elem, name, value ) :\n\t\t\t\tjQuery.css( elem, name );\n\t\t}, name, value, arguments.length > 1 );\n\t},\n\tshow: function() {\n\t\treturn showHide( this, true );\n\t},\n\thide: function() {\n\t\treturn showHide( this );\n\t},\n\ttoggle: function( state ) {\n\t\tif ( typeof state === \"boolean\" ) {\n\t\t\treturn state ? this.show() : this.hide();\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( isHidden( this ) ) {\n\t\t\t\tjQuery( this ).show();\n\t\t\t} else {\n\t\t\t\tjQuery( this ).hide();\n\t\t\t}\n\t\t});\n\t}\n});\n\n\nfunction Tween( elem, options, prop, end, easing ) {\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\n}\njQuery.Tween = Tween;\n\nTween.prototype = {\n\tconstructor: Tween,\n\tinit: function( elem, options, prop, end, easing, unit ) {\n\t\tthis.elem = elem;\n\t\tthis.prop = prop;\n\t\tthis.easing = easing || \"swing\";\n\t\tthis.options = options;\n\t\tthis.start = this.now = this.cur();\n\t\tthis.end = end;\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\t},\n\tcur: function() {\n\t\tvar hooks = Tween.propHooks[ this.prop ];\n\n\t\treturn hooks && hooks.get ?\n\t\t\thooks.get( this ) :\n\t\t\tTween.propHooks._default.get( this );\n\t},\n\trun: function( percent ) {\n\t\tvar eased,\n\t\t\thooks = Tween.propHooks[ this.prop ];\n\n\t\tif ( this.options.duration ) {\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t} else {\n\t\t\tthis.pos = eased = percent;\n\t\t}\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\n\n\t\tif ( this.options.step ) {\n\t\t\tthis.options.step.call( this.elem, this.now, this );\n\t\t}\n\n\t\tif ( hooks && hooks.set ) {\n\t\t\thooks.set( this );\n\t\t} else {\n\t\t\tTween.propHooks._default.set( this );\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype = Tween.prototype;\n\nTween.propHooks = {\n\t_default: {\n\t\tget: function( tween ) {\n\t\t\tvar result;\n\n\t\t\tif ( tween.elem[ tween.prop ] != null &&\n\t\t\t\t(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// passing an empty string as a 3rd parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails\n\t\t\t// so, simple values such as \"10px\" are parsed to Float.\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as is.\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, \"\" );\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\n\t\t},\n\t\tset: function( tween ) {\n\t\t\t// use step hook for back compat - use cssHook if its there - use .style if its\n\t\t\t// available and use plain properties where available\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\n\t\t\t} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\n\t\t\t} else {\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Support: IE9\n// Panic based approach to setting things on disconnected nodes\n\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n\tset: function( tween ) {\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\n\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t}\n\t}\n};\n\njQuery.easing = {\n\tlinear: function( p ) {\n\t\treturn p;\n\t},\n\tswing: function( p ) {\n\t\treturn 0.5 - Math.cos( p * Math.PI ) / 2;\n\t}\n};\n\njQuery.fx = Tween.prototype.init;\n\n// Back Compat <1.8 extension point\njQuery.fx.step = {};\n\n\n\n\nvar\n\tfxNow, timerId,\n\trfxtypes = /^(?:toggle|show|hide)$/,\n\trfxnum = new RegExp( \"^(?:([+-])=|)(\" + pnum + \")([a-z%]*)$\", \"i\" ),\n\trrun = /queueHooks$/,\n\tanimationPrefilters = [ defaultPrefilter ],\n\ttweeners = {\n\t\t\"*\": [ function( prop, value ) {\n\t\t\tvar tween = this.createTween( prop, value ),\n\t\t\t\ttarget = tween.cur(),\n\t\t\t\tparts = rfxnum.exec( value ),\n\t\t\t\tunit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\n\n\t\t\t\t// Starting value computation is required for potential unit mismatches\n\t\t\t\tstart = ( jQuery.cssNumber[ prop ] || unit !== \"px\" && +target ) &&\n\t\t\t\t\trfxnum.exec( jQuery.css( tween.elem, prop ) ),\n\t\t\t\tscale = 1,\n\t\t\t\tmaxIterations = 20;\n\n\t\t\tif ( start && start[ 3 ] !== unit ) {\n\t\t\t\t// Trust units reported by jQuery.css\n\t\t\t\tunit = unit || start[ 3 ];\n\n\t\t\t\t// Make sure we update the tween properties later on\n\t\t\t\tparts = parts || [];\n\n\t\t\t\t// Iteratively approximate from a nonzero starting point\n\t\t\t\tstart = +target || 1;\n\n\t\t\t\tdo {\n\t\t\t\t\t// If previous iteration zeroed out, double until we get *something*\n\t\t\t\t\t// Use a string for doubling factor so we don't accidentally see scale as unchanged below\n\t\t\t\t\tscale = scale || \".5\";\n\n\t\t\t\t\t// Adjust and apply\n\t\t\t\t\tstart = start / scale;\n\t\t\t\t\tjQuery.style( tween.elem, prop, start + unit );\n\n\t\t\t\t// Update scale, tolerating zero or NaN from tween.cur()\n\t\t\t\t// And breaking the loop if scale is unchanged or perfect, or if we've just had enough\n\t\t\t\t} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );\n\t\t\t}\n\n\t\t\t// Update tween properties\n\t\t\tif ( parts ) {\n\t\t\t\tstart = tween.start = +start || +target || 0;\n\t\t\t\ttween.unit = unit;\n\t\t\t\t// If a +=/-= token was provided, we're doing a relative animation\n\t\t\t\ttween.end = parts[ 1 ] ?\n\t\t\t\t\tstart + ( parts[ 1 ] + 1 ) * parts[ 2 ] :\n\t\t\t\t\t+parts[ 2 ];\n\t\t\t}\n\n\t\t\treturn tween;\n\t\t} ]\n\t};\n\n// Animations created synchronously will run synchronously\nfunction createFxNow() {\n\tsetTimeout(function() {\n\t\tfxNow = undefined;\n\t});\n\treturn ( fxNow = jQuery.now() );\n}\n\n// Generate parameters to create a standard animation\nfunction genFx( type, includeWidth ) {\n\tvar which,\n\t\ti = 0,\n\t\tattrs = { height: type };\n\n\t// if we include width, step value is 1 to do all cssExpand values,\n\t// if we don't include width, step value is 2 to skip over Left and Right\n\tincludeWidth = includeWidth ? 1 : 0;\n\tfor ( ; i < 4 ; i += 2 - includeWidth ) {\n\t\twhich = cssExpand[ i ];\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n\t}\n\n\tif ( includeWidth ) {\n\t\tattrs.opacity = attrs.width = type;\n\t}\n\n\treturn attrs;\n}\n\nfunction createTween( value, prop, animation ) {\n\tvar tween,\n\t\tcollection = ( tweeners[ prop ] || [] ).concat( tweeners[ \"*\" ] ),\n\t\tindex = 0,\n\t\tlength = collection.length;\n\tfor ( ; index < length; index++ ) {\n\t\tif ( (tween = collection[ index ].call( animation, prop, value )) ) {\n\n\t\t\t// we're done with this property\n\t\t\treturn tween;\n\t\t}\n\t}\n}\n\nfunction defaultPrefilter( elem, props, opts ) {\n\t/* jshint validthis: true */\n\tvar prop, value, toggle, tween, hooks, oldfire, display,\n\t\tanim = this,\n\t\torig = {},\n\t\tstyle = elem.style,\n\t\thidden = elem.nodeType && isHidden( elem ),\n\t\tdataShow = data_priv.get( elem, \"fxshow\" );\n\n\t// handle queue: false promises\n\tif ( !opts.queue ) {\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\n\t\tif ( hooks.unqueued == null ) {\n\t\t\thooks.unqueued = 0;\n\t\t\toldfire = hooks.empty.fire;\n\t\t\thooks.empty.fire = function() {\n\t\t\t\tif ( !hooks.unqueued ) {\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always(function() {\n\t\t\t// doing this makes sure that the complete handler will be called\n\t\t\t// before this completes\n\t\t\tanim.always(function() {\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t// height/width overflow pass\n\tif ( elem.nodeType === 1 && ( \"height\" in props || \"width\" in props ) ) {\n\t\t// Make sure that nothing sneaks out\n\t\t// Record all 3 overflow attributes because IE9-10 do not\n\t\t// change the overflow attribute when overflowX and\n\t\t// overflowY are set to the same value\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Set display property to inline-block for height/width\n\t\t// animations on inline elements that are having width/height animated\n\t\tdisplay = jQuery.css( elem, \"display\" );\n\t\t// Get default display if display is currently \"none\"\n\t\tif ( display === \"none\" ) {\n\t\t\tdisplay = defaultDisplay( elem.nodeName );\n\t\t}\n\t\tif ( display === \"inline\" &&\n\t\t\t\tjQuery.css( elem, \"float\" ) === \"none\" ) {\n\n\t\t\tstyle.display = \"inline-block\";\n\t\t}\n\t}\n\n\tif ( opts.overflow ) {\n\t\tstyle.overflow = \"hidden\";\n\t\tanim.always(function() {\n\t\t\tstyle.overflow = opts.overflow[ 0 ];\n\t\t\tstyle.overflowX = opts.overflow[ 1 ];\n\t\t\tstyle.overflowY = opts.overflow[ 2 ];\n\t\t});\n\t}\n\n\t// show/hide pass\n\tfor ( prop in props ) {\n\t\tvalue = props[ prop ];\n\t\tif ( rfxtypes.exec( value ) ) {\n\t\t\tdelete props[ prop ];\n\t\t\ttoggle = toggle || value === \"toggle\";\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\n\n\t\t\t\t// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden\n\t\t\t\tif ( value === \"show\" && dataShow && dataShow[ prop ] !== undefined ) {\n\t\t\t\t\thidden = true;\n\t\t\t\t} else {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\torig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );\n\t\t}\n\t}\n\n\tif ( !jQuery.isEmptyObject( orig ) ) {\n\t\tif ( dataShow ) {\n\t\t\tif ( \"hidden\" in dataShow ) {\n\t\t\t\thidden = dataShow.hidden;\n\t\t\t}\n\t\t} else {\n\t\t\tdataShow = data_priv.access( elem, \"fxshow\", {} );\n\t\t}\n\n\t\t// store state if its toggle - enables .stop().toggle() to \"reverse\"\n\t\tif ( toggle ) {\n\t\t\tdataShow.hidden = !hidden;\n\t\t}\n\t\tif ( hidden ) {\n\t\t\tjQuery( elem ).show();\n\t\t} else {\n\t\t\tanim.done(function() {\n\t\t\t\tjQuery( elem ).hide();\n\t\t\t});\n\t\t}\n\t\tanim.done(function() {\n\t\t\tvar prop;\n\n\t\t\tdata_priv.remove( elem, \"fxshow\" );\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\n\t\t\t}\n\t\t});\n\t\tfor ( prop in orig ) {\n\t\t\ttween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );\n\n\t\t\tif ( !( prop in dataShow ) ) {\n\t\t\t\tdataShow[ prop ] = tween.start;\n\t\t\t\tif ( hidden ) {\n\t\t\t\t\ttween.end = tween.start;\n\t\t\t\t\ttween.start = prop === \"width\" || prop === \"height\" ? 1 : 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction propFilter( props, specialEasing ) {\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor ( index in props ) {\n\t\tname = jQuery.camelCase( index );\n\t\teasing = specialEasing[ name ];\n\t\tvalue = props[ index ];\n\t\tif ( jQuery.isArray( value ) ) {\n\t\t\teasing = value[ 1 ];\n\t\t\tvalue = props[ index ] = value[ 0 ];\n\t\t}\n\n\t\tif ( index !== name ) {\n\t\t\tprops[ name ] = value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks = jQuery.cssHooks[ name ];\n\t\tif ( hooks && \"expand\" in hooks ) {\n\t\t\tvalue = hooks.expand( value );\n\t\t\tdelete props[ name ];\n\n\t\t\t// not quite $.extend, this wont overwrite keys already present.\n\t\t\t// also - reusing 'index' from above because we have the correct \"name\"\n\t\t\tfor ( index in value ) {\n\t\t\t\tif ( !( index in props ) ) {\n\t\t\t\t\tprops[ index ] = value[ index ];\n\t\t\t\t\tspecialEasing[ index ] = easing;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tspecialEasing[ name ] = easing;\n\t\t}\n\t}\n}\n\nfunction Animation( elem, properties, options ) {\n\tvar result,\n\t\tstopped,\n\t\tindex = 0,\n\t\tlength = animationPrefilters.length,\n\t\tdeferred = jQuery.Deferred().always( function() {\n\t\t\t// don't match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t}),\n\t\ttick = function() {\n\t\t\tif ( stopped ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar currentTime = fxNow || createFxNow(),\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\n\t\t\t\t// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)\n\t\t\t\ttemp = remaining / animation.duration || 0,\n\t\t\t\tpercent = 1 - temp,\n\t\t\t\tindex = 0,\n\t\t\t\tlength = animation.tweens.length;\n\n\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\tanimation.tweens[ index ].run( percent );\n\t\t\t}\n\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ]);\n\n\t\t\tif ( percent < 1 && length ) {\n\t\t\t\treturn remaining;\n\t\t\t} else {\n\t\t\t\tdeferred.resolveWith( elem, [ animation ] );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tanimation = deferred.promise({\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend( {}, properties ),\n\t\t\topts: jQuery.extend( true, { specialEasing: {} }, options ),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow || createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function( prop, end ) {\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\n\t\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\n\t\t\t\tanimation.tweens.push( tween );\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function( gotoEnd ) {\n\t\t\t\tvar index = 0,\n\t\t\t\t\t// if we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\n\t\t\t\tif ( stopped ) {\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tstopped = true;\n\t\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\n\t\t\t\t}\n\n\t\t\t\t// resolve when we played the last frame\n\t\t\t\t// otherwise, reject\n\t\t\t\tif ( gotoEnd ) {\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t} else {\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}),\n\t\tprops = animation.props;\n\n\tpropFilter( props, animation.opts.specialEasing );\n\n\tfor ( ; index < length ; index++ ) {\n\t\tresult = animationPrefilters[ index ].call( animation, elem, props, animation.opts );\n\t\tif ( result ) {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tjQuery.map( props, createTween, animation );\n\n\tif ( jQuery.isFunction( animation.opts.start ) ) {\n\t\tanimation.opts.start.call( elem, animation );\n\t}\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend( tick, {\n\t\t\telem: elem,\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue\n\t\t})\n\t);\n\n\t// attach callbacks from options\n\treturn animation.progress( animation.opts.progress )\n\t\t.done( animation.opts.done, animation.opts.complete )\n\t\t.fail( animation.opts.fail )\n\t\t.always( animation.opts.always );\n}\n\njQuery.Animation = jQuery.extend( Animation, {\n\n\ttweener: function( props, callback ) {\n\t\tif ( jQuery.isFunction( props ) ) {\n\t\t\tcallback = props;\n\t\t\tprops = [ \"*\" ];\n\t\t} else {\n\t\t\tprops = props.split(\" \");\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex = 0,\n\t\t\tlength = props.length;\n\n\t\tfor ( ; index < length ; index++ ) {\n\t\t\tprop = props[ index ];\n\t\t\ttweeners[ prop ] = tweeners[ prop ] || [];\n\t\t\ttweeners[ prop ].unshift( callback );\n\t\t}\n\t},\n\n\tprefilter: function( callback, prepend ) {\n\t\tif ( prepend ) {\n\t\t\tanimationPrefilters.unshift( callback );\n\t\t} else {\n\t\t\tanimationPrefilters.push( callback );\n\t\t}\n\t}\n});\n\njQuery.speed = function( speed, easing, fn ) {\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\n\t\tcomplete: fn || !fn && easing ||\n\t\t\tjQuery.isFunction( speed ) && speed,\n\t\tduration: speed,\n\t\teasing: fn && easing || easing && !jQuery.isFunction( easing ) && easing\n\t};\n\n\topt.duration = jQuery.fx.off ? 0 : typeof opt.duration === \"number\" ? opt.duration :\n\t\topt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;\n\n\t// normalize opt.queue - true/undefined/null -> \"fx\"\n\tif ( opt.queue == null || opt.queue === true ) {\n\t\topt.queue = \"fx\";\n\t}\n\n\t// Queueing\n\topt.old = opt.complete;\n\n\topt.complete = function() {\n\t\tif ( jQuery.isFunction( opt.old ) ) {\n\t\t\topt.old.call( this );\n\t\t}\n\n\t\tif ( opt.queue ) {\n\t\t\tjQuery.dequeue( this, opt.queue );\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.fn.extend({\n\tfadeTo: function( speed, to, easing, callback ) {\n\n\t\t// show any hidden elements after setting opacity to 0\n\t\treturn this.filter( isHidden ).css( \"opacity\", 0 ).show()\n\n\t\t\t// animate to the value specified\n\t\t\t.end().animate({ opacity: to }, speed, easing, callback );\n\t},\n\tanimate: function( prop, speed, easing, callback ) {\n\t\tvar empty = jQuery.isEmptyObject( prop ),\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\n\t\t\tdoAnimation = function() {\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\n\n\t\t\t\t// Empty animations, or finishing resolves immediately\n\t\t\t\tif ( empty || data_priv.get( this, \"finish\" ) ) {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t}\n\t\t\t};\n\t\t\tdoAnimation.finish = doAnimation;\n\n\t\treturn empty || optall.queue === false ?\n\t\t\tthis.each( doAnimation ) :\n\t\t\tthis.queue( optall.queue, doAnimation );\n\t},\n\tstop: function( type, clearQueue, gotoEnd ) {\n\t\tvar stopQueue = function( hooks ) {\n\t\t\tvar stop = hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop( gotoEnd );\n\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tgotoEnd = clearQueue;\n\t\t\tclearQueue = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\tif ( clearQueue && type !== false ) {\n\t\t\tthis.queue( type || \"fx\", [] );\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar dequeue = true,\n\t\t\t\tindex = type != null && type + \"queueHooks\",\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tdata = data_priv.get( this );\n\n\t\t\tif ( index ) {\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\n\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( index in data ) {\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\n\t\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\n\t\t\t\t\tdequeue = false;\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// start the next in the queue if the last step wasn't forced\n\t\t\t// timers currently will call their complete callbacks, which will dequeue\n\t\t\t// but only if they were gotoEnd\n\t\t\tif ( dequeue || !gotoEnd ) {\n\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t}\n\t\t});\n\t},\n\tfinish: function( type ) {\n\t\tif ( type !== false ) {\n\t\t\ttype = type || \"fx\";\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tvar index,\n\t\t\t\tdata = data_priv.get( this ),\n\t\t\t\tqueue = data[ type + \"queue\" ],\n\t\t\t\thooks = data[ type + \"queueHooks\" ],\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tlength = queue ? queue.length : 0;\n\n\t\t\t// enable finishing flag on private data\n\t\t\tdata.finish = true;\n\n\t\t\t// empty the queue first\n\t\t\tjQuery.queue( this, type, [] );\n\n\t\t\tif ( hooks && hooks.stop ) {\n\t\t\t\thooks.stop.call( this, true );\n\t\t\t}\n\n\t\t\t// look for any active animations, and finish them\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && timers[ index ].queue === type ) {\n\t\t\t\t\ttimers[ index ].anim.stop( true );\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// look for any animations in the old queue and finish them\n\t\t\tfor ( index = 0; index < length; index++ ) {\n\t\t\t\tif ( queue[ index ] && queue[ index ].finish ) {\n\t\t\t\t\tqueue[ index ].finish.call( this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// turn off finishing flag\n\t\t\tdelete data.finish;\n\t\t});\n\t}\n});\n\njQuery.each([ \"toggle\", \"show\", \"hide\" ], function( i, name ) {\n\tvar cssFn = jQuery.fn[ name ];\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn speed == null || typeof speed === \"boolean\" ?\n\t\t\tcssFn.apply( this, arguments ) :\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\n\t};\n});\n\n// Generate shortcuts for custom animations\njQuery.each({\n\tslideDown: genFx(\"show\"),\n\tslideUp: genFx(\"hide\"),\n\tslideToggle: genFx(\"toggle\"),\n\tfadeIn: { opacity: \"show\" },\n\tfadeOut: { opacity: \"hide\" },\n\tfadeToggle: { opacity: \"toggle\" }\n}, function( name, props ) {\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn this.animate( props, speed, easing, callback );\n\t};\n});\n\njQuery.timers = [];\njQuery.fx.tick = function() {\n\tvar timer,\n\t\ti = 0,\n\t\ttimers = jQuery.timers;\n\n\tfxNow = jQuery.now();\n\n\tfor ( ; i < timers.length; i++ ) {\n\t\ttimer = timers[ i ];\n\t\t// Checks the timer has not already been removed\n\t\tif ( !timer() && timers[ i ] === timer ) {\n\t\t\ttimers.splice( i--, 1 );\n\t\t}\n\t}\n\n\tif ( !timers.length ) {\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow = undefined;\n};\n\njQuery.fx.timer = function( timer ) {\n\tjQuery.timers.push( timer );\n\tif ( timer() ) {\n\t\tjQuery.fx.start();\n\t} else {\n\t\tjQuery.timers.pop();\n\t}\n};\n\njQuery.fx.interval = 13;\n\njQuery.fx.start = function() {\n\tif ( !timerId ) {\n\t\ttimerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );\n\t}\n};\n\njQuery.fx.stop = function() {\n\tclearInterval( timerId );\n\ttimerId = null;\n};\n\njQuery.fx.speeds = {\n\tslow: 600,\n\tfast: 200,\n\t// Default speed\n\t_default: 400\n};\n\n\n// Based off of the plugin by Clint Helfers, with permission.\n// http://blindsignals.com/index.php/2009/07/jquery-delay/\njQuery.fn.delay = function( time, type ) {\n\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n\ttype = type || \"fx\";\n\n\treturn this.queue( type, function( next, hooks ) {\n\t\tvar timeout = setTimeout( next, time );\n\t\thooks.stop = function() {\n\t\t\tclearTimeout( timeout );\n\t\t};\n\t});\n};\n\n\n(function() {\n\tvar input = document.createElement( \"input\" ),\n\t\tselect = document.createElement( \"select\" ),\n\t\topt = select.appendChild( document.createElement( \"option\" ) );\n\n\tinput.type = \"checkbox\";\n\n\t// Support: iOS 5.1, Android 4.x, Android 2.3\n\t// Check the default checkbox/radio value (\"\" on old WebKit; \"on\" elsewhere)\n\tsupport.checkOn = input.value !== \"\";\n\n\t// Must access the parent to make an option select properly\n\t// Support: IE9, IE10\n\tsupport.optSelected = opt.selected;\n\n\t// Make sure that the options inside disabled selects aren't marked as disabled\n\t// (WebKit marks them as disabled)\n\tselect.disabled = true;\n\tsupport.optDisabled = !opt.disabled;\n\n\t// Check if an input maintains its value after becoming a radio\n\t// Support: IE9, IE10\n\tinput = document.createElement( \"input\" );\n\tinput.value = \"t\";\n\tinput.type = \"radio\";\n\tsupport.radioValue = input.value === \"t\";\n})();\n\n\nvar nodeHook, boolHook,\n\tattrHandle = jQuery.expr.attrHandle;\n\njQuery.fn.extend({\n\tattr: function( name, value ) {\n\t\treturn access( this, jQuery.attr, name, value, arguments.length > 1 );\n\t},\n\n\tremoveAttr: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeAttr( this, name );\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tattr: function( elem, name, value ) {\n\t\tvar hooks, ret,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set attributes on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif ( typeof elem.getAttribute === strundefined ) {\n\t\t\treturn jQuery.prop( elem, name, value );\n\t\t}\n\n\t\t// All attributes are lowercase\n\t\t// Grab necessary hook if one is defined\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\t\t\tname = name.toLowerCase();\n\t\t\thooks = jQuery.attrHooks[ name ] ||\n\t\t\t\t( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\n\t\t\tif ( value === null ) {\n\t\t\t\tjQuery.removeAttr( elem, name );\n\n\t\t\t} else if ( hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {\n\t\t\t\treturn ret;\n\n\t\t\t} else {\n\t\t\t\telem.setAttribute( name, value + \"\" );\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t} else if ( hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ) {\n\t\t\treturn ret;\n\n\t\t} else {\n\t\t\tret = jQuery.find.attr( elem, name );\n\n\t\t\t// Non-existent attributes return null, we normalize to undefined\n\t\t\treturn ret == null ?\n\t\t\t\tundefined :\n\t\t\t\tret;\n\t\t}\n\t},\n\n\tremoveAttr: function( elem, value ) {\n\t\tvar name, propName,\n\t\t\ti = 0,\n\t\t\tattrNames = value && value.match( rnotwhite );\n\n\t\tif ( attrNames && elem.nodeType === 1 ) {\n\t\t\twhile ( (name = attrNames[i++]) ) {\n\t\t\t\tpropName = jQuery.propFix[ name ] || name;\n\n\t\t\t\t// Boolean attributes get special treatment (#10870)\n\t\t\t\tif ( jQuery.expr.match.bool.test( name ) ) {\n\t\t\t\t\t// Set corresponding property to false\n\t\t\t\t\telem[ propName ] = false;\n\t\t\t\t}\n\n\t\t\t\telem.removeAttribute( name );\n\t\t\t}\n\t\t}\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( !support.radioValue && value === \"radio\" &&\n\t\t\t\t\tjQuery.nodeName( elem, \"input\" ) ) {\n\t\t\t\t\t// Setting the type on a radio button after the value resets the value in IE6-9\n\t\t\t\t\t// Reset value to default in case type is set after value during creation\n\t\t\t\t\tvar val = elem.value;\n\t\t\t\t\telem.setAttribute( \"type\", value );\n\t\t\t\t\tif ( val ) {\n\t\t\t\t\t\telem.value = val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Hooks for boolean attributes\nboolHook = {\n\tset: function( elem, value, name ) {\n\t\tif ( value === false ) {\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr( elem, name );\n\t\t} else {\n\t\t\telem.setAttribute( name, name );\n\t\t}\n\t\treturn name;\n\t}\n};\njQuery.each( jQuery.expr.match.bool.source.match( /\\w+/g ), function( i, name ) {\n\tvar getter = attrHandle[ name ] || jQuery.find.attr;\n\n\tattrHandle[ name ] = function( elem, name, isXML ) {\n\t\tvar ret, handle;\n\t\tif ( !isXML ) {\n\t\t\t// Avoid an infinite loop by temporarily removing this function from the getter\n\t\t\thandle = attrHandle[ name ];\n\t\t\tattrHandle[ name ] = ret;\n\t\t\tret = getter( elem, name, isXML ) != null ?\n\t\t\t\tname.toLowerCase() :\n\t\t\t\tnull;\n\t\t\tattrHandle[ name ] = handle;\n\t\t}\n\t\treturn ret;\n\t};\n});\n\n\n\n\nvar rfocusable = /^(?:input|select|textarea|button)$/i;\n\njQuery.fn.extend({\n\tprop: function( name, value ) {\n\t\treturn access( this, jQuery.prop, name, value, arguments.length > 1 );\n\t},\n\n\tremoveProp: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tdelete this[ jQuery.propFix[ name ] || name ];\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tpropFix: {\n\t\t\"for\": \"htmlFor\",\n\t\t\"class\": \"className\"\n\t},\n\n\tprop: function( elem, name, value ) {\n\t\tvar ret, hooks, notxml,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set properties on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnotxml = nType !== 1 || !jQuery.isXMLDoc( elem );\n\n\t\tif ( notxml ) {\n\t\t\t// Fix name and attach hooks\n\t\t\tname = jQuery.propFix[ name ] || name;\n\t\t\thooks = jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\treturn hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?\n\t\t\t\tret :\n\t\t\t\t( elem[ name ] = value );\n\n\t\t} else {\n\t\t\treturn hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ?\n\t\t\t\tret :\n\t\t\t\telem[ name ];\n\t\t}\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function( elem ) {\n\t\t\t\treturn elem.hasAttribute( \"tabindex\" ) || rfocusable.test( elem.nodeName ) || elem.href ?\n\t\t\t\t\telem.tabIndex :\n\t\t\t\t\t-1;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Support: IE9+\n// Selectedness for an option in an optgroup can be inaccurate\nif ( !support.optSelected ) {\n\tjQuery.propHooks.selected = {\n\t\tget: function( elem ) {\n\t\t\tvar parent = elem.parentNode;\n\t\t\tif ( parent && parent.parentNode ) {\n\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t};\n}\n\njQuery.each([\n\t\"tabIndex\",\n\t\"readOnly\",\n\t\"maxLength\",\n\t\"cellSpacing\",\n\t\"cellPadding\",\n\t\"rowSpan\",\n\t\"colSpan\",\n\t\"useMap\",\n\t\"frameBorder\",\n\t\"contentEditable\"\n], function() {\n\tjQuery.propFix[ this.toLowerCase() ] = this;\n});\n\n\n\n\nvar rclass = /[\\t\\r\\n\\f]/g;\n\njQuery.fn.extend({\n\taddClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j, finalValue,\n\t\t\tproceed = typeof value === \"string\" && value,\n\t\t\ti = 0,\n\t\t\tlen = this.length;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).addClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\n\t\tif ( proceed ) {\n\t\t\t// The disjunction here is for better compressibility (see removeClass)\n\t\t\tclasses = ( value || \"\" ).match( rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\" \"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\tif ( cur.indexOf( \" \" + clazz + \" \" ) < 0 ) {\n\t\t\t\t\t\t\tcur += clazz + \" \";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = jQuery.trim( cur );\n\t\t\t\t\tif ( elem.className !== finalValue ) {\n\t\t\t\t\t\telem.className = finalValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j, finalValue,\n\t\t\tproceed = arguments.length === 0 || typeof value === \"string\" && value,\n\t\t\ti = 0,\n\t\t\tlen = this.length;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).removeClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\t\tif ( proceed ) {\n\t\t\tclasses = ( value || \"\" ).match( rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\t// This expression is here for better compressibility (see addClass)\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\"\"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\t// Remove *all* instances\n\t\t\t\t\t\twhile ( cur.indexOf( \" \" + clazz + \" \" ) >= 0 ) {\n\t\t\t\t\t\t\tcur = cur.replace( \" \" + clazz + \" \", \" \" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = value ? jQuery.trim( cur ) : \"\";\n\t\t\t\t\tif ( elem.className !== finalValue ) {\n\t\t\t\t\t\telem.className = finalValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function( value, stateVal ) {\n\t\tvar type = typeof value;\n\n\t\tif ( typeof stateVal === \"boolean\" && type === \"string\" ) {\n\t\t\treturn stateVal ? this.addClass( value ) : this.removeClass( value );\n\t\t}\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( type === \"string\" ) {\n\t\t\t\t// toggle individual class names\n\t\t\t\tvar className,\n\t\t\t\t\ti = 0,\n\t\t\t\t\tself = jQuery( this ),\n\t\t\t\t\tclassNames = value.match( rnotwhite ) || [];\n\n\t\t\t\twhile ( (className = classNames[ i++ ]) ) {\n\t\t\t\t\t// check each className given, space separated list\n\t\t\t\t\tif ( self.hasClass( className ) ) {\n\t\t\t\t\t\tself.removeClass( className );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.addClass( className );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Toggle whole class name\n\t\t\t} else if ( type === strundefined || type === \"boolean\" ) {\n\t\t\t\tif ( this.className ) {\n\t\t\t\t\t// store className if set\n\t\t\t\t\tdata_priv.set( this, \"__className__\", this.className );\n\t\t\t\t}\n\n\t\t\t\t// If the element has a class name or if we're passed \"false\",\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\n\t\t\t\t// falling back to the empty string if nothing was stored.\n\t\t\t\tthis.className = this.className || value === false ? \"\" : data_priv.get( this, \"__className__\" ) || \"\";\n\t\t\t}\n\t\t});\n\t},\n\n\thasClass: function( selector ) {\n\t\tvar className = \" \" + selector + \" \",\n\t\t\ti = 0,\n\t\t\tl = this.length;\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tif ( this[i].nodeType === 1 && (\" \" + this[i].className + \" \").replace(rclass, \" \").indexOf( className ) >= 0 ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n});\n\n\n\n\nvar rreturn = /\\r/g;\n\njQuery.fn.extend({\n\tval: function( value ) {\n\t\tvar hooks, ret, isFunction,\n\t\t\telem = this[0];\n\n\t\tif ( !arguments.length ) {\n\t\t\tif ( elem ) {\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, \"value\" )) !== undefined ) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret = elem.value;\n\n\t\t\t\treturn typeof ret === \"string\" ?\n\t\t\t\t\t// handle most common string cases\n\t\t\t\t\tret.replace(rreturn, \"\") :\n\t\t\t\t\t// handle cases where value is null/undef or number\n\t\t\t\t\tret == null ? \"\" : ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tisFunction = jQuery.isFunction( value );\n\n\t\treturn this.each(function( i ) {\n\t\t\tvar val;\n\n\t\t\tif ( this.nodeType !== 1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( isFunction ) {\n\t\t\t\tval = value.call( this, i, jQuery( this ).val() );\n\t\t\t} else {\n\t\t\t\tval = value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\n\t\t\tif ( val == null ) {\n\t\t\t\tval = \"\";\n\n\t\t\t} else if ( typeof val === \"number\" ) {\n\t\t\t\tval += \"\";\n\n\t\t\t} else if ( jQuery.isArray( val ) ) {\n\t\t\t\tval = jQuery.map( val, function( value ) {\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\n\t\t\t\t});\n\t\t\t}\n\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif ( !hooks || !(\"set\" in hooks) || hooks.set( this, val, \"value\" ) === undefined ) {\n\t\t\t\tthis.value = val;\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tvalHooks: {\n\t\tselect: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar value, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tindex = elem.selectedIndex,\n\t\t\t\t\tone = elem.type === \"select-one\" || index < 0,\n\t\t\t\t\tvalues = one ? null : [],\n\t\t\t\t\tmax = one ? index + 1 : options.length,\n\t\t\t\t\ti = index < 0 ?\n\t\t\t\t\t\tmax :\n\t\t\t\t\t\tone ? index : 0;\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor ( ; i < max; i++ ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t// IE6-9 doesn't update selected after form reset (#2551)\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t( support.optDisabled ? !option.disabled : option.getAttribute( \"disabled\" ) === null ) &&\n\t\t\t\t\t\t\t( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, \"optgroup\" ) ) ) {\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue = jQuery( option ).val();\n\n\t\t\t\t\t\t// We don't need an array for one selects\n\t\t\t\t\t\tif ( one ) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push( value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar optionSet, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tvalues = jQuery.makeArray( value ),\n\t\t\t\t\ti = options.length;\n\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\toption = options[ i ];\n\t\t\t\t\tif ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {\n\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// force browsers to behave consistently when non-matching value is set\n\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\telem.selectedIndex = -1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Radios and checkboxes getter/setter\njQuery.each([ \"radio\", \"checkbox\" ], function() {\n\tjQuery.valHooks[ this ] = {\n\t\tset: function( elem, value ) {\n\t\t\tif ( jQuery.isArray( value ) ) {\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );\n\t\t\t}\n\t\t}\n\t};\n\tif ( !support.checkOn ) {\n\t\tjQuery.valHooks[ this ].get = function( elem ) {\n\t\t\t// Support: Webkit\n\t\t\t// \"\" is returned instead of \"on\" if a value isn't specified\n\t\t\treturn elem.getAttribute(\"value\") === null ? \"on\" : elem.value;\n\t\t};\n\t}\n});\n\n\n\n\n// Return jQuery for attributes-only inclusion\n\n\njQuery.each( (\"blur focus focusin focusout load resize scroll unload click dblclick \" +\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n\t\"change select submit keydown keypress keyup error contextmenu\").split(\" \"), function( i, name ) {\n\n\t// Handle event binding\n\tjQuery.fn[ name ] = function( data, fn ) {\n\t\treturn arguments.length > 0 ?\n\t\t\tthis.on( name, null, data, fn ) :\n\t\t\tthis.trigger( name );\n\t};\n});\n\njQuery.fn.extend({\n\thover: function( fnOver, fnOut ) {\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\n\t},\n\n\tbind: function( types, data, fn ) {\n\t\treturn this.on( types, null, data, fn );\n\t},\n\tunbind: function( types, fn ) {\n\t\treturn this.off( types, null, fn );\n\t},\n\n\tdelegate: function( selector, types, data, fn ) {\n\t\treturn this.on( types, selector, data, fn );\n\t},\n\tundelegate: function( selector, types, fn ) {\n\t\t// ( namespace ) or ( selector, types [, fn] )\n\t\treturn arguments.length === 1 ? this.off( selector, \"**\" ) : this.off( types, selector || \"**\", fn );\n\t}\n});\n\n\nvar nonce = jQuery.now();\n\nvar rquery = (/\\?/);\n\n\n\n// Support: Android 2.3\n// Workaround failure to string-cast null input\njQuery.parseJSON = function( data ) {\n\treturn JSON.parse( data + \"\" );\n};\n\n\n// Cross-browser xml parsing\njQuery.parseXML = function( data ) {\n\tvar xml, tmp;\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\n\t// Support: IE9\n\ttry {\n\t\ttmp = new DOMParser();\n\t\txml = tmp.parseFromString( data, \"text/xml\" );\n\t} catch ( e ) {\n\t\txml = undefined;\n\t}\n\n\tif ( !xml || xml.getElementsByTagName( \"parsererror\" ).length ) {\n\t\tjQuery.error( \"Invalid XML: \" + data );\n\t}\n\treturn xml;\n};\n\n\nvar\n\t// Document location\n\tajaxLocParts,\n\tajaxLocation,\n\n\trhash = /#.*$/,\n\trts = /([?&])_=[^&]*/,\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)$/mg,\n\t// #7653, #8125, #8152: local protocol detection\n\trlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n\trnoContent = /^(?:GET|HEAD)$/,\n\trprotocol = /^\\/\\//,\n\trurl = /^([\\w.+-]+:)(?:\\/\\/(?:[^\\/?#]*@|)([^\\/?#:]*)(?::(\\d+)|)|)/,\n\n\t/* Prefilters\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n\t * 2) These are called:\n\t *    - BEFORE asking for a transport\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\n\t * 3) key is the dataType\n\t * 4) the catchall symbol \"*\" can be used\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n\t */\n\tprefilters = {},\n\n\t/* Transports bindings\n\t * 1) key is the dataType\n\t * 2) the catchall symbol \"*\" can be used\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n\t */\n\ttransports = {},\n\n\t// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\n\tallTypes = \"*/\".concat(\"*\");\n\n// #8138, IE may throw an exception when accessing\n// a field from window.location if document.domain has been set\ntry {\n\tajaxLocation = location.href;\n} catch( e ) {\n\t// Use the href attribute of an A element\n\t// since IE will modify it given document.location\n\tajaxLocation = document.createElement( \"a\" );\n\tajaxLocation.href = \"\";\n\tajaxLocation = ajaxLocation.href;\n}\n\n// Segment location into parts\najaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports( structure ) {\n\n\t// dataTypeExpression is optional and defaults to \"*\"\n\treturn function( dataTypeExpression, func ) {\n\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\n\t\t\tfunc = dataTypeExpression;\n\t\t\tdataTypeExpression = \"*\";\n\t\t}\n\n\t\tvar dataType,\n\t\t\ti = 0,\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];\n\n\t\tif ( jQuery.isFunction( func ) ) {\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\twhile ( (dataType = dataTypes[i++]) ) {\n\t\t\t\t// Prepend if requested\n\t\t\t\tif ( dataType[0] === \"+\" ) {\n\t\t\t\t\tdataType = dataType.slice( 1 ) || \"*\";\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).unshift( func );\n\n\t\t\t\t// Otherwise append\n\t\t\t\t} else {\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).push( func );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {\n\n\tvar inspected = {},\n\t\tseekingTransport = ( structure === transports );\n\n\tfunction inspect( dataType ) {\n\t\tvar selected;\n\t\tinspected[ dataType ] = true;\n\t\tjQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {\n\t\t\tvar dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );\n\t\t\tif ( typeof dataTypeOrTransport === \"string\" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {\n\t\t\t\toptions.dataTypes.unshift( dataTypeOrTransport );\n\t\t\t\tinspect( dataTypeOrTransport );\n\t\t\t\treturn false;\n\t\t\t} else if ( seekingTransport ) {\n\t\t\t\treturn !( selected = dataTypeOrTransport );\n\t\t\t}\n\t\t});\n\t\treturn selected;\n\t}\n\n\treturn inspect( options.dataTypes[ 0 ] ) || !inspected[ \"*\" ] && inspect( \"*\" );\n}\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes #9887\nfunction ajaxExtend( target, src ) {\n\tvar key, deep,\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\n\n\tfor ( key in src ) {\n\t\tif ( src[ key ] !== undefined ) {\n\t\t\t( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];\n\t\t}\n\t}\n\tif ( deep ) {\n\t\tjQuery.extend( true, target, deep );\n\t}\n\n\treturn target;\n}\n\n/* Handles responses to an ajax request:\n * - finds the right dataType (mediates between content-type and expected dataType)\n * - returns the corresponding response\n */\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\n\n\tvar ct, type, finalDataType, firstDataType,\n\t\tcontents = s.contents,\n\t\tdataTypes = s.dataTypes;\n\n\t// Remove auto dataType and get content-type in the process\n\twhile ( dataTypes[ 0 ] === \"*\" ) {\n\t\tdataTypes.shift();\n\t\tif ( ct === undefined ) {\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader(\"Content-Type\");\n\t\t}\n\t}\n\n\t// Check if we're dealing with a known content-type\n\tif ( ct ) {\n\t\tfor ( type in contents ) {\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\n\t\t\t\tdataTypes.unshift( type );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif ( dataTypes[ 0 ] in responses ) {\n\t\tfinalDataType = dataTypes[ 0 ];\n\t} else {\n\t\t// Try convertible dataTypes\n\t\tfor ( type in responses ) {\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[0] ] ) {\n\t\t\t\tfinalDataType = type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !firstDataType ) {\n\t\t\t\tfirstDataType = type;\n\t\t\t}\n\t\t}\n\t\t// Or just use first one\n\t\tfinalDataType = finalDataType || firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif ( finalDataType ) {\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\n\t\t\tdataTypes.unshift( finalDataType );\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n/* Chain conversions given the request and the original response\n * Also sets the responseXXX fields on the jqXHR instance\n */\nfunction ajaxConvert( s, response, jqXHR, isSuccess ) {\n\tvar conv2, current, conv, tmp, prev,\n\t\tconverters = {},\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes = s.dataTypes.slice();\n\n\t// Create converters map with lowercased keys\n\tif ( dataTypes[ 1 ] ) {\n\t\tfor ( conv in s.converters ) {\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\n\t\t}\n\t}\n\n\tcurrent = dataTypes.shift();\n\n\t// Convert to each sequential dataType\n\twhile ( current ) {\n\n\t\tif ( s.responseFields[ current ] ) {\n\t\t\tjqXHR[ s.responseFields[ current ] ] = response;\n\t\t}\n\n\t\t// Apply the dataFilter if provided\n\t\tif ( !prev && isSuccess && s.dataFilter ) {\n\t\t\tresponse = s.dataFilter( response, s.dataType );\n\t\t}\n\n\t\tprev = current;\n\t\tcurrent = dataTypes.shift();\n\n\t\tif ( current ) {\n\n\t\t// There's only work to do if current dataType is non-auto\n\t\t\tif ( current === \"*\" ) {\n\n\t\t\t\tcurrent = prev;\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\t} else if ( prev !== \"*\" && prev !== current ) {\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif ( !conv ) {\n\t\t\t\t\tfor ( conv2 in converters ) {\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp = conv2.split( \" \" );\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif ( conv ) {\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif ( conv === true ) {\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.unshift( tmp[ 1 ] );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif ( conv !== true ) {\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif ( conv && s[ \"throws\" ] ) {\n\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\treturn { state: \"parsererror\", error: conv ? e : \"No conversion from \" + prev + \" to \" + current };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { state: \"success\", data: response };\n}\n\njQuery.extend({\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {},\n\n\tajaxSettings: {\n\t\turl: ajaxLocation,\n\t\ttype: \"GET\",\n\t\tisLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),\n\t\tglobal: true,\n\t\tprocessData: true,\n\t\tasync: true,\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t\t/*\n\t\ttimeout: 0,\n\t\tdata: null,\n\t\tdataType: null,\n\t\tusername: null,\n\t\tpassword: null,\n\t\tcache: null,\n\t\tthrows: false,\n\t\ttraditional: false,\n\t\theaders: {},\n\t\t*/\n\n\t\taccepts: {\n\t\t\t\"*\": allTypes,\n\t\t\ttext: \"text/plain\",\n\t\t\thtml: \"text/html\",\n\t\t\txml: \"application/xml, text/xml\",\n\t\t\tjson: \"application/json, text/javascript\"\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /xml/,\n\t\t\thtml: /html/,\n\t\t\tjson: /json/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: \"responseXML\",\n\t\t\ttext: \"responseText\",\n\t\t\tjson: \"responseJSON\"\n\t\t},\n\n\t\t// Data converters\n\t\t// Keys separate source (or catchall \"*\") and destination types with a single space\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t\"* text\": String,\n\n\t\t\t// Text to html (true = no transformation)\n\t\t\t\"text html\": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t\"text json\": jQuery.parseJSON,\n\n\t\t\t// Parse text as xml\n\t\t\t\"text xml\": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn't be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn't be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\turl: true,\n\t\t\tcontext: true\n\t\t}\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function( target, settings ) {\n\t\treturn settings ?\n\n\t\t\t// Building a settings object\n\t\t\tajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :\n\n\t\t\t// Extending ajaxSettings\n\t\t\tajaxExtend( jQuery.ajaxSettings, target );\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\n\n\t// Main method\n\tajax: function( url, options ) {\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif ( typeof url === \"object\" ) {\n\t\t\toptions = url;\n\t\t\turl = undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions = options || {};\n\n\t\tvar transport,\n\t\t\t// URL without anti-cache param\n\t\t\tcacheURL,\n\t\t\t// Response headers\n\t\t\tresponseHeadersString,\n\t\t\tresponseHeaders,\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\t\t\t// Cross-domain detection vars\n\t\t\tparts,\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\t\t\t// Loop variable\n\t\t\ti,\n\t\t\t// Create the final options object\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\n\t\t\t// Callbacks context\n\t\t\tcallbackContext = s.context || s,\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\n\t\t\tglobalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?\n\t\t\t\tjQuery( callbackContext ) :\n\t\t\t\tjQuery.event,\n\t\t\t// Deferreds\n\t\t\tdeferred = jQuery.Deferred(),\n\t\t\tcompleteDeferred = jQuery.Callbacks(\"once memory\"),\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode = s.statusCode || {},\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders = {},\n\t\t\trequestHeadersNames = {},\n\t\t\t// The jqXHR state\n\t\t\tstate = 0,\n\t\t\t// Default abort message\n\t\t\tstrAbort = \"canceled\",\n\t\t\t// Fake xhr\n\t\t\tjqXHR = {\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function( key ) {\n\t\t\t\t\tvar match;\n\t\t\t\t\tif ( state === 2 ) {\n\t\t\t\t\t\tif ( !responseHeaders ) {\n\t\t\t\t\t\t\tresponseHeaders = {};\n\t\t\t\t\t\t\twhile ( (match = rheaders.exec( responseHeadersString )) ) {\n\t\t\t\t\t\t\t\tresponseHeaders[ match[1].toLowerCase() ] = match[ 2 ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match == null ? null : match;\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function() {\n\t\t\t\t\treturn state === 2 ? responseHeadersString : null;\n\t\t\t\t},\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function( name, value ) {\n\t\t\t\t\tvar lname = name.toLowerCase();\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\tname = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;\n\t\t\t\t\t\trequestHeaders[ name ] = value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function( type ) {\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\ts.mimeType = type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Status-dependent callbacks\n\t\t\t\tstatusCode: function( map ) {\n\t\t\t\t\tvar code;\n\t\t\t\t\tif ( map ) {\n\t\t\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\t\t\tfor ( code in map ) {\n\t\t\t\t\t\t\t\t// Lazy-add the new callback in a way that preserves old ones\n\t\t\t\t\t\t\t\tstatusCode[ code ] = [ statusCode[ code ], map[ code ] ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\n\t\t\t\t\t\t\tjqXHR.always( map[ jqXHR.status ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function( statusText ) {\n\t\t\t\t\tvar finalText = statusText || strAbort;\n\t\t\t\t\tif ( transport ) {\n\t\t\t\t\t\ttransport.abort( finalText );\n\t\t\t\t\t}\n\t\t\t\t\tdone( 0, finalText );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Attach deferreds\n\t\tdeferred.promise( jqXHR ).complete = completeDeferred.add;\n\t\tjqXHR.success = jqXHR.done;\n\t\tjqXHR.error = jqXHR.fail;\n\n\t\t// Remove hash character (#7531: and string promotion)\n\t\t// Add protocol if not provided (prefilters might expect it)\n\t\t// Handle falsy url in the settings object (#10093: consistency with old signature)\n\t\t// We also use the url parameter if available\n\t\ts.url = ( ( url || s.url || ajaxLocation ) + \"\" ).replace( rhash, \"\" )\n\t\t\t.replace( rprotocol, ajaxLocParts[ 1 ] + \"//\" );\n\n\t\t// Alias method option to type as per ticket #12004\n\t\ts.type = options.method || options.type || s.method || s.type;\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes = jQuery.trim( s.dataType || \"*\" ).toLowerCase().match( rnotwhite ) || [ \"\" ];\n\n\t\t// A cross-domain request is in order when we have a protocol:host:port mismatch\n\t\tif ( s.crossDomain == null ) {\n\t\t\tparts = rurl.exec( s.url.toLowerCase() );\n\t\t\ts.crossDomain = !!( parts &&\n\t\t\t\t( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||\n\t\t\t\t\t( parts[ 3 ] || ( parts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) !==\n\t\t\t\t\t\t( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) )\n\t\t\t);\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif ( state === 2 ) {\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\tfireGlobals = s.global;\n\n\t\t// Watch for a new set of requests\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\n\t\t\tjQuery.event.trigger(\"ajaxStart\");\n\t\t}\n\n\t\t// Uppercase the type\n\t\ts.type = s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test( s.type );\n\n\t\t// Save the URL in case we're toying with the If-Modified-Since\n\t\t// and/or If-None-Match header later on\n\t\tcacheURL = s.url;\n\n\t\t// More options handling for requests with no content\n\t\tif ( !s.hasContent ) {\n\n\t\t\t// If data is available, append data to url\n\t\t\tif ( s.data ) {\n\t\t\t\tcacheURL = ( s.url += ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + s.data );\n\t\t\t\t// #9682: remove data so that it's not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Add anti-cache in url if needed\n\t\t\tif ( s.cache === false ) {\n\t\t\t\ts.url = rts.test( cacheURL ) ?\n\n\t\t\t\t\t// If there is already a '_' parameter, set its value\n\t\t\t\t\tcacheURL.replace( rts, \"$1_=\" + nonce++ ) :\n\n\t\t\t\t\t// Otherwise add one to the end\n\t\t\t\t\tcacheURL + ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + \"_=\" + nonce++;\n\t\t\t}\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif ( s.ifModified ) {\n\t\t\tif ( jQuery.lastModified[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ cacheURL ] );\n\t\t\t}\n\t\t\tif ( jQuery.etag[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ cacheURL ] );\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t\"Accept\",\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n\t\t\t\ts.accepts[ \"*\" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor ( i in s.headers ) {\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {\n\t\t\t// Abort if not done already and return\n\t\t\treturn jqXHR.abort();\n\t\t}\n\n\t\t// aborting is no longer a cancellation\n\t\tstrAbort = \"abort\";\n\n\t\t// Install callbacks on deferreds\n\t\tfor ( i in { success: 1, error: 1, complete: 1 } ) {\n\t\t\tjqXHR[ i ]( s[ i ] );\n\t\t}\n\n\t\t// Get transport\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\n\n\t\t// If no transport, we auto-abort\n\t\tif ( !transport ) {\n\t\t\tdone( -1, \"No Transport\" );\n\t\t} else {\n\t\t\tjqXHR.readyState = 1;\n\n\t\t\t// Send global event\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\n\t\t\t}\n\t\t\t// Timeout\n\t\t\tif ( s.async && s.timeout > 0 ) {\n\t\t\t\ttimeoutTimer = setTimeout(function() {\n\t\t\t\t\tjqXHR.abort(\"timeout\");\n\t\t\t\t}, s.timeout );\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tstate = 1;\n\t\t\t\ttransport.send( requestHeaders, done );\n\t\t\t} catch ( e ) {\n\t\t\t\t// Propagate exception as error if not done\n\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\tdone( -1, e );\n\t\t\t\t// Simply rethrow otherwise\n\t\t\t\t} else {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Callback for when everything is done\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText = nativeStatusText;\n\n\t\t\t// Called once\n\t\t\tif ( state === 2 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// State is \"done\" now\n\t\t\tstate = 2;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif ( timeoutTimer ) {\n\t\t\t\tclearTimeout( timeoutTimer );\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport = undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString = headers || \"\";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\n\n\t\t\t// Determine if successful\n\t\t\tisSuccess = status >= 200 && status < 300 || status === 304;\n\n\t\t\t// Get response data\n\t\t\tif ( responses ) {\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\n\t\t\t}\n\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\n\t\t\tresponse = ajaxConvert( s, response, jqXHR, isSuccess );\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif ( isSuccess ) {\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif ( s.ifModified ) {\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"Last-Modified\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"etag\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.etag[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// if no content\n\t\t\t\tif ( status === 204 || s.type === \"HEAD\" ) {\n\t\t\t\t\tstatusText = \"nocontent\";\n\n\t\t\t\t// if not modified\n\t\t\t\t} else if ( status === 304 ) {\n\t\t\t\t\tstatusText = \"notmodified\";\n\n\t\t\t\t// If we have data, let's convert it\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = response.state;\n\t\t\t\t\tsuccess = response.data;\n\t\t\t\t\terror = response.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// We extract error from statusText\n\t\t\t\t// then normalize statusText and status for non-aborts\n\t\t\t\terror = statusText;\n\t\t\t\tif ( status || !statusText ) {\n\t\t\t\t\tstatusText = \"error\";\n\t\t\t\t\tif ( status < 0 ) {\n\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status = status;\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n\t\t\t// Success/Error\n\t\t\tif ( isSuccess ) {\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\n\t\t\t} else {\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode( statusCode );\n\t\t\tstatusCode = undefined;\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif ( !( --jQuery.active ) ) {\n\t\t\t\t\tjQuery.event.trigger(\"ajaxStop\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\tgetJSON: function( url, data, callback ) {\n\t\treturn jQuery.get( url, data, callback, \"json\" );\n\t},\n\n\tgetScript: function( url, callback ) {\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\n\t}\n});\n\njQuery.each( [ \"get\", \"post\" ], function( i, method ) {\n\tjQuery[ method ] = function( url, data, callback, type ) {\n\t\t// shift arguments if data argument was omitted\n\t\tif ( jQuery.isFunction( data ) ) {\n\t\t\ttype = type || callback;\n\t\t\tcallback = data;\n\t\t\tdata = undefined;\n\t\t}\n\n\t\treturn jQuery.ajax({\n\t\t\turl: url,\n\t\t\ttype: method,\n\t\t\tdataType: type,\n\t\t\tdata: data,\n\t\t\tsuccess: callback\n\t\t});\n\t};\n});\n\n// Attach a bunch of functions for handling common AJAX events\njQuery.each( [ \"ajaxStart\", \"ajaxStop\", \"ajaxComplete\", \"ajaxError\", \"ajaxSuccess\", \"ajaxSend\" ], function( i, type ) {\n\tjQuery.fn[ type ] = function( fn ) {\n\t\treturn this.on( type, fn );\n\t};\n});\n\n\njQuery._evalUrl = function( url ) {\n\treturn jQuery.ajax({\n\t\turl: url,\n\t\ttype: \"GET\",\n\t\tdataType: \"script\",\n\t\tasync: false,\n\t\tglobal: false,\n\t\t\"throws\": true\n\t});\n};\n\n\njQuery.fn.extend({\n\twrapAll: function( html ) {\n\t\tvar wrap;\n\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).wrapAll( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\tif ( this[ 0 ] ) {\n\n\t\t\t// The elements to wrap the target around\n\t\t\twrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );\n\n\t\t\tif ( this[ 0 ].parentNode ) {\n\t\t\t\twrap.insertBefore( this[ 0 ] );\n\t\t\t}\n\n\t\t\twrap.map(function() {\n\t\t\t\tvar elem = this;\n\n\t\t\t\twhile ( elem.firstElementChild ) {\n\t\t\t\t\telem = elem.firstElementChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t}).append( this );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).wrapInner( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar self = jQuery( this ),\n\t\t\t\tcontents = self.contents();\n\n\t\t\tif ( contents.length ) {\n\t\t\t\tcontents.wrapAll( html );\n\n\t\t\t} else {\n\t\t\t\tself.append( html );\n\t\t\t}\n\t\t});\n\t},\n\n\twrap: function( html ) {\n\t\tvar isFunction = jQuery.isFunction( html );\n\n\t\treturn this.each(function( i ) {\n\t\t\tjQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );\n\t\t});\n\t},\n\n\tunwrap: function() {\n\t\treturn this.parent().each(function() {\n\t\t\tif ( !jQuery.nodeName( this, \"body\" ) ) {\n\t\t\t\tjQuery( this ).replaceWith( this.childNodes );\n\t\t\t}\n\t\t}).end();\n\t}\n});\n\n\njQuery.expr.filters.hidden = function( elem ) {\n\t// Support: Opera <= 12.12\n\t// Opera reports offsetWidths and offsetHeights less than zero on some elements\n\treturn elem.offsetWidth <= 0 && elem.offsetHeight <= 0;\n};\njQuery.expr.filters.visible = function( elem ) {\n\treturn !jQuery.expr.filters.hidden( elem );\n};\n\n\n\n\nvar r20 = /%20/g,\n\trbracket = /\\[\\]$/,\n\trCRLF = /\\r?\\n/g,\n\trsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\n\trsubmittable = /^(?:input|select|textarea|keygen)/i;\n\nfunction buildParams( prefix, obj, traditional, add ) {\n\tvar name;\n\n\tif ( jQuery.isArray( obj ) ) {\n\t\t// Serialize array item.\n\t\tjQuery.each( obj, function( i, v ) {\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd( prefix, v );\n\n\t\t\t} else {\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\n\t\t\t\tbuildParams( prefix + \"[\" + ( typeof v === \"object\" ? i : \"\" ) + \"]\", v, traditional, add );\n\t\t\t}\n\t\t});\n\n\t} else if ( !traditional && jQuery.type( obj ) === \"object\" ) {\n\t\t// Serialize object item.\n\t\tfor ( name in obj ) {\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\n\t\t}\n\n\t} else {\n\t\t// Serialize scalar item.\n\t\tadd( prefix, obj );\n\t}\n}\n\n// Serialize an array of form elements or a set of\n// key/values into a query string\njQuery.param = function( a, traditional ) {\n\tvar prefix,\n\t\ts = [],\n\t\tadd = function( key, value ) {\n\t\t\t// If value is a function, invoke it and return its value\n\t\t\tvalue = jQuery.isFunction( value ) ? value() : ( value == null ? \"\" : value );\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" + encodeURIComponent( value );\n\t\t};\n\n\t// Set traditional to true for jQuery <= 1.3.2 behavior.\n\tif ( traditional === undefined ) {\n\t\ttraditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\n\t\t// Serialize the form elements\n\t\tjQuery.each( a, function() {\n\t\t\tadd( this.name, this.value );\n\t\t});\n\n\t} else {\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor ( prefix in a ) {\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join( \"&\" ).replace( r20, \"+\" );\n};\n\njQuery.fn.extend({\n\tserialize: function() {\n\t\treturn jQuery.param( this.serializeArray() );\n\t},\n\tserializeArray: function() {\n\t\treturn this.map(function() {\n\t\t\t// Can add propHook for \"elements\" to filter or add form elements\n\t\t\tvar elements = jQuery.prop( this, \"elements\" );\n\t\t\treturn elements ? jQuery.makeArray( elements ) : this;\n\t\t})\n\t\t.filter(function() {\n\t\t\tvar type = this.type;\n\n\t\t\t// Use .is( \":disabled\" ) so that fieldset[disabled] works\n\t\t\treturn this.name && !jQuery( this ).is( \":disabled\" ) &&\n\t\t\t\trsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&\n\t\t\t\t( this.checked || !rcheckableType.test( type ) );\n\t\t})\n\t\t.map(function( i, elem ) {\n\t\t\tvar val = jQuery( this ).val();\n\n\t\t\treturn val == null ?\n\t\t\t\tnull :\n\t\t\t\tjQuery.isArray( val ) ?\n\t\t\t\t\tjQuery.map( val, function( val ) {\n\t\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t\t\t\t}) :\n\t\t\t\t\t{ name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t}).get();\n\t}\n});\n\n\njQuery.ajaxSettings.xhr = function() {\n\ttry {\n\t\treturn new XMLHttpRequest();\n\t} catch( e ) {}\n};\n\nvar xhrId = 0,\n\txhrCallbacks = {},\n\txhrSuccessStatus = {\n\t\t// file protocol always yields status code 0, assume 200\n\t\t0: 200,\n\t\t// Support: IE9\n\t\t// #1450: sometimes IE returns 1223 when it should be 204\n\t\t1223: 204\n\t},\n\txhrSupported = jQuery.ajaxSettings.xhr();\n\n// Support: IE9\n// Open requests must be manually aborted on unload (#5280)\nif ( window.ActiveXObject ) {\n\tjQuery( window ).on( \"unload\", function() {\n\t\tfor ( var key in xhrCallbacks ) {\n\t\t\txhrCallbacks[ key ]();\n\t\t}\n\t});\n}\n\nsupport.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\nsupport.ajax = xhrSupported = !!xhrSupported;\n\njQuery.ajaxTransport(function( options ) {\n\tvar callback;\n\n\t// Cross domain only allowed if supported through XMLHttpRequest\n\tif ( support.cors || xhrSupported && !options.crossDomain ) {\n\t\treturn {\n\t\t\tsend: function( headers, complete ) {\n\t\t\t\tvar i,\n\t\t\t\t\txhr = options.xhr(),\n\t\t\t\t\tid = ++xhrId;\n\n\t\t\t\txhr.open( options.type, options.url, options.async, options.username, options.password );\n\n\t\t\t\t// Apply custom fields if provided\n\t\t\t\tif ( options.xhrFields ) {\n\t\t\t\t\tfor ( i in options.xhrFields ) {\n\t\t\t\t\t\txhr[ i ] = options.xhrFields[ i ];\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Override mime type if needed\n\t\t\t\tif ( options.mimeType && xhr.overrideMimeType ) {\n\t\t\t\t\txhr.overrideMimeType( options.mimeType );\n\t\t\t\t}\n\n\t\t\t\t// X-Requested-With header\n\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t// For same-domain requests, won't change header if already provided.\n\t\t\t\tif ( !options.crossDomain && !headers[\"X-Requested-With\"] ) {\n\t\t\t\t\theaders[\"X-Requested-With\"] = \"XMLHttpRequest\";\n\t\t\t\t}\n\n\t\t\t\t// Set headers\n\t\t\t\tfor ( i in headers ) {\n\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\n\t\t\t\t}\n\n\t\t\t\t// Callback\n\t\t\t\tcallback = function( type ) {\n\t\t\t\t\treturn function() {\n\t\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\t\tdelete xhrCallbacks[ id ];\n\t\t\t\t\t\t\tcallback = xhr.onload = xhr.onerror = null;\n\n\t\t\t\t\t\t\tif ( type === \"abort\" ) {\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t} else if ( type === \"error\" ) {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\t// file: protocol always yields status 0; see #8605, #14207\n\t\t\t\t\t\t\t\t\txhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\txhrSuccessStatus[ xhr.status ] || xhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText,\n\t\t\t\t\t\t\t\t\t// Support: IE9\n\t\t\t\t\t\t\t\t\t// Accessing binary-data responseText throws an exception\n\t\t\t\t\t\t\t\t\t// (#11426)\n\t\t\t\t\t\t\t\t\ttypeof xhr.responseText === \"string\" ? {\n\t\t\t\t\t\t\t\t\t\ttext: xhr.responseText\n\t\t\t\t\t\t\t\t\t} : undefined,\n\t\t\t\t\t\t\t\t\txhr.getAllResponseHeaders()\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\t// Listen to events\n\t\t\t\txhr.onload = callback();\n\t\t\t\txhr.onerror = callback(\"error\");\n\n\t\t\t\t// Create the abort callback\n\t\t\t\tcallback = xhrCallbacks[ id ] = callback(\"abort\");\n\n\t\t\t\t// Do send the request\n\t\t\t\t// This may raise an exception which is actually\n\t\t\t\t// handled in jQuery.ajax (so no try/catch here)\n\t\t\t\txhr.send( options.hasContent && options.data || null );\n\t\t\t},\n\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\n// Install script dataType\njQuery.ajaxSetup({\n\taccepts: {\n\t\tscript: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\n\t},\n\tcontents: {\n\t\tscript: /(?:java|ecma)script/\n\t},\n\tconverters: {\n\t\t\"text script\": function( text ) {\n\t\t\tjQuery.globalEval( text );\n\t\t\treturn text;\n\t\t}\n\t}\n});\n\n// Handle cache's special case and crossDomain\njQuery.ajaxPrefilter( \"script\", function( s ) {\n\tif ( s.cache === undefined ) {\n\t\ts.cache = false;\n\t}\n\tif ( s.crossDomain ) {\n\t\ts.type = \"GET\";\n\t}\n});\n\n// Bind script tag hack transport\njQuery.ajaxTransport( \"script\", function( s ) {\n\t// This transport only deals with cross domain requests\n\tif ( s.crossDomain ) {\n\t\tvar script, callback;\n\t\treturn {\n\t\t\tsend: function( _, complete ) {\n\t\t\t\tscript = jQuery(\"<script>\").prop({\n\t\t\t\t\tasync: true,\n\t\t\t\t\tcharset: s.scriptCharset,\n\t\t\t\t\tsrc: s.url\n\t\t\t\t}).on(\n\t\t\t\t\t\"load error\",\n\t\t\t\t\tcallback = function( evt ) {\n\t\t\t\t\t\tscript.remove();\n\t\t\t\t\t\tcallback = null;\n\t\t\t\t\t\tif ( evt ) {\n\t\t\t\t\t\t\tcomplete( evt.type === \"error\" ? 404 : 200, evt.type );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tdocument.head.appendChild( script[ 0 ] );\n\t\t\t},\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\nvar oldCallbacks = [],\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\njQuery.ajaxSetup({\n\tjsonp: \"callback\",\n\tjsonpCallback: function() {\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( nonce++ ) );\n\t\tthis[ callback ] = true;\n\t\treturn callback;\n\t}\n});\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tjsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?\n\t\t\t\"url\" :\n\t\t\ttypeof s.data === \"string\" && !( s.contentType || \"\" ).indexOf(\"application/x-www-form-urlencoded\") && rjsonp.test( s.data ) && \"data\"\n\t\t);\n\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n\tif ( jsonProp || s.dataTypes[ 0 ] === \"jsonp\" ) {\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\n\t\t// Insert callback into url or form data\n\t\tif ( jsonProp ) {\n\t\t\ts[ jsonProp ] = s[ jsonProp ].replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( s.jsonp !== false ) {\n\t\t\ts.url += ( rquery.test( s.url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[\"script json\"] = function() {\n\t\t\tif ( !responseContainer ) {\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// force json dataType\n\t\ts.dataTypes[ 0 ] = \"json\";\n\n\t\t// Install callback\n\t\toverwritten = window[ callbackName ];\n\t\twindow[ callbackName ] = function() {\n\t\t\tresponseContainer = arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always(function() {\n\t\t\t// Restore preexisting value\n\t\t\twindow[ callbackName ] = overwritten;\n\n\t\t\t// Save back as free\n\t\t\tif ( s[ callbackName ] ) {\n\t\t\t\t// make sure that re-using the options doesn't screw things around\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\n\n\t\t\t\t// save the callback name for future use\n\t\t\t\toldCallbacks.push( callbackName );\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif ( responseContainer && jQuery.isFunction( overwritten ) ) {\n\t\t\t\toverwritten( responseContainer[ 0 ] );\n\t\t\t}\n\n\t\t\tresponseContainer = overwritten = undefined;\n\t\t});\n\n\t\t// Delegate to script\n\t\treturn \"script\";\n\t}\n});\n\n\n\n\n// data: string of html\n// context (optional): If specified, the fragment will be created in this context, defaults to document\n// keepScripts (optional): If true, will include scripts passed in the html string\njQuery.parseHTML = function( data, context, keepScripts ) {\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\tif ( typeof context === \"boolean\" ) {\n\t\tkeepScripts = context;\n\t\tcontext = false;\n\t}\n\tcontext = context || document;\n\n\tvar parsed = rsingleTag.exec( data ),\n\t\tscripts = !keepScripts && [];\n\n\t// Single tag\n\tif ( parsed ) {\n\t\treturn [ context.createElement( parsed[1] ) ];\n\t}\n\n\tparsed = jQuery.buildFragment( [ data ], context, scripts );\n\n\tif ( scripts && scripts.length ) {\n\t\tjQuery( scripts ).remove();\n\t}\n\n\treturn jQuery.merge( [], parsed.childNodes );\n};\n\n\n// Keep a copy of the old load method\nvar _load = jQuery.fn.load;\n\n/**\n * Load a url into a page\n */\njQuery.fn.load = function( url, params, callback ) {\n\tif ( typeof url !== \"string\" && _load ) {\n\t\treturn _load.apply( this, arguments );\n\t}\n\n\tvar selector, type, response,\n\t\tself = this,\n\t\toff = url.indexOf(\" \");\n\n\tif ( off >= 0 ) {\n\t\tselector = url.slice( off );\n\t\turl = url.slice( 0, off );\n\t}\n\n\t// If it's a function\n\tif ( jQuery.isFunction( params ) ) {\n\n\t\t// We assume that it's the callback\n\t\tcallback = params;\n\t\tparams = undefined;\n\n\t// Otherwise, build a param string\n\t} else if ( params && typeof params === \"object\" ) {\n\t\ttype = \"POST\";\n\t}\n\n\t// If we have elements to modify, make the request\n\tif ( self.length > 0 ) {\n\t\tjQuery.ajax({\n\t\t\turl: url,\n\n\t\t\t// if \"type\" variable is undefined, then \"GET\" method will be used\n\t\t\ttype: type,\n\t\t\tdataType: \"html\",\n\t\t\tdata: params\n\t\t}).done(function( responseText ) {\n\n\t\t\t// Save response for use in complete callback\n\t\t\tresponse = arguments;\n\n\t\t\tself.html( selector ?\n\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\n\t\t\t\t// Exclude scripts to avoid IE 'Permission Denied' errors\n\t\t\t\tjQuery(\"<div>\").append( jQuery.parseHTML( responseText ) ).find( selector ) :\n\n\t\t\t\t// Otherwise use the full result\n\t\t\t\tresponseText );\n\n\t\t}).complete( callback && function( jqXHR, status ) {\n\t\t\tself.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );\n\t\t});\n\t}\n\n\treturn this;\n};\n\n\n\n\njQuery.expr.filters.animated = function( elem ) {\n\treturn jQuery.grep(jQuery.timers, function( fn ) {\n\t\treturn elem === fn.elem;\n\t}).length;\n};\n\n\n\n\nvar docElem = window.document.documentElement;\n\n/**\n * Gets a window from an element\n */\nfunction getWindow( elem ) {\n\treturn jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;\n}\n\njQuery.offset = {\n\tsetOffset: function( elem, options, i ) {\n\t\tvar curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,\n\t\t\tposition = jQuery.css( elem, \"position\" ),\n\t\t\tcurElem = jQuery( elem ),\n\t\t\tprops = {};\n\n\t\t// Set position first, in-case top/left are set even on static elem\n\t\tif ( position === \"static\" ) {\n\t\t\telem.style.position = \"relative\";\n\t\t}\n\n\t\tcurOffset = curElem.offset();\n\t\tcurCSSTop = jQuery.css( elem, \"top\" );\n\t\tcurCSSLeft = jQuery.css( elem, \"left\" );\n\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) &&\n\t\t\t( curCSSTop + curCSSLeft ).indexOf(\"auto\") > -1;\n\n\t\t// Need to be able to calculate position if either top or left is auto and position is either absolute or fixed\n\t\tif ( calculatePosition ) {\n\t\t\tcurPosition = curElem.position();\n\t\t\tcurTop = curPosition.top;\n\t\t\tcurLeft = curPosition.left;\n\n\t\t} else {\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\n\t\t}\n\n\t\tif ( jQuery.isFunction( options ) ) {\n\t\t\toptions = options.call( elem, i, curOffset );\n\t\t}\n\n\t\tif ( options.top != null ) {\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\n\t\t}\n\t\tif ( options.left != null ) {\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\n\t\t}\n\n\t\tif ( \"using\" in options ) {\n\t\t\toptions.using.call( elem, props );\n\n\t\t} else {\n\t\t\tcurElem.css( props );\n\t\t}\n\t}\n};\n\njQuery.fn.extend({\n\toffset: function( options ) {\n\t\tif ( arguments.length ) {\n\t\t\treturn options === undefined ?\n\t\t\t\tthis :\n\t\t\t\tthis.each(function( i ) {\n\t\t\t\t\tjQuery.offset.setOffset( this, options, i );\n\t\t\t\t});\n\t\t}\n\n\t\tvar docElem, win,\n\t\t\telem = this[ 0 ],\n\t\t\tbox = { top: 0, left: 0 },\n\t\t\tdoc = elem && elem.ownerDocument;\n\n\t\tif ( !doc ) {\n\t\t\treturn;\n\t\t}\n\n\t\tdocElem = doc.documentElement;\n\n\t\t// Make sure it's not a disconnected DOM node\n\t\tif ( !jQuery.contains( docElem, elem ) ) {\n\t\t\treturn box;\n\t\t}\n\n\t\t// If we don't have gBCR, just use 0,0 rather than error\n\t\t// BlackBerry 5, iOS 3 (original iPhone)\n\t\tif ( typeof elem.getBoundingClientRect !== strundefined ) {\n\t\t\tbox = elem.getBoundingClientRect();\n\t\t}\n\t\twin = getWindow( doc );\n\t\treturn {\n\t\t\ttop: box.top + win.pageYOffset - docElem.clientTop,\n\t\t\tleft: box.left + win.pageXOffset - docElem.clientLeft\n\t\t};\n\t},\n\n\tposition: function() {\n\t\tif ( !this[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar offsetParent, offset,\n\t\t\telem = this[ 0 ],\n\t\t\tparentOffset = { top: 0, left: 0 };\n\n\t\t// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent\n\t\tif ( jQuery.css( elem, \"position\" ) === \"fixed\" ) {\n\t\t\t// We assume that getBoundingClientRect is available when computed position is fixed\n\t\t\toffset = elem.getBoundingClientRect();\n\n\t\t} else {\n\t\t\t// Get *real* offsetParent\n\t\t\toffsetParent = this.offsetParent();\n\n\t\t\t// Get correct offsets\n\t\t\toffset = this.offset();\n\t\t\tif ( !jQuery.nodeName( offsetParent[ 0 ], \"html\" ) ) {\n\t\t\t\tparentOffset = offsetParent.offset();\n\t\t\t}\n\n\t\t\t// Add offsetParent borders\n\t\t\tparentOffset.top += jQuery.css( offsetParent[ 0 ], \"borderTopWidth\", true );\n\t\t\tparentOffset.left += jQuery.css( offsetParent[ 0 ], \"borderLeftWidth\", true );\n\t\t}\n\n\t\t// Subtract parent offsets and element margins\n\t\treturn {\n\t\t\ttop: offset.top - parentOffset.top - jQuery.css( elem, \"marginTop\", true ),\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css( elem, \"marginLeft\", true )\n\t\t};\n\t},\n\n\toffsetParent: function() {\n\t\treturn this.map(function() {\n\t\t\tvar offsetParent = this.offsetParent || docElem;\n\n\t\t\twhile ( offsetParent && ( !jQuery.nodeName( offsetParent, \"html\" ) && jQuery.css( offsetParent, \"position\" ) === \"static\" ) ) {\n\t\t\t\toffsetParent = offsetParent.offsetParent;\n\t\t\t}\n\n\t\t\treturn offsetParent || docElem;\n\t\t});\n\t}\n});\n\n// Create scrollLeft and scrollTop methods\njQuery.each( { scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\" }, function( method, prop ) {\n\tvar top = \"pageYOffset\" === prop;\n\n\tjQuery.fn[ method ] = function( val ) {\n\t\treturn access( this, function( elem, method, val ) {\n\t\t\tvar win = getWindow( elem );\n\n\t\t\tif ( val === undefined ) {\n\t\t\t\treturn win ? win[ prop ] : elem[ method ];\n\t\t\t}\n\n\t\t\tif ( win ) {\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val : window.pageXOffset,\n\t\t\t\t\ttop ? val : window.pageYOffset\n\t\t\t\t);\n\n\t\t\t} else {\n\t\t\t\telem[ method ] = val;\n\t\t\t}\n\t\t}, method, val, arguments.length, null );\n\t};\n});\n\n// Add the top/left cssHooks using jQuery.fn.position\n// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n// getComputedStyle returns percent when specified for top/left/bottom/right\n// rather than make the css module depend on the offset module, we just check for it here\njQuery.each( [ \"top\", \"left\" ], function( i, prop ) {\n\tjQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,\n\t\tfunction( elem, computed ) {\n\t\t\tif ( computed ) {\n\t\t\t\tcomputed = curCSS( elem, prop );\n\t\t\t\t// if curCSS returns percentage, fallback to offset\n\t\t\t\treturn rnumnonpx.test( computed ) ?\n\t\t\t\t\tjQuery( elem ).position()[ prop ] + \"px\" :\n\t\t\t\t\tcomputed;\n\t\t\t}\n\t\t}\n\t);\n});\n\n\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\n\tjQuery.each( { padding: \"inner\" + name, content: type, \"\": \"outer\" + name }, function( defaultExtra, funcName ) {\n\t\t// margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n\t\t\treturn access( this, function( elem, type, value ) {\n\t\t\t\tvar doc;\n\n\t\t\t\tif ( jQuery.isWindow( elem ) ) {\n\t\t\t\t\t// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there\n\t\t\t\t\t// isn't a whole lot we can do. See pull request at this URL for discussion:\n\t\t\t\t\t// https://github.com/jquery/jquery/pull/764\n\t\t\t\t\treturn elem.document.documentElement[ \"client\" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif ( elem.nodeType === 9 ) {\n\t\t\t\t\tdoc = elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],\n\t\t\t\t\t// whichever is greatest\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n\t\t\t\t\t\tdoc[ \"client\" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value === undefined ?\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css( elem, type, extra ) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\n\t\t\t}, type, chainable ? margin : undefined, chainable, null );\n\t\t};\n\t});\n});\n\n\n// The number of elements contained in the matched element set\njQuery.fn.size = function() {\n\treturn this.length;\n};\n\njQuery.fn.andSelf = jQuery.fn.addBack;\n\n\n\n\n// Register as a named AMD module, since jQuery can be concatenated with other\n// files that may use define, but not via a proper concatenation script that\n// understands anonymous AMD modules. A named AMD is safest and most robust\n// way to register. Lowercase jquery is used because AMD module names are\n// derived from file names, and jQuery is normally delivered in a lowercase\n// file name. Do this after creating the global so that if an AMD module wants\n// to call noConflict to hide this version of jQuery, it will work.\nif ( typeof define === \"function\" && define.amd ) {\n\tdefine( \"jquery\", [], function() {\n\t\treturn jQuery;\n\t});\n}\n\n\n\n\nvar\n\t// Map over jQuery in case of overwrite\n\t_jQuery = window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$ = window.$;\n\njQuery.noConflict = function( deep ) {\n\tif ( window.$ === jQuery ) {\n\t\twindow.$ = _$;\n\t}\n\n\tif ( deep && window.jQuery === jQuery ) {\n\t\twindow.jQuery = _jQuery;\n\t}\n\n\treturn jQuery;\n};\n\n// Expose jQuery and $ identifiers, even in\n// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)\n// and CommonJS for browser emulators (#13566)\nif ( typeof noGlobal === strundefined ) {\n\twindow.jQuery = window.$ = jQuery;\n}\n\n\n\n\nreturn jQuery;\n\n}));\n"
  },
  {
    "path": "samples/remote-sync/websocket/WebSocketSyncProtocol.js",
    "content": "﻿/*  WebSocketSyncProtocol\n\n    An implementation of ISyncProtocol that use a WebSocket to interact with a remote server and syncronize continously and immediately when\n    any change occur on any side.\n\n    This sample is compatible with the corresponing WebSocketSyncServer.js - a node.js server that syncs with RAM memory only, but does it in a waterproof\n    way and handles conflicts correctly. WebSocketSyncServer.js can be used as a template for implementing a sync server against a real database engine (NOSQL\n    or SQL) such as mysql, mongodb, etc.\n*/\n\n(function () {\n\n    // Constants:\n    var RECONNECT_DELAY = 5000; // Reconnect delay in case of errors such as network down.\n\n    Dexie.Syncable.registerSyncProtocol (\"websocket\", {\n\n        sync: function (context, url, options, baseRevision, syncedRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError) {\n\n\n            // The following vars are needed because we must know which callback to ack when server sends it's ack to us.\n            var requestId = 0;\n            var acceptCallbacks = {};\n\n            // Connect the WebSocket to given url:\n            var ws = new WebSocket(url);\n\n            // sendChanges() method:\n            function sendChanges(changes, baseRevision, partial, onChangesAccepted) {\n                ++requestId;\n                acceptCallbacks[requestId.toString()] = onChangesAccepted;\n\n                // In this example, the server expects the following JSON format of the request:\n                //  {\n                //      type: \"changes\"\n                //      baseRevision: baseRevision,\n                //      changes: changes,\n                //      partial: partial,\n                //      requestId: id\n                //  }\n                //  To make the sample simplified, we assume the server has the exact same specification of how changes are structured.\n                //  In real world, you would have to pre-process the changes array to fit the server specification.\n                //  However, this example shows how to deal with the WebSocket to fullfill the API.\n\n                ws.send(JSON.stringify({\n                    type: 'changes',\n                    changes: changes,\n                    partial: partial,\n                    baseRevision: baseRevision,\n                    requestId: requestId\n                }));\n            }\n\n            // When WebSocket opens, send our changes to the server.\n            ws.onopen = function (event) {\n                // Initiate this socket connection by sending our clientIdentity. If we dont have a clientIdentity yet,\n                // server will call back with a new client identity that we should use in future WebSocket connections.\n                ws.send(JSON.stringify({\n                    type: \"clientIdentity\",\n                    clientIdentity: context.clientIdentity || null\n                }));\n\n                // Send our changes:\n                sendChanges(changes, baseRevision, partial, onChangesAccepted);\n\n                // Subscribe to server changes:\n                ws.send(JSON.stringify({\n                    type: \"subscribe\",\n                    syncedRevision: syncedRevision\n                }));\n            }\n\n            // If network down or other error, tell the framework to reconnect again in some time:\n            ws.onerror = function (event) {\n                ws.close();\n                onError(event.message, RECONNECT_DELAY);\n            }\n\n            // If socket is closed (network disconnected), inform framework and make it reconnect\n            ws.onclose = function (event) {\n                onError(\"Socket closed: \" + event.reason, RECONNECT_DELAY);\n            }\n\n\n            // isFirstRound: Will need to call onSuccess() only when we are in sync the first time.\n            // onSuccess() will unblock Dexie to be used by application code.\n            // If for example app code writes: db.friends.where('shoeSize').above(40).toArray(callback), the execution of that query\n            // will not run until we have called onSuccess(). This is because we want application code to get results that are as\n            // accurate as possible. Specifically when connected the first time and the entire DB is being synced down to the browser,\n            // it is important that queries starts running first when db is in sync.\n            var isFirstRound = true;\n            // When message arrive from the server, deal with the message accordingly:\n            ws.onmessage = function (event) {\n                try {\n                    // Assume we have a server that should send JSON messages of the following format:\n                    // {\n                    //     type: \"clientIdentity\", \"changes\", \"ack\" or \"error\"\n                    //     clientIdentity: unique value for our database client node to persist in the context. (Only applicable if type=\"clientIdentity\")\n                    //     message: Error message (Only applicable if type=\"error\")\n                    //     requestId: ID of change request that is acked by the server (Only applicable if type=\"ack\" or \"error\")\n                    //     changes: changes from server (Only applicable if type=\"changes\")\n                    //     lastRevision: last revision of changes sent (applicable if type=\"changes\")\n                    //     partial: true if server has additionalChanges to send. False if these changes were the last known. (applicable if type=\"changes\")\n                    // }\n                    var requestFromServer = JSON.parse(event.data);\n                    if (requestFromServer.type == \"changes\") {\n                        applyRemoteChanges(requestFromServer.changes, requestFromServer.currentRevision, requestFromServer.partial);\n                        if (isFirstRound && !requestFromServer.partial) {\n                            // Since this is the first sync round and server sais we've got all changes - now is the time to call onsuccess()\n                            onSuccess({\n                                // Specify a react function that will react on additional client changes\n                                react: function (changes, baseRevision, partial, onChangesAccepted) {\n                                    sendChanges(changes, baseRevision, partial, onChangesAccepted);\n                                },\n                                // Specify a disconnect function that will close our socket so that we dont continue to monitor changes.\n                                disconnect: function () {\n                                    ws.close();\n                                }\n                            });\n                            isFirstRound = false;\n                        }\n                    } else if (requestFromServer.type == \"ack\") {\n                        var requestId = requestFromServer.requestId;\n                        var acceptCallback = acceptCallbacks[requestId.toString()];\n                        acceptCallback(); // Tell framework that server has acknowledged the changes sent.\n                        delete acceptCallbacks[requestId.toString()];\n                    } else if (requestFromServer.type == \"clientIdentity\") {\n                        context.clientIdentity = requestFromServer.clientIdentity;\n                        context.save();\n                    } else if (requestFromServer.type == \"error\") {\n                        var requestId = requestFromServer.requestId;\n                        ws.close();\n                        onError(requestFromServer.message, Infinity); // Don't reconnect - an error in application level means we have done something wrong.\n                    }\n                } catch (e) {\n                    ws.close();\n                    onError(e, Infinity); // Something went crazy. Server sends invalid format or our code is buggy. Dont reconnect - it would continue failing.\n                }\n            }\n        }\n    });\n})();\n"
  },
  {
    "path": "samples/remote-sync/websocket/WebSocketSyncServer.js",
    "content": "﻿/** WebSocketSyncServer.\n\n    WebSocket server that can be used as a template for how to implement a sync server that interchange changes\n    between a Dexie.Syncable client and a database of any kind.\n\n    The code is only dependant on nodejs-websocket. For simplicity reasons, it uses a non-persistent RAM database. It handles conflicts according to\n    the Dexie.Syncable specification; The rules of thumb for conflict handling is that:\n        1. Client- and server state must be exact the same after a sync operation.\n        2. Server changes are applied after client changes - thereby winning over the latter except when client\n           already has deleted an object - then the server update wont affect any object since it doesnt exist\n           on client.\n\n    In this code, the resolveConflicts() function handles changes on the server AS IF the server changes where\n    applied after client changes.\n */\n\nvar ws = require(\"nodejs-websocket\"); // This will work also in browser if \"websocketserver-shim.js\" is included.\n\n// CREATE / UPDATE / DELETE constants:\nvar CREATE = 1,\n    UPDATE = 2,\n    DELETE = 3;\n\nfunction SyncServer(port) {\n    // This sample sync server works against a RAM database - an object of tables + an array of changes to the database\n\n    // ----------------------------------------------------------------------------\n    //\n    //\n    //\n    //                               THE DATABASE\n    //\n    //\n    //\n    // ----------------------------------------------------------------------------\n    var db = {\n        tables: {},  // Tables: Each key is a table and its value is another object where each key is the primary key and value is the record / object that is stored in ram.\n        changes: [], // Special table that records all changes made to the db. In this simple sample, we let it grow infinitly. In real world, we would have had a regular cleanup of old changes.\n        uncommittedChanges: {}, // Map<clientID,Array<change>> Changes where partial=true buffered for being committed later on.\n        revision: 0, // Current revision of the database.\n        subscribers: [], // Subscribers to when database got changes. Used by server connections to be able to push out changes to their clients as they occur.\n\n        create: function (table, key, obj, clientIdentity) {\n            // Create table if it doesnt exist:\n            db.tables[table] = db.tables[table] || {};\n            // Put the obj into to table\n            db.tables[table][key] = obj;\n            // Register the change:\n            db.changes.push({\n                rev: ++db.revision,\n                source: clientIdentity,\n                type: CREATE,\n                table: table,\n                key: key,\n                obj: obj\n            });\n            db.trigger();\n        },\n        update: function (table, key, modifications, clientIdentity) {\n            if (db.tables[table]) {\n                var obj = db.tables[table][key];\n                if (obj) {\n                    applyModifications(obj, modifications);\n                    db.changes.push({\n                        rev: ++db.revision,\n                        source: clientIdentity,\n                        type: UPDATE,\n                        table: table,\n                        key: key,\n                        mods: modifications\n                    });\n                    db.trigger();\n                }\n            }\n        },\n        'delete': function (table, key, clientIdentity) {\n            if (db.tables[table]) {\n                if (db.tables[table][key]) {\n                    delete db.tables[table][key];\n                    db.changes.push({\n                        rev: ++db.revision,\n                        source: clientIdentity,\n                        type: DELETE,\n                        table: table,\n                        key: key,\n                    });\n                    db.trigger();\n                }\n            }\n        },\n        trigger: function () {\n            if (!db.trigger.delayedHandle) {\n                // Delay the trigger so that it's only called once per bunch of changes instead of being called for each single change.\n                db.trigger.delayedHandle = setTimeout(function () {\n                    delete db.trigger.delayedHandle;\n                    db.subscribers.forEach(function (subscriber) {\n                        try { subscriber(); } catch (e) { }\n                    });\n                }, 0);\n            }\n        },\n        subscribe: function (fn) {\n            db.subscribers.push(fn);\n        },\n        unsubscribe: function (fn) {\n            db.subscribers.splice(db.subscribers.indexOf(fn), 1);\n        }\n    };\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n    // ----------------------------------------------------------------------------\n    //\n    //\n    //\n    //                               THE SERVER\n    //\n    //\n    //\n    // ----------------------------------------------------------------------------\n\n    var nextClientIdentity = 1;\n\n    this.start = function () {\n        ws.createServer(function (conn) {\n\n            var syncedRevision = 0; // Used when sending changes to client. Only send changes above syncedRevision since client is already in sync with syncedRevision.\n\n            function sendAnyChanges() {\n                // Get all changes after syncedRevision that was not performed by the client we're talkin' to.\n                var changes = db.changes.filter(function (change) { return change.rev > syncedRevision && change.source !== conn.clientIdentity; });\n                // Compact changes so that multiple changes on same object is merged into a single change.\n                var reducedSet = reduceChanges(changes, conn.clientIdentity);\n                // Convert the reduced set into an array again.\n                var reducedArray = Object.keys(reducedSet).map(function (key) { return reducedSet[key]; });\n                // Notice the current revision of the database. We want to send it to client so it knows what to ask for next time.\n                var currentRevision = db.revision;\n\n                conn.sendText(JSON.stringify({\n                    type: \"changes\",\n                    changes: reducedArray,\n                    currentRevision: currentRevision,\n                    partial: false // Tell client that these are the only changes we are aware of. Since our mem DB is syncronous, we got all changes in one chunk.\n                }));\n\n                syncedRevision = currentRevision; // Make sure we only send revisions coming after this revision next time and not resend the above changes over and over.\n            }\n\n            conn.on(\"text\", function (message) {\n                var request = JSON.parse(message);\n                var type = request.type;\n                if (type == \"clientIdentity\") {\n                    // Client Hello: Client says \"Hello, My name is <clientIdentity>!\" or \"Hello, I'm newborn. Please give me a name!\"\n                    // Client identity is used for the following purpose:\n                    //  * When client sends its changes, register the changes into server database and mark each change with the clientIdentity.\n                    //  * When sending back changes to client, leave out those marked with the client id so that changes aren't echoed back.\n                    // The client should initiate the connection by submitting or requesting a client identity.\n                    // This should be done before sending any changes to us.\n\n                    // Client submits his identity or requests one\n                    if (request.clientIdentity) {\n                        // Client has an identity that we have given earlier\n                        conn.clientIdentity = request.clientIdentity;\n                    } else {\n                        // Client requests an identity. Provide one.\n                        conn.clientIdentity = nextClientIdentity++;\n                        conn.sendText(JSON.stringify({\n                            type: \"clientIdentity\",\n                            clientIdentity: conn.clientIdentity\n                        }));\n                    }\n                } else if (type == \"subscribe\") {\n                    // Client wants to subscribe to server changes happened or happening after given syncedRevision\n                    syncedRevision = request.syncedRevision || 0;\n                    // Send any changes we have currently:\n                    sendAnyChanges();\n                    // Start subscribing for additional changes:\n                    db.subscribe(sendAnyChanges);\n\n                } else if (type == \"changes\") {\n                    // Client sends its changes to us.\n                    var requestId = request.requestId;\n                    try {\n                        if (!request.changes instanceof Array) {\n                            throw \"Property 'changes' must be provided and must be an array\";\n                        }\n                        if (!(\"baseRevision\" in request)) {\n                            throw \"Property 'baseRevision' missing\";\n                        }\n                        // First, if sent change set is partial. \n                        if (request.partial) {\n                            // Don't commit changes just yet. Store it in the partialChanges table so far. (In real db, uncommittedChanges would be its own table with columns: {clientID, type, table, key, obj, mods}).\n                            // Get or create db.uncommittedChanges array for current client\n                            if (db.uncommittedChanges[conn.clientIdentity]) {\n                                // Concat the changes to existing change set:\n                                db.uncommittedChanges[conn.clientIdentity] = db.uncommittedChanges[conn.clientIdentity].concat(request.changes);\n                            } else {\n                                // Create the change set:\n                                db.uncommittedChanges[conn.clientIdentity] = request.changes;\n                            }\n                        } else {\n                            // This request is not partial. Time to commit.\n                            // But first, check if we have previous changes from that client in uncommittedChanges because now is the time to commit them too.\n                            if (db.uncommittedChanges[conn.clientIdentity]) {\n                                request.changes = db.uncommittedChanges[conn.clientIdentity].concat(request.changes);\n                                delete db.uncommittedChanges[conn.clientIdentity];\n                            }\n\n                            // ----------------------------------------------\n                            //\n                            //\n                            //\n                            // HERE COMES THE QUITE IMPORTANT SYNC ALGORITHM!\n                            //\n                            // 1. Reduce all server changes (not client changes) that have occurred after given\n                            //    baseRevision (our changes) to a set (key/value object where key is the combination of table/primaryKey)\n                            // 2. Check all client changes against reduced server\n                            //    changes to detect conflict. Resolve conflicts:\n                            //      If server created an object with same key as client creates, updates or deletes: Always discard client change.\n                            //      If server deleted an object with same key as client creates, updates or deletes: Always discard client change.\n                            //      If server updated an object with same key as client updates: Apply all properties the client updates unless they conflict with server updates\n                            //      If server updated an object with same key as client creates: Apply the client create but apply the server update on top\n                            //      If server updated an object with same key as client deletes: Let client win. Deletes always wins over Updates.\n                            //\n                            // 3. After resolving conflicts, apply client changes into server database.\n                            // 4. Send an ack to the client that we have persisted its changes\n                            //\n                            //\n                            // ----------------------------------------------\n                            var baseRevision = request.baseRevision || 0;\n                            var serverChanges = db.changes.filter(function (change) { return change.rev > baseRevision });\n                            var reducedServerChangeSet = reduceChanges(serverChanges);\n                            var resolved = resolveConflicts(request.changes, reducedServerChangeSet);\n\n                            // Now apply the resolved changes:\n                            resolved.forEach(function (change) {\n                                switch (change.type) {\n                                    case CREATE:\n                                        db.create(change.table, change.key, change.obj, conn.clientIdentity);\n                                        break;\n                                    case UPDATE:\n                                        db.update(change.table, change.key, change.mods, conn.clientIdentity);\n                                        break;\n                                    case DELETE:\n                                        db.delete(change.table, change.key, conn.clientIdentity);\n                                        break;\n                                }\n                            });\n                        }\n\n                        // Now ack client that we have recieved his changes. This should be done no matter if the're buffered into uncommittedChanges\n                        // or if the're actually committed to db.\n                        conn.sendText(JSON.stringify({\n                            type: \"ack\",\n                            requestId: requestId,\n                        }));\n                    } catch (e) {\n                        conn.sendText(JSON.stringify({\n                            type: \"error\",\n                            requestId: requestId,\n                            message: e.toString()\n                        }));\n                        conn.close();\n                    }\n                }\n\n            });\n\n            conn.on(\"close\", function () {\n                // When client disconnects, stop subscribing from db.\n                db.unsubscribe(sendAnyChanges);\n            });\n        }).listen(port);\n    }\n}\n\nfunction reduceChanges(changes) {\n    // Converts an Array of change objects to a set of change objects based on its unique combination of (table \":\" key).\n    // If several changes were applied to the same object, the resulting set will only contain one change for that object.\n    return changes.reduce(function (set, nextChange) {\n        var id = nextChange.table + \":\" + nextChange.key;\n        var prevChange = set[id];\n        if (!prevChange) {\n            // This is the first change on this key. Add it unless it comes from the source that we are working against\n            set[id] = nextChange;\n        } else {\n            // Merge the oldchange with the new change\n            set[id] = (function () {\n                switch (prevChange.type) {\n                    case CREATE:\n                        switch (nextChange.type) {\n                            case CREATE: return nextChange; // Another CREATE replaces previous CREATE.\n                            case UPDATE: return combineCreateAndUpdate(prevChange, nextChange); // Apply nextChange.mods into prevChange.obj\n                            case DELETE: return nextChange;  // Object created and then deleted. If it wasnt for that we MUST handle resent changes, we would skip entire change here. But what if the CREATE was sent earlier, and then CREATE/DELETE at later stage? It would become a ghost object in DB. Therefore, we MUST keep the delete change! If object doesnt exist, it wont harm!\n                        }\n                        break;\n                    case UPDATE:\n                        switch (nextChange.type) {\n                            case CREATE: return nextChange; // Another CREATE replaces previous update.\n                            case UPDATE: return combineUpdateAndUpdate(prevChange, nextChange); // Add the additional modifications to existing modification set.\n                            case DELETE: return nextChange;  // Only send the delete change. What was updated earlier is no longer of interest.\n                        }\n                        break;\n                    case DELETE:\n                        switch (nextChange.type) {\n                            case CREATE: return nextChange; // A resurection occurred. Only create change is of interest.\n                            case UPDATE: return prevChange; // Nothing to do. We cannot update an object that doesnt exist. Leave the delete change there.\n                            case DELETE: return prevChange; // Still a delete change. Leave as is.\n                        }\n                        break;\n                }\n            })();\n        }\n        return set;\n    }, {});\n}\n\nfunction resolveConflicts(clientChanges, serverChangeSet) {\n    var resolved = [];\n    clientChanges.forEach(function (clientChange) {\n        var id = clientChange.table + \":\" + clientChange.key;\n        var serverChange = serverChangeSet[id];\n        if (!serverChange) {\n            // No server change on same object. Totally conflict free!\n            resolved.push(clientChange);\n        } else if (serverChange.type == UPDATE) {\n            // Server change overlaps. Only if server change is not CREATE or DELETE, we should consider merging in the client change.\n            switch (clientChange.type) {\n                case CREATE:\n                    // Server has updated an object with same key as client has recreated. Let the client recreation go through, but also apply server modifications.\n                    applyModifications(clientChange.obj, serverChange.mods); // No need to clone clientChange.obj beofre applying modifications since noone else refers to clientChanges (it was retrieved from the socket connection in current request)\n                    resolved.push(clientChange);\n                    break;\n                case UPDATE:\n                    // Server and client has updated the same obejct. Just remove any overlapping keyPaths and only apply non-conflicting parts.\n                    Object.keys(serverChange.mods).forEach(function (keyPath) {\n                        // Remote this property from the client change\n                        delete clientChange.mods[keyPath];\n                        // Also, remote all changes to nestled objects under this keyPath from the client change:\n                        Object.keys(clientChange.mods).forEach(function (clientKeyPath) {\n                            if (clientKeyPath.indexOf(keyPath + '.') == 0) {\n                                delete clientChange.mods[clientKeyPath];\n                            }\n                        });\n                    });\n                    // Did we delete all keyPaths in the modification set of the clientChange?\n                    if (Object.keys(clientChange.mods).length > 0) {\n                        // No, there were some still there. Let this wing-clipped change be applied:\n                        resolved.push(clientChange);\n                    }\n                    break;\n                case DELETE:\n                    // Delete always win over update. Even client over a server\n                    resolved.push(clientChange);\n                    break;\n            }\n        } // else if serverChange.type is CREATE or DELETE, dont push anything to resolved, because the client change is not of any interest (CREATE or DELETE would eliminate any client change with same key!)\n    });\n    return resolved;\n}\n\nfunction deepClone(obj) {\n    return JSON.parse(JSON.stringify(obj));\n}\n\nfunction applyModifications(obj, modifications) {\n    Object.keys(modifications).forEach(function (keyPath) {\n        setByKeyPath(obj, keyPath, modifications[keyPath]);\n    });\n    return obj;\n}\n\nfunction combineCreateAndUpdate(prevChange, nextChange) {\n    var clonedChange = deepClone(prevChange);// Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.\n    applyModifications(clonedChange.obj, nextChange.mods); // Apply modifications to existing object.\n    return clonedChange;\n}\n\nfunction combineUpdateAndUpdate(prevChange, nextChange) {\n    var clonedChange = deepClone(prevChange); // Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.\n    Object.keys(nextChange.mods).forEach(function (keyPath) {\n        // If prev-change was changing a parent path of this keyPath, we must update the parent path rather than adding this keyPath\n        var hadParentPath = false;\n        Object.keys(prevChange.mods).filter(function (parentPath) { return keyPath.indexOf(parentPath + '.') === 0 }).forEach(function (parentPath) {\n            setByKeyPath(clonedChange.mods[parentPath], keyPath.substr(parentPath.length + 1), nextChange.mods[keyPath]);\n            hadParentPath = true;\n        });\n        if (!hadParentPath) {\n            // Add or replace this keyPath and its new value\n            clonedChange.mods[keyPath] = nextChange.mods[keyPath];\n        }\n        // In case prevChange contained sub-paths to the new keyPath, we must make sure that those sub-paths are removed since\n        // we must mimic what would happen if applying the two changes after each other:\n        Object.keys(prevChange.mods).filter(function (subPath) { return subPath.indexOf(keyPath + '.') === 0 }).forEach(function (subPath) {\n            delete clonedChange.mods[subPath];\n        });\n    });\n    return clonedChange;\n}\n\nfunction setByKeyPath(obj, keyPath, value) {\n    if (!obj || typeof keyPath !== 'string') return;\n    var period = keyPath.indexOf('.');\n    if (period !== -1) {\n        var currentKeyPath = keyPath.substr(0, period);\n        var remainingKeyPath = keyPath.substr(period + 1);\n        if (remainingKeyPath === \"\")\n            obj[currentKeyPath] = value;\n        else {\n            var innerObj = obj[currentKeyPath];\n            if (!innerObj) innerObj = (obj[currentKeyPath] = {});\n            setByKeyPath(innerObj, remainingKeyPath, value);\n        }\n    } else {\n        obj[keyPath] = value;\n    }\n}\n\nmodule.export = SyncServer;\n"
  },
  {
    "path": "samples/remote-sync/websocket/websocketserver-shim.js",
    "content": "﻿/* This shim helps you unit test both WebSocket client and a websocket server in the browser.\n   \n   This shim will implement a simple web socket server with same API as \"nodejs-websocket\".\n   It will also replace the browser's WebSocket class with a emulated WebSocket class that\n   will only communicate with instances of the emulated websocket server.\n\n   You can then unit test your WebSocket client and server in the same browser without relying\n   on any network card or TCP stack.\n*/\n\n(function () {\n\n    // To be able to test both server and client in a browser, emulate a WebSocketServer in the browser, and replace the\n    // WebSocket api with an emulated WebSocket that communicates with emulated WebSocketServer.\n    function EmulatedWebSocketServerFactory() {\n        this.createServer = function (connectCallback) {\n            var server = {\n                _connect: function (socket) {\n                    var conn = {\n                        _client: socket,\n                        _listeners: { \"text\": [], \"close\": [] },\n                        on: function (type, listener) {\n                            conn._listeners[type].push(listener);\n                        },\n                        _fire: function (type, args) {\n                            conn._listeners[type].forEach(function (listener) {\n                                listener.apply(null, args);\n                            });\n                        },\n\n                        _text: function (msg) {\n                            conn._fire(\"text\", [msg]);\n                        },\n                        sendText: function (msg) {\n                            setTimeout(function () {\n                                conn._client._text(msg);\n                            }, 0);\n                        },\n                        close: function (code, reason) {\n                            setTimeout(function () {\n                                conn._client._disconnect(code, reason);\n                            }, 0);\n                        },\n                        _disconnect: function (code, reason) {\n                            conn._fire(\"close\", [code, reason]);\n                        }\n                    };\n                    connectCallback(conn);\n                    return conn;\n                },\n                listen: function (port) {\n                    EmulatedWebSocketServerFactory.listeners[port] = this;\n                    return this;\n                }\n            }\n            return server;\n        }\n    }\n    EmulatedWebSocketServerFactory.listeners = {};\n\n    function EmulatedWebSocket(url) {\n        var uri = parseURL(url);\n        var self = this;\n        this.conn = null;\n        this.send = function (msg) {\n            if (!this.conn) throw \"Not connected\";\n            setTimeout(function () {\n                self.conn._text(msg);\n            }, 0);\n        }\n        this.close = function (code, reason) {\n            setTimeout(function () {\n                if (self.conn) self.conn._disconnect(code, reason);\n                self.conn = null;\n            }, 0);\n        }\n        this._text = function (msg) {\n            if (this.onmessage) {\n                this.onmessage({ target: this, data: msg });\n            }\n        }\n        this._disconnect = function (code, reason) {\n            if (this.onclose) {\n                this.onclose({code: code, reason: reason, wasClean: true });\n            }\n        }\n        setTimeout(function () {\n            try {\n                if (EmulatedWebSocketServerFactory.listeners[uri.port]) {\n                    var server = EmulatedWebSocketServerFactory.listeners[uri.port];\n                    var conn = server._connect(self);\n                    self.conn = conn;\n                    if (self.onopen) {\n                        self.onopen({ target: self });\n                    }\n                } else {\n                    throw \"Could not connect\";\n                }\n            } catch (e) {\n                if (self.onerror) {\n                    self.onerror({\n                        target: self,\n                        message: e.toString()\n                    });\n                }\n            }\n        }, 0);\n    }\n\n    function parseURL(url) {\n        var a = document.createElement('a');\n        a.href = url;\n        return {\n            source: url,\n            protocol: a.protocol.replace(':', ''),\n            host: a.hostname,\n            port: a.port,\n            query: a.search,\n            params: (function () {\n                var ret = {},\n                    seg = a.search.replace(/^\\?/, '').split('&'),\n                    len = seg.length, i = 0, s;\n                for (; i < len; i++) {\n                    if (!seg[i]) { continue; }\n                    s = seg[i].split('=');\n                    ret[s[0]] = s[1];\n                }\n                return ret;\n            })(),\n            file: (a.pathname.match(/\\/([^\\/?#]+)$/i) || [, ''])[1],\n            hash: a.hash.replace('#', ''),\n            path: a.pathname.replace(/^([^\\/])/, '/$1'),\n            relative: (a.href.match(/tps?:\\/\\/[^\\/]+(.+)/) || [, ''])[1],\n            segments: a.pathname.replace(/^\\//, '').split('/')\n        };\n    }\n\n    function override(origFunc, overridedFactory) {\n        return overridedFactory(origFunc);\n    }\n\n    window.require = override(window.require, function (origFunc) {\n        return function (module) {\n            if (module == \"nodejs-websocket\")\n                return new EmulatedWebSocketServerFactory();\n            else\n                return origFunc.apply(this, arguments);\n        }\n    });\n\n    window.WebSocket = EmulatedWebSocket;\n\n})();\n"
  },
  {
    "path": "samples/svelte/README.md",
    "content": "See [codesandbox example](https://codesandbox.io/s/svelte-with-dexie-livequery-2n8bd?file=/App.svelte)\n\nSee also [dexie.org/docs/Tutorial/Svelte](https://dexie.org/docs/Tutorial/Svelte)\n"
  },
  {
    "path": "samples/vanilla-js/hello-world-modern.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <script type=\"module\">\n      import Dexie from 'https://unpkg.com/dexie@latest/dist/modern/dexie.min.mjs';\n\n      //\n      // Declare Database\n      //\n      const db = new Dexie('FriendDatabase');\n      db.version(1).stores({\n        friends: '++id, name, age',\n      });\n\n      //\n      // Manipulate and Query Database\n      //\n      try {\n        let id = await db.friends.add({\n          name: 'Josephine',\n          age: 21,\n        });\n        alert(`Successfully added a friend with id ${id}`);\n\n        let youngFriends = await db.friends.where('age').below(25).toArray();\n\n        alert(`My young friends: ${JSON.stringify(youngFriends)}`);\n      } catch (error) {\n        alert(`Oops, error: ${error}`);\n      }\n    </script>\n  </head>\n</html>\n"
  },
  {
    "path": "samples/vanilla-js/hello-world.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <script src=\"https://unpkg.com/dexie@latest/dist/dexie.min.js\"></script>\n    <script>\n      //\n      // Declare Database\n      //\n      var db = new Dexie('FriendDatabase');\n      db.version(1).stores({\n        friends: '++id, name, age',\n      });\n\n      //\n      // Manipulate and Query Database\n      //\n      db.friends.add({\n        name: 'Josephine',\n        age: 21,\n      }).then((id) => {\n        alert(`Successfully added a friend with id ${id}`);\n      }).then(() => {\n        return db.friends.where('age').below(25).toArray();\n      }).then((youngFriends) => {\n        alert(`My young friends: ${JSON.stringify(youngFriends)}`);\n      }).catch((error) => {\n        alert(`Oops, error: ${error}`);\n      });\n    </script>\n  </head>\n</html>\n"
  },
  {
    "path": "samples/vue/.gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "samples/vue/README.md",
    "content": "# Vue + Dexie Todo Example\n\nThis project was bootstrapped with [Vite](https://vitejs.dev/). Its arrangement of components is designed to be similar to that of the components in the [React](../react) example. The code is organized into:\n\n- `src/database.js`: contains all database logic in its exported `Database` class.\n- `src/App.vue`: the top-level Vue.js app instantiates a `Database` on its creation to handle database changes.\n- `src/components/*.vue`: the components to the to-do list; sends events up to the `App.vue` component on user interactions.\n\n## Install dependencies\n\nBefore you can run the app in your browser, you will have to install its dependencies with:\n\n```\nnpm install\n```\n\n## Development server\n\nRun `npm run dev` for a dev server. Navigate to `http://localhost:1123`. The app will automatically reload if you change any of the source files. You will also see any lint errors in the console.\n\n## Build\n\nRun `npm run build` to build the project. The build artifacts will be stored in the `build/` directory.\n\n## Further help\n\n- [Vite guide](https://vitejs.dev)\n- [Vue.js Guide](https://vuejs.org/guide/introduction.html) for core Vue 3 concepts\n- To get more help on Dexie check out the [Dexie Docs](https://dexie.org/docs/)\n\n## See Also\n\n[dexie.org/docs/Tutorial/Vue](https://dexie.org/docs/Tutorial/Vue)\n"
  },
  {
    "path": "samples/vue/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Dexie.js vue sample</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n\n    <script type=\"module\" src=\"src/main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "samples/vue/package.json",
    "content": "{\n  \"name\": \"vue\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"lint\": \"eslint .\"\n  },\n  \"dependencies\": {\n    \"core-js\": \"^3.6.5\",\n    \"dexie\": \"^3.0.3\",\n    \"vue\": \"^3.0.0\"\n  },\n  \"devDependencies\": {\n    \"@vitejs/plugin-vue\": \"^3.1.0\",\n    \"eslint\": \"^6.7.2\",\n    \"eslint-plugin-vue\": \"^7.0.0-0\",\n    \"vite\": \"^3.1.3\",\n    \"vite-plugin-vue\": \"^0.0.0\"\n  },\n  \"eslintConfig\": {\n    \"root\": true,\n    \"env\": {\n      \"node\": true\n    },\n    \"extends\": [\n      \"plugin:vue/vue3-essential\",\n      \"eslint:recommended\"\n    ],\n    \"rules\": {}\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not dead\"\n  ]\n}\n"
  },
  {
    "path": "samples/vue/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\">\n    <title><%= htmlWebpackPlugin.options.title %></title>\n  </head>\n  <body>\n    <noscript>\n      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n    </noscript>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "samples/vue/src/App.vue",
    "content": "<template>\n  <div class=\"app\">\n    <div class=\"app-header\">\n      <h2>Vue + Dexie Todo Example</h2>\n    </div>\n    <AddTodo @add-todo=\"addTodo\" />\n    <TodoList\n      :todos=\"todos\"\n      @toggle-todo=\"toggleTodo\"\n      @delete-todo=\"deleteTodo\"\n      @sort-todos=\"updateTodoOrder\"\n    />\n  </div>\n</template>\n\n<script>\nimport AddTodo from './components/AddTodo.vue';\nimport TodoList from './components/TodoList.vue';\n\nimport { Database, forwardOrder } from './database.js';\n\nexport default {\n  name: 'App',\n  components: {\n    AddTodo,\n    TodoList,\n  },\n  data() {\n    return {\n      todos: [],\n      order: forwardOrder,\n    }\n  },\n\n  created() {\n    this.db = new Database();\n    this.updateTodos();\n  },\n\n  methods: {\n    // addTodo adds a todo to the database and ultimately the displayed to-do\n    // list.\n    async addTodo(todo) {\n      await this.db.addTodo(todo.text);\n      this.updateTodos(false);\n    },\n\n    // toggleTodo toggles the todo with the ID passed in between complete and\n    // incomplete in the database and ultimately the displayed to-do list.\n    async toggleTodo(togglePayload) {\n      await this.db.setTodoDone(togglePayload.id, togglePayload.done);\n      this.updateTodos(false);\n    },\n\n    // deleteTodo deletes the todo with the ID passed in from the database and\n    // ultimately the displayed to-do list.\n    async deleteTodo(deletePayload) {\n      await this.db.deleteTodo(deletePayload.id);\n      this.updateTodos(false);\n    },\n\n    // updateTodoOrder retrieves todos from the database in the order passed\n    // in, changing their order in the displayed to-do list.\n    updateTodoOrder(sortTodosPayload) {\n      this.order = sortTodosPayload.order;\n      this.updateTodos(true);\n    },\n\n    // updateTodos retrieves todos from the database, updating the displayed\n    // list.\n    async updateTodos(orderUpdated) {\n      let todos = await this.db.getTodos(this.order);\n\n      if (orderUpdated) {\n        this.todos = todos;\n        return\n      }\n\n      // if we are not updating the order of the todos, then we update the\n      // todos in place. The reason for this is because if we are in\n      // \"unfinished first\" order, then we don't want to-dos to suddenly bounce\n      // in the to-do list because the task at the top of the to-do list\n      // became marked as finished, which would reduce accessibility.\n      let idToIndex = {};\n      for (let i = 0; i < this.todos.length; i++) {\n        idToIndex[this.todos[i].id] = i;\n      }\n      this.todos = todos.sort((a, b) => {\n        // handle new items\n        if (idToIndex[a.id] == undefined) {\n          return 1;\n        } else if (idToIndex[b.id] == undefined) {\n          return -1;\n        }\n        \n        return idToIndex[a.id] < idToIndex[b.id] ? -1 : 1;\n      })\n    },\n  },\n}\n</script>\n\n<style>\nbody {\n  margin: 0;\n  padding: 0;\n  font-family: sans-serif;\n}\n\n.app {\n  text-align: center;\n}\n\n.app-header {\n  background-color: #222;\n  height: 40px;\n  padding: 20px;\n  margin-bottom: 40px;\n  color: white;\n}\n</style>\n"
  },
  {
    "path": "samples/vue/src/components/AddTodo.vue",
    "content": "<template>\n  <div role=\"group\" aria-label=\"Add todo\">\n    <input type=\"text\" v-model=\"value\" />\n    <button type=\"button\" @click=\"addTodo\">Add Todo</button>\n  </div>\n</template>\n\n<script setup>\n// vue reactivityTransform details on https://vuejs.org/guide/extras/reactivity-transform.html\nlet value = $ref();\nconst emits = defineEmits(['add-todo']);\n// addTodo emits an event to add a todo to the todo list.\nfunction addTodo() {\n  emits('add-todo', { text: value });\n  value = '';\n}\n</script>\n"
  },
  {
    "path": "samples/vue/src/components/Todo.vue",
    "content": "<template>\n  <li>\n    <div role=\"group\" :aria-label=\"titleLabel\">\n      <input type=\"checkbox\" :checked=\"done\" @change=\"toggleTodo\" />\n      <span :class=\"{ done: done }\" :aria-label=\"titleLabel\">\n        {{ text }}\n      </span>\n      <button type=\"button\" @click=\"deleteTodo\">Delete</button>\n    </div>\n  </li>\n</template>\n\n<script setup>\nimport { computed } from 'vue';\n\nconst props = defineProps(['todoID', 'text', 'done']);\nconst emits = defineEmits(['toggle-todo', 'delete-todo']);\n// toggleTodo emits an event to toggle this to-do between complete and\n// incomplete.\nfunction toggleTodo() {\n  emits('toggle-todo', { id: props.todoID, done: !props.done });\n}\n\n// deleteTodo emits an event to delete this to-do.\nfunction deleteTodo() {\n  emits('delete-todo', { id: props.todoID });\n}\n\nconst titleLabel = computed(() => {\n  return props.done ? `${props.text} (completed task)` : props.text;\n});\n</script>\n\n<style scoped>\nul li {\n  margin-top: 7px;\n  list-style: none;\n}\n\nul li button {\n  margin-left: 7px;\n}\n\n.done {\n  text-decoration: line-through;\n}\n</style>\n"
  },
  {
    "path": "samples/vue/src/components/TodoList.vue",
    "content": "<template>\n  <div class=\"todo-list\">\n    <div class=\"select-order-group\" role=\"group\" aria-label=\"Update order\">\n      <span class=\"select-order-header\" aria-hidden=\"true\">Update order</span>\n      <!-- [TODO] Define sort events these buttons will emit when we add the\n           database.js file -->\n      <button aria-label=\"oldest first\" @click=\"sortTodos(forwardOrder)\">\n        🦖 Oldest first\n      </button>\n      <button aria-label=\"newest first\" @click=\"sortTodos(reverseOrder)\">\n        🛸 Newest first\n      </button>\n      <button\n        aria-label=\"unfinished first\"\n        @click=\"sortTodos(unfinishedFirstOrder)\"\n      >\n        🚧 Unfinished first\n      </button>\n    </div>\n    <ul>\n      <Todo\n        v-for=\"todo in todos\"\n        :key=\"todo.id\"\n        :todoID=\"todo.id\"\n        :text=\"todo.text\"\n        :done=\"todo.done\"\n        @toggle-todo=\"toggleTodo\"\n        @delete-todo=\"deleteTodo\"\n      />\n    </ul>\n  </div>\n</template>\n\n<script setup>\nimport Todo from './Todo.vue';\nimport { forwardOrder, reverseOrder, unfinishedFirstOrder } from '../database';\n\ndefineProps(['todos']);\n\nconst emits = defineEmits(['toggle-todo', 'delete-todo', 'sort-todos']);\n\n// toggleTodo emits an event to toggle a todo between finished and\n// unfinished.\nfunction toggleTodo(togglePayload) {\n  emits('toggle-todo', togglePayload);\n}\n\n// deleteTodo emits an event to delete a todo of the given ID\nfunction deleteTodo(deletePayload) {\n  emits('delete-todo', deletePayload);\n}\n\n// sortTodo emits an event to sort the todo list by a given order.\nfunction sortTodos(order) {\n  emits('sort-todos', { order: order });\n}\n</script>\n\n<style scoped>\n.todo-list {\n  margin-top: 20px;\n}\n\n.select-order-group {\n  font-size: 18px;\n}\n\n.select-order-group button {\n  margin-left: 10px;\n}\n</style>\n"
  },
  {
    "path": "samples/vue/src/database.js",
    "content": "import Dexie from 'dexie';\n\n// Database inherits from the Dexie class to handle all database logic for the\n// todo app.\n// NOTE: For an app like this where the database interactions are pretty\n// simple, it's not strictly necessary to subclass Dexie, but I personally\n// prefer the subclassing pattern over having a global Dexie database class\n// in order to structure all the database logic in a single class.\nexport class Database extends Dexie {\n  constructor() {\n    // run the super constructor Dexie(databaseName) to create the IndexedDB\n    // database.\n    super('database');\n\n    // create the todos store by passing an object into the stores method. We\n    // declare which object fields we want to index using a comma-separated\n    // string; the ++ for the index on the id field indicates that \"id\" is an\n    // auto-incrementing primary key, while the \"done\" field is just a regukar\n    // IndexedDB index.\n    this.version(1).stores({\n      todos: '++id,done',\n    });\n\n    // we can retrieve our todos store with Dexie.table, and then use it as a\n    // field on our Database class for convenience; we can now write code such\n    // as \"this.todos.add(...)\" rather than \"this.table('todos').add(...)\"\n    this.todos = this.table('todos');\n  }\n\n  // getTodos retrieves all todos from the todos object store in a defined\n  // order; order can be:\n  // - forwardOrder to get the todos in forward chronological order\n  // - reverseOrder to get the todos in reverse chronological order\n  // - unfinishedFirstOrder to get the todos in reverse chronological order\n  async getTodos(order) {\n    // In Dexie, we create queries by chaining methods, such as orderBy to\n    // sort by an indexed field, and reverse to reverse the order we retrieve\n    // data in. The toArray method returns a promise that resolves to the array\n    // of the items in the todos store.\n    let todos = [];\n    switch (order) {\n      case forwardOrder:\n        todos = await this.todos.orderBy('id').toArray();\n        break;\n      case reverseOrder:\n        todos = await this.todos.orderBy('id').reverse().toArray();\n        break;\n      case unfinishedFirstOrder:\n        todos = await this.todos.orderBy('done').toArray();\n        break;\n      default:\n        // as a default just fall back to forward order\n        todos = await this.todos.orderBy('id').toArray();\n    }\n\n    // The reason we need to modify the done field on each todo is because in\n    // IndexedDB, integers can be indexed, but booleans cannot, so we represent\n    // \"done\" status as an integer. Only the database logic needs to know that\n    // detail, though, so for convenience when we return the todos, their \"done\"\n    // status is a boolean.\n    return todos.map((t) => {\n      t.done = !!t.done;\n      return t;\n    });\n  }\n\n  // setTodoDone sets whether or not the todo with the ID passed in is done.\n  // Returns a promise that resolves if the update is successful.\n  setTodoDone(id, done) {\n    return this.todos.update(id, { done: done ? 1 : 0 })\n  }\n\n  // addTodo adds a todo with the text passed in to the todos object store.\n  // Returns a promise that resolves if the addition is successful.\n  addTodo(text) {\n    // add a todo by passing in an object using Table.add.\n    return this.todos.add({ text: text, done: 0 })\n  }\n\n  // deleteTodo deletes a todo with the ID passed in from the todos object\n  // store. Returns a promise that resolves if the deletion is successful.\n  deleteTodo(todoID) {\n    // delete a todo by passing in the ID of that todo.\n    return this.todos.delete(todoID);\n  }\n}\n\n// forwardOrder is passed into getTodos to retrieve todos in chronological\n// order.\nexport const forwardOrder = 'forward';\n\n// reverseOrder is passed into getTodos to retrieve todos in reverse\n// chronological order.\nexport const reverseOrder = 'reverse';\n\n// unfinishedFirstOrder is passed into getTodos to retrieve todos such that\n// unfinished todos come before finished todos in the returned array.\nexport const unfinishedFirstOrder = 'unfinished-first';"
  },
  {
    "path": "samples/vue/src/main.js",
    "content": "import { createApp } from 'vue'\nimport App from './App.vue'\n\ncreateApp(App).mount('#app')\n"
  },
  {
    "path": "samples/vue/vite.config.js",
    "content": "import { defineConfig } from 'vite';\nimport Vue from '@vitejs/plugin-vue';\n\nexport default defineConfig({\n  plugins: [\n    Vue({\n      // https://vuejs.org/guide/extras/reactivity-transform.html\n      reactivityTransform: true,\n    }),\n  ],\n  server: {\n    port: 1123,\n  },\n});\n"
  },
  {
    "path": "src/classes/collection/collection-constructor.ts",
    "content": "import { Dexie } from '../../classes/dexie';\nimport { makeClassConstructor } from '../../functions/make-class-constructor';\nimport { Collection } from './collection';\nimport { WhereClause } from '../where-clause/where-clause';\nimport { AnyRange } from '../../dbcore/keyrange';\nimport { DBCoreKeyRange } from '../../public/types/dbcore';\nimport { mirror } from '../../functions/chaining-functions';\n\n/** Constructs a Collection instance. */\nexport interface CollectionConstructor {\n  new(whereClause?: WhereClause | null, keyRangeGenerator?: () => DBCoreKeyRange): Collection;\n  prototype: Collection;\n}\n\n/** Generates a Collection constructor bound to given Dexie instance.\n * \n * The purpose of having dynamically created constructors, is to allow\n * addons to extend classes for a certain Dexie instance without affecting\n * other db instances.\n */\nexport function createCollectionConstructor(db: Dexie) {\n  return makeClassConstructor<CollectionConstructor>(\n    Collection.prototype,\n\n    function Collection(\n      this: Collection,\n      whereClause?: WhereClause | null,\n      keyRangeGenerator?: () => DBCoreKeyRange)\n    {\n      this.db = db;\n      let keyRange = AnyRange, error = null;\n      if (keyRangeGenerator) try {\n        keyRange = keyRangeGenerator();\n      } catch (ex) {\n        error = ex;\n      }\n\n      const whereCtx = whereClause._ctx;\n      const table = whereCtx.table;\n      const readingHook = table.hook.reading.fire;\n      this._ctx = {\n        table: table,\n        index: whereCtx.index,\n        isPrimKey: (!whereCtx.index || (table.schema.primKey.keyPath && whereCtx.index === table.schema.primKey.name)),\n        range: keyRange,\n        keysOnly: false,\n        dir: \"next\",\n        unique: \"\",\n        algorithm: null,\n        filter: null,\n        replayFilter: null,\n        justLimit: true, // True if a replayFilter is just a filter that performs a \"limit\" operation (or none at all)\n        isMatch: null,\n        offset: 0,\n        limit: Infinity,\n        error: error, // If set, any promise must be rejected with this error\n        or: whereCtx.or,\n        valueMapper: readingHook !== mirror ? readingHook : null\n      };\n    }\n  );\n}\n"
  },
  {
    "path": "src/classes/collection/collection-helpers.ts",
    "content": "import { combine } from \"../../functions/combine\";\nimport { exceptions } from \"../../errors\";\nimport { hasOwn } from \"../../functions/utils\";\nimport { wrap } from \"../../helpers/promise\";\nimport { Collection } from './';\nimport { DBCoreCursor, DBCoreTable, DBCoreTransaction, DBCoreTableSchema, DBCoreRangeType } from '../../public/types/dbcore';\nimport { nop } from '../../functions/chaining-functions';\n\ntype CollectionContext = Collection[\"_ctx\"];\n\nexport function isPlainKeyRange (ctx: CollectionContext, ignoreLimitFilter?: boolean) {\n  return !(ctx.filter || ctx.algorithm || ctx.or) &&\n      (ignoreLimitFilter ? ctx.justLimit : !ctx.replayFilter);\n}    \n\nexport function addFilter(ctx: CollectionContext, fn: Function) {\n  ctx.filter = combine(ctx.filter, fn);\n}\n\nexport function addReplayFilter (ctx: CollectionContext, factory, isLimitFilter?) {\n  var curr = ctx.replayFilter;\n  ctx.replayFilter = curr ? ()=>combine(curr(), factory()) : factory;\n  ctx.justLimit = isLimitFilter && !curr;\n}\n\nexport function addMatchFilter(ctx: CollectionContext, fn) {\n  ctx.isMatch = combine(ctx.isMatch, fn);\n}\n\nexport function getIndexOrStore(ctx: CollectionContext, coreSchema: DBCoreTableSchema) {\n  // TODO: Rewrite this. No need to know ctx.isPrimKey. ctx.index should hold the keypath.\n  // Still, throw if not found!\n  if (ctx.isPrimKey) return coreSchema.primaryKey;\n  const index = coreSchema.getIndexByKeyPath(ctx.index);\n  if (!index) throw new exceptions.Schema(\"KeyPath \" + ctx.index + \" on object store \" + coreSchema.name + \" is not indexed\");\n  return index;\n}\n\nexport function openCursor(ctx: CollectionContext, coreTable: DBCoreTable, trans: DBCoreTransaction) {\n  const index = getIndexOrStore(ctx, coreTable.schema);\n  return coreTable.openCursor({\n    trans,\n    values: !ctx.keysOnly,\n    reverse: ctx.dir === 'prev',\n    unique: !!ctx.unique,\n    query: {\n      index, \n      range: ctx.range\n    }\n  });\n}\n\nexport function iter (\n  ctx: CollectionContext, \n  fn: (item, cursor: DBCoreCursor, advance: Function)=>void,\n  coreTrans: DBCoreTransaction,\n  coreTable: DBCoreTable): Promise<any>\n{\n  const filter = ctx.replayFilter ? combine(ctx.filter, ctx.replayFilter()) : ctx.filter;\n  if (!ctx.or) {\n      return iterate(\n        openCursor(ctx, coreTable, coreTrans),\n        combine(ctx.algorithm, filter), fn, !ctx.keysOnly && ctx.valueMapper);\n  } else {\n      const set = {};\n\n      const union = (item: any, cursor: DBCoreCursor, advance) => {\n          if (!filter || filter(cursor, advance, result=>cursor.stop(result), err => cursor.fail(err))) {\n              var primaryKey = cursor.primaryKey;\n              var key = '' + primaryKey;\n              if (key === '[object ArrayBuffer]') key = '' + new Uint8Array(primaryKey);\n              if (!hasOwn(set, key)) {\n                  set[key] = true;\n                  fn(item, cursor, advance);\n              }\n          }\n      }\n\n      return Promise.all([\n        ctx.or._iterate(union, coreTrans),\n        iterate(openCursor(ctx, coreTable, coreTrans), ctx.algorithm, union, !ctx.keysOnly && ctx.valueMapper)\n      ]);\n  }\n}\n\nfunction iterate(cursorPromise: Promise<DBCoreCursor>, filter, fn, valueMapper): Promise<any> {\n  \n  // Apply valueMapper (hook('reading') or mappped class)\n  var mappedFn = valueMapper ? (x,c,a) => fn(valueMapper(x),c,a) : fn;\n  // Wrap fn with PSD and microtick stuff from Promise.\n  var wrappedFn = wrap(mappedFn);\n  \n  return cursorPromise.then(cursor => {\n    if (cursor) {\n      return cursor.start(()=>{\n        var c = ()=>cursor.continue();\n        if (!filter || filter(cursor, advancer => c = advancer, val=>{cursor.stop(val);c=nop}, e => {cursor.fail(e);c = nop;}))\n          wrappedFn(cursor.value, cursor, advancer => c = advancer);\n        c();\n      });\n    }\n  });\n}\n"
  },
  {
    "path": "src/classes/collection/collection.ts",
    "content": "import { Collection as ICollection } from \"../../public/types/collection\";\nimport { Dexie } from \"../dexie\";\nimport { Table } from \"../table\";\nimport { IndexableType, IndexableTypeArrayReadonly } from \"../../public/types/indexable-type\";\nimport { PromiseExtended } from \"../../public/types/promise-extended\";\nimport { iter, isPlainKeyRange, getIndexOrStore, addReplayFilter, addFilter, addMatchFilter } from \"./collection-helpers\";\nimport { rejection } from \"../../helpers/promise\";\nimport { combine } from \"../../functions/combine\";\nimport { extend, hasOwn, deepClone, keys, setByKeyPath, getByKeyPath } from \"../../functions/utils\";\nimport { ModifyError } from \"../../errors\";\nimport { ThenShortcut } from \"../../public/types/then-shortcut\";\nimport { Transaction } from '../transaction';\nimport { DBCoreCursor, DBCoreTransaction, DBCoreRangeType, DBCoreMutateResponse, DBCoreKeyRange } from '../../public/types/dbcore';\nimport { cmp } from \"../../functions/cmp\";\nimport { PropModification } from \"../../helpers/prop-modification\";\nimport { UpdateSpec } from \"../../public/types/update-spec\";\nimport { builtInDeletionTrigger } from \"../table/table-helpers\";\nimport { applyUpdateSpec } from \"../../functions/apply-update-spec\";\n\n/** class Collection\n * \n * https://dexie.org/docs/Collection/Collection\n */\nexport class Collection implements ICollection {\n  db: Dexie;\n  _ctx: {\n    table: Table;\n    index?: string | null;\n    isPrimKey?: boolean;\n    range: DBCoreKeyRange;\n    keysOnly: boolean;\n    dir: \"next\" | \"prev\";\n    unique: \"\" | \"unique\";\n    algorithm?: Function | null;\n    filter?: Function | null;\n    replayFilter: Function | null;\n    justLimit: boolean; // True if a replayFilter is just a filter that performs a \"limit\" operation (or none at all)\n    isMatch: Function | null;\n    offset: number,\n    limit: number,\n    error: any, // If set, any promise must be rejected with this error\n    or: Collection,\n    valueMapper: (any) => any\n  }\n  \n  _ondirectionchange?: Function;\n\n  _read<T>(fn: (idbtrans: IDBTransaction, dxTrans: Transaction) => PromiseLike<T>, cb?): PromiseExtended<T> {\n    var ctx = this._ctx;\n    return ctx.error ?\n      ctx.table._trans(null, rejection.bind(null, ctx.error)) :\n      ctx.table._trans('readonly', fn).then(cb);\n  }\n\n  _write<T>(fn: (idbtrans: IDBTransaction, dxTrans: Transaction) => PromiseLike<T>): PromiseExtended<T> {\n    var ctx = this._ctx;\n    return ctx.error ?\n      ctx.table._trans(null, rejection.bind(null, ctx.error)) :\n      ctx.table._trans('readwrite', fn, \"locked\"); // When doing write operations on collections, always lock the operation so that upcoming operations gets queued.\n  }\n\n  _addAlgorithm(fn) {\n    var ctx = this._ctx;\n    ctx.algorithm = combine(ctx.algorithm, fn);\n  }\n\n  _iterate(\n    fn: (item, cursor: DBCoreCursor, advance: Function) => void,\n    coreTrans: DBCoreTransaction) : Promise<any>\n  {\n    return iter(this._ctx, fn, coreTrans, this._ctx.table.core);\n  }\n\n  /** Collection.clone()\n   * \n   * https://dexie.org/docs/Collection/Collection.clone()\n   * \n   **/\n  clone(props?): this {\n    var rv = Object.create(this.constructor.prototype),\n      ctx = Object.create(this._ctx);\n    if (props) extend(ctx, props);\n    rv._ctx = ctx;\n    return rv;\n  }\n\n  /** Collection.raw()\n   * \n   * https://dexie.org/docs/Collection/Collection.raw()\n   * \n   **/\n  raw(): this {\n    this._ctx.valueMapper = null;\n    return this;\n  }\n\n  /** Collection.each()\n   * \n   * https://dexie.org/docs/Collection/Collection.each()\n   * \n   **/\n  each(fn: (obj, cursor: DBCoreCursor) => any): PromiseExtended<void> {\n    var ctx = this._ctx;\n\n    return this._read(trans => iter(ctx, fn, trans, ctx.table.core));\n  }\n\n  /** Collection.count()\n   * \n   * https://dexie.org/docs/Collection/Collection.count()\n   * \n   **/\n  count(cb?) {\n    return this._read(trans => {\n      const ctx = this._ctx;\n      const coreTable = ctx.table.core;\n      if (isPlainKeyRange(ctx, true)) {\n        // This is a plain key range. We can use the count() method if the index.\n        return coreTable.count({\n          trans,\n          query: {\n            index: getIndexOrStore(ctx, coreTable.schema),\n            range: ctx.range\n          }\n        }).then(count => Math.min(count, ctx.limit));\n      } else {\n        // Algorithms, filters or expressions are applied. Need to count manually.\n        var count = 0;\n        return iter(ctx, () => { ++count; return false; }, trans, coreTable)\n        .then(()=>count);\n      }\n    }).then(cb);\n  }\n\n  /** Collection.sortBy()\n   * \n   * https://dexie.org/docs/Collection/Collection.sortBy()\n   * \n   **/\n  sortBy(keyPath: string): PromiseExtended<any[]>;\n  sortBy<R>(keyPath: string, thenShortcut: ThenShortcut<any[], R>) : PromiseExtended<R>;\n  sortBy(keyPath: string, cb?: ThenShortcut<any[], any>) {\n    const parts = keyPath.split('.').reverse(),\n      lastPart = parts[0],\n      lastIndex = parts.length - 1;\n    function getval(obj, i) {\n      if (i) return getval(obj[parts[i]], i - 1);\n      return obj[lastPart];\n    }\n    var order = this._ctx.dir === \"next\" ? 1 : -1;\n\n    function sorter(a, b) {\n      var aVal = getval(a, lastIndex),\n        bVal = getval(b, lastIndex);\n      return cmp(aVal, bVal) * order;\n    }\n    return this.toArray(function (a) {\n      return a.sort(sorter);\n    }).then(cb);\n  }\n\n  /** Collection.toArray()\n   * \n   * https://dexie.org/docs/Collection/Collection.toArray()\n   * \n   **/\n  toArray(cb?): PromiseExtended<any[]> {\n    return this._read(trans => {\n      var ctx = this._ctx;\n      if (isPlainKeyRange(ctx, true) && ctx.limit > 0) {\n        // Special optimization using IDBObjectStore.getAll() or IDBKeyRange.getAll().\n        // For reverse queries on IDB 3.0+ browsers, uses getAll(options) with direction.\n        const {valueMapper} = ctx;\n        const index = getIndexOrStore(ctx, ctx.table.core.schema);\n        return ctx.table.core.query({\n          trans,\n          limit: ctx.limit,\n          values: true,\n          direction: ctx.dir === 'prev' ? 'prev' : undefined,\n          query: {\n            index,\n            range: ctx.range\n          }\n        }).then(({result}) => valueMapper ? result.map(valueMapper) : result);\n      } else {\n        // Getting array through a cursor.\n        const a = [];\n        return iter(ctx, item => a.push(item), trans, ctx.table.core).then(()=>a);\n      }\n    }, cb);\n  }\n\n  /** Collection.offset()\n   * \n   * https://dexie.org/docs/Collection/Collection.offset()\n   * \n   **/\n  offset(offset: number) : Collection{\n    var ctx = this._ctx;\n    if (offset <= 0) return this;\n    ctx.offset += offset; // For count()\n    if (isPlainKeyRange(ctx)) {\n      addReplayFilter(ctx, () => {\n        var offsetLeft = offset;\n        return (cursor, advance) => {\n          if (offsetLeft === 0) return true;\n          if (offsetLeft === 1) { --offsetLeft; return false; }\n          advance(() => {\n            cursor.advance(offsetLeft);\n            offsetLeft = 0;\n          });\n          return false;\n        };\n      });\n    } else {\n      addReplayFilter(ctx, () => {\n        var offsetLeft = offset;\n        return () => (--offsetLeft < 0);\n      });\n    }\n    return this;\n  }\n\n  /** Collection.limit()\n   * \n   * https://dexie.org/docs/Collection/Collection.limit()\n   * \n   **/\n  limit(numRows: number) : Collection {\n    this._ctx.limit = Math.min(this._ctx.limit, numRows); // For count()\n    addReplayFilter(this._ctx, () => {\n      var rowsLeft = numRows;\n      return function (cursor, advance, resolve) {\n        if (--rowsLeft <= 0) advance(resolve); // Stop after this item has been included\n        return rowsLeft >= 0; // If numRows is already below 0, return false because then 0 was passed to numRows initially. Otherwise we wouldnt come here.\n      };\n    }, true);\n    return this;\n  }\n\n  /** Collection.until()\n   * \n   * https://dexie.org/docs/Collection/Collection.until()\n   * \n   **/\n  until(filterFunction: (x) => boolean, bIncludeStopEntry?) {\n    addFilter(this._ctx, function (cursor, advance, resolve) {\n      if (filterFunction(cursor.value)) {\n        advance(resolve);\n        return bIncludeStopEntry;\n      } else {\n        return true;\n      }\n    });\n    return this;\n  }\n\n  /** Collection.first()\n   * \n   * https://dexie.org/docs/Collection/Collection.first()\n   * \n   **/\n  first(cb?) {\n    return this.limit(1).toArray(function (a) { return a[0]; }).then(cb);\n  }\n\n  /** Collection.last()\n   * \n   * https://dexie.org/docs/Collection/Collection.last()\n   * \n   **/\n  last(cb?) {\n    return this.reverse().first(cb);\n  }\n\n  /** Collection.filter()\n   * \n   * https://dexie.org/docs/Collection/Collection.filter()\n   * \n   **/\n  filter(filterFunction: (x) => boolean): Collection {\n    /// <param name=\"jsFunctionFilter\" type=\"Function\">function(val){return true/false}</param>\n    addFilter(this._ctx, function (cursor) {\n      return filterFunction(cursor.value);\n    });\n    // match filters not used in Dexie.js but can be used by 3rd part libraries to test a\n    // collection for a match without querying DB. Used by Dexie.Observable.\n    addMatchFilter(this._ctx, filterFunction);\n    return this;\n  }\n\n  /** Collection.and()\n   * \n   * https://dexie.org/docs/Collection/Collection.and()\n   * \n   **/\n  and(filter: (x) => boolean) {\n    return this.filter(filter);\n  }\n\n  /** Collection.or()\n   * \n   * https://dexie.org/docs/Collection/Collection.or()\n   * \n   **/\n  or(indexName: string) {\n    return new this.db.WhereClause(this._ctx.table, indexName, this);\n  }\n\n  /** Collection.reverse()\n   * \n   * https://dexie.org/docs/Collection/Collection.reverse()\n   * \n   **/\n  reverse() {\n    this._ctx.dir = (this._ctx.dir === \"prev\" ? \"next\" : \"prev\");\n    if (this._ondirectionchange) this._ondirectionchange(this._ctx.dir);\n    return this;\n  }\n\n  /** Collection.desc()\n   * \n   * https://dexie.org/docs/Collection/Collection.desc()\n   * \n   **/\n  desc() {\n    return this.reverse();\n  }\n\n  /** Collection.eachKey()\n   * \n   * https://dexie.org/docs/Collection/Collection.eachKey()\n   * \n   **/\n  eachKey(cb?) {\n    var ctx = this._ctx;\n    ctx.keysOnly = !ctx.isMatch;\n    return this.each(function (val, cursor) { cb(cursor.key, cursor); });\n  }\n\n  /** Collection.eachUniqueKey()\n   * \n   * https://dexie.org/docs/Collection/Collection.eachUniqueKey()\n   * \n   **/\n  eachUniqueKey(cb?) {\n    this._ctx.unique = \"unique\";\n    return this.eachKey(cb);\n  }\n\n  /** Collection.eachPrimaryKey()\n   * \n   * https://dexie.org/docs/Collection/Collection.eachPrimaryKey()\n   * \n   **/\n  eachPrimaryKey(cb?) {\n    var ctx = this._ctx;\n    ctx.keysOnly = !ctx.isMatch;\n    return this.each(function (val, cursor) { cb(cursor.primaryKey, cursor); });\n  }\n\n  /** Collection.keys()\n   * \n   * https://dexie.org/docs/Collection/Collection.keys()\n   * \n   **/\n  keys(cb?) {\n    var ctx = this._ctx;\n    ctx.keysOnly = !ctx.isMatch;\n    var a = [];\n    return this.each(function (item, cursor) {\n      a.push(cursor.key);\n    }).then(function () {\n      return a;\n    }).then(cb);\n  }\n\n  /** Collection.primaryKeys()\n   * \n   * https://dexie.org/docs/Collection/Collection.primaryKeys()\n   * \n   **/\n  primaryKeys(cb?) : PromiseExtended<IndexableType[]> {\n    var ctx = this._ctx;\n    if (isPlainKeyRange(ctx, true) && ctx.limit > 0) {\n      // Special optimization using IDBObjectStore.getAllKeys() or IDBKeyRange.getAllKeys().\n      // For reverse queries on IDB 3.0+ browsers, uses getAllKeys(options) with direction.\n      return this._read(trans => {\n        var index = getIndexOrStore(ctx, ctx.table.core.schema);\n        return ctx.table.core.query({\n          trans,\n          values: false,\n          limit: ctx.limit,\n          direction: ctx.dir === 'prev' ? 'prev' : undefined,\n          query: {\n            index,\n            range: ctx.range\n          }});\n      }).then(({result})=>result).then(cb);\n    }\n    ctx.keysOnly = !ctx.isMatch;\n    var a = [];\n    return this.each(function (item, cursor) {\n      a.push(cursor.primaryKey);\n    }).then(function () {\n      return a;\n    }).then(cb);\n  }\n\n  /** Collection.uniqueKeys()\n   * \n   * https://dexie.org/docs/Collection/Collection.uniqueKeys()\n   * \n   **/\n  uniqueKeys(cb?) {\n    this._ctx.unique = \"unique\";\n    return this.keys(cb);\n  }\n\n  /** Collection.firstKey()\n   * \n   * https://dexie.org/docs/Collection/Collection.firstKey()\n   * \n   **/\n  firstKey(cb?) {\n    return this.limit(1).keys(function (a) { return a[0]; }).then(cb);\n  }\n\n  /** Collection.lastKey()\n   * \n   * https://dexie.org/docs/Collection/Collection.lastKey()\n   * \n   **/\n  lastKey(cb?) {\n    return this.reverse().firstKey(cb);\n  }\n\n  /** Collection.distinct()\n   * \n   * https://dexie.org/docs/Collection/Collection.distinct()\n   * \n   **/\n  distinct() {\n    var ctx = this._ctx,\n      idx = ctx.index && ctx.table.schema.idxByName[ctx.index];\n    if (!idx || !idx.multi) return this; // distinct() only makes differencies on multiEntry indexes.\n    var set = {};\n    addFilter(this._ctx, function (cursor: DBCoreCursor) {\n      var strKey = cursor.primaryKey.toString(); // Converts any Date to String, String to String, Number to String and Array to comma-separated string\n      var found = hasOwn(set, strKey);\n      set[strKey] = true;\n      return !found;\n    });\n    return this;\n  }\n\n  //\n  // Methods that mutate storage\n  //\n\n  /** Collection.modify()\n   * \n   * https://dexie.org/docs/Collection/Collection.modify()\n   * \n   **/\n  modify(changes: UpdateSpec<any> | ((obj: any, ctx:{value: any, primKey: IndexableType}) => void | boolean)): PromiseExtended<number> {\n    var ctx = this._ctx;\n    return this._write(trans => {\n      var modifyer: (obj: any, ctx:{value: any, primKey: IndexableType}) => void | boolean\n      if (typeof changes === 'function') {\n        // Changes is a function that may update, add or delete propterties or even require a deletion the object itself (delete this.item)\n        modifyer = changes as (obj: any, ctx:{value: any, primKey: IndexableType}) => void | boolean;\n      } else {\n        // changes is a set of {keyPath: value} and no one is listening to the updating hook.\n        modifyer = item => applyUpdateSpec(item, changes);\n      }\n\n      const coreTable = ctx.table.core;\n      const {outbound, extractKey} = coreTable.schema.primaryKey;\n      let limit = 200;\n      const modifyChunkSize = this.db._options.modifyChunkSize;\n      if (modifyChunkSize) {\n        if (typeof modifyChunkSize == 'object') {\n          limit = modifyChunkSize[coreTable.name] || modifyChunkSize['*'] || 200;\n        } else {\n          limit = modifyChunkSize;\n        }\n      }\n      const totalFailures = [];\n      let successCount = 0;\n      const failedKeys: IndexableType[] = [];\n      const applyMutateResult = (expectedCount: number, res: DBCoreMutateResponse) => {\n        const {failures, numFailures} = res;\n        successCount += expectedCount - numFailures;\n        for (let pos of keys(failures)) {\n          totalFailures.push(failures[pos]);\n        }\n      }\n      const isUnconditionalDelete = changes === deleteCallback; // Collection.delete() calls this.\n      return this.clone().primaryKeys().then(keys => {\n        const criteria = isPlainKeyRange(ctx) &&\n          ctx.limit === Infinity &&\n          (typeof changes !== 'function' || isUnconditionalDelete) && {\n            index: ctx.index,\n            range: ctx.range\n          };\n\n        const nextChunk = (offset: number) => {\n          const count = Math.min(limit, keys.length - offset);\n          const keysInChunk = keys.slice(offset, offset + count);\n          return (isUnconditionalDelete ? Promise.resolve([]) : coreTable.getMany({\n            trans,\n            keys: keysInChunk,\n            cache: \"immutable\" // Optimize for 2 things:\n            // 1) observability-middleware can track changes better.\n            // 2) hooks middleware don't have to query the existing values again when tracking changes.\n            // We can use \"immutable\" because we promise to not touch the values we retrieve here!\n          })).then(values => {\n            const addValues = [];\n            const putValues = [];\n            const putKeys = outbound ? [] : null;\n            const deleteKeys = isUnconditionalDelete ? keysInChunk : [];\n            if (!isUnconditionalDelete) for (let i=0; i<count; ++i) {\n              const origValue = values[i];\n              const ctx = {\n                value: deepClone(origValue),\n                primKey: keys[offset+i]\n              };\n              if (modifyer.call(ctx, ctx.value, ctx) !== false) {\n                if (ctx.value == null) {\n                  // Deleted\n                  deleteKeys.push(keys[offset+i]);\n                } else if (!outbound && cmp(extractKey(origValue), extractKey(ctx.value)) !== 0) {\n                  // Changed primary key of inbound\n                  deleteKeys.push(keys[offset+i]);\n                  addValues.push(ctx.value)\n                } else {\n                  // Changed value\n                  putValues.push(ctx.value);\n                  if (outbound) putKeys.push(keys[offset+i]);\n                }\n              }\n            }\n\n            return Promise.resolve(addValues.length > 0 &&\n              coreTable.mutate({trans, type: 'add', values: addValues})\n                .then(res => {\n                  for (let pos in res.failures) {\n                    // Remove from deleteKeys the key of the object that failed to change its primary key\n                    deleteKeys.splice(parseInt(pos), 1);\n                  }\n                  applyMutateResult(addValues.length, res);\n                })\n            ).then(()=>(putValues.length > 0 || (criteria && typeof changes === 'object')) &&\n                coreTable.mutate({\n                  trans,\n                  type: 'put',\n                  keys: putKeys,\n                  values: putValues,\n                  criteria,\n                  changeSpec: typeof changes !== 'function'\n                    && changes,\n                  isAdditionalChunk: offset > 0\n                }).then(res=>applyMutateResult(putValues.length, res))\n            ).then(()=>(deleteKeys.length > 0 || (criteria && isUnconditionalDelete)) &&\n                coreTable.mutate({\n                  trans,\n                  type: 'delete',\n                  keys: deleteKeys,\n                  criteria,\n                  isAdditionalChunk: offset > 0\n                }).then(res => builtInDeletionTrigger(ctx.table, deleteKeys, res))\n                .then(res => applyMutateResult(deleteKeys.length, res))\n            ).then(()=>{\n              return keys.length > offset + count && nextChunk(offset + limit);\n            });\n          });\n        }\n\n        return nextChunk(0).then(()=>{\n          if (totalFailures.length > 0)\n            throw new ModifyError(\"Error modifying one or more objects\", totalFailures, successCount, failedKeys as IndexableTypeArrayReadonly);\n\n          return keys.length;\n        });\n      });\n\n    });\n  }\n\n  /** Collection.delete()\n   * \n   * https://dexie.org/docs/Collection/Collection.delete()\n   * \n   **/\n  delete() : PromiseExtended<number> {\n    var ctx = this._ctx,\n      range = ctx.range;\n      //deletingHook = ctx.table.hook.deleting.fire,\n      //hasDeleteHook = deletingHook !== nop;\n    if (isPlainKeyRange(ctx) &&\n      !ctx.table.schema.yProps && // No Y documents on this table (requires special care)\n      (ctx.isPrimKey || range.type === DBCoreRangeType.Any)) // if no range, we'll use clear().\n    {\n      // May use IDBObjectStore.delete(IDBKeyRange) in this case (Issue #208)\n      // For chromium, this is the way most optimized version.\n      // For IE/Edge, this could hang the indexedDB engine and make operating system instable\n      // (https://gist.github.com/dfahlander/5a39328f029de18222cf2125d56c38f7)\n      return this._write(trans => {\n        // Our API contract is to return a count of deleted items, so we have to count() before delete().\n        const {primaryKey} = ctx.table.core.schema;\n        const coreRange = range;\n\n        return ctx.table.core.count({trans, query: {index: primaryKey, range: coreRange}}).then(count => {\n          return ctx.table.core.mutate({trans, type: 'deleteRange', range: coreRange})\n          .then(({failures, numFailures}) => {\n            if (numFailures) throw new ModifyError(\"Could not delete some values\",\n              Object.keys(failures).map(pos => failures[pos]),\n              count - numFailures);\n            return count - numFailures;\n          });\n        });\n      });\n    }\n\n    return this.modify(deleteCallback);\n  }\n}\n\nconst deleteCallback = (value, ctx) => ctx.value = null;\n"
  },
  {
    "path": "src/classes/collection/index.ts",
    "content": "export * from './collection';\nexport * from './collection-constructor';\n"
  },
  {
    "path": "src/classes/dexie/dexie-dom-dependencies.ts",
    "content": "import { _global } from '../../globals/global';\nimport { DexieDOMDependencies } from '../../public/types/dexie-dom-dependencies';\n\nexport let domDeps: DexieDOMDependencies\n\ntry {\n  domDeps = {\n    // Required:\n    indexedDB: _global.indexedDB || _global.mozIndexedDB || _global.webkitIndexedDB || _global.msIndexedDB,\n    IDBKeyRange: _global.IDBKeyRange || _global.webkitIDBKeyRange\n  };\n} catch (e) {\n  domDeps = { indexedDB: null, IDBKeyRange: null };\n}\n"
  },
  {
    "path": "src/classes/dexie/dexie-open.ts",
    "content": "import { Dexie } from './dexie';\nimport * as Debug from '../../helpers/debug';\nimport { rejection } from '../../helpers/promise';\nimport { exceptions } from '../../errors';\nimport { eventRejectHandler, preventDefault } from '../../functions/event-wrappers';\nimport Promise, { wrap } from '../../helpers/promise';\nimport { connections } from '../../globals/connections';\nimport { runUpgraders, readGlobalSchema, adjustToExistingIndexNames, verifyInstalledSchema, patchCurrentVersion } from '../version/schema-helpers';\nimport { safariMultiStoreFix } from '../../functions/quirks';\nimport { _onDatabaseCreated } from '../../helpers/database-enumerator';\nimport { vip } from './vip';\nimport { promisableChain, nop } from '../../functions/chaining-functions';\nimport { generateMiddlewareStacks } from './generate-middleware-stacks';\nimport { slice } from '../../functions/utils';\nimport safari14Workaround from 'safari-14-idb-fix';\nimport { type ObservabilitySet } from '../../public/types/db-events';\nimport { RangeSet } from '../../helpers/rangeset';\nimport { DEXIE_STORAGE_MUTATED_EVENT_NAME, globalEvents } from '../../globals/global-events';\nimport { signalSubscribersNow } from '../../live-query/cache/signalSubscribers';\n\nexport function dexieOpen (db: Dexie) {\n  const state = db._state;\n  const {indexedDB} = db._deps;\n  if (state.isBeingOpened || db.idbdb)\n      return state.dbReadyPromise.then<Dexie>(() => state.dbOpenError ?\n        rejection (state.dbOpenError) :\n        db);\n  state.isBeingOpened = true;\n  state.dbOpenError = null;\n  state.openComplete = false;\n  const openCanceller = state.openCanceller;\n  let nativeVerToOpen = Math.round(db.verno * 10);\n  let schemaPatchMode = false;\n\n  function throwIfCancelled() {\n    // If state.openCanceller object reference is replaced, it means db.close() has been called,\n    // meaning this open flow should be cancelled.\n    if (state.openCanceller !== openCanceller) throw new exceptions.DatabaseClosed('db.open() was cancelled');\n  }\n  \n  // Function pointers to call when the core opening process completes.\n  let resolveDbReady = state.dbReadyResolve,\n      // upgradeTransaction to abort on failure.\n      upgradeTransaction: (IDBTransaction | null) = null,\n      wasCreated = false;\n\n  const tryOpenDB = () => new Promise((resolve, reject) => {\n    throwIfCancelled();\n    // If no API, throw!\n    if (!indexedDB) throw new exceptions.MissingAPI();\n    const dbName = db.name;\n    \n    const req = state.autoSchema || !nativeVerToOpen ?\n      indexedDB.open(dbName) :\n      indexedDB.open(dbName, nativeVerToOpen);\n    if (!req) throw new exceptions.MissingAPI(); // May happen in Safari private mode, see https://github.com/dfahlander/Dexie.js/issues/134\n    req.onerror = eventRejectHandler(reject);\n    req.onblocked = wrap(db._fireOnBlocked);\n    req.onupgradeneeded = wrap (e => {\n        upgradeTransaction = req.transaction;\n        if (state.autoSchema && !db._options.allowEmptyDB) { // Unless an addon has specified db._allowEmptyDB, lets make the call fail.\n            // Caller did not specify a version or schema. Doing that is only acceptable for opening alread existing databases.\n            // If onupgradeneeded is called it means database did not exist. Reject the open() promise and make sure that we\n            // do not create a new database by accident here.\n            req.onerror = preventDefault; // Prohibit onabort error from firing before we're done!\n            upgradeTransaction.abort(); // Abort transaction (would hope that this would make DB disappear but it doesnt.)\n            // Close database and delete it.\n            req.result.close();\n            const delreq = indexedDB.deleteDatabase(dbName); // The upgrade transaction is atomic, and javascript is single threaded - meaning that there is no risk that we delete someone elses database here!\n            delreq.onsuccess = delreq.onerror = wrap(() => {\n                reject (new exceptions.NoSuchDatabase(`Database ${dbName} doesnt exist`));\n            });\n        } else {\n            upgradeTransaction.onerror = eventRejectHandler(reject);\n            const oldVer = e.oldVersion > Math.pow(2, 62) ? 0 : e.oldVersion; // Safari 8 fix.\n            wasCreated = oldVer < 1;\n            db.idbdb = req.result;\n            if (schemaPatchMode) {\n              patchCurrentVersion(db, upgradeTransaction);\n            }\n            runUpgraders(db, oldVer / 10, upgradeTransaction, reject);\n        }\n    }, reject);\n    \n    req.onsuccess = wrap (() => {\n        // Core opening procedure complete. Now let's just record some stuff.\n        upgradeTransaction = null;\n        const idbdb = db.idbdb = req.result;\n\n        const objectStoreNames = slice(idbdb.objectStoreNames);\n        if (objectStoreNames.length > 0) try {\n          const tmpTrans = idbdb.transaction(safariMultiStoreFix(objectStoreNames), 'readonly');\n          if (state.autoSchema) readGlobalSchema(db, idbdb, tmpTrans);\n          else {\n              adjustToExistingIndexNames(db, db._dbSchema, tmpTrans);\n              if (!verifyInstalledSchema(db, tmpTrans) && !schemaPatchMode) {\n                console.warn(`Dexie SchemaDiff: Schema was extended without increasing the number passed to db.version(). Dexie will add missing parts and increment native version number to workaround this.`);\n                idbdb.close();\n                nativeVerToOpen = idbdb.version + 1;\n                schemaPatchMode = true;\n                return resolve (tryOpenDB()); // Try again with new version (nativeVerToOpen\n              }\n          }\n          generateMiddlewareStacks(db, tmpTrans);\n        } catch (e) {\n          // Safari 8 may bail out if > 1 store names. However, this shouldnt be a showstopper. Issue #120.\n          // BUGBUG: It will bail out anyway as of Dexie 3.\n          // Should we support Safari 8 anymore? Believe all\n          // Dexie users use the shim for that platform anyway?!\n          // If removing Safari 8 support, go ahead and remove the safariMultiStoreFix() function\n          // as well as absurd upgrade version quirk for Safari.\n        }\n        \n        connections.add(db);\n\n        idbdb.onversionchange = wrap(ev => {\n            state.vcFired = true; // detect implementations that not support versionchange (IE/Edge/Safari)\n            db.on(\"versionchange\").fire(ev);\n        });\n        \n        idbdb.onclose = wrap(() => {\n          // Resolve issue #2186: Once Dexie.on.close is triggered, Dexie.isOpen() is still true.\n          // Let the code path be the same as for db.close() so that db.isOpen() returns false\n          // and every other state is reset the same way.          \n          db.close({ disableAutoOpen: false })\n        });\n\n        if (wasCreated) _onDatabaseCreated(db._deps, dbName);\n\n        resolve();\n\n    }, reject);\n  }).catch(err => {\n    switch (err?.name) {\n      case \"UnknownError\":\n        if (state.PR1398_maxLoop > 0) {\n          // Bug in Chrome after clearing site data\n          // https://github.com/dexie/Dexie.js/issues/543#issuecomment-1795736695\n          state.PR1398_maxLoop--;\n          console.warn('Dexie: Workaround for Chrome UnknownError on open()');\n          return tryOpenDB();\n        }\n        break;\n      case \"VersionError\":\n        if (nativeVerToOpen > 0) {\n          nativeVerToOpen = 0;\n          return tryOpenDB();\n        }\n        break;\n    }\n    return Promise.reject(err);\n  });\n  \n  // safari14Workaround = Workaround by jakearchibald for new nasty bug in safari 14.\n  return Promise.race([\n    openCanceller,\n    (typeof navigator === 'undefined' ? Promise.resolve() : safari14Workaround()).then(tryOpenDB)\n  ]).then(() => {\n      // Before finally resolving the dbReadyPromise and this promise,\n      // call and await all on('ready') subscribers:\n      // Dexie.vip() makes subscribers able to use the database while being opened.\n      // This is a must since these subscribers take part of the opening procedure.\n      throwIfCancelled();\n      state.onReadyBeingFired = [];\n      return Promise.resolve(vip(()=>db.on.ready.fire(db.vip))).then(function fireRemainders() {\n          if (state.onReadyBeingFired.length > 0) {\n              // In case additional subscribers to db.on('ready') were added during the time db.on.ready.fire was executed.\n              let remainders = state.onReadyBeingFired.reduce(promisableChain, nop);\n              state.onReadyBeingFired = [];\n              return Promise.resolve(vip(()=>remainders(db.vip))).then(fireRemainders)\n          }\n      });\n  }).finally(()=>{\n      if (state.openCanceller === openCanceller) {\n        // Only modify state if not cancelled in the mean time.\n        state.onReadyBeingFired = null;\n        state.isBeingOpened = false;\n      }\n  }).catch(err => {\n      state.dbOpenError = err; // Record the error. It will be used to reject further promises of db operations.\n      try {\n        // Did we fail within onupgradeneeded? Make sure to abort the upgrade transaction so it doesnt commit.\n        upgradeTransaction && upgradeTransaction.abort();\n      } catch { }\n      if (openCanceller === state.openCanceller) {\n        // Still in the same open flow - The error reason was not due to external call to db.close().\n        // Make sure to call db.close() to finalize resources.\n        db._close(); // Closes and resets idbdb, removes connections, resets dbReadyPromise and openCanceller so that a later db.open() is fresh.\n      }\n      return rejection (err);\n  }).finally(()=>{\n    state.openComplete = true;\n    resolveDbReady(); // dbReadyPromise is resolved no matter if open() rejects or resolved. It's just to wake up waiters.\n  }).then(()=>{\n    if (wasCreated) {\n      // Propagate full range on primary keys and indexes on all tables now that the DB is ready and opened,\n      // and all upgraders and on('ready') subscribers have run.\n      const everything: ObservabilitySet = {};\n      db.tables.forEach(table => {\n        table.schema.indexes.forEach(idx => {\n          if (idx.name) everything[`idb://${db.name}/${table.name}/${idx.name}`] = new RangeSet(-Infinity, [[[]]]);\n        });\n        everything[`idb://${db.name}/${table.name}/`] = everything[`idb://${db.name}/${table.name}/:dels`] = new RangeSet(-Infinity, [[[]]]);\n      });\n      // Database was created. If another tab had it open when it was deleted and reopened, that tab must be updated now.\n      globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME).fire(everything);\n      // Wipe the cache and trigger optimistic queries:\n      signalSubscribersNow(everything, true);\n    }\n    // Resolve the db.open() with the db instance.\n    return db;\n  });\n}\n"
  },
  {
    "path": "src/classes/dexie/dexie-static-props.ts",
    "content": "import { Dexie as _Dexie } from './dexie';\nimport { _global } from '../../globals/global';\nimport { props, derive, extend, override, getByKeyPath, setByKeyPath, delByKeyPath, shallowClone, deepClone, asap } from '../../functions/utils';\nimport { getObjectDiff } from \"../../functions/get-object-diff\";\nimport { fullNameExceptions } from '../../errors';\nimport { DexieConstructor } from '../../public/types/dexie-constructor';\nimport { getDatabaseNames } from '../../helpers/database-enumerator';\nimport { PSD } from '../../helpers/promise';\nimport { usePSD } from '../../helpers/promise';\nimport { newScope } from '../../helpers/promise';\nimport { rejection } from '../../helpers/promise';\nimport { awaitIterator } from '../../helpers/yield-support';\nimport Promise from '../../helpers/promise';\nimport * as Debug from '../../helpers/debug';\nimport { dexieStackFrameFilter, minKey, DEXIE_VERSION } from '../../globals/constants';\nimport Events from '../../helpers/Events';\nimport { exceptions } from '../../errors';\nimport { errnames } from '../../errors';\nimport { getMaxKey } from '../../functions/quirks';\nimport { vip } from './vip';\nimport { globalEvents } from '../../globals/global-events';\nimport { liveQuery } from '../../live-query/live-query';\nimport { extendObservabilitySet } from '../../live-query/extend-observability-set';\nimport { domDeps } from './dexie-dom-dependencies';\nimport { cmp } from '../../functions/cmp';\nimport { cache } from '../../live-query/cache/cache';\nimport { connections } from '../../globals/connections';\n\n/* (Dexie) is an instance of DexieConstructor, as defined in public/types/dexie-constructor.d.ts\n*  (new Dexie()) is an instance of Dexie, as defined in public/types/dexie.d.ts\n* \n* Why we're doing this?\n\n* Because we've choosen to define the public Dexie API using a DexieConstructor interface\n* rather than declaring a class. On that interface, all static props are defined.\n* In practice, class Dexie's constructor implements DexieConstructor and all member props\n* are defined in interface Dexie. We could say, it's a typescript limitation of not being\n* able to define a static interface that forces us to do the cast below.\n*/\nconst Dexie = _Dexie as any as DexieConstructor;\n\n//\n// Set all static methods and properties onto Dexie:\n// \nprops(Dexie, {\n\n  // Dexie.BulkError = class BulkError {...};\n  // Dexie.XXXError = class XXXError {...};\n  ...fullNameExceptions,\n\n  //\n  // Static delete() method.\n  //\n  delete(databaseName: string) {\n    const db = new Dexie(databaseName, {addons: []});\n    return db.delete();\n  },\n\n  //\n  // Static exists() method.\n  //\n  exists(name: string) {\n    return new Dexie(name, { addons: [] }).open().then(db => {\n      db.close();\n      return true;\n    }).catch('NoSuchDatabaseError', () => false);\n  },\n\n  //\n  // Static method for retrieving a list of all existing databases at current host.\n  //\n  getDatabaseNames(cb) {\n    try {\n      return getDatabaseNames(Dexie.dependencies).then(cb);\n    } catch {\n      return rejection(new exceptions.MissingAPI());\n    }\n  },\n\n  /** @deprecated */\n  defineClass() {\n    function Class(content) {\n      extend(this, content);\n    }\n    return Class;\n  },\n\n  ignoreTransaction(scopeFunc) {\n    // In case caller is within a transaction but needs to create a separate transaction.\n    // Example of usage:\n    //\n    // Let's say we have a logger function in our app. Other application-logic should be unaware of the\n    // logger function and not need to include the 'logentries' table in all transaction it performs.\n    // The logging should always be done in a separate transaction and not be dependant on the current\n    // running transaction context. Then you could use Dexie.ignoreTransaction() to run code that starts a new transaction.\n    //\n    //     Dexie.ignoreTransaction(function() {\n    //         db.logentries.add(newLogEntry);\n    //     });\n    //\n    // Unless using Dexie.ignoreTransaction(), the above example would try to reuse the current transaction\n    // in current Promise-scope.\n    //\n    // An alternative to Dexie.ignoreTransaction() would be setImmediate() or setTimeout(). The reason we still provide an\n    // API for this because\n    //  1) The intention of writing the statement could be unclear if using setImmediate() or setTimeout().\n    //  2) setTimeout() would wait unnescessary until firing. This is however not the case with setImmediate().\n    //  3) setImmediate() is not supported in the ES standard.\n    //  4) You might want to keep other PSD state that was set in a parent PSD, such as PSD.letThrough.\n    return PSD.trans ?\n      usePSD(PSD.transless, scopeFunc) : // Use the closest parent that was non-transactional.\n      scopeFunc(); // No need to change scope because there is no ongoing transaction.\n  },\n\n  vip,\n\n  async: function (generatorFn: Function) {\n    return function () {\n      try {\n        var rv = awaitIterator(generatorFn.apply(this, arguments));\n        if (!rv || typeof rv.then !== 'function')\n          return Promise.resolve(rv);\n        return rv;\n      } catch (e) {\n        return rejection(e);\n      }\n    };\n  },\n\n  spawn: function (generatorFn, args, thiz) {\n    try {\n      var rv = awaitIterator(generatorFn.apply(thiz, args || []));\n      if (!rv || typeof rv.then !== 'function')\n        return Promise.resolve(rv);\n      return rv;\n    } catch (e) {\n      return rejection(e);\n    }\n  },\n\n  // Dexie.currentTransaction property\n  currentTransaction: {\n    get: () => PSD.trans || null\n  },\n\n  waitFor: function (promiseOrFunction, optionalTimeout) {\n    // If a function is provided, invoke it and pass the returning value to Transaction.waitFor()\n    const promise = Promise.resolve(\n      typeof promiseOrFunction === 'function' ?\n        Dexie.ignoreTransaction(promiseOrFunction) :\n        promiseOrFunction)\n      .timeout(optionalTimeout || 60000); // Default the timeout to one minute. Caller may specify Infinity if required.       \n\n    // Run given promise on current transaction. If no current transaction, just return a Dexie promise based\n    // on given value.\n    return PSD.trans ?\n      PSD.trans.waitFor(promise) :\n      promise;\n  },\n\n  // Export our Promise implementation since it can be handy as a standalone Promise implementation\n  Promise: Promise,\n\n  // Dexie.debug proptery:\n  // Dexie.debug = false\n  // Dexie.debug = true\n  // Dexie.debug = \"dexie\" - don't hide dexie's stack frames.\n  debug: {\n    get: () => Debug.debug,\n    set: value => {\n      Debug.setDebug(value, value === 'dexie' ? () => true : dexieStackFrameFilter);\n    }\n  },\n\n  // Export our derive/extend/override methodology\n  derive: derive, // Deprecate?\n  extend: extend, // Deprecate?\n  props: props,\n  override: override, // Deprecate?\n  // Export our Events() function - can be handy as a toolkit\n  Events: Events,\n  on: globalEvents,\n  liveQuery,\n  extendObservabilitySet,\n  // Utilities\n  getByKeyPath: getByKeyPath,\n  setByKeyPath: setByKeyPath,\n  delByKeyPath: delByKeyPath,\n  shallowClone: shallowClone,\n  deepClone: deepClone,\n  getObjectDiff: getObjectDiff,\n  cmp,\n  asap: asap,\n  //maxKey: new Dexie('',{addons:[]})._maxKey,\n  minKey: minKey,\n  // Addon registry\n  addons: [],\n  // Global DB connection list\n  connections: {\n    get: connections.toArray\n  },\n\n  //MultiModifyError: exceptions.Modify, // Obsolete!\n  errnames: errnames,\n\n  // Export other static classes\n  //IndexSpec: IndexSpec, Obsolete!\n  //TableSchema: TableSchema, Obsolete!\n\n  //\n  // Dependencies\n  //\n  // These will automatically work in browsers with indexedDB support, or where an indexedDB polyfill has been included.\n  //\n  // In node.js, however, these properties must be set \"manually\" before instansiating a new Dexie().\n  // For node.js, you need to require indexeddb-js or similar and then set these deps.\n  //\n  dependencies: domDeps,\n  cache,\n\n  // API Version Number: Type Number, make sure to always set a version number that can be comparable correctly. Example: 0.9, 0.91, 0.92, 1.0, 1.01, 1.1, 1.2, 1.21, etc.\n  semVer: DEXIE_VERSION,\n  version: DEXIE_VERSION.split('.')\n    .map(n => parseInt(n))\n    .reduce((p, c, i) => p + (c / Math.pow(10, i * 2))),\n\n  // https://github.com/dfahlander/Dexie.js/issues/186\n  // typescript compiler tsc in mode ts-->es5 & commonJS, will expect require() to return\n  // x.default. Workaround: Set Dexie.default = Dexie.\n  // default: Dexie, // Commented because solved in index-umd.ts instead.\n  // Make it possible to import {Dexie} (non-default import)\n  // Reason 1: May switch to that in future.\n  // Reason 2: We declare it both default and named exported in d.ts to make it possible\n  // to let addons extend the Dexie interface with Typescript 2.1 (works only when explicitely\n  // exporting the symbol, not just default exporting)\n  // Dexie: Dexie // Commented because solved in index-umd.ts instead.\n});\n\nDexie.maxKey = getMaxKey(Dexie.dependencies.IDBKeyRange);\n"
  },
  {
    "path": "src/classes/dexie/dexie.ts",
    "content": "// Import types from the public API\nimport { Dexie as IDexie } from \"../../public/types/dexie\";\nimport { DexieOptions, DexieConstructor } from \"../../public/types/dexie-constructor\";\nimport { DbEvents, DbEventFns } from \"../../public/types/db-events\";\n//import { PromiseExtended, PromiseExtendedConstructor } from '../../public/types/promise-extended';\nimport { Table as ITable } from '../../public/types/table';\nimport { TableSchema } from \"../../public/types/table-schema\";\nimport { DbSchema } from '../../public/types/db-schema';\n\n// Internal imports\nimport { Table, TableConstructor, createTableConstructor } from \"../table\";\nimport { Collection, CollectionConstructor, createCollectionConstructor } from '../collection';\nimport { WhereClause } from '../where-clause/where-clause';\nimport { WhereClauseConstructor, createWhereClauseConstructor } from '../where-clause/where-clause-constructor';\nimport { Transaction } from '../transaction';\nimport { TransactionConstructor, createTransactionConstructor } from '../transaction/transaction-constructor';\nimport { Version } from \"../version/version\";\nimport { VersionConstructor, createVersionConstructor } from '../version/version-constructor';\n\n// Other imports...\nimport { DexieEventSet } from '../../public/types/dexie-event-set';\nimport { DexieExceptionClasses } from '../../public/types/errors';\nimport { DexieDOMDependencies } from '../../public/types/dexie-dom-dependencies';\nimport { nop, promisableChain } from '../../functions/chaining-functions';\nimport Promise, { PSD, globalPSD } from '../../helpers/promise';\nimport { extend, override, keys, hasOwn } from '../../functions/utils';\nimport Events from '../../helpers/Events';\nimport { maxString, READONLY, READWRITE, DEFAULT_MAX_CONNECTIONS } from '../../globals/constants';\nimport { getMaxKey } from '../../functions/quirks';\nimport { exceptions } from '../../errors';\nimport { lowerVersionFirst } from '../version/schema-helpers';\nimport { dexieOpen } from './dexie-open';\nimport { wrap } from '../../helpers/promise';\nimport { _onDatabaseDeleted } from '../../helpers/database-enumerator';\nimport { eventRejectHandler } from '../../functions/event-wrappers';\nimport { extractTransactionArgs, enterTransactionScope } from './transaction-helpers';\nimport { TransactionMode } from '../../public/types/transaction-mode';\nimport { rejection } from '../../helpers/promise';\nimport { usePSD } from '../../helpers/promise';\nimport { DBCore } from '../../public/types/dbcore';\nimport { Middleware, DexieStacks } from '../../public/types/middleware';\nimport { virtualIndexMiddleware } from '../../dbcore/virtual-index-middleware';\nimport { hooksMiddleware } from '../../hooks/hooks-middleware';\nimport { IndexableType } from '../../public';\nimport { observabilityMiddleware } from '../../live-query/observability-middleware';\nimport { cacheExistingValuesMiddleware } from '../../dbcore/cache-existing-values-middleware';\nimport { cacheMiddleware } from \"../../live-query/cache/cache-middleware\";\nimport { vipify } from \"../../helpers/vipify\";\nimport { connections } from \"../../globals/connections\";\n\nexport interface DbReadyState {\n  dbOpenError: any;\n  isBeingOpened: boolean;\n  onReadyBeingFired: undefined | Function[];\n  openComplete: boolean;\n  dbReadyResolve: () => void;\n  dbReadyPromise: Promise<any>;\n  cancelOpen: (reason?: Error) => void;\n  openCanceller: Promise<any> & { _stackHolder?: Error };\n  autoSchema: boolean;\n  vcFired?: boolean;\n  PR1398_maxLoop?: number;\n  autoOpen?: boolean;\n}\n\nexport class Dexie implements IDexie {\n  _options: DexieOptions;\n  _state: DbReadyState;\n  _versions: Version[];\n  _storeNames: string[];\n  _deps: DexieDOMDependencies;\n  _allTables: { [name: string]: Table; };\n  _createTransaction: (this: Dexie, mode: IDBTransactionMode, storeNames: ArrayLike<string>, dbschema: { [tableName: string]: TableSchema; }, parentTransaction?: Transaction) => Transaction;\n  _dbSchema: { [tableName: string]: TableSchema; };\n  _hasGetAll?: boolean;\n  _maxKey: IndexableType;\n  _fireOnBlocked: (ev: Event) => void;\n  _middlewares: {[StackName in keyof DexieStacks]?: Middleware<DexieStacks[StackName]>[]} = {};\n  _vip?: boolean;\n  _novip: Dexie;// db._novip is to escape to orig db from db.vip.\n  core: DBCore;\n\n  name: string;\n  verno: number = 0;\n  idbdb: IDBDatabase | null;\n  vip: Dexie;\n  on: DbEvents;\n  once: DbEventFns;\n\n  Table: TableConstructor;\n  WhereClause: WhereClauseConstructor;\n  Collection: CollectionConstructor;\n  Version: VersionConstructor;\n  Transaction: TransactionConstructor;\n  static disableBfCache?: boolean;\n\n  constructor(name: string, options?: DexieOptions) {\n    const deps = (Dexie as any as DexieConstructor).dependencies;\n    this._options = options = {\n      // Default Options\n      addons: (Dexie as any as DexieConstructor).addons, // Pick statically registered addons by default\n      autoOpen: true,                 // Don't require db.open() explicitely.\n      // Default DOM dependency implementations from static prop.\n      indexedDB: deps.indexedDB,      // Backend IndexedDB api. Default to browser env.\n      IDBKeyRange: deps.IDBKeyRange,  // Backend IDBKeyRange api. Default to browser env.\n      cache: 'cloned', // Default to cloned for backward compatibility. For best performance and least memory consumption use 'immutable'.\n      maxConnections: DEFAULT_MAX_CONNECTIONS, // Default max connections\n      ...options\n    };  \n    this._deps = {\n      indexedDB: options.indexedDB as IDBFactory,\n      IDBKeyRange: options.IDBKeyRange as typeof IDBKeyRange\n    };\n    const {\n      addons,\n    } = options;\n    this._dbSchema = {};\n    this._versions = [];\n    this._storeNames = [];\n    this._allTables = {};\n    this.idbdb = null;\n    this._novip = this;\n    const state: DbReadyState = {\n      dbOpenError: null,\n      isBeingOpened: false,\n      onReadyBeingFired: null,\n      openComplete: false,\n      dbReadyResolve: nop,\n      dbReadyPromise: null as Promise,\n      cancelOpen: nop,\n      openCanceller: null as Promise,\n      autoSchema: true,\n      PR1398_maxLoop: 3,\n      autoOpen: options.autoOpen,\n    };\n    state.dbReadyPromise = new Promise(resolve => {\n      state.dbReadyResolve = resolve;\n    });\n    state.openCanceller = new Promise((_, reject) => {\n      state.cancelOpen = reject;\n    });\n    this._state = state;\n    this.name = name;\n    this.on = Events(this,\n      \"populate\",\n      \"blocked\",\n      \"versionchange\",\n      \"close\",\n      { ready: [promisableChain, nop] }\n    ) as DbEvents;\n    this.once = (event: any, callback: any) => {\n      const fn = (...args: any[]) => {\n        this.on(event).unsubscribe(fn);\n        callback.apply(this, args);\n      };\n      return this.on(event as any, fn);\n    };\n    this.on.ready.subscribe = override(this.on.ready.subscribe, subscribe => {\n      return (subscriber, bSticky) => {\n        (Dexie as any as DexieConstructor).vip(() => {\n          const state = this._state;\n          if (state.openComplete) {\n            // Database already open. Call subscriber asap.\n            if (!state.dbOpenError) Promise.resolve().then(subscriber);\n            // bSticky: Also subscribe to future open sucesses (after close / reopen) \n            if (bSticky) subscribe(subscriber);\n          } else if (state.onReadyBeingFired) {\n            // db.on('ready') subscribers are currently being executed and have not yet resolved or rejected\n            state.onReadyBeingFired.push(subscriber);\n            if (bSticky) subscribe(subscriber);\n          } else {\n            // Database not yet open. Subscribe to it.\n            subscribe(subscriber);\n            // If bSticky is falsy, make sure to unsubscribe subscriber when fired once.\n            const db = this;\n            if (!bSticky) subscribe(function unsubscribe() {\n              db.on.ready.unsubscribe(subscriber);\n              db.on.ready.unsubscribe(unsubscribe);\n            });\n          }\n        });\n      }\n    });\n\n    // Create derived classes bound to this instance of Dexie:\n    this.Collection = createCollectionConstructor(this);\n    this.Table = createTableConstructor(this);\n    this.Transaction = createTransactionConstructor(this);\n    this.Version = createVersionConstructor(this);\n    this.WhereClause = createWhereClauseConstructor(this);\n\n    // Default subscribers to \"versionchange\" and \"blocked\".\n    // Can be overridden by custom handlers. If custom handlers return false, these default\n    // behaviours will be prevented.\n    this.on(\"versionchange\", ev => {\n      // Default behavior for versionchange event is to close database connection.\n      // Caller can override this behavior by doing db.on(\"versionchange\", function(){ return false; });\n      // Let's not block the other window from making it's delete() or open() call.\n      // NOTE! This event is never fired in IE,Edge or Safari.\n      if (ev.newVersion > 0)\n        console.warn(`Another connection wants to upgrade database '${this.name}'. Closing db now to resume the upgrade.`);\n      else\n        console.warn(`Another connection wants to delete database '${this.name}'. Closing db now to resume the delete request.`);\n      this.close({disableAutoOpen: false});\n      // In many web applications, it would be recommended to force window.reload()\n      // when this event occurs. To do that, subscribe to the versionchange event\n      // and call window.location.reload(true) if ev.newVersion > 0 (not a deletion)\n      // The reason for this is that your current web app obviously has old schema code that needs\n      // to be updated. Another window got a newer version of the app and needs to upgrade DB but\n      // your window is blocking it unless we close it here.\n    });\n    this.on(\"blocked\", ev => {\n      if (!ev.newVersion || ev.newVersion < ev.oldVersion)\n        console.warn(`Dexie.delete('${this.name}') was blocked`);\n      else\n        console.warn(`Upgrade '${this.name}' blocked by other connection holding version ${ev.oldVersion / 10}`);\n    });\n\n    this._maxKey = getMaxKey(options.IDBKeyRange as typeof IDBKeyRange);\n\n    this._createTransaction = (\n      mode: IDBTransactionMode,\n      storeNames: string[],\n      dbschema: DbSchema,\n      parentTransaction?: Transaction) => new this.Transaction(mode, storeNames, dbschema, this._options.chromeTransactionDurability, parentTransaction);\n\n    this._fireOnBlocked = ev => {\n      this.on(\"blocked\").fire(ev);\n      // Workaround (not fully*) for missing \"versionchange\" event in IE,Edge and Safari:\n      // TODO: Should be safe to remove this workaround below! Do it in another PR.\n      connections.toArray()\n        .filter(c => c.name === this.name && c !== this && !c._state.vcFired)\n        .map(c => c.on(\"versionchange\").fire(ev));\n    }\n\n    // Default middlewares:\n    this.use(cacheExistingValuesMiddleware);\n    this.use(cacheMiddleware);\n    this.use(observabilityMiddleware);\n    this.use(virtualIndexMiddleware);\n    this.use(hooksMiddleware);\n\n    const vipDB = new Proxy(this, {\n      get: (_, prop, receiver) => {\n        if (prop === '_vip') return true;\n        if (prop === 'table') return (tableName: string) => vipify(this.table(tableName), vipDB);\n        const rv = Reflect.get(_, prop, receiver);\n        if (rv instanceof Table) return vipify(rv, vipDB);\n        if (prop === 'tables') return (rv as Table[]).map(t => vipify(t, vipDB));\n        if (prop === '_createTransaction') return function() {\n          const tx: Transaction = (rv as typeof this._createTransaction).apply(this, arguments);\n          return vipify(tx, vipDB);\n        }\n        return rv;\n      }\n    });\n    this.vip = vipDB;\n\n    // Call each addon:\n    addons.forEach(addon => addon(this));\n  }\n\n  version(versionNumber: number): Version {\n    if (isNaN(versionNumber) || versionNumber < 0.1) throw new exceptions.Type(`Given version is not a positive number`);\n    versionNumber = Math.round(versionNumber * 10) / 10;\n    if (this.idbdb || this._state.isBeingOpened)\n      throw new exceptions.Schema(\"Cannot add version when database is open\");\n    this.verno = Math.max(this.verno, versionNumber);\n    const versions = this._versions;\n    var versionInstance = versions.filter(\n      v => v._cfg.version === versionNumber)[0];\n    if (versionInstance) return versionInstance;\n    versionInstance = new this.Version(versionNumber);\n    versions.push(versionInstance);\n    versions.sort(lowerVersionFirst);\n    versionInstance.stores({}); // Derive earlier schemas by default.\n    // Disable autoschema mode, as at least one version is specified.\n    this._state.autoSchema = false;\n    return versionInstance;\n  }\n\n  _whenReady<T>(fn: () => Promise<T>): Promise<T> {\n    return (this.idbdb && (this._state.openComplete || PSD.letThrough || this._vip)) ? fn() : new Promise<T>((resolve, reject) => {\n      if (this._state.openComplete) {\n        // idbdb is falsy but openComplete is true. Must have been an exception durin open.\n        // Don't wait for openComplete as it would lead to infinite loop.\n        return reject(new exceptions.DatabaseClosed(this._state.dbOpenError));\n      }\n      if (!this._state.isBeingOpened) {\n        if (!this._state.autoOpen) {\n          reject(new exceptions.DatabaseClosed());\n          return;\n        }\n        this.open().catch(nop); // Open in background. If if fails, it will be catched by the final promise anyway.\n      }\n      this._state.dbReadyPromise.then(resolve, reject);\n    }).then(fn);\n  }\n\n  use({stack, create, level, name}: Middleware<DBCore>): this {\n    if (name) this.unuse({stack, name}); // Be able to replace existing middleware.\n    const middlewares = this._middlewares[stack] || (this._middlewares[stack] = []);\n    middlewares.push({stack, create, level: level == null ? 10 : level, name});\n    middlewares.sort((a, b) => a.level - b.level);\n    // Todo update db.core and db.tables...core ? Or should be expect this to have effect\n    // only after next open()?\n    return this;\n  }\n\n  unuse({stack, create}: Middleware<{stack: keyof DexieStacks}>): this;\n  unuse({stack, name}: {stack: keyof DexieStacks, name: string}): this;\n  unuse({stack, name, create}: {stack: keyof DexieStacks, name?: string, create?: Function}) {\n    if (stack && this._middlewares[stack]) {\n      this._middlewares[stack] = this._middlewares[stack].filter(mw =>\n        create ? mw.create !== create : // Given middleware has a create method. Match that exactly.\n        name ? mw.name !== name : // Given middleware spec \n        false);\n    }\n    return this;\n  }\n\n  open() {\n    return usePSD(\n      globalPSD, // Enforce global scope here since db.open() can be part of a live query or transaction scope\n      () => dexieOpen(this)\n    );\n  }\n\n  _close(): void {\n    this.on.close.fire(new CustomEvent('close'));\n    const state = this._state;\n    connections.remove(this);\n    if (this.idbdb) {\n      try { this.idbdb.close(); } catch (e) { }\n      this.idbdb = null;\n    }    \n    // Reset dbReadyPromise promise:\n    if (!state.isBeingOpened) {\n      // Only if not being opened, reset these promises.\n      // Otherwise, keep them so existing promise consumers will resolve when db\n      // db is reopened later on, in case closing for purpose reopening, using {disableAutoOpen: false}.\n      state.dbReadyPromise = new Promise(resolve => {\n        state.dbReadyResolve = resolve;\n      });\n      state.openCanceller = new Promise((_, reject) => {\n        state.cancelOpen = reject;\n      });\n    }\n  }\n\n  close({disableAutoOpen} = {disableAutoOpen: true}): void {\n    const state = this._state;\n    if (disableAutoOpen) {\n      if (state.isBeingOpened) {\n        // cancel before the call to this._close() because this._close() will recreate dbReadyPromise and openCanceller.\n        state.cancelOpen(new exceptions.DatabaseClosed());\n      }\n      this._close();\n      state.autoOpen = false;\n      state.dbOpenError = new exceptions.DatabaseClosed();\n    } else {\n      this._close();\n      state.autoOpen = this._options.autoOpen ||\n        state.isBeingOpened; // If an open call is ongoing, that same promise will resolve when db is reopend.\n      state.openComplete = false;\n      state.dbOpenError = null;\n    }\n  }\n\n  delete(closeOptions = {disableAutoOpen: true}): Promise<void> {\n    // Prevent accidentially doing db.delete(1) when intention was to do db.[table].delete(1).\n    const hasInvalidArguments = arguments.length > 0 && typeof arguments[0] !== 'object'; \n    const state = this._state;\n    return new Promise((resolve, reject) => {\n      const doDelete = () => {\n        this.close(closeOptions);\n        var req = this._deps.indexedDB.deleteDatabase(this.name);\n        req.onsuccess = wrap(() => {\n          _onDatabaseDeleted(this._deps, this.name);\n          resolve();\n        });\n        req.onerror = eventRejectHandler(reject);\n        req.onblocked = this._fireOnBlocked;\n      }\n      // Prevent accidentially doing db.delete(1) when intention was to do db.[table].delete(1).\n      if (hasInvalidArguments) throw new exceptions.InvalidArgument(\"Invalid closeOptions argument to db.delete()\");\n      if (state.isBeingOpened) {\n        state.dbReadyPromise.then(doDelete);\n      } else {\n        doDelete();\n      }\n    });\n  }\n\n  backendDB() {\n    return this.idbdb;\n  }\n\n  isOpen() {\n    return this.idbdb !== null;\n  }\n\n  hasBeenClosed() {\n    const dbOpenError = this._state.dbOpenError;\n    return dbOpenError && (dbOpenError.name === 'DatabaseClosed');\n  }\n\n  hasFailed() {\n    return this._state.dbOpenError !== null;\n  }\n\n  dynamicallyOpened() {\n    return this._state.autoSchema;\n  }\n\n  get tables () {\n    return keys(this._allTables).map(name => this._allTables[name]);\n  }\n\n  transaction(): Promise {\n    const args = extractTransactionArgs.apply(this, arguments);\n    return this._transaction.apply(this, args);\n  }\n\n  _transaction(mode: TransactionMode, tables: Array<ITable | string>, scopeFunc: Function) {\n    let parentTransaction = PSD.trans as Transaction | undefined;\n    // Check if parent transactions is bound to this db instance, and if caller wants to reuse it\n    if (!parentTransaction || parentTransaction.db !== this || mode.indexOf('!') !== -1) parentTransaction = null;\n    const onlyIfCompatible = mode.indexOf('?') !== -1;\n    mode = mode.replace('!', '').replace('?', '') as TransactionMode; // Ok. Will change arguments[0] as well but we wont touch arguments henceforth.\n    let idbMode: IDBTransactionMode,\n        storeNames;\n\n    try {\n        //\n        // Get storeNames from arguments. Either through given table instances, or through given table names.\n        //\n        storeNames = tables.map(table => {\n            var storeName = table instanceof this.Table ? table.name : table;\n            if (typeof storeName !== 'string') throw new TypeError(\"Invalid table argument to Dexie.transaction(). Only Table or String are allowed\");\n            return storeName;\n        });\n\n        //\n        // Resolve mode. Allow shortcuts \"r\" and \"rw\".\n        //\n        if (mode == \"r\" || mode === READONLY)\n          idbMode = READONLY;\n        else if (mode == \"rw\" || mode == READWRITE)\n          idbMode = READWRITE;\n        else\n            throw new exceptions.InvalidArgument(\"Invalid transaction mode: \" + mode);\n\n        if (parentTransaction) {\n            // Basic checks\n            if (parentTransaction.mode === READONLY && idbMode === READWRITE) {\n                if (onlyIfCompatible) {\n                    // Spawn new transaction instead.\n                    parentTransaction = null; \n                }\n                else throw new exceptions.SubTransaction(\"Cannot enter a sub-transaction with READWRITE mode when parent transaction is READONLY\");\n            }\n            if (parentTransaction) {\n                storeNames.forEach(storeName => {\n                    if (parentTransaction && parentTransaction.storeNames.indexOf(storeName) === -1) {\n                        if (onlyIfCompatible) {\n                            // Spawn new transaction instead.\n                            parentTransaction = null; \n                        }\n                        else throw new exceptions.SubTransaction(\"Table \" + storeName +\n                            \" not included in parent transaction.\");\n                    }\n                });\n            }\n            if (onlyIfCompatible && parentTransaction && !parentTransaction.active) {\n                // '?' mode should not keep using an inactive transaction.\n                parentTransaction = null;\n            }\n        }\n    } catch (e) {\n        return parentTransaction ?\n            parentTransaction._promise(null, (_, reject) => {reject(e);}) :\n            rejection (e);\n    }\n    // If this is a sub-transaction, lock the parent and then launch the sub-transaction.\n    const enterTransaction = enterTransactionScope.bind(null, this, idbMode, storeNames, parentTransaction, scopeFunc);\n    return (parentTransaction ?\n        parentTransaction._promise(idbMode, enterTransaction, \"lock\") :\n        PSD.trans ?\n            // no parent transaction despite PSD.trans exists. Make sure also\n            // that the zone we create is not a sub-zone of current, because\n            // Promise.follow() should not wait for it if so.\n            usePSD(PSD.transless, ()=>this._whenReady(enterTransaction)) :\n            this._whenReady (enterTransaction));\n  }\n\n  table(tableName: string): Table;\n  table<T, TKey extends IndexableType=IndexableType>(tableName: string): ITable<T, TKey>;\n  table(tableName: string): Table {\n    if (!hasOwn(this._allTables, tableName)) {\n      throw new exceptions.InvalidTable(`Table ${tableName} does not exist`); }\n    return this._allTables[tableName];\n  }\n}\n"
  },
  {
    "path": "src/classes/dexie/generate-middleware-stacks.ts",
    "content": "import { Dexie } from './';\nimport { createDBCore } from '../../dbcore/dbcore-indexeddb';\nimport { DBCore } from '../../public/types/dbcore';\nimport { DexieDOMDependencies } from '../../public/types/dexie-dom-dependencies';\nimport { DexieStacks, Middleware } from '../../public/types/middleware';\nimport { exceptions } from '../../errors';\n\nfunction createMiddlewareStack<TStack extends {stack: string}>(\n  stackImpl: {stack: string},\n  middlewares: Middleware<{stack: string}>[]): TStack {\n  return middlewares.reduce((down, {create}) => ({...down, ...create(down)}), stackImpl) as TStack;\n} \n\nfunction createMiddlewareStacks(\n  middlewares: {[StackName in keyof DexieStacks]?: Middleware<DexieStacks[StackName]>[]},\n  idbdb: IDBDatabase,\n  {IDBKeyRange, indexedDB}: DexieDOMDependencies,\n  tmpTrans: IDBTransaction): {[StackName in keyof DexieStacks]?: DexieStacks[StackName]}\n{\n  const dbcore = createMiddlewareStack<DBCore>(\n    createDBCore(idbdb, IDBKeyRange, tmpTrans),\n    middlewares.dbcore);\n  \n  // TODO: Create other stacks the same way as above. They might be dependant on the result\n  // of creating dbcore stack.\n\n  return {\n    dbcore\n  };\n}\n\nexport function generateMiddlewareStacks(db: Dexie, tmpTrans: IDBTransaction) {\n  const idbdb = tmpTrans.db;\n  const stacks = createMiddlewareStacks(db._middlewares, idbdb, db._deps, tmpTrans);\n  db.core = stacks.dbcore!;\n  db.tables.forEach(table => {\n    const tableName = table.name;\n    if (db.core.schema.tables.some(tbl => tbl.name === tableName)) {\n      table.core = db.core.table(tableName);\n      if (db[tableName] instanceof db.Table) {\n          db[tableName].core = table.core;\n      }\n    }\n  });\n}\n"
  },
  {
    "path": "src/classes/dexie/index.ts",
    "content": "export * from './dexie';\n"
  },
  {
    "path": "src/classes/dexie/transaction-helpers.ts",
    "content": "import { TransactionMode } from '../../public/types/transaction-mode';\nimport { errnames, exceptions } from '../../errors';\nimport { flatten, isAsyncFunction } from '../../functions/utils';\nimport { Dexie } from './dexie';\nimport { Transaction } from '../transaction';\nimport { awaitIterator } from '../../helpers/yield-support';\nimport Promise, {\n  PSD,\n  NativePromise,\n  decrementExpectedAwaits,\n  rejection,\n  incrementExpectedAwaits\n} from '../../helpers/promise';\n\nexport function extractTransactionArgs(mode: TransactionMode, _tableArgs_, scopeFunc) {\n  // Let table arguments be all arguments between mode and last argument.\n  var i = arguments.length;\n  if (i < 2) throw new exceptions.InvalidArgument(\"Too few arguments\");\n  // Prevent optimzation killer (https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments)\n  // and clone arguments except the first one into local var 'args'.\n  var args = new Array(i - 1);\n  while (--i) args[i - 1] = arguments[i];\n  // Let scopeFunc be the last argument and pop it so that args now only contain the table arguments.\n  scopeFunc = args.pop();\n  var tables = flatten(args); // Support using array as middle argument, or a mix of arrays and non-arrays.\n  return [mode, tables, scopeFunc];\n}\n\nexport function enterTransactionScope(\n  db: Dexie,\n  mode: IDBTransactionMode,\n  storeNames: string[],\n  parentTransaction: Transaction | undefined,\n  scopeFunc: ()=>PromiseLike<any> | any\n) {\n  return Promise.resolve().then(() => {\n    // Keep a pointer to last non-transactional PSD to use if someone calls Dexie.ignoreTransaction().\n    const transless = PSD.transless || PSD;\n    // Our transaction.\n    //return new Promise((resolve, reject) => {\n    const trans = db._createTransaction(mode, storeNames, db._dbSchema, parentTransaction);\n    trans.explicit = true;\n    // Let the transaction instance be part of a Promise-specific data (PSD) value.\n    const zoneProps = {\n      trans: trans,\n      transless: transless\n    };\n\n    if (parentTransaction) {\n      // Emulate transaction commit awareness for inner transaction (must 'commit' when the inner transaction has no more operations ongoing)\n      trans.idbtrans = parentTransaction.idbtrans;\n    } else {\n      try {\n        trans.create(); // Create the native transaction so that complete() or error() will trigger even if no operation is made upon it.\n        // @ts-ignore Mark the idbtrans object with \"_explicit\". DBCore middleware won't have access to Dexie trans but will need to have this info.\n        trans.idbtrans._explicit = true;\n        db._state.PR1398_maxLoop = 3;\n      } catch (ex) {\n        if (ex.name === errnames.InvalidState && db.isOpen() && --db._state.PR1398_maxLoop > 0) {\n          console.warn('Dexie: Need to reopen db');\n          db.close({disableAutoOpen: false});\n          return db.open().then(() => enterTransactionScope(\n            db,\n            mode,\n            storeNames,\n            null,\n            scopeFunc\n          ));\n        }\n        return rejection(ex);\n      }\n    }\n\n    // Support for native async await.\n    const scopeFuncIsAsync = isAsyncFunction(scopeFunc);\n    if (scopeFuncIsAsync) {\n      incrementExpectedAwaits();\n    }\n\n    let returnValue;\n    const promiseFollowed = Promise.follow(() => {\n      // Finally, call the scope function with our table and transaction arguments.\n      returnValue = scopeFunc.call(trans, trans);\n      if (returnValue) {\n        if (scopeFuncIsAsync) {\n          // scopeFunc is a native async function - we know for sure returnValue is native promise.\n          var decrementor = decrementExpectedAwaits.bind(null, null);\n          returnValue.then(decrementor, decrementor);\n        } else if (typeof returnValue.next === 'function' && typeof returnValue.throw === 'function') {\n          // scopeFunc returned an iterator with throw-support. Handle yield as await.\n          returnValue = awaitIterator(returnValue);\n        }\n      }\n    }, zoneProps);\n    return (returnValue && typeof returnValue.then === 'function' ?\n      // Promise returned. User uses promise-style transactions.\n      Promise.resolve(returnValue).then(x => trans.active ?\n        x // Transaction still active. Continue.\n        : rejection(new exceptions.PrematureCommit(\n          \"Transaction committed too early. See http://bit.ly/2kdckMn\")))\n      // No promise returned. Wait for all outstanding promises before continuing. \n      : promiseFollowed.then(() => returnValue)\n    ).then(x => {\n      // sub transactions don't react to idbtrans.oncomplete. We must trigger a completion:\n      if (parentTransaction) trans._resolve();\n      // wait for trans._completion\n      // (if root transaction, this means 'complete' event. If sub-transaction, we've just fired it ourselves)\n      return trans._completion.then(() => x);\n    }).catch(e => {\n      trans._reject(e); // Yes, above then-handler were maybe not called because of an unhandled rejection in scopeFunc!\n      return rejection(e);\n    });\n  });\n}\n"
  },
  {
    "path": "src/classes/dexie/vip.ts",
    "content": "import { newScope } from '../../helpers/promise';\nimport { PSD } from '../../helpers/promise';\n\nexport function vip (fn) {\n  // To be used by subscribers to the on('ready') event.\n  // This will let caller through to access DB even when it is blocked while the db.ready() subscribers are firing.\n  // This would have worked automatically if we were certain that the Provider was using Dexie.Promise for all asyncronic operations. The promise PSD\n  // from the provider.connect() call would then be derived all the way to when provider would call localDatabase.applyChanges(). But since\n  // the provider more likely is using non-promise async APIs or other thenable implementations, we cannot assume that.\n  // Note that this method is only useful for on('ready') subscribers that is returning a Promise from the event. If not using vip()\n  // the database could deadlock since it wont open until the returned Promise is resolved, and any non-VIPed operation started by\n  // the caller will not resolve until database is opened.\n  return newScope(function () {\n    PSD.letThrough = true; // Make sure we are let through if still blocking db due to onready is firing.\n    return fn();\n  });\n}\n\n"
  },
  {
    "path": "src/classes/entity/Entity.ts",
    "content": "import { exceptions } from \"../../errors\";\n\nexport function Entity(){\n  throw exceptions.Type(`Entity instances must never be new:ed. Instances are generated by the framework bypassing the constructor.`);\n}\n"
  },
  {
    "path": "src/classes/observable/observable.ts",
    "content": "import {\n  Observable as IObservable,\n  Observer,\n  Subscription,\n} from \"../../public/types/observable\";\n\nconst symbolObservable: typeof Symbol.observable =\n  typeof Symbol !== \"undefined\" && \"observable\" in Symbol\n    ? Symbol.observable\n    : \"@@observable\" as any;\n\nexport class Observable<T> implements IObservable<T> {\n  private _subscribe: (observer: Observer<T>) => Subscription;\n  hasValue?: ()=>boolean;\n  getValue?: ()=>T;\n\n  constructor(subscribe: (observer: Observer<T>) => Subscription) {\n    this._subscribe = subscribe;\n  }\n\n  subscribe(\n    onNext?: ((value: T) => void) | null,\n    onError?: ((error: any) => void) | null,\n    onComplete?: (() => void) | null\n  ): Subscription;\n  subscribe(observer?: Observer<T> | null): Subscription;\n  subscribe(x?: any, error?: any, complete?: any): Subscription {\n    return this._subscribe(\n      !x || typeof x === \"function\" ? { next: x, error, complete } : x\n    );\n  }\n\n  [symbolObservable]() {\n    return this;\n  }\n}\n"
  },
  {
    "path": "src/classes/table/index.ts",
    "content": "export * from './table';\nexport * from './table-constructor';\n"
  },
  {
    "path": "src/classes/table/table-constructor.ts",
    "content": "import { Dexie } from '../dexie';\nimport { TableSchema } from '../../public/types/table-schema';\nimport { Transaction } from '../transaction/transaction';\nimport { hookCreatingChain, pureFunctionChain, nop, mirror, hookUpdatingChain, hookDeletingChain } from '../../functions/chaining-functions';\nimport { TableHooks } from '../../public/types/table-hooks';\nimport { Table } from './table';\nimport Events from '../../helpers/Events';\nimport { makeClassConstructor } from '../../functions/make-class-constructor';\n\nexport interface TableConstructor {\n  new (name: string, tableSchema: TableSchema, optionalTrans?: Transaction) : Table;\n  prototype: Table;\n}\n\n/** Generates a Table constructor bound to given Dexie instance.\n * \n * The purpose of having dynamically created constructors, is to allow\n * addons to extend classes for a certain Dexie instance without affecting\n * other db instances.\n */\nexport function createTableConstructor (db: Dexie) {\n  return makeClassConstructor<TableConstructor>(\n    Table.prototype,\n\n    function Table (this: Table, name: string, tableSchema: TableSchema, trans?: Transaction) {\n      this.db = db;\n      this._tx = trans;\n      this.name = name;\n      this.schema = tableSchema;\n      this.hook = db._allTables[name] ? db._allTables[name].hook : Events(null, {\n        \"creating\": [hookCreatingChain, nop],\n        \"reading\": [pureFunctionChain, mirror],\n        \"updating\": [hookUpdatingChain, nop],\n        \"deleting\": [hookDeletingChain, nop]\n      }) as TableHooks;\n    }\n\n  );\n}\n"
  },
  {
    "path": "src/classes/table/table-helpers.ts",
    "content": "import { DBCoreMutateResponse } from \"../../public/types/dbcore\";\nimport { Table } from \"./table\";\n\n\nexport function builtInDeletionTrigger (table: Table, keys: null | readonly any[], res: DBCoreMutateResponse): DBCoreMutateResponse | Promise<DBCoreMutateResponse> {\n  // Delete related document updates. Otherwise, if a row with same ID is created\n  // again, its document would not be empty.\n  // Document providers will get notified on the main table's row deletion and destroy\n  // document. Sync of this action is outside of the Y.js scope but will be handled\n  // by the dexie cloud sync layer or equivalent sync layer.\n  const { yProps } = table.schema;\n  if (!yProps) return res;\n  if (keys && res.numFailures > 0) keys = keys.filter((_, i) => !res.failures[i]);\n  return Promise.all(yProps.map(({updatesTable}) => \n    keys\n    ? table.db.table(updatesTable).where('k').anyOf(keys).delete()\n    : table.db.table(updatesTable).clear()\n  )).then(() => res);\n}"
  },
  {
    "path": "src/classes/table/table.ts",
    "content": "import { BulkError, exceptions } from '../../errors';\nimport { Table as ITable } from '../../public/types/table';\nimport { TableSchema } from '../../public/types/table-schema';\nimport { TableHooks } from '../../public/types/table-hooks';\nimport { DexiePromise as Promise, PSD, newScope, rejection, beginMicroTickScope, endMicroTickScope } from '../../helpers/promise';\nimport { Transaction } from '../transaction';\nimport { Dexie } from '../dexie';\nimport { tempTransaction } from '../../functions/temp-transaction';\nimport { Collection } from '../collection';\nimport { isArray, keys, getByKeyPath, setByKeyPath, extend, getProto } from '../../functions/utils';\nimport { maxString } from '../../globals/constants';\nimport { combine } from '../../functions/combine';\nimport { PromiseExtended } from \"../../public/types/promise-extended\";\nimport { IndexableType } from '../../public/types/indexable-type';\nimport { debug } from '../../helpers/debug';\nimport { DBCoreTable } from '../../public/types/dbcore';\nimport { AnyRange } from '../../dbcore/keyrange';\nimport { workaroundForUndefinedPrimKey } from '../../functions/workaround-undefined-primkey';\nimport { Entity } from '../entity/Entity';\nimport { UpdateSpec } from '../../public';\nimport { cmp } from '../../functions/cmp';\nimport { builtInDeletionTrigger } from './table-helpers';\nimport { applyUpdateSpec } from '../../functions/apply-update-spec';\n\n/** class Table\n * \n * https://dexie.org/docs/Table/Table\n */\nexport class Table implements ITable<any, IndexableType> {\n  db: Dexie;\n  _tx?: Transaction;\n  name: string;\n  schema: TableSchema;\n  hook: TableHooks;\n  core: DBCoreTable;\n\n  _trans(\n    mode: IDBTransactionMode,\n    fn: (idbtrans: IDBTransaction, dxTrans: Transaction) => PromiseLike<any> | void,\n    writeLocked?: boolean | string) : PromiseExtended<any>\n  {\n    const trans: Transaction = this._tx || PSD.trans;\n    const tableName = this.name;\n    // @ts-ignore: Use Chrome's Async Stack Tagging API to allow tracing and simplify debugging for dexie users.\n    const task = debug && typeof console !== 'undefined' && console.createTask && console.createTask(`Dexie: ${mode === 'readonly' ? 'read' : 'write' } ${this.name}`);\n    \n    function checkTableInTransaction(resolve, reject, trans: Transaction) {\n      if (!trans.schema[tableName])\n        throw new exceptions.NotFound(\"Table \" + tableName + \" not part of transaction\");\n      return fn(trans.idbtrans, trans) as Promise<any>;\n    }\n    // Surround all in a microtick scope.\n    // Reason: Browsers (modern Safari + older others)\n    // still as of 2018-10-10 has problems keeping a transaction\n    // alive between micro ticks. Safari because if transaction\n    // is created but not used in same microtick, it will go\n    // away. That specific issue could be solved in DBCore\n    // by opening the transaction just before using it instead.\n    // But older Firefoxes and IE11 (with Promise polyfills)\n    // will still have probs.\n    // The beginMicrotickScope()/endMicrotickScope() works\n    // in cooperation with Dexie.Promise to orchestrate\n    // the micro-ticks in endMicrotickScope() rather than\n    // in native engine.\n    const wasRootExec = beginMicroTickScope();\n    try {\n      let p = trans && trans.db._novip === this.db._novip ?\n        trans === PSD.trans ?\n          trans._promise(mode, checkTableInTransaction, writeLocked) :\n          newScope(() => trans._promise(mode, checkTableInTransaction, writeLocked), { trans: trans, transless: PSD.transless || PSD }) :\n        tempTransaction(this.db, mode, [this.name], checkTableInTransaction);\n      if (task) { // Dexie.debug = true so we trace errors\n        p._consoleTask = task;\n        p = p.catch(err => {\n          console.trace(err);\n          return rejection(err);\n        });\n      }\n      return p;  \n    } finally {\n      if (wasRootExec) endMicroTickScope();\n    }\n  }\n\n  /** Table.get()\n   * \n   * https://dexie.org/docs/Table/Table.get()\n   * \n   **/\n  get(keyOrCrit, cb?) {\n    if (keyOrCrit && keyOrCrit.constructor === Object)\n      return this.where(keyOrCrit as { [key: string]: IndexableType }).first(cb);\n    if (keyOrCrit == null) return rejection(new exceptions.Type(`Invalid argument to Table.get()`));\n\n    return this._trans('readonly', (trans) => {\n      return this.core.get({trans, key: keyOrCrit})\n        .then(res => this.hook.reading.fire(res));\n    }).then(cb);\n  }\n\n  /** Table.where()\n   * \n   * https://dexie.org/docs/Table/Table.where()\n   * \n   **/\n  where(indexOrCrit: string | string[] | { [key: string]: IndexableType }) {\n    if (typeof indexOrCrit === 'string')\n      return new this.db.WhereClause(this, indexOrCrit);\n    if (isArray(indexOrCrit))\n      return new this.db.WhereClause(this, `[${indexOrCrit.join('+')}]`);\n    // indexOrCrit is an object map of {[keyPath]:value} \n    const keyPaths = keys(indexOrCrit);\n    if (keyPaths.length === 1)\n      // Only one critera. This was the easy case:\n      return this\n        .where(keyPaths[0])\n        .equals(indexOrCrit[keyPaths[0]]);\n\n    // Multiple criterias.\n    // Let's try finding a compound index that matches all keyPaths in\n    // arbritary order:\n    const compoundIndex = this.schema.indexes.concat(this.schema.primKey).filter(ix => {\n      if (\n        ix.compound &&\n        keyPaths.every(keyPath => ix.keyPath.indexOf(keyPath) >= 0)) {\n          for (let i=0; i<keyPaths.length; ++i) {\n            if (keyPaths.indexOf(ix.keyPath[i]) === -1) return false;\n          }\n          return true;\n        }\n        return false;\n      }).sort((a,b) => a.keyPath.length - b.keyPath.length)[0];\n            \n    if (compoundIndex && this.db._maxKey !== maxString) {\n      // Cool! We found such compound index\n      // and this browser supports compound indexes (maxKey !== maxString)!\n      const keyPathsInValidOrder = (compoundIndex.keyPath as string[]).slice(0, keyPaths.length);\n      return this\n        .where(keyPathsInValidOrder)\n        .equals(keyPathsInValidOrder.map(kp => indexOrCrit[kp]));\n    }\n\n    if (!compoundIndex && debug) console.warn(\n      `The query ${JSON.stringify(indexOrCrit)} on ${this.name} would benefit from a ` +\n      `compound index [${keyPaths.join('+')}]`);\n\n    // Ok, now let's fallback to finding at least one matching index\n    // and filter the rest.\n    const { idxByName } = this.schema;\n\n    function equals(a, b) {\n      return cmp(a, b) === 0; // Works with all indexable types including binary keys.\n    }\n\n    const [idx, filterFunction] = keyPaths.reduce(([prevIndex, prevFilterFn], keyPath) => {\n      const index = idxByName[keyPath];\n      const value = indexOrCrit[keyPath];\n      return [\n        prevIndex || index, // idx::=Pick index of first matching keypath\n        prevIndex || !index ? // filter::=null if not needed, otherwise combine function filter\n          combine(\n            prevFilterFn,\n            index && index.multi ?\n              x => {\n                const prop = getByKeyPath(x, keyPath);\n                return isArray(prop) && prop.some(item => equals(value, item));\n              } : x => equals(value, getByKeyPath(x, keyPath)))\n          : prevFilterFn\n      ];\n    }, [null, null]);\n\n    return idx ?\n      this.where(idx.name).equals(indexOrCrit[idx.keyPath])\n        .filter(filterFunction) :\n      compoundIndex ?\n        this.filter(filterFunction) : // Has compound but browser bad. Allow filter.\n        this.where(keyPaths).equals(''); // No index at all. Fail lazily with \"[a+b+c] is not indexed\"\n  }\n\n  /** Table.filter()\n   * \n   * https://dexie.org/docs/Table/Table.filter()\n   * \n   **/\n  filter(filterFunction: (obj: any) => boolean) {\n    return this.toCollection().and(filterFunction);\n  }\n\n  /** Table.count()\n   * \n   * https://dexie.org/docs/Table/Table.count()\n   * \n   **/\n  count(thenShortcut?: any) {\n    return this.toCollection().count(thenShortcut);\n  }\n\n  /** Table.offset()\n   * \n   * https://dexie.org/docs/Table/Table.offset()\n   * \n   **/\n  offset(offset: number) {\n    return this.toCollection().offset(offset);\n  }\n\n  /** Table.limit()\n   * \n   * https://dexie.org/docs/Table/Table.limit()\n   * \n   **/\n  limit(numRows: number) {\n    return this.toCollection().limit(numRows);\n  }\n\n  /** Table.each()\n   * \n   * https://dexie.org/docs/Table/Table.each()\n   * \n   **/\n  each(callback: (obj: any, cursor: { key: IndexableType, primaryKey: IndexableType }) => any) {\n    return this.toCollection().each(callback);\n  }\n\n  /** Table.toArray()\n   * \n   * https://dexie.org/docs/Table/Table.toArray()\n   * \n   **/\n  toArray(thenShortcut?: any) {\n    return this.toCollection().toArray(thenShortcut);\n  }\n\n  /** Table.toCollection()\n   * \n   * https://dexie.org/docs/Table/Table.toCollection()\n   * \n   **/\n  toCollection() {\n    return new this.db.Collection(new this.db.WhereClause(this));\n  }\n\n  /** Table.orderBy()\n   * \n   * https://dexie.org/docs/Table/Table.orderBy()\n   * \n   **/\n  orderBy(index: string | string[]) {\n    return new this.db.Collection(\n      new this.db.WhereClause(this, isArray(index) ?\n        `[${index.join('+')}]` :\n        index));\n  }\n\n  /** Table.reverse()\n   * \n   * https://dexie.org/docs/Table/Table.reverse()\n   * \n   **/\n  reverse(): Collection {\n    return this.toCollection().reverse();\n  }\n\n  /** Table.mapToClass()\n   * \n   * https://dexie.org/docs/Table/Table.mapToClass()\n   * \n   **/\n  mapToClass(constructor: Function) {\n    const {db, name: tableName} = this;\n    this.schema.mappedClass = constructor;\n    if (constructor.prototype instanceof Entity) {\n      constructor = class extends (constructor as any) {\n        get db () { return db; }\n        table() { return tableName; }\n      }\n    }\n    // Collect all inherited property names (including method names) by\n    // walking the prototype chain. This is to avoid overwriting them from\n    // database data - so application code can rely on inherited props never\n    // becoming shadowed by database object props.\n    const inheritedProps = new Set<string>();\n    for (let proto = constructor.prototype; proto; proto = getProto(proto)) {\n      Object.getOwnPropertyNames(proto).forEach(propName => inheritedProps.add(propName));\n    }\n  \n    // Now, subscribe to the when(\"reading\") event to make all objects that come out from this table inherit from given class\n    // no matter which method to use for reading (Table.get() or Table.where(...)... )\n    const readHook = (obj: Object) => {\n      if (!obj) return obj; // No valid object. (Value is null or undefined). Return as is.\n      // Create a new object that derives from constructor:\n      const res = Object.create(constructor.prototype);\n      // Clone members (but never those that collide with a property in the prototype\n      // hierchary (MUST BE ABLE TO RELY ON Entity methods and props!)):\n      for (let m in obj) if (!inheritedProps.has(m)) try { res[m] = obj[m]; } catch (_) { }\n      return res;\n    };\n\n    if (this.schema.readHook) {\n      this.hook.reading.unsubscribe(this.schema.readHook);\n    }\n    this.schema.readHook = readHook;\n    this.hook(\"reading\", readHook);\n    return constructor;\n  }\n\n  /** @deprecated */\n  defineClass() {\n    function Class (content){\n      extend(this, content);\n    };\n    return this.mapToClass(Class);\n  }\n\n  /** Table.add()\n   * \n   * https://dexie.org/docs/Table/Table.add()\n   * \n   **/\n  add(obj, key?: IndexableType): PromiseExtended<IndexableType> {\n    const {auto, keyPath} = this.schema.primKey;\n    let objToAdd = obj;\n    if (keyPath && auto) {\n      objToAdd = workaroundForUndefinedPrimKey(keyPath)(obj);\n    }\n    return this._trans('readwrite', trans => {\n      return this.core.mutate({trans, type: 'add', keys: key != null ? [key] : null, values: [objToAdd]});\n    }).then(res => res.numFailures ? Promise.reject(res.failures[0]) : res.lastResult)\n    .then(lastResult => {\n      if (keyPath) {\n        // This part should be here for backward compatibility.\n        // If ever feeling too bad about this, please wait to a new major before removing it,\n        // and document the change thoroughly.\n        try{setByKeyPath(obj, keyPath, lastResult);}catch(_){};\n      }\n      return lastResult;\n    });\n  }\n\n  /** Table.upsert()\n   * \n   * https://dexie.org/docs/Table/Table.upsert()\n   * \n   **/\n  upsert(key: IndexableType, modifications: { [keyPath: string]: any; }): PromiseExtended<boolean> {\n    const {keyPath} = this.schema.primKey;\n    return this._trans('readwrite', trans => {\n      return this.core.get({trans, key}).then(existing => {\n        const obj = existing ?? {};\n        applyUpdateSpec(obj, modifications);\n        if (keyPath) setByKeyPath(obj, keyPath, key);\n        return this.core.mutate({\n          trans,\n          type: 'put',\n          values: [obj],\n          keys: [key],\n          upsert: true,\n          updates: {keys: [key], changeSpecs: [modifications]}\n        }).then(res => res.numFailures ? Promise.reject(res.failures[0]) : !!existing);\n      });\n    });\n  }\n\n  /** Table.update()\n   * \n   * https://dexie.org/docs/Table/Table.update()\n   * \n   **/\n  update(keyOrObject, modifications: { [keyPath: string]: any; } | ((obj: any, ctx:{value: any, primKey: IndexableType}) => void | boolean)): PromiseExtended<number> {\n    if (typeof keyOrObject === 'object' && !isArray(keyOrObject)) {\n      const key = getByKeyPath(keyOrObject, this.schema.primKey.keyPath);\n      if (key === undefined) return rejection(new exceptions.InvalidArgument(\n        \"Given object does not contain its primary key\"));\n      /*// object to modify. Also modify given object with the modifications:\n      // This part should be here for backward compatibility.\n      // If ever feeling too bad about mutating given object, please wait to a new major before removing it,\n      // and document the change thoroughly. TODO: Document this change!\n      if (!Object.isFrozen(keyOrObject)) try {\n        if (typeof modifications !== \"function\") {\n          keys(modifications).forEach(keyPath => {\n            setByKeyPath(keyOrObject, keyPath, modifications[keyPath]);\n          });\n        } else {\n          // Now since we support function argument, we should have a similar behavior here as well\n          // (as long as we do this mutability stuff on the given object)\n          modifications(keyOrObject, {value: keyOrObject, primKey: key});\n        }\n      } catch {\n        // Maybe given object was frozen.\n        // This part is not essential. Just move on as nothing happened...\n      }*/\n      return this.where(\":id\").equals(key).modify(modifications);\n    } else {\n      // key to modify\n      return this.where(\":id\").equals(keyOrObject).modify(modifications);\n    }\n  }\n\n  /** Table.put()\n   * \n   * https://dexie.org/docs/Table/Table.put()\n   * \n   **/\n  put(obj, key?: IndexableType): PromiseExtended<IndexableType> {\n    const {auto, keyPath} = this.schema.primKey;\n    let objToAdd = obj;\n    if (keyPath && auto) {\n      objToAdd = workaroundForUndefinedPrimKey(keyPath)(obj);\n    }\n    return this._trans(\n      'readwrite',\n      trans => this.core.mutate({trans, type: 'put', values: [objToAdd], keys: key != null ? [key] : null}))\n    .then(res => res.numFailures ? Promise.reject(res.failures[0]) : res.lastResult)\n    .then(lastResult => {\n      if (keyPath) {\n        // This part should be here for backward compatibility.\n        // If ever feeling too bad about this, please wait to a new major before removing it,\n        // and document the change thoroughly.\n        try{setByKeyPath(obj, keyPath, lastResult);}catch(_){};\n      }\n      return lastResult;\n    });\n  }\n\n  /** Table.delete()\n   * \n   * https://dexie.org/docs/Table/Table.delete()\n   * \n   **/\n  delete(key: IndexableType): PromiseExtended<void> {\n    return this._trans('readwrite',\n      trans => this.core.mutate({trans, type: 'delete', keys: [key]})\n        .then(res => builtInDeletionTrigger(this, [key], res))\n        .then(res => res.numFailures ? Promise.reject(res.failures[0]) : undefined));\n    ;\n  }\n\n  /** Table.clear()\n   * \n   * https://dexie.org/docs/Table/Table.clear()\n   * \n   **/\n  clear() {\n    return this._trans('readwrite',\n      trans => this.core.mutate({trans, type: 'deleteRange', range: AnyRange})\n        .then(res => builtInDeletionTrigger(this, null, res)))\n      .then(res => res.numFailures ? Promise.reject(res.failures[0]) : undefined);\n  }\n\n  /** Table.bulkGet()\n   * \n   * https://dexie.org/docs/Table/Table.bulkGet()\n   * \n   * @param keys \n   */\n  bulkGet(keys: IndexableType[]) {\n    return this._trans('readonly', trans => {\n      return this.core.getMany({\n        keys,\n        trans\n      }).then(result => result.map(res => this.hook.reading.fire(res)));\n    });\n  }\n\n  /** Table.bulkAdd()\n   * \n   * https://dexie.org/docs/Table/Table.bulkAdd()\n   * \n   **/\n  bulkAdd(\n    objects: readonly any[],\n    keysOrOptions?: ReadonlyArray<IndexableType> | { allKeys?: boolean },\n    options?: { allKeys?: boolean }\n  ) {    \n    const keys = Array.isArray(keysOrOptions) ? keysOrOptions : undefined;\n    options = options || (keys ? undefined : keysOrOptions as { allKeys?: boolean });\n    const wantResults = options ? options.allKeys : undefined;\n\n    return this._trans('readwrite', trans => {\n      const {auto, keyPath} = this.schema.primKey;\n      if (keyPath && keys)\n        throw new exceptions.InvalidArgument(\"bulkAdd(): keys argument invalid on tables with inbound keys\");\n      if (keys && keys.length !== objects.length)\n        throw new exceptions.InvalidArgument(\"Arguments objects and keys must have the same length\");\n\n      const numObjects = objects.length; // Pick length here to allow garbage collection of objects later\n      let objectsToAdd = keyPath && auto ?\n        objects.map(workaroundForUndefinedPrimKey(keyPath)) :\n        objects;\n      return this.core.mutate(\n        {trans, type: 'add', keys: keys as IndexableType[], values: objectsToAdd, wantResults}\n      )\n        .then(({numFailures, results,lastResult, failures}) => {\n          const result = wantResults ? results : lastResult;\n          if (numFailures === 0) return result;\n          throw new BulkError(\n            `${this.name}.bulkAdd(): ${numFailures} of ${numObjects} operations failed`, failures);\n        });\n    });\n  }\n\n  /** Table.bulkPut()\n   * \n   * https://dexie.org/docs/Table/Table.bulkPut()\n   * \n   **/\n  bulkPut(\n    objects: readonly any[],\n    keysOrOptions?: ReadonlyArray<IndexableType> | { allKeys?: boolean },\n    options?: { allKeys?: boolean }\n  ) {   \n    const keys = Array.isArray(keysOrOptions) ? keysOrOptions : undefined;\n    options = options || (keys ? undefined : keysOrOptions as { allKeys?: boolean });\n    const wantResults = options ? options.allKeys : undefined;\n\n    return this._trans('readwrite', trans => {\n      const {auto, keyPath} = this.schema.primKey;\n      if (keyPath && keys)\n        throw new exceptions.InvalidArgument(\"bulkPut(): keys argument invalid on tables with inbound keys\");\n      if (keys && keys.length !== objects.length)\n        throw new exceptions.InvalidArgument(\"Arguments objects and keys must have the same length\");\n\n      const numObjects = objects.length; // Pick length here to allow garbage collection of objects later\n      let objectsToPut = keyPath && auto ?\n        objects.map(workaroundForUndefinedPrimKey(keyPath)) :\n        objects;\n\n      return this.core.mutate(\n        {trans, type: 'put', keys: keys as IndexableType[], values: objectsToPut, wantResults}\n      )\n        .then(({numFailures, results, lastResult, failures}) => {\n          const result = wantResults ? results : lastResult;\n          if (numFailures === 0) return result;\n          throw new BulkError(\n            `${this.name}.bulkPut(): ${numFailures} of ${numObjects} operations failed`, failures);\n        });\n    });\n  }\n\n  /** Table.bulkUpdate()\n   *\n   * https://dexie.org/docs/Table.Table.bulkUpdate()\n   */\n   bulkUpdate(\n    keysAndChanges: readonly { key: any; changes: UpdateSpec<any> }[]\n  ): PromiseExtended<number> {\n    const coreTable = this.core;\n    const keys = keysAndChanges.map((entry) => entry.key);\n    const changeSpecs = keysAndChanges.map((entry) => entry.changes);\n    const offsetMap: number[] = [];\n    return this._trans('readwrite', (trans) => {\n      return coreTable.getMany({ trans, keys, cache: 'clone' }).then((objs) => {\n        const resultKeys: any[] = [];\n        const resultObjs: any[] = [];\n        keysAndChanges.forEach(({ key, changes }, idx) => {\n          const obj = objs[idx];\n          if (obj) {\n            for (const keyPath of Object.keys(changes)) {\n              const value = changes[keyPath];\n              if (keyPath === this.schema.primKey.keyPath) {\n                if (cmp(value, key) !== 0) {\n                  throw new exceptions.Constraint(\n                    `Cannot update primary key in bulkUpdate()`\n                  );\n                }\n              } else {\n                setByKeyPath(obj, keyPath, value);\n              }\n            }\n            offsetMap.push(idx);\n            resultKeys.push(key);\n            resultObjs.push(obj);\n          }\n        });\n        const numEntries = resultKeys.length;\n        return coreTable\n          .mutate({\n            trans,\n            type: 'put',\n            keys: resultKeys,\n            values: resultObjs,\n            updates: {\n              keys,\n              changeSpecs\n            }\n          })\n          .then(({ numFailures, failures }) => {\n            if (numFailures === 0) return numEntries;\n            // Failure. bulkPut() may have a subset of keys\n            // so we must translate returned 'failutes' into the offsets of given argument:\n            for (const offset of Object.keys(failures)) {\n              const mappedOffset = offsetMap[Number(offset)];\n              if (mappedOffset != null) {\n                const failure = failures[offset];\n                delete failures[offset];\n                failures[mappedOffset] = failure;\n              }\n            }\n            throw new BulkError(\n              `${this.name}.bulkUpdate(): ${numFailures} of ${numEntries} operations failed`,\n              failures\n            );\n          });\n      });\n    });\n  }\n\n  /** Table.bulkDelete()\n   * \n   * https://dexie.org/docs/Table/Table.bulkDelete()\n   * \n   **/\n  bulkDelete(keys: ReadonlyArray<IndexableType>): PromiseExtended<void> {\n    const numKeys = keys.length;\n    return this._trans('readwrite', trans => {\n      return this.core.mutate({trans, type: 'delete', keys: keys as IndexableType[]})\n        .then(res => builtInDeletionTrigger(this, keys, res));\n    }).then(({numFailures, lastResult, failures}) => {\n      if (numFailures === 0) return lastResult;\n      throw new BulkError(\n        `${this.name}.bulkDelete(): ${numFailures} of ${numKeys} operations failed`, failures);\n    });\n  }\n}\n"
  },
  {
    "path": "src/classes/transaction/index.ts",
    "content": "export * from './transaction';\nexport * from './transaction-constructor';\n"
  },
  {
    "path": "src/classes/transaction/transaction-constructor.ts",
    "content": "import { Dexie } from '../dexie';\nimport { makeClassConstructor } from '../../functions/make-class-constructor';\nimport { Transaction } from './transaction';\nimport { DbSchema } from '../../public/types/db-schema';\nimport Events from '../../helpers/Events';\nimport Promise, { rejection } from '../../helpers/promise';\n\nexport interface TransactionConstructor<T extends Transaction=Transaction> {\n  new (\n    mode: IDBTransactionMode,\n    storeNames: string[],\n    dbschema: DbSchema,\n    chromeTransactionDurability: IDBTransactionDurability,\n    parent?: Transaction) : T;\n  prototype: T;\n}\n\n/** Generates a Transaction constructor bound to given Dexie instance.\n * \n * The purpose of having dynamically created constructors, is to allow\n * addons to extend classes for a certain Dexie instance without affecting\n * other db instances.\n */\nexport function createTransactionConstructor(db: Dexie) {\n  return makeClassConstructor<TransactionConstructor<Transaction>>(\n    Transaction.prototype,\n    function Transaction (\n      this: Transaction,\n      mode: IDBTransactionMode,\n      storeNames: string[],\n      dbschema: DbSchema,\n      chromeTransactionDurability: IDBTransactionDurability,\n      parent?: Transaction)\n    {\n      if (mode !== 'readonly') storeNames.forEach(storeName => {\n        // Uncollapse storeName to include Y update tables in case deletion of a record - then we must also delete its Y updates.\n        const yProps = dbschema[storeName]?.yProps;\n        if (yProps) storeNames = storeNames.concat(yProps.map(p => p.updatesTable));\n      });\n\n      this.db = db;\n      this.mode = mode;\n      this.storeNames = storeNames;\n      this.schema = dbschema;\n      this.chromeTransactionDurability = chromeTransactionDurability;\n      this.idbtrans = null;\n      this.on = Events(this, \"complete\", \"error\", \"abort\");\n      this.parent = parent || null;\n      this.active = true;\n      this._reculock = 0;\n      this._blockedFuncs = [];\n      this._resolve = null;\n      this._reject = null;\n      this._waitingFor = null;\n      this._waitingQueue = null;\n      this._spinCount = 0; // Just for debugging waitFor()\n      this._completion = new Promise ((resolve, reject) => {\n          this._resolve = resolve;\n          this._reject = reject;\n      });\n      \n      this._completion.then(\n          ()=> {\n              this.active = false;\n              this.on.complete.fire();\n          },\n          e => {\n              var wasActive = this.active;\n              this.active = false;\n              this.on.error.fire(e);\n              this.parent ?\n                  this.parent._reject(e) :\n                  wasActive && this.idbtrans && this.idbtrans.abort();\n              return rejection(e); // Indicate we actually DO NOT catch this error.\n          });\n    \n    });\n}\n"
  },
  {
    "path": "src/classes/transaction/transaction.ts",
    "content": "import { Transaction as ITransaction } from '../../public/types/transaction';\nimport { DexiePromise, wrap, rejection } from \"../../helpers/promise\";\nimport { DbSchema } from '../../public/types/db-schema';\nimport { assert, hasOwn } from '../../functions/utils';\nimport { PSD, usePSD } from '../../helpers/promise';\nimport { Dexie } from '../dexie';\nimport { exceptions } from '../../errors';\nimport { safariMultiStoreFix } from '../../functions/quirks';\nimport { preventDefault } from '../../functions/event-wrappers';\nimport { newScope } from '../../helpers/promise';\nimport * as Debug from '../../helpers/debug';\nimport { Table } from '../table';\nimport { globalEvents } from '../../globals/global-events';\n\n/** Transaction\n * \n * https://dexie.org/docs/Transaction/Transaction\n * \n **/\nexport class Transaction implements ITransaction {\n  db: Dexie;\n  active: boolean;\n  mode: IDBTransactionMode;\n  chromeTransactionDurability: IDBTransactionDurability;\n  idbtrans: IDBTransaction;\n  storeNames: string[];\n  explicit?: boolean;\n  on: any;\n  parent?: Transaction;\n  schema: DbSchema;\n  _memoizedTables: {[tableName: string]: Table};\n\n  _reculock: number;\n  _blockedFuncs: { 0: () => any, 1: any }[];\n  _resolve: () => void;\n  _reject: (Error) => void;\n  _waitingFor: DexiePromise; // for waitFor()\n  _waitingQueue: Function[]; // for waitFor()\n  _spinCount: number; // Just for debugging waitFor()\n  _completion: DexiePromise;\n\n  //\n  // Transaction internal methods (not required by API users, but needed internally and eventually by dexie extensions)\n  //\n\n  /** Transaction._lock()\n   * \n   * Internal method.\n   */\n  _lock() {\n    assert(!PSD.global); // Locking and unlocking reuires to be within a PSD scope.\n    // Temporary set all requests into a pending queue if they are called before database is ready.\n    ++this._reculock; // Recursive read/write lock pattern using PSD (Promise Specific Data) instead of TLS (Thread Local Storage)\n    if (this._reculock === 1 && !PSD.global) PSD.lockOwnerFor = this;\n    return this;\n  }\n\n  /** Transaction._unlock()\n   * \n   * Internal method.\n   */\n  _unlock() {\n    assert(!PSD.global); // Locking and unlocking reuires to be within a PSD scope.\n    if (--this._reculock === 0) {\n      if (!PSD.global) PSD.lockOwnerFor = null;\n      while (this._blockedFuncs.length > 0 && !this._locked()) {\n        var fnAndPSD = this._blockedFuncs.shift();\n        try { usePSD(fnAndPSD[1], fnAndPSD[0]); } catch (e) { }\n      }\n    }\n    return this;\n  }\n\n  /** Transaction._lock()\n   * \n   * Internal method.\n   */\n  _locked() {\n    // Checks if any write-lock is applied on this transaction.\n    // To simplify the Dexie API for extension implementations, we support recursive locks.\n    // This is accomplished by using \"Promise Specific Data\" (PSD).\n    // PSD data is bound to a Promise and any child Promise emitted through then() or resolve( new Promise() ).\n    // PSD is local to code executing on top of the call stacks of any of any code executed by Promise():\n    //         * callback given to the Promise() constructor  (function (resolve, reject){...})\n    //         * callbacks given to then()/catch()/finally() methods (function (value){...})\n    // If creating a new independant Promise instance from within a Promise call stack, the new Promise will derive the PSD from the call stack of the parent Promise.\n    // Derivation is done so that the inner PSD __proto__ points to the outer PSD.\n    // PSD.lockOwnerFor will point to current transaction object if the currently executing PSD scope owns the lock.\n    return this._reculock && PSD.lockOwnerFor !== this;\n  }\n\n  /** Transaction.create()\n   * \n   * Internal method.\n   * \n   */\n  create(idbtrans?: IDBTransaction & {[prop: string]: any}) {\n    if (!this.mode) return this;\n    const idbdb = this.db.idbdb;\n    const dbOpenError = this.db._state.dbOpenError;\n    assert(!this.idbtrans);\n    if (!idbtrans && !idbdb) {\n      switch (dbOpenError && dbOpenError.name) {\n        case \"DatabaseClosedError\":\n          // Errors where it is no difference whether it was caused by the user operation or an earlier call to db.open()\n          throw new exceptions.DatabaseClosed(dbOpenError);\n        case \"MissingAPIError\":\n          // Errors where it is no difference whether it was caused by the user operation or an earlier call to db.open()\n          throw new exceptions.MissingAPI(dbOpenError.message, dbOpenError);\n        default:\n          // Make it clear that the user operation was not what caused the error - the error had occurred earlier on db.open()!\n          throw new exceptions.OpenFailed(dbOpenError);\n      }\n    }\n    if (!this.active) throw new exceptions.TransactionInactive();\n    assert(this._completion._state === null); // Completion Promise must still be pending.\n\n    idbtrans = this.idbtrans = idbtrans ||\n      (this.db.core \n        ? this.db.core.transaction(this.storeNames, this.mode as 'readwrite' | 'readonly', { durability: this.chromeTransactionDurability })\n        : idbdb.transaction(this.storeNames, this.mode, { durability: this.chromeTransactionDurability })\n      ) as IDBTransaction;\n\n    idbtrans.onerror = wrap(ev => {\n      preventDefault(ev);// Prohibit default bubbling to window.error\n      this._reject(idbtrans.error);\n    });\n    idbtrans.onabort = wrap(ev => {\n      preventDefault(ev);\n      this.active && this._reject(new exceptions.Abort(idbtrans.error));\n      this.active = false;\n      this.on(\"abort\").fire(ev);\n    });\n    idbtrans.oncomplete = wrap(() => {\n      this.active = false;\n      this._resolve();\n      if ('mutatedParts' in idbtrans) {\n        globalEvents.storagemutated.fire(idbtrans[\"mutatedParts\"]);\n      }\n    });\n    return this;\n  }\n\n  /** Transaction._promise()\n   * \n   * Internal method.\n   */\n  _promise(\n    mode: IDBTransactionMode,\n    fn: (resolve, reject, trans: Transaction) => PromiseLike<any> | void,\n    bWriteLock?: string | boolean): DexiePromise\n  {\n    if (mode === 'readwrite' && this.mode !== 'readwrite')\n      return rejection(new exceptions.ReadOnly(\"Transaction is readonly\"));\n\n    if (!this.active)\n      return rejection(new exceptions.TransactionInactive());\n\n    if (this._locked()) {\n      return new DexiePromise((resolve, reject) => {\n        this._blockedFuncs.push([() => {\n          this._promise(mode, fn, bWriteLock).then(resolve, reject);\n        }, PSD]);\n      });\n\n    } else if (bWriteLock) {\n      return newScope(() => {\n        var p = new DexiePromise((resolve, reject) => {\n          this._lock();\n          const rv = fn(resolve, reject, this);\n          if (rv && rv.then) rv.then(resolve, reject);\n        });\n        p.finally(() => this._unlock());\n        p._lib = true;\n        return p;\n      });\n\n    } else {\n      var p = new DexiePromise((resolve, reject) => {\n        var rv = fn(resolve, reject, this);\n        if (rv && rv.then) rv.then(resolve, reject);\n      });\n      p._lib = true;\n      return p;\n    }\n  }\n\n  /** Transaction._root()\n   * \n   * Internal method. Retrieves the root transaction in the tree of sub transactions.\n   */\n  _root() {\n    return this.parent ? this.parent._root() : this;\n  }\n\n  /** Transaction.waitFor()\n   * \n   * Internal method. Can be accessed from the public API through\n   * Dexie.waitFor(): https://dexie.org/docs/Dexie/Dexie.waitFor()\n   * \n   **/\n  waitFor(promiseLike: PromiseLike<any>) {\n    // Always operate on the root transaction (in case this is a sub stransaction)\n    var root = this._root();\n    // For stability reasons, convert parameter to promise no matter what type is passed to waitFor().\n    // (We must be able to call .then() on it.)\n    const promise = DexiePromise.resolve(promiseLike);\n    if (root._waitingFor) {\n      // Already called waitFor(). Wait for both to complete.\n      root._waitingFor = root._waitingFor.then(() => promise);\n    } else {\n      // We're not in waiting state. Start waiting state.\n      root._waitingFor = promise;\n      root._waitingQueue = [];\n      // Start interacting with indexedDB until promise completes:\n      var store = root.idbtrans.objectStore(root.storeNames[0]);\n      (function spin() {\n        ++root._spinCount; // For debugging only\n        while (root._waitingQueue.length) (root._waitingQueue.shift())();\n        if (root._waitingFor) store.get(-Infinity).onsuccess = spin;\n      }());\n    }\n    var currentWaitPromise = root._waitingFor;\n    return new DexiePromise((resolve, reject) => {\n      promise.then(\n        res => root._waitingQueue.push(wrap(resolve.bind(null, res))),\n        err => root._waitingQueue.push(wrap(reject.bind(null, err)))\n      ).finally(() => {\n        if (root._waitingFor === currentWaitPromise) {\n          // No one added a wait after us. Safe to stop the spinning.\n          root._waitingFor = null;\n        }\n      });\n    });\n  }  \n\n  /** Transaction.abort()\n   * \n   * https://dexie.org/docs/Transaction/Transaction.abort()\n   */\n  abort() {\n    if (this.active) {\n      this.active = false;\n      if (this.idbtrans) this.idbtrans.abort();\n      this._reject(new exceptions.Abort());\n    }\n  }\n\n  /** Transaction.table()\n   * \n   * https://dexie.org/docs/Transaction/Transaction.table()\n   */\n  table(tableName: string) {\n    const memoizedTables = (this._memoizedTables || (this._memoizedTables = {}));\n    if (hasOwn(memoizedTables, tableName))\n      return memoizedTables[tableName];\n    const tableSchema = this.schema[tableName];\n    if (!tableSchema) {\n      throw new exceptions.NotFound(\"Table \" + tableName + \" not part of transaction\");        \n    }\n\n    const transactionBoundTable = new this.db.Table(tableName, tableSchema, this);\n    transactionBoundTable.core = this.db.core.table(tableName);\n    memoizedTables[tableName] = transactionBoundTable;\n    return transactionBoundTable;\n  }\n}\n"
  },
  {
    "path": "src/classes/version/schema-helpers.ts",
    "content": "import { Dexie } from '../dexie';\nimport { DbSchema } from '../../public/types/db-schema';\nimport { _global } from \"../../globals/global\";\nimport { setProp, keys, slice, isArray, shallowClone, isAsyncFunction, defineProperty, getPropertyDescriptor } from '../../functions/utils';\nimport { Transaction } from '../transaction';\nimport { Version } from './version';\nimport Promise, { PSD, newScope, NativePromise, decrementExpectedAwaits, incrementExpectedAwaits } from '../../helpers/promise';\nimport { exceptions } from '../../errors';\nimport { TableSchema } from '../../public/types/table-schema';\nimport { IndexSpec } from '../../public/types/index-spec';\nimport { createIndexSpec, nameFromKeyPath } from '../../helpers/index-spec';\nimport { createTableSchema } from '../../helpers/table-schema';\nimport { generateMiddlewareStacks } from '../dexie/generate-middleware-stacks';\nimport { debug } from '../../helpers/debug';\nimport { PromiseExtended } from '../../public/types/promise-extended';\n\nexport function setApiOnPlace(db: Dexie, objs: Object[], tableNames: string[], dbschema: DbSchema) {\n  tableNames.forEach(tableName => {\n    const schema = dbschema[tableName];\n    objs.forEach(obj => {\n      const propDesc = getPropertyDescriptor(obj, tableName);\n      if (!propDesc || (\"value\" in propDesc && propDesc.value === undefined)) {\n        // Either the prop is not declared, or it is initialized to undefined.\n        if (obj === db.Transaction.prototype || obj instanceof db.Transaction) {\n          // obj is a Transaction prototype (or prototype of a subclass to Transaction)\n          // Make the API a getter that returns this.table(tableName)\n          setProp(obj, tableName, {\n            get(this: Transaction) { return this.table(tableName); },\n            set(value: any) {\n              // Issue #1039\n              // Let \"this.schema = dbschema;\" and other props in transaction constructor work even if there's a name collision with the table name.\n              defineProperty(this, tableName, {value, writable: true, configurable: true, enumerable: true});\n            }\n          });\n        } else {\n          // Table will not be bound to a transaction (will use Dexie.currentTransaction)\n          obj[tableName] = new db.Table(tableName, schema);\n        }\n      }\n    });\n  });\n}\n\nexport function removeTablesApi(db: Dexie, objs: Object[]) {\n  objs.forEach(obj => {\n    for (let key in obj) {\n      if (obj[key] instanceof db.Table) delete obj[key];\n    }\n  });\n}\n\nexport function lowerVersionFirst(a: Version, b: Version) {\n  return a._cfg.version - b._cfg.version;\n}\n\nexport function runUpgraders(db: Dexie, oldVersion: number, idbUpgradeTrans: IDBTransaction, reject) {\n  const globalSchema = db._dbSchema;\n  if (idbUpgradeTrans.objectStoreNames.contains('$meta') && !globalSchema.$meta) {\n    globalSchema.$meta = createTableSchema(\"$meta\", parseIndexSyntax(\"\")[0], []);\n    db._storeNames.push('$meta');\n  }\n  const trans = db._createTransaction('readwrite', db._storeNames, globalSchema);\n  trans.create(idbUpgradeTrans);\n  trans._completion.catch(reject);\n  const rejectTransaction = trans._reject.bind(trans);\n  const transless = PSD.transless || PSD;\n  newScope(() => {\n    PSD.trans = trans;\n    PSD.transless = transless;\n    if (oldVersion === 0) {\n      // Create tables:\n      keys(globalSchema).forEach(tableName => {\n        createTable(idbUpgradeTrans, tableName, globalSchema[tableName].primKey, globalSchema[tableName].indexes);\n      });\n      generateMiddlewareStacks(db, idbUpgradeTrans);\n      Promise.follow(() => db.on.populate.fire(trans)).catch(rejectTransaction);\n    } else {\n      generateMiddlewareStacks(db, idbUpgradeTrans);\n      return getExistingVersion(db, trans, oldVersion)\n        .then(oldVersion => updateTablesAndIndexes(db, oldVersion, trans, idbUpgradeTrans))\n        .catch(rejectTransaction);\n    }\n  });\n}\n\nexport type UpgradeQueueItem = (idbtrans: IDBTransaction) => PromiseLike<any> | void;\n\nexport function patchCurrentVersion(db: Dexie, idbUpgradeTrans: IDBTransaction) {\n  createMissingTables(db._dbSchema, idbUpgradeTrans);\n  if (idbUpgradeTrans.db.version % 10 === 0 && !idbUpgradeTrans.objectStoreNames.contains('$meta')) {\n    // Rolled over to the next 10-ies due to many schema upgrades without bumping version.\n    // No problem! We pin the database to its expected version by adding the $meta table so that next\n    // time the programmer bumps the version and attaches, an upgrader, that upgrader will indeed run,\n    // as well any further upgraders coming after that.\n    idbUpgradeTrans.db.createObjectStore('$meta').add(Math.ceil((idbUpgradeTrans.db.version / 10) - 1), 'version');\n  }\n  const globalSchema = buildGlobalSchema(db, db.idbdb, idbUpgradeTrans);\n  adjustToExistingIndexNames(db, db._dbSchema, idbUpgradeTrans);\n  const diff = getSchemaDiff(globalSchema, db._dbSchema);\n  for (const tableChange of diff.change) {\n    if (tableChange.change.length || tableChange.recreate) {\n      console.warn(`Unable to patch indexes of table ${tableChange.name} because it has changes on the type of index or primary key.`);\n      return;\n    }\n    const store = idbUpgradeTrans.objectStore(tableChange.name);\n    tableChange.add.forEach(idx => {\n      if (debug) console.debug(`Dexie upgrade patch: Creating missing index ${tableChange.name}.${idx.src}`);\n      addIndex(store, idx);\n    });\n  }\n}\n\nfunction getExistingVersion(db: Dexie, trans: Transaction, oldVersion: number): PromiseExtended<number> {\n  // In normal case, existing version is the native installed version divided by 10.\n  // However, in case more than 10 schema changes have been made on the same version (such as while\n  // developing an app), the native version may have passed beyond a multiple of 10 within the same version.\n  // When that happens, a table $meta will have been created, containing a single entry with key \"version\"\n  // and the value of the real old version to use when running upgraders going forward.\n  if (trans.storeNames.includes('$meta')) {\n    return trans.table('$meta').get('version').then(metaVersion => {\n      return metaVersion != null ? metaVersion : oldVersion\n    })\n  } else {\n    return Promise.resolve(oldVersion);\n  }\n}\n\nfunction updateTablesAndIndexes(\n  db: Dexie,\n  oldVersion: number,\n  trans: Transaction,\n  idbUpgradeTrans: IDBTransaction)\n{\n  // Upgrade version to version, step-by-step from oldest to newest version.\n  // Each transaction object will contain the table set that was current in that version (but also not-yet-deleted tables from its previous version)\n  const queue: UpgradeQueueItem[] = [];\n  const versions = db._versions;\n  let globalSchema = db._dbSchema = buildGlobalSchema(db, db.idbdb, idbUpgradeTrans);\n  let anyContentUpgraderHasRun = false;\n  \n  const versToRun = versions.filter(v => v._cfg.version >= oldVersion);\n  if (versToRun.length === 0) {\n    // Important not to continue at this point.\n    // Coming here means we've already patched schema in patchCurrentVersion() after having\n    // incremented native version to a value above the declared highest version.\n    // When being in this mode, it means that there might be different versions the db competing\n    // about it with different version of the schema. Therefore, we must avoid deleting tables\n    // or indexes here so that both versions can co-exist until the application has been upgraded to\n    // a version that declares no lower than the native version.\n    // If after that, a downgrade happens again, we'll end up here again, accepting both versions\n    // And we'll stay in this state until app developer releases a new declared version.\n    return Promise.resolve(); \n  }\n  \n  versToRun.forEach(version => {\n    queue.push(() => {\n      const oldSchema = globalSchema;\n      const newSchema = version._cfg.dbschema;\n      adjustToExistingIndexNames(db, oldSchema, idbUpgradeTrans);\n      adjustToExistingIndexNames(db, newSchema, idbUpgradeTrans);\n\n      globalSchema = db._dbSchema = newSchema;\n\n      const diff = getSchemaDiff(oldSchema, newSchema);\n      // Add tables           \n      diff.add.forEach(tuple => {\n        createTable(idbUpgradeTrans, tuple[0], tuple[1].primKey, tuple[1].indexes);\n      });\n      // Change tables\n      diff.change.forEach(change => {\n        if (change.recreate) {\n          throw new exceptions.Upgrade(\"Not yet support for changing primary key\");\n        } else {\n          const store = idbUpgradeTrans.objectStore(change.name);\n          // Add indexes\n          change.add.forEach(idx => addIndex(store, idx));\n          // Update indexes\n          change.change.forEach(idx => {\n            store.deleteIndex(idx.name);\n            addIndex(store, idx);\n          });\n          // Delete indexes\n          change.del.forEach(idxName => store.deleteIndex(idxName));\n        }\n      });\n\n      const contentUpgrade = version._cfg.contentUpgrade;\n\n      if (contentUpgrade && version._cfg.version > oldVersion) {\n        // Update db.core with new tables and indexes:\n        generateMiddlewareStacks(db, idbUpgradeTrans);\n        trans._memoizedTables = {}; // Invalidate memoization as transaction shape may change between versions.\n\n        anyContentUpgraderHasRun = true;\n\n        // Add to-be-deleted tables to contentUpgrade transaction\n        let upgradeSchema = shallowClone(newSchema);\n        diff.del.forEach(table => {\n          upgradeSchema[table] = oldSchema[table];\n        });\n\n        // Safe to affect Transaction.prototype globally in this moment,\n        // because when this code runs, there may not be any other code\n        // that can access any transaction instance, else than this particular\n        // upgrader function.\n        removeTablesApi(db, [db.Transaction.prototype]);\n        setApiOnPlace(db, [db.Transaction.prototype], keys(upgradeSchema), upgradeSchema);\n        trans.schema = upgradeSchema;\n\n        // Support for native async await.\n        const contentUpgradeIsAsync = isAsyncFunction(contentUpgrade);\n        if (contentUpgradeIsAsync) {\n          incrementExpectedAwaits();\n        }\n        \n        let returnValue: any;\n        const promiseFollowed = Promise.follow(() => {\n          // Finally, call the scope function with our table and transaction arguments.\n          returnValue = contentUpgrade(trans);\n          if (returnValue) {\n            if (contentUpgradeIsAsync) {\n              // contentUpgrade is a native async function - we know for sure returnValue is native promise.\n              var decrementor = decrementExpectedAwaits.bind(null, null);\n              returnValue.then(decrementor, decrementor);\n            }\n          }\n        });\n        return (returnValue && typeof returnValue.then === 'function' ?\n          Promise.resolve(returnValue) : promiseFollowed.then(()=>returnValue));\n      }\n    });\n    queue.push(idbtrans => {\n      const newSchema = version._cfg.dbschema;\n      // Delete old tables\n      deleteRemovedTables(newSchema, idbtrans);\n      // Restore the final API\n      removeTablesApi(db, [db.Transaction.prototype]);\n      setApiOnPlace(db, [db.Transaction.prototype], db._storeNames, db._dbSchema);\n      trans.schema = db._dbSchema;\n    });\n    // Maintain the $meta table after this version's tables and indexes has been created and content upgraders have run.\n    queue.push(idbtrans => {\n      if (db.idbdb.objectStoreNames.contains('$meta')) {\n        if (Math.ceil(db.idbdb.version / 10) === version._cfg.version) {\n          // Remove $meta table if it's no more needed - we are in line with the native version\n          db.idbdb.deleteObjectStore('$meta');\n          delete db._dbSchema.$meta;\n          db._storeNames = db._storeNames.filter(name => name !== '$meta');\n        } else {\n          // We're still not in line with the native version. Make sure to update the virtual version\n          // to the successfully run version\n          idbtrans.objectStore('$meta').put(version._cfg.version, 'version');\n        }\n      }\n    }); \n  });\n\n  // Now, create a queue execution engine\n  function runQueue() {\n    return queue.length ? Promise.resolve(queue.shift()(trans.idbtrans)).then(runQueue) :\n      Promise.resolve();\n  }\n\n  return runQueue().then(() => {\n    createMissingTables(globalSchema, idbUpgradeTrans); // At last, make sure to create any missing tables. (Needed by addons that add stores to DB without specifying version)\n  });\n}\n\nexport interface SchemaDiff {\n  del: string[],\n  add: [string, TableSchema][];\n  change: TableSchemaDiff[];\n}\n\nexport interface TableSchemaDiff {\n  name: string,\n  recreate: boolean,\n  del: string[],\n  add: IndexSpec[],\n  change: IndexSpec[]\n}\n\nexport function getSchemaDiff(oldSchema: DbSchema, newSchema: DbSchema): SchemaDiff {\n  const diff: SchemaDiff = {\n    del: [], // Array of table names\n    add: [], // Array of [tableName, newDefinition]\n    change: [] // Array of {name: tableName, recreate: newDefinition, del: delIndexNames, add: newIndexDefs, change: changedIndexDefs}\n  };\n  let table: string;\n  for (table in oldSchema) {\n    if (!newSchema[table]) diff.del.push(table);\n  }\n  for (table in newSchema) {\n    const oldDef = oldSchema[table],\n      newDef = newSchema[table];\n    if (!oldDef) {\n      diff.add.push([table, newDef]);\n    } else {\n      const change = {\n        name: table,\n        def: newDef,\n        recreate: false,\n        del: [],\n        add: [],\n        change: []\n      };\n      if (\n          (\n             // compare keyPaths no matter if string or string[]\n             // compare falsy keypaths same no matter if they are null or empty string.\n            ''+(oldDef.primKey.keyPath||'')\n          ) !== (\n            ''+(newDef.primKey.keyPath||'')\n          ) ||\n            // Compare the autoIncrement flag also\n          (oldDef.primKey.auto !== newDef.primKey.auto))\n      {\n        // Primary key has changed. Remove and re-add table.\n        change.recreate = true;\n        diff.change.push(change);\n      } else {\n        // Same primary key. Just find out what differs:\n        const oldIndexes = oldDef.idxByName;\n        const newIndexes = newDef.idxByName;\n        let idxName: string;\n        for (idxName in oldIndexes) {\n          if (!newIndexes[idxName]) change.del.push(idxName);\n        }\n        for (idxName in newIndexes) {\n          const oldIdx = oldIndexes[idxName],\n            newIdx = newIndexes[idxName];\n          if (!oldIdx) change.add.push(newIdx);\n          else if (oldIdx.src !== newIdx.src) change.change.push(newIdx);\n        }\n        if (change.del.length > 0 || change.add.length > 0 || change.change.length > 0) {\n          diff.change.push(change);\n        }\n      }\n    }\n  }\n  return diff;\n}\n\nexport function createTable(\n  idbtrans: IDBTransaction,\n  tableName: string,\n  primKey: IndexSpec,\n  indexes: IndexSpec[]\n) {\n  const store = idbtrans.db.createObjectStore(\n    tableName,\n    primKey.keyPath ?\n      { keyPath: primKey.keyPath, autoIncrement: primKey.auto } :\n      { autoIncrement: primKey.auto }\n  );\n  indexes.forEach(idx => addIndex(store, idx));\n  return store;\n}\n\nexport function createMissingTables(newSchema: DbSchema, idbtrans: IDBTransaction) {\n  keys(newSchema).forEach(tableName => {\n    if (!idbtrans.db.objectStoreNames.contains(tableName)) {\n      if (debug) console.debug('Dexie: Creating missing table', tableName);\n      createTable(idbtrans, tableName, newSchema[tableName].primKey, newSchema[tableName].indexes);\n    }\n  });\n}\n\nexport function deleteRemovedTables(newSchema: DbSchema, idbtrans: IDBTransaction) {\n  [].slice.call(idbtrans.db.objectStoreNames).forEach(storeName =>\n    newSchema[storeName] == null && idbtrans.db.deleteObjectStore(storeName));\n}\n\nexport function addIndex(store: IDBObjectStore, idx: IndexSpec) {\n  store.createIndex(idx.name, idx.keyPath, { unique: idx.unique, multiEntry: idx.multi });\n}\n\nfunction buildGlobalSchema(\n  db: Dexie,\n  idbdb: IDBDatabase,\n  tmpTrans: IDBTransaction\n) {\n  const globalSchema = {};\n  const dbStoreNames = slice(idbdb.objectStoreNames, 0);\n  dbStoreNames.forEach(storeName => {\n    const store = tmpTrans.objectStore(storeName);\n    let keyPath = store.keyPath;\n    const primKey = createIndexSpec(\n      nameFromKeyPath(keyPath),\n      keyPath || \"\",\n      true,\n      false,\n      !!store.autoIncrement,\n      keyPath && typeof keyPath !== \"string\",\n      true\n    );\n    const indexes: IndexSpec[] = [];\n    for (let j = 0; j < store.indexNames.length; ++j) {\n      const idbindex = store.index(store.indexNames[j]);\n      keyPath = idbindex.keyPath;\n      var index = createIndexSpec(\n        idbindex.name,\n        keyPath,\n        !!idbindex.unique,\n        !!idbindex.multiEntry,\n        false,\n        keyPath && typeof keyPath !== \"string\",\n        false\n      );\n      indexes.push(index);\n    }\n    globalSchema[storeName] = createTableSchema(storeName, primKey, indexes);\n  });\n  return globalSchema;\n}\n\nexport function readGlobalSchema(db: Dexie, idbdb: IDBDatabase, tmpTrans: IDBTransaction) {\n  db.verno = idbdb.version / 10;\n  const globalSchema = db._dbSchema = buildGlobalSchema(db, idbdb, tmpTrans);\n  db._storeNames = slice(idbdb.objectStoreNames, 0);\n  setApiOnPlace(db, [db._allTables], keys(globalSchema), globalSchema);\n}\n\nexport function verifyInstalledSchema(db: Dexie, tmpTrans: IDBTransaction): boolean {\n  const installedSchema = buildGlobalSchema(db, db.idbdb, tmpTrans);\n  const diff = getSchemaDiff(installedSchema, db._dbSchema);\n  return !(diff.add.length || diff.change.some(ch => ch.add.length || ch.change.length));\n}\n\nexport function adjustToExistingIndexNames(db: Dexie, schema: DbSchema, idbtrans: IDBTransaction) {\n  // Issue #30 Problem with existing db - adjust to existing index names when migrating from non-dexie db\n  const storeNames = idbtrans.db.objectStoreNames;\n\n  for (let i = 0; i < storeNames.length; ++i) {\n    const storeName = storeNames[i];\n    const store = idbtrans.objectStore(storeName);\n    db._hasGetAll = 'getAll' in store;\n\n    for (let j = 0; j < store.indexNames.length; ++j) {\n      const indexName = store.indexNames[j];\n      const keyPath = store.index(indexName).keyPath;\n      const dexieName = typeof keyPath === 'string' ? keyPath : \"[\" + slice(keyPath).join('+') + \"]\";\n      if (schema[storeName]) {\n        const indexSpec = schema[storeName].idxByName[dexieName];\n        if (indexSpec) {\n          indexSpec.name = indexName;\n          delete schema[storeName].idxByName[dexieName];\n          schema[storeName].idxByName[indexName] = indexSpec;\n        }\n      }\n    }\n  }\n\n  // Bug with getAll() on Safari ver<604 on Workers only, see discussion following PR #579\n  if (typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) &&\n    !/(Chrome\\/|Edge\\/)/.test(navigator.userAgent) &&\n    _global.WorkerGlobalScope && _global instanceof _global.WorkerGlobalScope &&\n    [].concat(navigator.userAgent.match(/Safari\\/(\\d*)/))[1] < 604)\n  {\n    db._hasGetAll = false;\n  }\n}\n\nexport function parseIndexSyntax(primKeyAndIndexes: string): IndexSpec[] {\n  return primKeyAndIndexes.split(',').map((index, indexNum) => {\n    const typeSplit = index.split(':');\n    const type = typeSplit[1]?.trim();\n    index = typeSplit[0].trim();\n    const name = index.replace(/([&*]|\\+\\+)/g, \"\"); // Remove \"&\", \"++\" and \"*\"\n    // Let keyPath of \"[a+b]\" be [\"a\",\"b\"]:\n    const keyPath = /^\\[/.test(name) ? name.match(/^\\[(.*)\\]$/)[1].split('+') : name;\n\n    return createIndexSpec(\n      name,\n      keyPath || null,\n      /\\&/.test(index),\n      /\\*/.test(index),\n      /\\+\\+/.test(index),\n      isArray(keyPath),\n      indexNum === 0,\n      type\n    );\n  });\n}\n"
  },
  {
    "path": "src/classes/version/version-constructor.ts",
    "content": "import { Dexie } from '../dexie';\nimport { makeClassConstructor } from '../../functions/make-class-constructor';\nimport { Version } from './version';\n\nexport interface VersionConstructor {\n  new(versionNumber: number): Version;\n  prototype: Version;\n}\n\n/** Generates a Version constructor bound to given Dexie instance.\n * \n * The purpose of having dynamically created constructors, is to allow\n * addons to extend classes for a certain Dexie instance without affecting\n * other db instances.\n */\nexport function createVersionConstructor(db: Dexie) {\n  return makeClassConstructor<VersionConstructor>(\n    Version.prototype,\n\n    function Version(this: Version, versionNumber: number) {\n      this.db = db;\n      this._cfg = {\n        version: versionNumber,\n        storesSource: null,\n        dbschema: {},\n        tables: {},\n        contentUpgrade: null\n      };\n    });\n\n}\n"
  },
  {
    "path": "src/classes/version/version.ts",
    "content": "import { Version as IVersion } from '../../public/types/version';\nimport { DbSchema } from '../../public/types/db-schema';\nimport { extend, keys } from '../../functions/utils';\nimport { Dexie } from '../dexie';\nimport { Transaction } from '../transaction';\nimport { removeTablesApi, setApiOnPlace, parseIndexSyntax } from './schema-helpers';\nimport { exceptions } from '../../errors';\nimport { createTableSchema } from '../../helpers/table-schema';\nimport { nop, promisableChain } from '../../functions/chaining-functions';\nimport { IndexSpec } from '../../public/types/index-spec';\nimport { TableSchema } from '../../public/types/table-schema';\n\n/** class Version\n *\n * https://dexie.org/docs/Version/Version\n */\nexport class Version implements IVersion {\n  db: Dexie;\n  _cfg: {\n    version: number;\n    storesSource: { [tableName: string]: string | null };\n    dbschema: DbSchema;\n    tables: {};\n    contentUpgrade: Function | null;\n  };\n\n  _createTableSchema(\n    name: string,\n    primKey: IndexSpec,\n    indexes: IndexSpec[]\n  ): TableSchema {\n    return createTableSchema(name, primKey, indexes);\n  }\n\n  _parseIndexSyntax(primKeyAndIndexes: string): IndexSpec[] {\n    return parseIndexSyntax(primKeyAndIndexes);\n  }\n\n  _parseStoresSpec(\n    stores: { [tableName: string]: string | null },\n    outSchema: DbSchema\n  ): any {\n    keys(stores).forEach((tableName) => {\n      if (stores[tableName] !== null) {\n        let indexes = this._parseIndexSyntax(stores[tableName]);\n\n        const primKey = indexes.shift();\n        if (!primKey) {\n          // {table: ':Y'} not supported.\n          throw new exceptions.Schema(\n            'Invalid schema for table ' + tableName + ': ' + stores[tableName]\n          );\n        }\n\n        primKey.unique = true;\n        if (primKey.multi)\n          throw new exceptions.Schema('Primary key cannot be multiEntry*');\n        indexes.forEach((idx) => {\n          if (idx.auto)\n            throw new exceptions.Schema(\n              'Only primary key can be marked as autoIncrement (++)'\n            );\n          if (!idx.keyPath)\n            throw new exceptions.Schema(\n              'Index must have a name and cannot be an empty string'\n            );\n        });\n        const tblSchema = this._createTableSchema(\n          tableName,\n          primKey,\n          indexes\n        );\n        outSchema[tableName] = tblSchema;\n      }\n    });\n  }\n\n  stores(stores: { [key: string]: string | null }): this {\n    const db = this.db;\n    this._cfg.storesSource = this._cfg.storesSource\n      ? extend(this._cfg.storesSource, stores)\n      : stores;\n    const versions = db._versions;\n\n    // Derive stores from earlier versions if they are not explicitely specified as null or a new syntax.\n    const storesSpec: { [key: string]: string } = {};\n    let dbschema: DbSchema = {};\n    versions.forEach((version) => {\n      // 'versions' is always sorted by lowest version first.\n      extend(storesSpec, version._cfg.storesSource);\n      dbschema = version._cfg.dbschema = {};\n      version._parseStoresSpec(storesSpec, dbschema);\n    });\n    // Update the latest schema to this version\n    db._dbSchema = dbschema;\n    // Update APIs\n    removeTablesApi(db, [db._allTables, db, db.Transaction.prototype]);\n    setApiOnPlace(\n      db,\n      [db._allTables, db, db.Transaction.prototype, this._cfg.tables],\n      keys(dbschema),\n      dbschema\n    );\n    db._storeNames = keys(dbschema);\n    return this;\n  }\n\n  upgrade(\n    upgradeFunction: (trans: Transaction) => PromiseLike<any> | void\n  ): this {\n    this._cfg.contentUpgrade = promisableChain(\n      this._cfg.contentUpgrade || nop,\n      upgradeFunction\n    );\n    return this;\n  }\n}\n"
  },
  {
    "path": "src/classes/where-clause/where-clause-constructor.ts",
    "content": "import { Dexie } from '../dexie';\nimport { makeClassConstructor } from '../../functions/make-class-constructor';\nimport { WhereClause } from './where-clause';\nimport { Table } from '../table';\nimport { Collection } from '../collection';\nimport { exceptions } from '../../errors';\nimport { cmp } from '../../functions/cmp';\n\nexport interface WhereClauseConstructor {\n  new(table: Table, index?: string, orCollection?: Collection): WhereClause;\n  prototype: WhereClause;\n}\n\n/** Generates a WhereClause constructor.\n * \n * The purpose of having dynamically created constructors, is to allow\n * addons to extend classes for a certain Dexie instance without affecting\n * other db instances.\n */\nexport function createWhereClauseConstructor(db: Dexie) {\n  return makeClassConstructor<WhereClauseConstructor>(\n    WhereClause.prototype,\n\n    function WhereClause(this: WhereClause, table: Table, index?: string, orCollection?: Collection) {\n      this.db = db;\n      this._ctx = {\n        table: table,\n        index: index === \":id\" ? null : index,\n        or: orCollection\n      };\n      this._cmp = this._ascending = cmp;\n      this._descending = (a, b) => cmp(b, a);\n      this._max = (a, b) => cmp(a,b) > 0 ? a : b;\n      this._min = (a, b) => cmp(a,b) < 0 ? a : b;\n      this._IDBKeyRange = db._deps.IDBKeyRange;\n      if (!this._IDBKeyRange) throw new exceptions.MissingAPI();\n    }\n  );\n}\n"
  },
  {
    "path": "src/classes/where-clause/where-clause-helpers.ts",
    "content": "import { WhereClause } from './where-clause';\nimport { Collection } from '../collection';\nimport { STRING_EXPECTED } from '../../globals/constants';\nimport { simpleCompare, simpleCompareReverse } from '../../functions/compare-functions';\nimport { IndexableType } from '../../public';\nimport { DBCoreKeyRange, DBCoreRangeType } from '../../public/types/dbcore';\n\nexport function fail(collectionOrWhereClause: Collection | WhereClause, err, T?) {\n  var collection = collectionOrWhereClause instanceof WhereClause ?\n      new collectionOrWhereClause.Collection (collectionOrWhereClause) :\n      collectionOrWhereClause;\n      \n  collection._ctx.error = T ? new T(err) : new TypeError(err);\n  return collection;\n}\n\nexport function emptyCollection(whereClause: WhereClause) {\n  return new whereClause.Collection (whereClause, () => rangeEqual(\"\")).limit(0);\n}\n\nexport function upperFactory(dir: 'next' | 'prev') {\n  return dir === \"next\" ?\n    (s: string) => s.toUpperCase() :\n    (s: string) => s.toLowerCase();\n}\n\nexport function lowerFactory(dir: 'next' | 'prev') {\n  return dir === \"next\" ?\n    (s: string) => s.toLowerCase() :\n    (s: string) => s.toUpperCase();\n}\n\nexport function nextCasing(key, lowerKey, upperNeedle, lowerNeedle, cmp, dir) {\n  var length = Math.min(key.length, lowerNeedle.length);\n  var llp = -1;\n  for (var i = 0; i < length; ++i) {\n      var lwrKeyChar = lowerKey[i];\n      if (lwrKeyChar !== lowerNeedle[i]) {\n          if (cmp(key[i], upperNeedle[i]) < 0) return key.substr(0, i) + upperNeedle[i] + upperNeedle.substr(i + 1);\n          if (cmp(key[i], lowerNeedle[i]) < 0) return key.substr(0, i) + lowerNeedle[i] + upperNeedle.substr(i + 1);\n          if (llp >= 0) return key.substr(0, llp) + lowerKey[llp] + upperNeedle.substr(llp + 1);\n          return null;\n      }\n      if (cmp(key[i], lwrKeyChar) < 0) llp = i;\n  }\n  if (length < lowerNeedle.length && dir === \"next\") return key + upperNeedle.substr(key.length);\n  if (length < key.length && dir === \"prev\") return key.substr(0, upperNeedle.length);\n  return (llp < 0 ? null : key.substr(0, llp) + lowerNeedle[llp] + upperNeedle.substr(llp + 1));\n}\n\nexport function addIgnoreCaseAlgorithm(whereClause: WhereClause, match, needles, suffix) {\n  /// <param name=\"needles\" type=\"Array\" elementType=\"String\"></param>\n  var upper, lower, compare, upperNeedles, lowerNeedles, direction, nextKeySuffix,\n      needlesLen = needles.length;\n  if (!needles.every(s => typeof s === 'string')) {\n      return fail(whereClause, STRING_EXPECTED);\n  }\n  function initDirection(dir) {\n      upper = upperFactory(dir);\n      lower = lowerFactory(dir);\n      compare = (dir === \"next\" ? simpleCompare : simpleCompareReverse);\n      var needleBounds = needles.map(function (needle){\n          return {lower: lower(needle), upper: upper(needle)};\n      }).sort(function(a,b) {\n          return compare(a.lower, b.lower);\n      });\n      upperNeedles = needleBounds.map(function (nb){ return nb.upper; });\n      lowerNeedles = needleBounds.map(function (nb){ return nb.lower; });\n      direction = dir;\n      nextKeySuffix = (dir === \"next\" ? \"\" : suffix);\n  }\n  initDirection(\"next\");\n\n  var c = new whereClause.Collection (\n      whereClause,\n      ()=>createRange(upperNeedles[0], lowerNeedles[needlesLen-1] + suffix)\n  );\n\n  c._ondirectionchange = function (direction) {\n      // This event onlys occur before filter is called the first time.\n      initDirection(direction);\n  };\n\n  var firstPossibleNeedle = 0;\n\n  c._addAlgorithm(function (cursor, advance, resolve) {\n      /// <param name=\"cursor\" type=\"IDBCursor\"></param>\n      /// <param name=\"advance\" type=\"Function\"></param>\n      /// <param name=\"resolve\" type=\"Function\"></param>\n      var key = cursor.key;\n      if (typeof key !== 'string') return false;\n      var lowerKey = lower(key);\n      if (match(lowerKey, lowerNeedles, firstPossibleNeedle)) {\n          return true;\n      } else {\n          var lowestPossibleCasing = null;\n          for (var i=firstPossibleNeedle; i<needlesLen; ++i) {\n              var casing = nextCasing(key, lowerKey, upperNeedles[i], lowerNeedles[i], compare, direction);\n              if (casing === null && lowestPossibleCasing === null)\n                  firstPossibleNeedle = i + 1;\n              else if (lowestPossibleCasing === null || compare(lowestPossibleCasing, casing) > 0) {\n                  lowestPossibleCasing = casing;\n              }\n          }\n          if (lowestPossibleCasing !== null) {\n              advance(function () { cursor.continue(lowestPossibleCasing + nextKeySuffix); });\n          } else {\n              advance(resolve);\n          }\n          return false;\n      }\n  });\n  return c;\n}\n\nexport function createRange (lower: IndexableType, upper: IndexableType, lowerOpen?: boolean, upperOpen?: boolean): DBCoreKeyRange {\n    return {\n        type: DBCoreRangeType.Range,\n        lower,\n        upper,\n        lowerOpen,\n        upperOpen\n    };\n}\n\nexport function rangeEqual (value: IndexableType) : DBCoreKeyRange {\n    return {\n        type: DBCoreRangeType.Equal,\n        lower: value,\n        upper: value\n    };\n}\n"
  },
  {
    "path": "src/classes/where-clause/where-clause.ts",
    "content": "import { WhereClause as IWhereClause } from \"../../public/types/where-clause\";\nimport { Collection } from \"../collection\";\nimport { Table } from \"../table\";\nimport { IndexableType } from \"../../public/types/indexable-type\";\nimport { emptyCollection, fail, addIgnoreCaseAlgorithm, createRange, rangeEqual } from './where-clause-helpers';\nimport { INVALID_KEY_ARGUMENT, STRING_EXPECTED, maxString, minKey } from '../../globals/constants';\nimport { getArrayOf, NO_CHAR_ARRAY } from '../../functions/utils';\nimport { exceptions } from '../../errors';\nimport { Dexie } from '../dexie';\nimport { Collection as ICollection} from \"../../public/types/collection\";\n\n/** class WhereClause\n * \n * https://dexie.org/docs/WhereClause/WhereClause\n */\nexport class WhereClause implements IWhereClause {\n  db: Dexie;\n  _IDBKeyRange: typeof IDBKeyRange;\n  _ctx: {\n    table: Table;\n    index: string;\n    or: Collection;\n  }\n  _cmp: (a: IndexableType, b: IndexableType) => number;\n  _ascending: (a: IndexableType, b: IndexableType) => number;\n  _descending: (a: IndexableType, b: IndexableType) => number;\n  _min: (a: IndexableType, b: IndexableType) => IndexableType;\n  _max: (a: IndexableType, b: IndexableType) => IndexableType;\n\n  get Collection() {\n    return this._ctx.table.db.Collection;\n  }\n\n  /** WhereClause.between()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.between()\n   * \n   **/\n  between(lower: IndexableType, upper: IndexableType, includeLower?: boolean, includeUpper?: boolean) {\n    includeLower = includeLower !== false;   // Default to true\n    includeUpper = includeUpper === true;    // Default to false\n    try {\n      if ((this._cmp(lower, upper) > 0) ||\n        (this._cmp(lower, upper) === 0 && (includeLower || includeUpper) && !(includeLower && includeUpper)))\n        return emptyCollection(this); // Workaround for idiotic W3C Specification that DataError must be thrown if lower > upper. The natural result would be to return an empty collection.\n      return new this.Collection(this, ()=>createRange(lower, upper, !includeLower, !includeUpper));\n    } catch (e) {\n      return fail(this, INVALID_KEY_ARGUMENT);\n    }\n  }\n\n  /** WhereClause.equals()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.equals()\n   * \n   **/\n  equals(value: IndexableType) {\n    if (value == null) return fail(this, INVALID_KEY_ARGUMENT);\n    return new this.Collection(this, () => rangeEqual(value)) as ICollection;\n  }\n\n  /** WhereClause.above()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.above()\n   * \n   **/\n  above(value: IndexableType) {\n    if (value == null) return fail(this, INVALID_KEY_ARGUMENT);\n    return new this.Collection(this, () => createRange(value, undefined, true));\n  }\n\n  /** WhereClause.aboveOrEqual()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.aboveOrEqual()\n   * \n   **/\n  aboveOrEqual(value: IndexableType) {\n    if (value == null) return fail(this, INVALID_KEY_ARGUMENT);\n    return new this.Collection(this, () => createRange(value, undefined, false));\n  }\n\n  /** WhereClause.below()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.below()\n   * \n   **/\n  below(value: IndexableType) {\n    if (value == null) return fail(this, INVALID_KEY_ARGUMENT);\n    return new this.Collection(this, () => createRange(undefined, value, false, true));\n  }\n\n  /** WhereClause.belowOrEqual()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.belowOrEqual()\n   * \n   **/\n  belowOrEqual(value: IndexableType) {\n    if (value == null) return fail(this, INVALID_KEY_ARGUMENT);\n    return new this.Collection(this, () => createRange(undefined, value));\n  }\n\n  /** WhereClause.startsWith()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.startsWith()\n   * \n   **/\n  startsWith(str: string) {\n    if (typeof str !== 'string') return fail(this, STRING_EXPECTED);\n    return this.between(str, str + maxString, true, true);\n  }\n\n  /** WhereClause.startsWithIgnoreCase()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.startsWithIgnoreCase()\n   * \n   **/\n  startsWithIgnoreCase(str: string) {\n    if (str === \"\") return this.startsWith(str);\n    return addIgnoreCaseAlgorithm(this, (x, a) => x.indexOf(a[0]) === 0, [str], maxString);\n  }\n\n  /** WhereClause.equalsIgnoreCase()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.equalsIgnoreCase()\n   * \n   **/\n  equalsIgnoreCase(str: string) {\n    return addIgnoreCaseAlgorithm(this, (x, a) => x === a[0], [str], \"\");\n  }\n\n  /** WhereClause.anyOfIgnoreCase()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.anyOfIgnoreCase()\n   * \n   **/\n  anyOfIgnoreCase(...values: string[]): Collection;\n  anyOfIgnoreCase(values: string[]): Collection;\n  anyOfIgnoreCase() {\n    var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments);\n    if (set.length === 0) return emptyCollection(this);\n    return addIgnoreCaseAlgorithm(this, (x, a) => a.indexOf(x) !== -1, set, \"\");\n  }\n\n  /** WhereClause.startsWithAnyOfIgnoreCase()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.startsWithAnyOfIgnoreCase()\n   * \n   **/\n  startsWithAnyOfIgnoreCase(...values: string[]): Collection;\n  startsWithAnyOfIgnoreCase(values: string[]): Collection;\n  startsWithAnyOfIgnoreCase() {\n    var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments);\n    if (set.length === 0) return emptyCollection(this);\n    return addIgnoreCaseAlgorithm(this, (x, a) => a.some(n => x.indexOf(n) === 0), set, maxString);\n  }\n\n  /** WhereClause.anyOf()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.anyOf()\n   * \n   **/\n  anyOf(...values: string[]): Collection;\n  anyOf(values: string[]): Collection;\n  anyOf() {\n    const set = getArrayOf.apply(NO_CHAR_ARRAY, arguments);\n    let compare = this._cmp;\n    try { set.sort(compare); } catch (e) { return fail(this, INVALID_KEY_ARGUMENT); }\n    if (set.length === 0) return emptyCollection(this);\n    const c = new this.Collection(this, () => createRange(set[0], set[set.length - 1]));\n\n    c._ondirectionchange = direction => {\n      compare = (direction === \"next\" ?\n        this._ascending :\n        this._descending);\n      set.sort(compare);\n    };\n\n    let i = 0;\n    c._addAlgorithm((cursor, advance, resolve) => {\n      const key = cursor.key;\n      while (compare(key, set[i]) > 0) {\n        // The cursor has passed beyond this key. Check next.\n        ++i;\n        if (i === set.length) {\n          // There is no next. Stop searching.\n          advance(resolve);\n          return false;\n        }\n      }\n      if (compare(key, set[i]) === 0) {\n        // The current cursor value should be included and we should continue a single step in case next item has the same key or possibly our next key in set.\n        return true;\n      } else {\n        // cursor.key not yet at set[i]. Forward cursor to the next key to hunt for.\n        advance(() => { cursor.continue(set[i]); });\n        return false;\n      }\n    });\n    return c;\n  }\n\n  /** WhereClause.notEqual()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.notEqual()\n   * \n   **/\n  notEqual(value: IndexableType) {\n    return this.inAnyRange([[minKey, value], [value, this.db._maxKey]], { includeLowers: false, includeUppers: false });\n  }\n\n  /** WhereClause.noneOf()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.noneOf()\n   * \n   **/\n  noneOf(...values: string[]): Collection;\n  noneOf(values: string[]): Collection;\n  noneOf() {\n    const set = getArrayOf.apply(NO_CHAR_ARRAY, arguments);\n    if (set.length === 0) return new this.Collection(this); // Return entire collection.\n    try { set.sort(this._ascending); } catch (e) { return fail(this, INVALID_KEY_ARGUMENT); }\n    // Transform [\"a\",\"b\",\"c\"] to a set of ranges for between/above/below: [[minKey,\"a\"], [\"a\",\"b\"], [\"b\",\"c\"], [\"c\",maxKey]]\n    const ranges = set.reduce(\n      (res, val) => res ?\n        res.concat([[res[res.length - 1][1], val]]) :\n        [[minKey, val]],\n      null);\n    ranges.push([set[set.length - 1], this.db._maxKey]);\n    return this.inAnyRange(ranges, { includeLowers: false, includeUppers: false });\n  }\n\n  /** WhereClause.inAnyRange()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.inAnyRange()\n   * \n   **/\n  inAnyRange(\n    ranges: ReadonlyArray<{ 0: IndexableType, 1: IndexableType }>,\n    options?: { includeLowers?: boolean, includeUppers?: boolean })\n  {\n    const cmp = this._cmp,\n          ascending = this._ascending,\n          descending = this._descending,\n          min = this._min,\n          max = this._max;\n\n    if (ranges.length === 0) return emptyCollection(this);\n    if (!ranges.every(range =>\n      range[0] !== undefined &&\n      range[1] !== undefined &&\n      ascending(range[0], range[1]) <= 0)) {\n      return fail(\n        this,\n        \"First argument to inAnyRange() must be an Array of two-value Arrays [lower,upper] where upper must not be lower than lower\",\n        exceptions.InvalidArgument);\n    }\n    const includeLowers = !options || options.includeLowers !== false;   // Default to true\n    const includeUppers = options && options.includeUppers === true;    // Default to false\n\n    function addRange(ranges, newRange) {\n      let i = 0, l = ranges.length;\n      for (; i < l; ++i) {\n        const range = ranges[i];\n        if (cmp(newRange[0], range[1]) < 0 && cmp(newRange[1], range[0]) > 0) {\n          range[0] = min(range[0], newRange[0]);\n          range[1] = max(range[1], newRange[1]);\n          break;\n        }\n      }\n      if (i === l)\n        ranges.push(newRange);\n      return ranges;\n    }\n\n    let sortDirection = ascending;\n    function rangeSorter(a, b) { return sortDirection(a[0], b[0]); }\n\n    // Join overlapping ranges\n    let set;\n    try {\n      set = ranges.reduce(addRange, []);\n      set.sort(rangeSorter);\n    } catch (ex) {\n      return fail(this, INVALID_KEY_ARGUMENT);\n    }\n\n    let rangePos = 0;\n    const keyIsBeyondCurrentEntry = includeUppers ?\n      key => ascending(key, set[rangePos][1]) > 0 :\n      key => ascending(key, set[rangePos][1]) >= 0;\n\n    const keyIsBeforeCurrentEntry = includeLowers ?\n      key => descending(key, set[rangePos][0]) > 0 :\n      key => descending(key, set[rangePos][0]) >= 0;\n\n    function keyWithinCurrentRange(key) {\n      return !keyIsBeyondCurrentEntry(key) && !keyIsBeforeCurrentEntry(key);\n    }\n\n    let checkKey = keyIsBeyondCurrentEntry;\n\n    const c = new this.Collection(\n      this,\n      () => createRange(set[0][0], set[set.length - 1][1], !includeLowers, !includeUppers));\n\n    c._ondirectionchange = direction => {\n      if (direction === \"next\") {\n        checkKey = keyIsBeyondCurrentEntry;\n        sortDirection = ascending;\n      } else {\n        checkKey = keyIsBeforeCurrentEntry;\n        sortDirection = descending;\n      }\n      set.sort(rangeSorter);\n    };\n\n    c._addAlgorithm((cursor, advance, resolve) => {\n      var key = cursor.key;\n      while (checkKey(key)) {\n        // The cursor has passed beyond this key. Check next.\n        ++rangePos;\n        if (rangePos === set.length) {\n          // There is no next. Stop searching.\n          advance(resolve);\n          return false;\n        }\n      }\n      if (keyWithinCurrentRange(key)) {\n        // The current cursor value should be included and we should continue a single step in case next item has the same key or possibly our next key in set.\n        return true;\n      } else if (this._cmp(key, set[rangePos][1]) === 0 || this._cmp(key, set[rangePos][0]) === 0) {\n        // includeUpper or includeLower is false so keyWithinCurrentRange() returns false even though we are at range border.\n        // Continue to next key but don't include this one.\n        return false;\n      } else {\n        // cursor.key not yet at set[i]. Forward cursor to the next key to hunt for.\n        advance(() => {\n          if (sortDirection === ascending) cursor.continue(set[rangePos][0]);\n          else cursor.continue(set[rangePos][1]);\n        });\n        return false;\n      }\n    });\n    return c;\n  }\n\n  /** WhereClause.startsWithAnyOf()\n   * \n   * https://dexie.org/docs/WhereClause/WhereClause.startsWithAnyOf()\n   * \n   **/\n  startsWithAnyOf(...prefixes: string[]): Collection;\n  startsWithAnyOf(prefixes: string[]): Collection;\n  startsWithAnyOf() {\n    const set = getArrayOf.apply(NO_CHAR_ARRAY, arguments);\n\n    if (!set.every(s => typeof s === 'string')) {\n        return fail(this, \"startsWithAnyOf() only works with strings\");\n    }\n    if (set.length === 0) return emptyCollection(this);\n\n    return this.inAnyRange(set.map((str: string) => [str, str + maxString]));\n  }\n\n}\n"
  },
  {
    "path": "src/dbcore/cache-existing-values-middleware.ts",
    "content": "import { deepClone } from \"../functions/utils\";\nimport { DBCore } from \"../public/types/dbcore\";\nimport { Middleware } from \"../public/types/middleware\";\nimport Promise from \"../helpers/promise\";\nimport { cmp } from '../functions/cmp';\n\nexport function getFromTransactionCache(\n  keys: readonly any[],\n  cache: { keys: any[]; values: any[] } | undefined | null,\n  clone?: boolean\n) {\n  try {\n    if (!cache) return null;\n    if (cache.keys.length < keys.length) return null;\n    const result: any[] = [];\n    // Compare if the exact same order of keys was retrieved in same transaction:\n    // Allow some cached keys to be omitted from provided set of keys\n    // Use case: 1. getMany(keys) 2. update a subset of those 3. call put with the updated ones ==> middlewares should be able to find old values\n    for (let i = 0, j = 0; i < cache.keys.length && j < keys.length; ++i) {\n      if (cmp(cache.keys[i], keys[j]) !== 0) continue;\n      result.push(clone ? deepClone(cache.values[i]) : cache.values[i]);\n      ++j;\n    }\n    // If got all keys caller was looking for, return result.\n    return result.length === keys.length ? result : null;\n  } catch {\n    return null;\n  }\n}\n\nexport const cacheExistingValuesMiddleware: Middleware<DBCore> = {\n  stack: \"dbcore\",\n  level: -1,\n  create: (core) => {\n    return {\n      table: (tableName) => {\n        const table = core.table(tableName);\n        return {\n          ...table,\n          getMany: (req) => {\n            if (!req.cache) {\n              return table.getMany(req);\n            }\n            const cachedResult = getFromTransactionCache(\n              req.keys,\n              req.trans[\"_cache\"],\n              req.cache === \"clone\"\n            );\n            if (cachedResult) {\n              return Promise.resolve(cachedResult);\n            }\n            return table.getMany(req).then((res) => {\n              req.trans[\"_cache\"] = {\n                keys: req.keys,\n                values: req.cache === \"clone\" ? deepClone(res) : res,\n              };\n              return res;\n            });\n          },\n          mutate: (req) => {\n            // Invalidate cache on any mutate except \"add\" which can't change existing values:\n            if (req.type !== \"add\") req.trans[\"_cache\"] = null;\n            return table.mutate(req);\n          },\n        };\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "src/dbcore/dbcore-indexeddb.ts",
    "content": "import {\n  DBCore,\n  DBCoreCursor,\n  DBCoreOpenCursorRequest,\n  DBCoreQueryRequest,\n  DBCoreIndex,\n  DBCoreKeyRange,\n  DBCoreQueryResponse,\n  DBCoreRangeType,\n  DBCoreSchema,\n  DBCoreTableSchema,\n  DBCoreTable,\n  DBCoreMutateResponse,\n} from \"../public/types/dbcore\";\nimport { isArray } from '../functions/utils';\nimport { eventRejectHandler, preventDefault } from '../functions/event-wrappers';\nimport { wrap } from '../helpers/promise';\nimport { getMaxKey } from '../functions/quirks';\nimport { getKeyExtractor } from './get-key-extractor';\n\nexport function arrayify<T>(arrayLike: {length: number, [index: number]: T}): T[] {\n  return [].slice.call(arrayLike);\n}\nexport function pick<T,Prop extends keyof T>(obj: T, props: Prop[]): Pick<T, Prop> {\n  const result = {} as Pick<T, Prop>;\n  props.forEach(prop => result[prop] = obj[prop]);\n  return result;\n}\n\nlet _id_counter = 0;\n\nexport function getKeyPathAlias(keyPath: null | string | string[]) {\n  return keyPath == null ?\n    \":id\" :\n    typeof keyPath === 'string' ?\n      keyPath :\n      `[${keyPath.join('+')}]`;\n}\n\nexport function createDBCore (\n  db: IDBDatabase,\n  IdbKeyRange: typeof IDBKeyRange,\n  tmpTrans: IDBTransaction) : DBCore\n{\n  function extractSchema(db: IDBDatabase, trans: IDBTransaction) : {schema: DBCoreSchema, hasGetAll: boolean, hasIdb3Features: boolean} {\n    const tables = arrayify(db.objectStoreNames);\n    const tempStore: Partial<{getAll: any, getAllRecords: any}> = tables.length > 0\n      ? trans.objectStore(tables[0])\n      : {};\n    return {\n      schema: {\n        name: db.name,\n        tables: tables.map(table => trans.objectStore(table)).map(store => {\n          const {keyPath, autoIncrement} = store;\n          const compound = isArray(keyPath);\n          const outbound = keyPath == null;\n          const indexByKeyPath: {[keyPathAlias: string]: DBCoreIndex} = {};\n          const result = {\n            name: store.name,\n            primaryKey: {\n              name: null,\n              isPrimaryKey: true,\n              outbound,\n              compound,\n              keyPath,\n              autoIncrement,\n              unique: true,\n              extractKey: getKeyExtractor(keyPath)\n            } as DBCoreIndex,\n            indexes: arrayify(store.indexNames).map(indexName => store.index(indexName))\n              .map(index => {\n                const {name, unique, multiEntry, keyPath} = index;\n                const compound = isArray(keyPath);\n                const result: DBCoreIndex = {\n                  name,\n                  compound,\n                  keyPath,\n                  unique,\n                  multiEntry,\n                  extractKey: getKeyExtractor(keyPath)\n                };\n                indexByKeyPath[getKeyPathAlias(keyPath)] = result;\n                return result;\n              }),\n            getIndexByKeyPath: (keyPath: null | string | string[]) => indexByKeyPath[getKeyPathAlias(keyPath)]\n          };\n          indexByKeyPath[\":id\"] = result.primaryKey;\n          if (keyPath != null) {\n            indexByKeyPath[getKeyPathAlias(keyPath)] = result.primaryKey;\n          }\n          return result;\n        })\n      },\n      hasGetAll: tables.length > 0 && ('getAll' in tempStore) &&\n        !(typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) &&\n        !/(Chrome\\/|Edge\\/)/.test(navigator.userAgent) &&\n        [].concat(navigator.userAgent.match(/Safari\\/(\\d*)/))[1] < 604), // Bug with getAll() on Safari ver<604. See discussion following PR #579\n      hasIdb3Features: 'getAllRecords' in tempStore\n    };\n  }\n\n  function makeIDBKeyRange (range: DBCoreKeyRange) : IDBKeyRange | null {\n    if (range.type === DBCoreRangeType.Any) return null;\n    if (range.type === DBCoreRangeType.Never) throw new Error(\"Cannot convert never type to IDBKeyRange\");\n    const {lower, upper, lowerOpen, upperOpen} = range;\n    const idbRange = lower === undefined ?\n      upper === undefined ?\n        null : //IDBKeyRange.lowerBound(-Infinity, false) : // Any range (TODO: Should we return null instead?)\n        IdbKeyRange.upperBound(upper, !!upperOpen) : // below\n      upper === undefined ?\n        IdbKeyRange.lowerBound(lower, !!lowerOpen) : // above\n        IdbKeyRange.bound(lower, upper, !!lowerOpen, !!upperOpen);\n    return idbRange;\n  }\n\n  function createDbCoreTable(tableSchema: DBCoreTableSchema): DBCoreTable {\n    const tableName = tableSchema.name;\n\n    function mutate ({trans, type, keys, values, range}) {\n      return new Promise<DBCoreMutateResponse>((resolve, reject) => {\n        resolve = wrap(resolve);\n        const store = (trans as IDBTransaction).objectStore(tableName);\n        const outbound = store.keyPath == null;\n        const isAddOrPut = type === \"put\" || type === \"add\";\n        if (!isAddOrPut && type !== 'delete' && type !== 'deleteRange')\n          throw new Error (\"Invalid operation type: \" + type);\n\n        const {length} = keys || values || {length: 1}; // keys.length if keys. values.length if values. 1 if range.\n        if (keys && values && keys.length !== values.length) {\n          throw new Error(\"Given keys array must have same length as given values array.\");\n        }\n        if (length === 0)\n          // No items to write. Don't even bother!\n          return resolve({numFailures: 0, failures: {}, results: [], lastResult: undefined});\n\n        let req: IDBRequest;\n        const reqs: IDBRequest[] = [];\n          \n        const failures: {[operationNumber: number]: Error} = [];\n        let numFailures = 0;\n        const errorHandler = \n          event => {\n            ++numFailures;\n            preventDefault(event);\n          };\n  \n        if (type === 'deleteRange') {\n          // Here the argument is the range\n          if (range.type === DBCoreRangeType.Never)\n            return resolve({numFailures, failures, results: [], lastResult: undefined}); // Deleting the Never range shoulnt do anything.\n          if (range.type === DBCoreRangeType.Any)\n            reqs.push(req = store.clear()); // Deleting the Any range is equivalent to store.clear()\n          else\n            reqs.push(req = store.delete(makeIDBKeyRange(range)));\n        } else {\n          // No matter add, put or delete - find out arrays of first and second arguments to it.\n          const [args1, args2] = isAddOrPut ?\n            outbound ?\n              [values, keys] :\n              [values, null] :\n            [keys, null];\n\n          if (isAddOrPut) {\n            for (let i=0; i<length; ++i) {\n              reqs.push(req = (args2 && args2[i] !== undefined ?\n                store[type](args1[i], args2[i]) :\n                store[type](args1[i])) as IDBRequest);\n              req.onerror = errorHandler;\n            }\n          } else {\n            for (let i=0; i<length; ++i) {\n              reqs.push(req = store[type](args1[i]) as IDBRequest);\n              req.onerror = errorHandler;\n            }\n          }\n        }\n        const done = event => {\n          const lastResult = event.target.result;\n          reqs.forEach((req, i) => req.error != null && (failures[i] = req.error));\n          resolve({\n            numFailures,\n            failures,\n            results: type === \"delete\" ? keys : reqs.map(req => req.result),\n            lastResult\n          });\n        };\n  \n        req.onerror = event => { // wrap() not needed. All paths calling outside will wrap!\n          errorHandler(event);\n          done(event);\n        };\n  \n        req.onsuccess = done;\n      });\n    }\n    \n    function openCursor ({trans, values, query, reverse, unique}: DBCoreOpenCursorRequest): Promise<DBCoreCursor>\n    {\n      return new Promise((resolve, reject) => {\n        resolve = wrap(resolve);\n        const {index, range} = query;\n        const store = (trans as IDBTransaction).objectStore(tableName);\n        // source\n        const source = index.isPrimaryKey ?\n          store :\n          store.index(index.name);\n        // direction\n        const direction = reverse ?\n          unique ?\n            \"prevunique\" :\n            \"prev\" :\n          unique ?\n            \"nextunique\" :\n            \"next\";\n        // request\n        const req = values || !('openKeyCursor' in source) ?\n          source.openCursor(makeIDBKeyRange(range), direction) :\n          source.openKeyCursor(makeIDBKeyRange(range), direction);\n          \n        // iteration\n        req.onerror = eventRejectHandler(reject);\n        req.onsuccess = wrap(ev => {\n\n          const cursor = req.result as unknown as DBCoreCursor;\n          if (!cursor) {\n            resolve(null);\n            return;\n          }\n          (cursor as any).___id = ++_id_counter;\n          (cursor as any).done = false;\n          const _cursorContinue = cursor.continue.bind(cursor);\n          let _cursorContinuePrimaryKey = cursor.continuePrimaryKey;\n          if (_cursorContinuePrimaryKey) _cursorContinuePrimaryKey = _cursorContinuePrimaryKey.bind(cursor);\n          const _cursorAdvance = cursor.advance.bind(cursor);\n          const doThrowCursorIsNotStarted = ()=>{throw new Error(\"Cursor not started\");}\n          const doThrowCursorIsStopped = ()=>{throw new Error(\"Cursor not stopped\");}\n          (cursor as any).trans = trans;\n          cursor.stop = cursor.continue = cursor.continuePrimaryKey = cursor.advance = doThrowCursorIsNotStarted;\n          cursor.fail = wrap(reject);\n          cursor.next = function (this: DBCoreCursor) {\n            // next() must work with \"this\" pointer in order to function correctly for ProxyCursors (derived objects)\n            // without having to re-define next() on each child.\n            let gotOne = 1;\n            return this.start(() => gotOne-- ? this.continue() : this.stop()).then(() => this);\n          };\n          cursor.start = (callback) => {\n            //console.log(\"Starting cursor\", (cursor as any).___id);\n            const iterationPromise = new Promise<void>((resolveIteration, rejectIteration) =>{\n              resolveIteration = wrap(resolveIteration);\n              req.onerror = eventRejectHandler(rejectIteration);\n              cursor.fail = rejectIteration;\n              cursor.stop = value => {\n                //console.log(\"Cursor stop\", cursor);\n                cursor.stop = cursor.continue = cursor.continuePrimaryKey = cursor.advance = doThrowCursorIsStopped;\n                resolveIteration(value);\n              };\n            });\n            // Now change req.onsuccess to a callback that doesn't call initCursor but just observer.next()\n            const guardedCallback = () => {\n              if (req.result) {\n                //console.log(\"Next result\", cursor);\n                try {\n                  callback();\n                } catch (err) {\n                  cursor.fail(err);\n                }\n              } else {\n                (cursor as any).done = true;\n                cursor.start = ()=>{throw new Error(\"Cursor behind last entry\");}\n                cursor.stop();\n              }\n            }\n            req.onsuccess = wrap(ev => {\n              //cursor.continue = _cursorContinue;\n              //cursor.continuePrimaryKey = _cursorContinuePrimaryKey;\n              //cursor.advance = _cursorAdvance;\n              req.onsuccess = guardedCallback;\n              guardedCallback();\n            });\n            cursor.continue = _cursorContinue;\n            cursor.continuePrimaryKey = _cursorContinuePrimaryKey;\n            cursor.advance = _cursorAdvance;\n            guardedCallback();\n            return iterationPromise;\n          };\n          resolve(cursor);\n        }, reject); \n      });\n    }\n  \n    function query (hasGetAll: boolean, hasIdb3Features: boolean) {\n      return (request: DBCoreQueryRequest) => {\n        return new Promise<DBCoreQueryResponse>((resolve, reject) => {\n          resolve = wrap(resolve);\n          const {trans, values, limit, query} = request;\n          // Normalize direction once - undefined means 'next'\n          const direction = request.direction ?? 'next';\n          const nonInfinitLimit = limit === Infinity ? undefined : limit;\n          const {index, range} = query;\n          const store = (trans as IDBTransaction).objectStore(tableName);\n          const source = index.isPrimaryKey ? store : store.index(index.name);\n          const idbKeyRange = makeIDBKeyRange(range);\n          if (limit === 0) return resolve({result: []});\n          \n          // Use getAll/getAllKeys with direction option (IDB 3.0)\n          // This is 2-5x faster than cursor iteration for reverse queries\n          if (hasIdb3Features) {\n            const options = {\n              query: idbKeyRange,\n              count: nonInfinitLimit,\n              direction\n            };\n            const req = values ?\n                (source as any).getAll(options) :\n                (source as any).getAllKeys(options);\n            req.onsuccess = event => resolve({result: event.target.result});\n            req.onerror = eventRejectHandler(reject);\n          } else if (hasGetAll && direction === 'next') {\n            const req = values ?\n                (source as any).getAll(idbKeyRange, nonInfinitLimit) :\n                (source as any).getAllKeys(idbKeyRange, nonInfinitLimit);\n            req.onsuccess = (event: any) => resolve({result: event.target.result});\n            req.onerror = eventRejectHandler(reject);\n          } else {\n            // Fallback: use cursor for non-default direction when IDB 3.0 features not available\n            let count = 0;\n            const req = values || !('openKeyCursor' in source) ?\n              source.openCursor(idbKeyRange, direction) :\n              source.openKeyCursor(idbKeyRange, direction);\n            const result = [];\n            req.onsuccess = () => {\n              const cursor = req.result as IDBCursorWithValue;\n              if (!cursor) return resolve({result});\n              result.push(values ? cursor.value : cursor.primaryKey);\n              if (++count === limit) return resolve({result});\n              cursor.continue();\n            };\n            req.onerror = eventRejectHandler(reject);\n          }\n        });\n      };\n    }\n  \n    return {\n      name: tableName,\n      schema: tableSchema,\n      \n      mutate,\n\n      getMany ({trans, keys}) {\n        return new Promise<any[]>((resolve, reject) => {\n          resolve = wrap(resolve);\n          const store = (trans as IDBTransaction).objectStore(tableName);\n          const length = keys.length;\n          const result = new Array(length);\n          let keyCount = 0;\n          let callbackCount = 0;\n          let valueCount = 0;\n          let req: IDBRequest & {_pos?: number};\n    \n          const successHandler = event => {\n            const req = event.target;\n            if ((result[req._pos] = req.result) != null) ++valueCount;\n            if (++callbackCount === keyCount) resolve(result);\n          };\n          const errorHandler = eventRejectHandler(reject);\n    \n          for (let i=0; i<length; ++i) {\n            const key = keys[i];\n            if (key != null) {\n              req = store.get(keys[i]);\n              req._pos = i;\n              req.onsuccess = successHandler;\n              req.onerror = errorHandler;\n              ++keyCount;\n            }\n          }\n          if (keyCount === 0) resolve(result);\n        });\n      },\n\n      get ({trans, key}) {\n        return new Promise<any>((resolve, reject) => {\n          resolve = wrap (resolve);\n          const store = (trans as IDBTransaction).objectStore(tableName);\n          const req = store.get(key);\n          req.onsuccess = event => resolve((event.target as any).result);\n          req.onerror = eventRejectHandler(reject);\n        });\n      },\n\n      query: query(hasGetAll, hasIdb3Features),\n      \n      openCursor,\n\n      count ({query, trans}) {\n        const {index, range} = query;\n        return new Promise<number>((resolve, reject) => {\n          const store = (trans as IDBTransaction).objectStore(tableName);\n          const source = index.isPrimaryKey ? store : store.index(index.name);\n          const idbKeyRange = makeIDBKeyRange(range);\n          const req = idbKeyRange ? source.count(idbKeyRange) : source.count();\n          req.onsuccess = wrap(ev => resolve((ev.target as IDBRequest).result));\n          req.onerror = eventRejectHandler(reject);\n        });\n      }\n    };\n  }\n\n  const {schema, hasGetAll, hasIdb3Features} = extractSchema(db, tmpTrans);\n  const tables = schema.tables.map(tableSchema => createDbCoreTable(tableSchema));\n  const tableMap: {[name: string]: DBCoreTable} = {};\n  tables.forEach(table => tableMap[table.name] = table);\n  return {\n    stack: \"dbcore\",\n    \n    transaction: db.transaction.bind(db),\n\n    table(name: string) {\n      const result = tableMap[name];\n      if (!result) throw new Error(`Table '${name}' not found`);\n      return tableMap[name];\n    },\n\n    MIN_KEY: -Infinity,\n\n    MAX_KEY: getMaxKey(IdbKeyRange),\n\n    schema\n\n  };\n}\n"
  },
  {
    "path": "src/dbcore/get-effective-keys.ts",
    "content": "import {\n  DBCoreAddRequest,\n  DBCorePutRequest,\n  DBCoreDeleteRequest,\n  DBCoreIndex,\n  DBCoreTable,\n} from \"../public/types/dbcore\";\n\nexport function getEffectiveKeys (\n  primaryKey: DBCoreIndex,\n  req: (Pick<DBCoreAddRequest | DBCorePutRequest, \"type\" | \"values\"> & {keys?: any[]}) | Pick<DBCoreDeleteRequest, \"keys\" | \"type\">)\n{\n  //const {outbound} = primaryKey;\n  if (req.type === 'delete') return req.keys;\n  return req.keys || req.values.map(primaryKey.extractKey)\n}\n"
  },
  {
    "path": "src/dbcore/get-key-extractor.ts",
    "content": "import { getByKeyPath } from '../functions/utils';\n\nexport function getKeyExtractor (keyPath: null | string | string[]) : (a: any) => any {\n  if (keyPath == null) {\n    return () => undefined;\n  } else if (typeof keyPath === 'string') {\n    return getSinglePathKeyExtractor(keyPath);\n  } else {\n    return obj => getByKeyPath(obj, keyPath);\n  }\n}\n\nexport function getSinglePathKeyExtractor(keyPath: string) {\n  const split = keyPath.split('.');\n  if (split.length === 1) {\n    return obj => obj[keyPath];\n  } else {\n    return obj => getByKeyPath(obj, keyPath);\n  }\n}\n"
  },
  {
    "path": "src/dbcore/keyrange.ts",
    "content": "import { DBCoreKeyRange, DBCoreRangeType } from '../public/types/dbcore';\n\nexport const AnyRange: DBCoreKeyRange = {\n  type: DBCoreRangeType.Any,\n  lower: -Infinity,\n  lowerOpen: false,\n  upper: [[]],\n  upperOpen: false\n}\n\nexport const NeverRange: DBCoreKeyRange = {\n  type: DBCoreRangeType.Never,\n  lower: -Infinity,\n  lowerOpen: true,\n  upper: -Infinity,\n  upperOpen: true\n}\n"
  },
  {
    "path": "src/dbcore/proxy-cursor.ts",
    "content": "import { DBCoreCursor } from '../public/types/dbcore';\n\nexport interface ProxyCursorHooks {\n  // Methods\n  getKey?: () => any;\n  getPrimaryKey?: () => any;\n  getValue?: ( )=> any;\n  continue?: (key?: any, primaryKey?: any) => void;\n  start?: (onNext: ()=>void) => Promise<any>;\n}\n\nexport function ProxyCursor (\n  cursor: DBCoreCursor,\n  fixManualAdvance: boolean,\n  {\n    getKey,\n    getPrimaryKey,\n    getValue,\n    continue: doContinue,\n    start\n  }: ProxyCursorHooks\n) : DBCoreCursor\n{\n  if (!cursor) return null;\n  const props: PropertyDescriptorMap = {};\n  if (getKey) props.key = {get: getKey};\n  if (getPrimaryKey) props.primaryKey = {get: getPrimaryKey};\n  if (getValue) props.value = {get: getValue};\n  if (doContinue) {\n    props.continue = props.continuePrimaryKey = {value: doContinue};\n  }\n  if (fixManualAdvance) {\n    const continueNext = doContinue || cursor.continue;\n    const doStart = start || cursor.start;\n    let skip = 0;\n    props.start = {\n      value: (onNext: ()=>void) => {\n        return doStart(() => skip ? (--skip, continueNext()) : onNext());\n      }\n    };\n    props.advance = {\n      value: (count: number) => {\n        if (count > 1) skip = count;\n        continueNext();\n      }\n    };\n  } else if (start) {\n    props.start = {value: start};\n  }\n\n  return Object.create(cursor, props);\n}\n"
  },
  {
    "path": "src/dbcore/virtual-index-middleware.ts",
    "content": "import {\n  DBCore,\n  DBCoreIndex,\n  DBCoreKeyRange,\n  DBCoreQueryRequest,\n  DBCoreRangeType,\n  DBCoreOpenCursorRequest,\n  DBCoreCountRequest,\n  DBCoreCursor,\n  DBCoreTable,\n} from \"../public/types/dbcore\";\nimport { isArray } from '../functions/utils';\nimport { getKeyExtractor } from './get-key-extractor';\nimport { getKeyPathAlias } from './dbcore-indexeddb';\nimport { Middleware } from '../public/types/middleware';\n\ninterface VirtualIndex extends DBCoreIndex {\n  /** True if this index is virtual, i.e. represents a compound index internally,\n   * but makes it act as as having a subset of its keyPaths.\n   */\n  isVirtual: boolean;\n\n  /** Number of keypaths that this index comprises. Can be 0..N.\n   * Note: This is the length of the *virtual index*, not the real index.\n   */\n  keyLength: number;\n\n  /** Number of popped keypaths from the real index.\n   */\n  keyTail: number;\n\n  /** LowLevelIndex represents the actual IndexedDB index behind it */\n  lowLevelIndex: DBCoreIndex;\n}\n\n// Move into some util:\nexport function pad (a: any | any[], value: any, count: number) {\n  const result = isArray(a) ? a.slice() : [a];\n  for (let i=0; i<count; ++i) result.push(value);\n  return result;\n}\n\n\nexport function createVirtualIndexMiddleware (down: DBCore) : DBCore {\n  return {\n    ...down,\n    table(tableName: string) {\n      const table = down.table(tableName);\n      const {schema} = table;\n      const indexLookup: {[indexAlias: string]: VirtualIndex[]} = {};\n      const allVirtualIndexes: VirtualIndex[] = [];\n\n      function addVirtualIndexes (keyPath: null | string | string[], keyTail: number, lowLevelIndex: DBCoreIndex): VirtualIndex {\n        const keyPathAlias = getKeyPathAlias(keyPath);\n        const indexList = (indexLookup[keyPathAlias] = indexLookup[keyPathAlias] || []);\n        const keyLength = keyPath == null ? 0: typeof keyPath === 'string' ? 1 : keyPath.length;\n        const isVirtual = keyTail > 0;\n        const virtualIndex = {\n          ...lowLevelIndex,\n          name: isVirtual\n            ? `${keyPathAlias}(virtual-from:${lowLevelIndex.name})`\n            : lowLevelIndex.name,\n          lowLevelIndex,\n          isVirtual,\n          keyTail,\n          keyLength,\n          extractKey: getKeyExtractor(keyPath),\n          unique: !isVirtual && lowLevelIndex.unique\n        };\n        indexList.push(virtualIndex);\n        if (!virtualIndex.isPrimaryKey) {\n          allVirtualIndexes.push(virtualIndex);\n        }\n        if (keyLength > 1) {\n          const virtualKeyPath = keyLength === 2 ?\n            keyPath[0] : // This is a compound [a, b]. Add a virtual normal index a.\n            keyPath.slice(0, keyLength - 1); // This is compound [a,b,c]. Add virtual compound [a,b].\n          addVirtualIndexes(virtualKeyPath, keyTail + 1, lowLevelIndex);\n        }\n        indexList.sort((a,b) => a.keyTail - b.keyTail); // Shortest keyTail is the best one (represents real index)\n        return virtualIndex;\n      }\n    \n      const primaryKey = addVirtualIndexes(schema.primaryKey.keyPath, 0, schema.primaryKey);\n      indexLookup[\":id\"] = [primaryKey];\n      for (const index of schema.indexes) {\n        addVirtualIndexes(index.keyPath, 0, index);\n      }\n    \n      function findBestIndex(keyPath: null | string | string[]): VirtualIndex {\n        const result = indexLookup[getKeyPathAlias(keyPath)];\n        return result && result[0];\n      }\n    \n      function translateRange (range: DBCoreKeyRange, keyTail: number): DBCoreKeyRange {\n        return {\n          type: range.type === DBCoreRangeType.Equal ?\n            DBCoreRangeType.Range :\n            range.type,\n          lower: pad(range.lower, range.lowerOpen ? down.MAX_KEY : down.MIN_KEY, keyTail),\n          lowerOpen: true, // doesn't matter true or false\n          upper: pad(range.upper, range.upperOpen ? down.MIN_KEY : down.MAX_KEY, keyTail),\n          upperOpen: true // doesn't matter true or false\n        };\n      }\n    \n      function translateRequest (req: DBCoreQueryRequest): DBCoreQueryRequest;\n      function translateRequest (req: DBCoreOpenCursorRequest): DBCoreOpenCursorRequest;\n      function translateRequest (req: DBCoreCountRequest): DBCoreCountRequest {\n        const index = req.query.index as VirtualIndex;\n        return index.isVirtual ? {\n          ...req,\n          query: {\n            index: index.lowLevelIndex,\n            range: translateRange(req.query.range, index.keyTail)\n          }\n        } : req;\n      }\n    \n      const result: DBCoreTable = {\n        ...table,\n        schema: {\n          ...schema,\n          primaryKey,\n          indexes: allVirtualIndexes,\n          getIndexByKeyPath: findBestIndex\n        },\n\n        count(req) {\n          return table.count(translateRequest(req));\n        },    \n    \n        query(req) {\n          return table.query(translateRequest(req));\n        },\n    \n        openCursor(req) {\n          const {keyTail, isVirtual, keyLength} = (req.query.index as VirtualIndex);\n          if (!isVirtual) return table.openCursor(req);\n    \n          function createVirtualCursor(cursor: DBCoreCursor) : DBCoreCursor {\n            function _continue (key?: any) {\n              key != null ?\n                cursor.continue(pad(key, req.reverse ? down.MAX_KEY : down.MIN_KEY, keyTail)) :\n                req.unique ?\n                  cursor.continue(\n                    cursor.key.slice(0, keyLength)\n                      .concat(req.reverse\n                        ? down.MIN_KEY\n                        : down.MAX_KEY, keyTail)\n                  ) :\n                  cursor.continue()\n            }\n            const virtualCursor = Object.create(cursor, {\n              continue: {value: _continue},\n              continuePrimaryKey: {\n                value(key: any, primaryKey: any) {\n                  cursor.continuePrimaryKey(pad(key, down.MAX_KEY, keyTail), primaryKey);\n                }\n              },\n              primaryKey: {\n                get() {\n                  return cursor.primaryKey;\n                }\n              },\n              key: {\n                get() {\n                  const key = cursor.key as any[]; // A virtual cursor always operates on compound key\n                  return keyLength === 1 ?\n                    key[0] : // Cursor.key should not be an array.\n                    key.slice(0, keyLength); // Cursor.key should be first part of array.\n                }\n              },\n              value: {\n                get() {\n                  return cursor.value;\n                }\n              }\n            });\n            return virtualCursor;\n          }\n    \n          return table.openCursor(translateRequest(req))\n            .then(cursor => cursor && createVirtualCursor(cursor));\n        }\n      };\n      return result;\n    }\n  }\n}\n\nexport const virtualIndexMiddleware : Middleware<DBCore> = {\n  stack: \"dbcore\",\n  name: \"VirtualIndexMiddleware\",\n  level: 1,\n  create: createVirtualIndexMiddleware\n};\n\n"
  },
  {
    "path": "src/errors/errors.d.ts",
    "content": "import {\n  DexieError,\n  DexieErrorConstructor,\n  DexieErrors,\n  ModifyError,\n  ModifyErrorConstructor,\n  BulkError,\n  BulkErrorConstructor,\n  ExceptionAliasSet,\n  ExceptionSet\n} from \"../public/types/errors\";\n\nexport declare const DexieError: DexieErrorConstructor;\nexport declare const ModifyError: ModifyErrorConstructor;\nexport declare const BulkError: BulkErrorConstructor;\nexport declare const exceptions : ExceptionAliasSet & {\n  Syntax: ErrorConstructor,\n  Type: ErrorConstructor,\n  Range: ErrorConstructor\n};\nexport declare const errnames : DexieErrors;\nexport declare const fullNameExceptions : ExceptionSet;\nexport declare function mapError (domError, message?) : DexieError;\n"
  },
  {
    "path": "src/errors/errors.js",
    "content": "import { derive, setProp } from '../functions/utils';\n\nvar dexieErrorNames = [\n    'Modify',\n    'Bulk',\n    'OpenFailed',\n    'VersionChange',\n    'Schema',\n    'Upgrade',\n    'InvalidTable',\n    'MissingAPI',\n    'NoSuchDatabase',\n    'InvalidArgument',\n    'SubTransaction',\n    'Unsupported',\n    'Internal',\n    'DatabaseClosed',\n    'PrematureCommit',\n    'ForeignAwait'\n];\n\nvar idbDomErrorNames = [\n    'Unknown',\n    'Constraint',\n    'Data',\n    'TransactionInactive',\n    'ReadOnly',\n    'Version',\n    'NotFound',\n    'InvalidState',\n    'InvalidAccess',\n    'Abort',\n    'Timeout',\n    'QuotaExceeded',\n    'Syntax',\n    'DataClone'\n];\n\nvar errorList = dexieErrorNames.concat(idbDomErrorNames);\n\nvar defaultTexts = {\n    VersionChanged: \"Database version changed by other database connection\",\n    DatabaseClosed: \"Database has been closed\",\n    Abort: \"Transaction aborted\",\n    TransactionInactive: \"Transaction has already completed or failed\",\n    MissingAPI: \"IndexedDB API missing. Please visit https://tinyurl.com/y2uuvskb\"\n};\n\n//\n// DexieError - base class of all out exceptions.\n//\nexport function DexieError (name, msg) {\n    // Reason we don't use ES6 classes is because:\n    // 1. It bloats transpiled code and increases size of minified code.\n    // 2. It doesn't give us much in this case.\n    // 3. It would require sub classes to call super(), which\n    //    is not needed when deriving from Error.\n    this.name = name;\n    this.message = msg;\n}\n\nderive(DexieError).from(Error).extend({\n    toString: function(){ return this.name + \": \" + this.message; }\n});\n\nfunction getMultiErrorMessage (msg, failures) {\n    return msg + \". Errors: \" + Object.keys(failures)\n        .map(key=>failures[key].toString())\n        .filter((v,i,s)=>s.indexOf(v) === i) // Only unique error strings\n        .join('\\n');\n}\n\n//\n// ModifyError - thrown in Collection.modify()\n// Specific constructor because it contains members failures and failedKeys.\n//\nexport function ModifyError (msg, failures, successCount, failedKeys) {\n    this.failures = failures;\n    this.failedKeys = failedKeys;\n    this.successCount = successCount;\n    this.message = getMultiErrorMessage(msg, failures);\n}\nderive(ModifyError).from(DexieError);\n\nexport function BulkError (msg, failures) {\n    this.name = \"BulkError\";\n    this.failures = Object.keys(failures).map(pos => failures[pos]);\n    this.failuresByPos = failures;\n    this.message = getMultiErrorMessage(msg, this.failures);\n}\nderive(BulkError).from(DexieError);\n\n//\n//\n// Dynamically generate error names and exception classes based\n// on the names in errorList.\n//\n//\n\n// Map of {ErrorName -> ErrorName + \"Error\"}\nexport var errnames = errorList.reduce((obj,name)=>(obj[name]=name+\"Error\",obj),{});\n\n// Need an alias for DexieError because we're gonna create subclasses with the same name.\nconst BaseException = DexieError;\n// Map of {ErrorName -> exception constructor}\nexport var exceptions = errorList.reduce((obj,name)=>{\n    // Let the name be \"DexieError\" because this name may\n    // be shown in call stack and when debugging. DexieError is\n    // the most true name because it derives from DexieError,\n    // and we cannot change Function.name programatically without\n    // dynamically create a Function object, which would be considered\n    // 'eval-evil'.\n    var fullName = name + \"Error\";\n    function DexieError (msgOrInner, inner){\n        this.name = fullName;\n        if (!msgOrInner) {\n            this.message = defaultTexts[name] || fullName;\n            this.inner = null;\n        } else if (typeof msgOrInner === 'string') {\n            this.message = `${msgOrInner}${!inner ? '' : '\\n ' + inner}`;\n            this.inner = inner || null;\n        } else if (typeof msgOrInner === 'object') {\n            this.message = `${msgOrInner.name} ${msgOrInner.message}`;\n            this.inner = msgOrInner;\n        }\n    }\n    derive(DexieError).from(BaseException);\n    obj[name]=DexieError;\n    return obj;\n},{});\n\n// Use ECMASCRIPT standard exceptions where applicable:\nexceptions.Syntax = SyntaxError;\nexceptions.Type = TypeError;\nexceptions.Range = RangeError;\n\nexport var exceptionMap = idbDomErrorNames.reduce((obj, name)=>{\n    obj[name + \"Error\"] = exceptions[name];\n    return obj;\n}, {});\n\nexport function mapError (domError, message) {\n    if (!domError || domError instanceof DexieError || domError instanceof TypeError || domError instanceof SyntaxError || !domError.name || !exceptionMap[domError.name])\n        return domError;\n    var rv = new exceptionMap[domError.name](message || domError.message, domError);\n    if (\"stack\" in domError) {\n        // Derive stack from inner exception if it has a stack\n        setProp(rv, \"stack\", {get: function(){\n            return this.inner.stack;\n        }});\n    }\n    return rv;\n}\n\nexport var fullNameExceptions = errorList.reduce((obj, name)=>{\n    if ([\"Syntax\",\"Type\",\"Range\"].indexOf(name) === -1)\n        obj[name + \"Error\"] = exceptions[name];\n    return obj;\n}, {});\n\nfullNameExceptions.ModifyError = ModifyError;\nfullNameExceptions.DexieError = DexieError;\nfullNameExceptions.BulkError = BulkError;\n"
  },
  {
    "path": "src/errors/index.ts",
    "content": "export * from './errors';\n"
  },
  {
    "path": "src/functions/apply-update-spec.ts",
    "content": "import { getByKeyPath, keys, setByKeyPath } from './utils';\nimport { PropModification } from \"../helpers/prop-modification\";\n\nexport function applyUpdateSpec(\n  obj: any,\n  changes: { [keyPath: string]: any }\n): boolean {\n  const keyPaths = keys(changes);\n  const numKeys = keyPaths.length;\n  let anythingModified = false;\n  for (let i = 0; i < numKeys; ++i) {\n    const keyPath = keyPaths[i];\n    const value = changes[keyPath];\n    const origValue = getByKeyPath(obj, keyPath);\n    if (value instanceof PropModification) {\n      setByKeyPath(obj, keyPath, value.execute(origValue));\n      anythingModified = true;\n    } else if (origValue !== value) {\n      setByKeyPath(obj, keyPath, value); // Adding {keyPath: undefined} means that the keyPath should be deleted. Handled by setByKeyPath\n      anythingModified = true;\n    }\n  }\n  return anythingModified;\n}\n"
  },
  {
    "path": "src/functions/bulk-delete.ts",
    "content": "import { IndexableType } from '../public/types/indexable-type';\nimport { Transaction } from '../classes/transaction';\nimport { eventRejectHandler, hookedEventRejectHandler, hookedEventSuccessHandler } from './event-wrappers';\nimport { wrap } from '../helpers/promise';\nimport { tryCatch } from '../functions/utils';\n\nexport function bulkDelete(\n  idbstore: IDBObjectStore,\n  trans: Transaction,\n  keysOrTuples: ReadonlyArray<IndexableType> | {0: IndexableType, 1: any}[],\n  hasDeleteHook: boolean, deletingHook)\n{\n  // If hasDeleteHook, keysOrTuples must be an array of tuples: [[key1, value2],[key2,value2],...],\n  // else keysOrTuples must be just an array of keys: [key1, key2, ...].\n  return new Promise<void>((resolve, reject)=>{\n      const len = keysOrTuples.length;\n      const lastItem = len - 1;\n      if (len === 0) return resolve();\n      if (!hasDeleteHook) {\n          for (let i=0; i < len; ++i) {\n              const req = idbstore.delete(keysOrTuples[i] as IDBValidKey);\n              req.onerror = eventRejectHandler(reject);\n              if (i === lastItem) req.onsuccess = wrap(()=>resolve());\n          }\n      } else {\n          let hookCtx;\n          const errorHandler = hookedEventRejectHandler(reject);\n          const successHandler = hookedEventSuccessHandler(null);\n          tryCatch(()=> {\n              for (let i = 0; i < len; ++i) {\n                  hookCtx = {onsuccess: null, onerror: null};\n                  const tuple = keysOrTuples[i];\n                  deletingHook.call(hookCtx, tuple[0], tuple[1], trans);\n                  const req = idbstore.delete(tuple[0]) as IDBRequest & { _hookCtx?};\n                  req._hookCtx = hookCtx;\n                  req.onerror = errorHandler;\n                  if (i === lastItem)\n                      req.onsuccess = hookedEventSuccessHandler(resolve);\n                  else\n                      req.onsuccess = successHandler;\n              }\n          }, err=>{\n              hookCtx.onerror && hookCtx.onerror(err);\n              throw err;\n          });\n      }\n  });\n}    \n"
  },
  {
    "path": "src/functions/chaining-functions.js",
    "content": "import {extend} from './utils';\n\nexport function nop() { }\nexport function mirror(val) { return val; }\nexport function pureFunctionChain(f1, f2) {\n    // Enables chained events that takes ONE argument and returns it to the next function in chain.\n    // This pattern is used in the hook(\"reading\") event.\n    if (f1 == null || f1 === mirror) return f2;\n    return function (val) {\n        return f2(f1(val));\n    };\n}\n\nexport function callBoth(on1, on2) {\n    return function () {\n        on1.apply(this, arguments);\n        on2.apply(this, arguments);\n    };\n}\n\nexport function hookCreatingChain(f1, f2) {\n    // Enables chained events that takes several arguments and may modify first argument by making a modification and then returning the same instance.\n    // This pattern is used in the hook(\"creating\") event.\n    if (f1 === nop) return f2;\n    return function () {\n        var res = f1.apply(this, arguments);\n        if (res !== undefined) arguments[0] = res;\n        var onsuccess = this.onsuccess, // In case event listener has set this.onsuccess\n            onerror = this.onerror;     // In case event listener has set this.onerror\n        this.onsuccess = null;\n        this.onerror = null;\n        var res2 = f2.apply(this, arguments);\n        if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess;\n        if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror;\n        return res2 !== undefined ? res2 : res;\n    };\n}\n\nexport function hookDeletingChain(f1, f2) {\n    if (f1 === nop) return f2;\n    return function () {\n        f1.apply(this, arguments);\n        var onsuccess = this.onsuccess, // In case event listener has set this.onsuccess\n            onerror = this.onerror;     // In case event listener has set this.onerror\n        this.onsuccess = this.onerror = null;\n        f2.apply(this, arguments);\n        if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess;\n        if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror;\n    };\n}\n\nexport function hookUpdatingChain(f1, f2) {\n    if (f1 === nop) return f2;\n    return function (modifications) {\n        var res = f1.apply(this, arguments);\n        extend(modifications, res); // If f1 returns new modifications, extend caller's modifications with the result before calling next in chain.\n        var onsuccess = this.onsuccess, // In case event listener has set this.onsuccess\n            onerror = this.onerror;     // In case event listener has set this.onerror\n        this.onsuccess = null;\n        this.onerror = null;\n        var res2 = f2.apply(this, arguments);\n        if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess;\n        if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror;\n        return res === undefined ?\n            (res2 === undefined ? undefined : res2) :\n            (extend(res, res2));\n    };\n}\n\nexport function reverseStoppableEventChain(f1, f2) {\n    if (f1 === nop) return f2;\n    return function () {\n        if (f2.apply(this, arguments) === false) return false;\n        return f1.apply(this, arguments);\n    };\n}\n\nexport function nonStoppableEventChain(f1, f2) {\n    if (f1 === nop) return f2;\n    return function () {\n        f1.apply(this, arguments);\n        f2.apply(this, arguments);\n    };\n}\n\nexport function promisableChain(f1, f2) {\n    if (f1 === nop) return f2;\n    return function () {\n        var res = f1.apply(this, arguments);\n        if (res && typeof res.then === 'function') {\n            var thiz = this,\n                i = arguments.length,\n                args = new Array(i);\n            while (i--) args[i] = arguments[i];\n            return res.then(function () {\n                return f2.apply(thiz, args);\n            });\n        }\n        return f2.apply(this, arguments);\n    };\n}\n"
  },
  {
    "path": "src/functions/cmp.ts",
    "content": "// Implementation of https://www.w3.org/TR/IndexedDB-3/#compare-two-keys\n\nimport { toStringTag } from './utils';\n\n// ... with the adjustment to return NaN instead of throwing.\nexport function cmp(a: any, b: any): number {\n  try {\n    const ta = type(a);\n    const tb = type(b);\n    if (ta !== tb) {\n      if (ta === 'Array') return 1;\n      if (tb === 'Array') return -1;\n      if (ta === 'binary') return 1;\n      if (tb === 'binary') return -1;\n      if (ta === 'string') return 1;\n      if (tb === 'string') return -1;\n      if (ta === 'Date') return 1;\n      if (tb !== 'Date') return NaN;\n      return -1;\n    }\n    switch (ta) {\n      case 'number':\n      case 'Date':\n      case 'string':\n        return a > b ? 1 : a < b ? -1 : 0;\n      case 'binary': {\n        return compareUint8Arrays(getUint8Array(a), getUint8Array(b));\n      }\n      case 'Array':\n        return compareArrays(a, b);\n    }\n  } catch {}\n  return NaN; // Return value if any given args are valid keys.\n}\n\nexport function compareArrays(a: any[], b: any[]): number {\n  const al = a.length;\n  const bl = b.length;\n  const l = al < bl ? al : bl;\n  for (let i = 0; i < l; ++i) {\n    const res = cmp(a[i], b[i]);\n    if (res !== 0) return res;\n  }\n  return al === bl ? 0 : al < bl ? -1 : 1;\n}\n\nexport function compareUint8Arrays(\n  a: Uint8Array,\n  b: Uint8Array\n) {\n  const al = a.length;\n  const bl = b.length;\n  const l = al < bl ? al : bl;\n  for (let i = 0; i < l; ++i) {\n    if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;\n  }\n  return al === bl ? 0 : al < bl ? -1 : 1;\n}\n\n// Implementation of https://www.w3.org/TR/IndexedDB-3/#key-type\nfunction type(x: any) {\n  const t = typeof x;\n  if (t !== 'object') return t;\n  if (ArrayBuffer.isView(x)) return 'binary';\n  const tsTag = toStringTag(x); // Cannot use instanceof in Safari\n  return tsTag === 'ArrayBuffer' ? 'binary' : (tsTag as 'Array' | 'Date');\n}\n\ntype BinaryType =\n  | ArrayBuffer\n  | DataView\n  | Uint8ClampedArray\n  | ArrayBufferView\n  | Uint8Array\n  | Int8Array\n  | Uint16Array\n  | Int16Array\n  | Uint32Array\n  | Int32Array\n  | Float32Array\n  | Float64Array\n  | BigInt64Array \n  | BigUint64Array;\n\nfunction getUint8Array(a: BinaryType): Uint8Array {\n  if (a instanceof Uint8Array) return a;\n  if (ArrayBuffer.isView(a))\n    // TypedArray or DataView\n    return new Uint8Array(a.buffer, a.byteOffset, a.byteLength);\n  return new Uint8Array(a); // ArrayBuffer\n}\n"
  },
  {
    "path": "src/functions/combine.ts",
    "content": "export function combine(filter1, filter2) {\n  return filter1 ?\n      filter2 ?\n          function () { return filter1.apply(this, arguments) && filter2.apply(this, arguments); } :\n          filter1 :\n      filter2;\n}\n"
  },
  {
    "path": "src/functions/compare-functions.ts",
    "content": "import { IndexableType } from '../public/types/indexable-type';\n\nexport function simpleCompare(a, b) {\n  return a < b ? -1 : a === b ? 0 : 1;\n}\n\nexport function simpleCompareReverse(a, b) {\n  return a > b ? -1 : a === b ? 0 : 1;\n}\n"
  },
  {
    "path": "src/functions/event-wrappers.ts",
    "content": "import { wrap } from \"../helpers/promise\";\n\nexport function eventRejectHandler(reject) {\n  return wrap(function (event) {\n      preventDefault(event);\n      reject (event.target.error);\n      return false;\n  });\n}\n\nexport function eventSuccessHandler (resolve) {\n  return wrap(function (event){\n      resolve(event.target.result);\n  });\n}\n\nexport function hookedEventRejectHandler (reject) {\n  return wrap(function (event) {\n      // See comment on hookedEventSuccessHandler() why wrap() is needed only when supporting hooks.\n      \n      var req = event.target,\n          err = req.error,\n          ctx = req._hookCtx,// Contains the hook error handler. Put here instead of closure to boost performance.\n          hookErrorHandler = ctx && ctx.onerror;\n      hookErrorHandler && hookErrorHandler(err);\n      preventDefault(event);\n      reject (err);\n      return false;\n  });\n}\n\nexport function hookedEventSuccessHandler(resolve) {\n  // wrap() is needed when calling hooks because the rare scenario of:\n  //  * hook does a db operation that fails immediately (IDB throws exception)\n  //    For calling db operations on correct transaction, wrap makes sure to set PSD correctly.\n  //    wrap() will also execute in a virtual tick.\n  //  * If not wrapped in a virtual tick, direct exception will launch a new physical tick.\n  //  * If this was the last event in the bulk, the promise will resolve after a physical tick\n  //    and the transaction will have committed already.\n  // If no hook, the virtual tick will be executed in the reject()/resolve of the final promise,\n  // because it is always marked with _lib = true when created using Transaction._promise().\n  return wrap(function(event) {\n      var req = event.target,\n          ctx = req._hookCtx,// Contains the hook error handler. Put here instead of closure to boost performance.\n          result = ctx.value || req.result, // Pass the object value on updates. The result from IDB is the primary key.\n          hookSuccessHandler = ctx && ctx.onsuccess;\n      hookSuccessHandler && hookSuccessHandler(result);\n      resolve && resolve(result);\n  }, resolve);\n}\n\n\nexport function preventDefault(event) {\n  if (event.stopPropagation) // IndexedDBShim doesnt support this on Safari 8 and below.\n      event.stopPropagation();\n  if (event.preventDefault) // IndexedDBShim doesnt support this on Safari 8 and below.\n      event.preventDefault();\n}\n\nexport function BulkErrorHandlerCatchAll(errorList, done?, supportHooks?) {\n  return (supportHooks ? hookedEventRejectHandler : eventRejectHandler)(e => {\n      errorList.push(e);\n      done && done();\n  });\n}\n\n"
  },
  {
    "path": "src/functions/get-object-diff.ts",
    "content": "import { keys, hasOwn, toStringTag } from './utils';\n\nexport function getObjectDiff(a: any, b: any, rv?: any, prfx?: string) {\n  // Compares objects a and b and produces a diff object.\n  rv = rv || {};\n  prfx = prfx || '';\n  keys(a).forEach((prop) => {\n    if (!hasOwn(b, prop)) {\n      // Property removed\n      rv[prfx + prop] = undefined;\n    } else {\n      var ap = a[prop],\n        bp = b[prop];\n      if (typeof ap === 'object' && typeof bp === 'object' && ap && bp) {\n        const apTypeName = toStringTag(ap);\n        const bpTypeName = toStringTag(bp);\n\n        if (apTypeName !== bpTypeName) {\n          rv[prfx + prop] = b[prop]; // Property changed to other type\n        } else if (apTypeName === 'Object') {\n          // Pojo objects (not Date, ArrayBuffer, Array etc). Go deep.\n          getObjectDiff(ap, bp, rv, prfx + prop + '.');\n        } else if (ap !== bp) {\n          // Values differ.\n          // Could have checked if Date, arrays or binary types have same\n          // content here but I think that would be a suboptimation.\n          // Prefer simplicity.\n          rv[prfx + prop] = b[prop];\n        }\n      } else if (ap !== bp) rv[prfx + prop] = b[prop]; // Primitive value changed\n    }\n  });\n  keys(b).forEach((prop) => {\n    if (!hasOwn(a, prop)) {\n      rv[prfx + prop] = b[prop]; // Property added\n    }\n  });\n  return rv;\n}\n"
  },
  {
    "path": "src/functions/is-promise-like.ts",
    "content": "export function isPromiseLike(p): p is PromiseLike<any> {\n  return p && typeof p.then === 'function';\n}\n"
  },
  {
    "path": "src/functions/make-class-constructor.ts",
    "content": "import { arrayToObject, derive } from './utils';\n\n\nexport function makeClassConstructor<TConstructor> (prototype: Object, constructor: Function) {\n  /*const propertyDescriptorMap = arrayToObject(\n    Object.getOwnPropertyNames(prototype),\n    propKey => [propKey, Object.getOwnPropertyDescriptor(prototype, propKey)]);\n\n  // Both derive and clone the prototype.\n  //   derive: So that x instanceof T returns true when T is the class template.\n  //   clone: Optimizes method access a bit (but actually not nescessary)\n  const derivedPrototypeClone = Object.create(prototype, propertyDescriptorMap);\n  derivedPrototypeClone.constructor = constructor;\n  constructor.prototype = derivedPrototypeClone;\n  return constructor as any as TConstructor;*/\n\n  // Keep the above code in case we want to clone AND derive the parent prototype.\n  // Reason would be optimization of property access.\n  // The code below will only create a prototypal inheritance from given constructor function\n  // to given prototype.\n  derive(constructor).from({prototype});\n  return constructor as any as TConstructor;  \n}\n"
  },
  {
    "path": "src/functions/propmods/add.ts",
    "content": "import { PropModification } from \"../../helpers/prop-modification\";\n\nexport function add(value: number | bigint | Array<string | number>) {\n  return new PropModification({add: value});\n}\n"
  },
  {
    "path": "src/functions/propmods/index.ts",
    "content": "export * from \"./add\";\nexport * from \"./remove\";\nexport * from \"./replace-prefix\";\n"
  },
  {
    "path": "src/functions/propmods/remove.ts",
    "content": "import { PropModification } from \"../../helpers/prop-modification\";\n\nexport function remove(value: number | bigint | Array<string | number>) {\n  return new PropModification({remove: value});\n}\n"
  },
  {
    "path": "src/functions/propmods/replace-prefix.ts",
    "content": "import { PropModification } from \"../../helpers/prop-modification\";\n\nexport function replacePrefix(a: string, b:string) {\n  return new PropModification({replacePrefix: [a, b]});\n}\n"
  },
  {
    "path": "src/functions/quirks.ts",
    "content": "import { maxString } from '../globals/constants';\n\nexport function safariMultiStoreFix(storeNames: string[]) {\n  return storeNames.length === 1 ? storeNames[0] : storeNames;\n}\n\nexport function getNativeGetDatabaseNamesFn(indexedDB) {\n  var fn = indexedDB && (indexedDB.getDatabaseNames || indexedDB.webkitGetDatabaseNames);\n  return fn && fn.bind(indexedDB);\n}\n\nexport let getMaxKey = (IdbKeyRange: typeof IDBKeyRange) => {\n  try {\n    IdbKeyRange.only([[]]);\n    getMaxKey = () => [[]];\n    return [[]];\n  } catch (e) {\n    getMaxKey = () => maxString;\n    return maxString;\n  }\n}\n"
  },
  {
    "path": "src/functions/stringify-key.d.ts",
    "content": "import { IndexableType } from \"../public/types/indexable-type\";\n\n/** Makes any IndexableType instance a string that is unique within it's own\n * type range and satisfies the equality algorithm indexedDB keys, for example\n *  stringifyKey(new Date(1230000000)) === stringifyKey(new Date(1230000000))\n *  stringifyKey(new Uint8Array([1,2,3])) === stringifyKey(new Uint8Array([1,2,3]))\n * \n * @param key {IndexableType} Key to string\n */\nexport function stringifyKey (key: IndexableType): string;\nexport function unstringifyKey (str: string): IndexableType;\n"
  },
  {
    "path": "src/functions/stringify-key.js",
    "content": "/* Keeping this file \"js\" because typescript wouldn't assist us here (as of Typescript version 2.6.1)\n* By keeping it as plain JS, we can supply a corresponding d.ts file with a typed signature.\n*/\nimport { isArray } from './utils';\nimport { exceptions } from '../errors';\n\nexport function stringifyKey (key) { // key is IndexableType but declaring type here forces us to cast a lot.\n  return typeof key === 'string' ?\n    // string do not need stringification\n    'S'+key :\n    typeof key === 'number' ?\n      // number can be stringified via Number.prototype.toString(). Use (''+key) to invoke:\n      'N'+key :\n      // Now we know key is neither string or number. Check if it is Array:\n      isArray(key) ?\n        // key is an Array. Not an Typed Array or so - just a plain Array.\n        // Array.prototype.toString() would be good enough stringifaction if it wasn't for that contained\n        // keys may be of type ArrayBufferView or DataView, so we will have to call ourselves recursively via\n        // Array.prototype.map():\n        'A'+JSON.stringify(key.map(stringifyKey)) :\n        // key is any IDBValidKey except Array, string or number:\n        'byteOffset' in key ? // Could also use ArrayBuffer.isView(key) here\n          // DataView or ArrayBufferView (for example Uint8Array, Uin16Array, etc).\n          // Canonicalize it by stringifying it's underlying buffer as Uint8Array\n          'B'+new Uint8Array(key.buffer, key.byteOffset, key.byteLength) :\n          // key must now be a Date or ArrayBuffer\n          // Date or ArrayBuffer?\n          'byteLength' in key ?\n            // ArrayBuffer\n            'B'+new Uint8Array(key) :\n            // Date\n            'D'+key.getTime();\n}\n\nexport function unstringifyKey(str) {\n  const value = str.substr(1);\n  switch (str[0]) {\n    case 'S': return value;\n    case 'N': return parseFloat(value);\n    case 'A': return JSON.parse(value).map(unstringifyKey);\n    case 'B': return new Uint8Array(value.split(','));\n    case 'D': return new Date(parseInt(value));\n    default: throw new exceptions.InvalidArgument(\"Invalid key\");\n  }\n}\n"
  },
  {
    "path": "src/functions/temp-transaction.ts",
    "content": "import { PSD, rejection, newScope } from \"../helpers/promise\";\nimport { DexieOptions } from \"../public/types/dexie-constructor\";\nimport { errnames, exceptions } from \"../errors\";\nimport { nop } from \"./chaining-functions\";\nimport { Transaction } from \"../classes/transaction\";\nimport { Dexie } from '../classes/dexie';\n\n/* Generate a temporary transaction when db operations are done outside a transaction scope.\n*/\nexport function tempTransaction (\n  db: Dexie,\n  mode: IDBTransactionMode,\n  storeNames: string[],\n  fn: (resolve, reject, trans: Transaction) => any)\n  // Last argument is \"writeLocked\". But this doesnt apply to oneshot direct db operations, so we ignore it.\n{\n  if (!db.idbdb || (!db._state.openComplete && (!PSD.letThrough && !db._vip))) {\n    if (db._state.openComplete) {\n      // db.idbdb is falsy but openComplete is true. Must have been an exception durin open.\n      // Don't wait for openComplete as it would lead to infinite loop.\n      return rejection(new exceptions.DatabaseClosed(db._state.dbOpenError));\n    }\n    if (!db._state.isBeingOpened) {\n      if (!db._state.autoOpen)\n        return rejection(new exceptions.DatabaseClosed());\n      db.open().catch(nop); // Open in background. If if fails, it will be catched by the final promise anyway.\n    }\n    return db._state.dbReadyPromise.then(() => tempTransaction(db, mode, storeNames, fn));\n  } else {\n    var trans = db._createTransaction(mode, storeNames, db._dbSchema);\n    try {\n      trans.create();\n      db._state.PR1398_maxLoop = 3;\n    } catch (ex) {\n      if (ex.name === errnames.InvalidState && db.isOpen() && --db._state.PR1398_maxLoop > 0) {\n        console.warn('Dexie: Need to reopen db');\n        db.close({disableAutoOpen: false});\n        return db.open().then(()=>tempTransaction(db, mode, storeNames, fn));\n      }\n      return rejection(ex);\n    }\n    return trans._promise(mode, (resolve, reject) => {\n      return newScope(() => { // OPTIMIZATION POSSIBLE? newScope() not needed because it's already done in _promise.\n        PSD.trans = trans;\n        return fn(resolve, reject, trans);\n      });\n    }).then(result => {\n      // Instead of resolving value directly, wait with resolving it until transaction has completed.\n      // Otherwise the data would not be in the DB if requesting it in the then() operation.\n      // Specifically, to ensure that the following expression will work:\n      //\n      //   db.friends.put({name: \"Arne\"}).then(function () {\n      //       db.friends.where(\"name\").equals(\"Arne\").count(function(count) {\n      //           assert (count === 1);\n      //       });\n      //   });\n      //\n      if (mode === 'readwrite') try {trans.idbtrans.commit();} catch {}\n      return mode === 'readonly' ? result : trans._completion.then(() => result);\n    });/*.catch(err => { // Don't do this as of now. If would affect bulk- and modify methods in a way that could be more intuitive. But wait! Maybe change in next major.\n          trans._reject(err);\n          return rejection(err);\n      });*/\n  }\n}\n"
  },
  {
    "path": "src/functions/utils.ts",
    "content": "﻿import { _global } from \"../globals/global\";\nexport const keys = Object.keys;\nexport const isArray = Array.isArray;\nif (typeof Promise !== 'undefined' && !_global.Promise){\n    // In jsdom, this it can be the case that Promise is not put on the global object.\n    // If so, we need to patch the global object for the rest of the code to work as expected.\n    // Other dexie code expects Promise to be on the global object (like normal browser environments)\n    _global.Promise = Promise;\n}\nexport { _global }\n\nexport function extend<T extends object,X extends object>(obj: T, extension: X): T & X  {\n    if (typeof extension !== 'object') return obj as T & X;\n    keys(extension).forEach(function (key) {\n        obj[key] = extension[key];\n    });\n    return obj as T & X;\n}\n\nexport const getProto = Object.getPrototypeOf;\nexport const _hasOwn = {}.hasOwnProperty;\nexport function hasOwn(obj, prop) {\n    return _hasOwn.call(obj, prop);\n}\n\nexport function props (proto, extension) {\n    if (typeof extension === 'function') extension = extension(getProto(proto));\n    (typeof Reflect === \"undefined\" ? keys : Reflect.ownKeys)(extension).forEach(key => {\n        setProp(proto, key, extension[key]);\n    });\n}\n\nexport const defineProperty = Object.defineProperty;\n\nexport function setProp(obj, prop, functionOrGetSet, options?) {\n    defineProperty(obj, prop, extend(functionOrGetSet && hasOwn(functionOrGetSet, \"get\") && typeof functionOrGetSet.get === 'function' ?\n        {get: functionOrGetSet.get, set: functionOrGetSet.set, configurable: true} :\n        {value: functionOrGetSet, configurable: true, writable: true}, options));\n}\n\nexport function derive(Child) {\n    return {\n        from: function (Parent) {\n            Child.prototype = Object.create(Parent.prototype);\n            setProp(Child.prototype, \"constructor\", Child);\n            return {\n                extend: props.bind(null, Child.prototype)\n            };\n        }\n    };\n}\n\nexport const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\n\nexport function getPropertyDescriptor(obj, prop) {\n    const pd = getOwnPropertyDescriptor(obj, prop);\n    let proto;\n    return pd || (proto = getProto(obj)) && getPropertyDescriptor (proto, prop);\n}\n\nconst _slice = [].slice;\nexport function slice(args, start?, end?) {\n    return _slice.call(args, start, end);\n}\n\nexport function override(origFunc, overridedFactory) {\n    return overridedFactory(origFunc);\n}\n\nexport function assert (b) {\n    if (!b) throw new Error(\"Assertion Failed\");\n}\n\nexport function asap(fn) {\n    // @ts-ignore\n    if (_global.setImmediate) setImmediate(fn); else setTimeout(fn, 0);\n}\n\nexport function getUniqueArray(a) {\n    return a.filter((value, index, self) => self.indexOf(value) === index);\n}\n\n/** Generate an object (hash map) based on given array.\n * @param extractor Function taking an array item and its index and returning an array of 2 items ([key, value]) to\n *        instert on the resulting object for each item in the array. If this function returns a falsy value, the\n *        current item wont affect the resulting object.\n */\nexport function arrayToObject<T,R> (array: T[], extractor: (x:T, idx: number)=>[string, R]): {[name: string]: R} {\n    return array.reduce((result, item, i) => {\n        var nameAndValue = extractor(item, i);\n        if (nameAndValue) result[nameAndValue[0]] = nameAndValue[1];\n        return result;\n    }, {});\n}\n\nexport function trycatcher(fn, reject) {\n    return function () {\n        try {\n            fn.apply(this, arguments);\n        } catch (e) {\n            reject(e);\n        }\n    };\n}\n\nexport function tryCatch(fn: (...args: any[])=>void, onerror, args?) : void {\n    try {\n        fn.apply(null, args);\n    } catch (ex) {\n        onerror && onerror(ex);\n    }\n}\n\nexport function getByKeyPath(obj, keyPath) {\n    // http://www.w3.org/TR/IndexedDB/#steps-for-extracting-a-key-from-a-value-using-a-key-path\n    if (typeof keyPath === 'string' && hasOwn(obj, keyPath)) return obj[keyPath]; // This line is moved from last to first for optimization purpose.\n    if (!keyPath) return obj;\n    if (typeof keyPath !== 'string') {\n        var rv = [];\n        for (var i = 0, l = keyPath.length; i < l; ++i) {\n            var val = getByKeyPath(obj, keyPath[i]);\n            rv.push(val);\n        }\n        return rv;\n    }\n    var period = keyPath.indexOf('.');\n    if (period !== -1) {\n        var innerObj = obj[keyPath.substr(0, period)];\n        return innerObj == null ? undefined : getByKeyPath(innerObj, keyPath.substr(period + 1));\n    }\n    return undefined;\n}\n\nexport function setByKeyPath(obj, keyPath, value) {\n    if (!obj || keyPath === undefined) return;\n    if ('isFrozen' in Object && Object.isFrozen(obj)) return;\n    if (typeof keyPath !== 'string' && 'length' in keyPath) {\n        assert(typeof value !== 'string' && 'length' in value);\n        for (var i = 0, l = keyPath.length; i < l; ++i) {\n            setByKeyPath(obj, keyPath[i], value[i]);\n        }\n    } else {\n        var period = keyPath.indexOf('.');\n        if (period !== -1) {\n            var currentKeyPath = keyPath.substr(0, period);\n            var remainingKeyPath = keyPath.substr(period + 1);\n            if (remainingKeyPath === \"\")\n                if (value === undefined) {\n                    if (isArray(obj) && !isNaN(parseInt(currentKeyPath))) obj.splice(currentKeyPath, 1);\n                    else delete obj[currentKeyPath];\n                } else obj[currentKeyPath] = value;\n            else {\n                var innerObj = obj[currentKeyPath];\n                if (!innerObj || !hasOwn(obj, currentKeyPath)) innerObj = (obj[currentKeyPath] = {});\n                setByKeyPath(innerObj, remainingKeyPath, value);\n            }\n        } else {\n            if (value === undefined) {\n                if (isArray(obj) && !isNaN(parseInt(keyPath))) obj.splice(keyPath, 1);\n                else delete obj[keyPath];\n            } else obj[keyPath] = value;\n        }\n    }\n}\n\nexport function delByKeyPath(obj, keyPath) {\n    if (typeof keyPath === 'string')\n        setByKeyPath(obj, keyPath, undefined);\n    else if ('length' in keyPath)\n        [].map.call(keyPath, function(kp) {\n            setByKeyPath(obj, kp, undefined);\n        });\n}\n\nexport function shallowClone(obj) {\n    var rv = {};\n    for (var m in obj) {\n        if (hasOwn(obj, m)) rv[m] = obj[m];\n    }\n    return rv;\n}\n\nconst concat = [].concat;\nexport function flatten<T> (a: (T | T[])[]) : T[] {\n    return concat.apply([], a);\n}\n\n//https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm\nconst intrinsicTypeNames =\n    \"BigUint64Array,BigInt64Array,Array,Boolean,String,Date,RegExp,Blob,File,FileList,FileSystemFileHandle,FileSystemDirectoryHandle,ArrayBuffer,DataView,Uint8ClampedArray,ImageBitmap,ImageData,Map,Set,CryptoKey\"\n    .split(',').concat(\n        flatten([8,16,32,64].map(num=>[\"Int\",\"Uint\",\"Float\"].map(t=>t+num+\"Array\")))\n    ).filter(t=>_global[t]);\nconst intrinsicTypes = new Set(intrinsicTypeNames.map(t=>_global[t]));\n\n/** Deep clone a simple object tree.\n * \n * Copies object tree deeply, but does not deep-copy arrays,\n * typed arrays, Dates or other intrinsic types.\n * \n * Does not check for cyclic references.\n * \n * This function is 6 times faster than structuredClone() on chromium 111.\n * \n * This function can safely be used for cloning ObservabilitySets and RangeSets.\n * \n * @param o Object to clone\n * @returns Cloned object\n */\nexport function cloneSimpleObjectTree<T extends object>(o: T): T {\n    const rv = {} as T;\n    for (const k in o) if (hasOwn(o, k)) {\n        const v = o[k];\n        rv[k] = !v || typeof v !== 'object' || intrinsicTypes.has(v.constructor) ? v : cloneSimpleObjectTree(v);\n    }\n    return rv;\n}\n\nexport function objectIsEmpty(o: object) {\n    for (const k in o) if (hasOwn(o, k)) return false;\n    return true;\n}\n\nlet circularRefs: null | WeakMap<any,any> = null;\n\n/** Deep clone an object or array.\n * \n * \n * @param any \n * @returns \n */\nexport function deepClone<T>(any: T): T {\n    circularRefs = new WeakMap();\n    const rv = innerDeepClone(any);\n    circularRefs = null;\n    return rv;\n}\n\nfunction innerDeepClone<T>(x: T): T {\n    if (!x || typeof x !== 'object') return x;\n    let rv = circularRefs.get(x); // Resolve circular references\n    if (rv) return rv;\n    if (isArray(x)) {\n        rv = [];\n        circularRefs.set(x, rv);\n        for (var i = 0, l = x.length; i < l; ++i) {\n            rv.push(innerDeepClone(x[i]));\n        }\n    } else if (intrinsicTypes.has(x.constructor)) {\n        // For performance, we're less strict than structuredClone - we're only\n        // cloning arrays and custom objects.\n        // Typed arrays, Dates etc are not cloned.\n        rv = x;\n    } else {\n        // We're nicer to custom classes than what structuredClone() is -\n        // we preserve the proto of each object.\n        const proto = getProto(x);\n        rv = proto === Object.prototype ? {} : Object.create(proto);\n        circularRefs.set(x, rv);\n        for (var prop in x) {\n            if (hasOwn(x, prop)) {\n                rv[prop] = innerDeepClone(x[prop]);\n            }\n        }\n    }\n    return rv;\n}\n\nconst {toString} = {};\nexport function toStringTag(o: Object) {\n    return toString.call(o).slice(8, -1);\n}\n\n// If first argument is iterable or array-like, return it as an array\nexport const iteratorSymbol = typeof Symbol !== 'undefined' ?\n    Symbol.iterator :\n    '@@iterator';\nexport const getIteratorOf = typeof iteratorSymbol === \"symbol\" ? function(x) {\n    var i;\n    return x != null && (i = x[iteratorSymbol]) && i.apply(x);\n} : function () { return null; };\nexport const asyncIteratorSymbol = typeof Symbol !== 'undefined'\n    ? Symbol.asyncIterator || Symbol.for(\"Symbol.asyncIterator\")\n    : '@asyncIterator';\n\nexport function delArrayItem(a: any[], x: any) {\n    const i = a.indexOf(x);\n    if (i >= 0) a.splice(i, 1);\n    return i >= 0;\n}\n\nexport const NO_CHAR_ARRAY = {};\n// Takes one or several arguments and returns an array based on the following criteras:\n// * If several arguments provided, return arguments converted to an array in a way that\n//   still allows javascript engine to optimize the code.\n// * If single argument is an array, return a clone of it.\n// * If this-pointer equals NO_CHAR_ARRAY, don't accept strings as valid iterables as a special\n//   case to the two bullets below.\n// * If single argument is an iterable, convert it to an array and return the resulting array.\n// * If single argument is array-like (has length of type number), convert it to an array.\nexport function getArrayOf (arrayLike) {\n    var i, a, x, it;\n    if (arguments.length === 1) {\n        if (isArray(arrayLike)) return arrayLike.slice();\n        if (this === NO_CHAR_ARRAY && typeof arrayLike === 'string') return [arrayLike];\n        if ((it = getIteratorOf(arrayLike))) {\n            a = [];\n            while ((x = it.next()), !x.done) a.push(x.value);\n            return a;\n        }\n        if (arrayLike == null) return [arrayLike];\n        i = arrayLike.length;\n        if (typeof i === 'number') {\n            a = new Array(i);\n            while (i--) a[i] = arrayLike[i];\n            return a;\n        }\n        return [arrayLike];\n    }\n    i = arguments.length;\n    a = new Array(i);\n    while (i--) a[i] = arguments[i];\n    return a;\n}\nexport const isAsyncFunction = typeof Symbol !== 'undefined'\n    ? (fn: Function) => fn[Symbol.toStringTag] === 'AsyncFunction'\n    : ()=>false;\n"
  },
  {
    "path": "src/functions/workaround-undefined-primkey.ts",
    "content": "import { deepClone, delByKeyPath, getByKeyPath } from './utils';\n\n// This workaround is needed since obj could be a custom-class instance with an\n// uninitialized keyPath. See the following comment for more context:\n// https://github.com/dfahlander/Dexie.js/issues/1280#issuecomment-823557881\nexport function workaroundForUndefinedPrimKey(keyPath: string | ArrayLike<string>) {\n  // Workaround only needed for plain non-dotted keyPaths\n  return typeof keyPath === \"string\" && !/\\./.test(keyPath) \n  ? (obj: object) => {\n    if (obj[keyPath] === undefined && (keyPath in obj)) {\n      // property exists but is undefined. This will not be liked by Indexeddb.\n      // Need to remove the property before adding it but we need to clone it before\n      // doing that to not be intrusive.\n      obj = deepClone(obj);\n      delete obj[keyPath];\n    }\n    return obj;\n  }\n  : (obj: object) => obj;\n}"
  },
  {
    "path": "src/globals/connections.ts",
    "content": "import { type Dexie } from \"../classes/dexie\";\n\nexport const connections = createConnectionsManager();\n\nfunction createConnectionsManager() {\n  if (typeof FinalizationRegistry !== 'undefined' && typeof WeakRef !== 'undefined') {\n    const _refs = new Set<WeakRef<Dexie>>();\n    const _registry = new FinalizationRegistry((ref: WeakRef<Dexie>) => {\n      _refs.delete(ref);\n    });\n\n\n    const toArray = (): ReadonlyArray<Dexie> => {\n      return Array.from(_refs)\n        .map(ref => ref.deref())\n        .filter((db): db is Dexie => db !== undefined);\n    }\n\n    const add = (db: Dexie) => {\n      const ref = new WeakRef(db._novip);\n      _refs.add(ref);\n      _registry.register(db._novip, ref, ref);\n      if (_refs.size > db._options.maxConnections) {\n        // Remove the oldest connection (the one that was added first)\n        const oldestRef = _refs.values().next().value;\n        _refs.delete(oldestRef);\n        _registry.unregister(oldestRef);\n      }\n    }\n\n    const remove = (db: Dexie | undefined) => {\n      if (!db) return;\n      const iterator = _refs.values();\n      let result = iterator.next();\n\n      while (!result.done) {\n        const ref = result.value;\n        if (ref.deref() === db._novip) {\n          _refs.delete(ref);\n          _registry.unregister(ref);\n          return; // Early return once deleted\n        }\n        result = iterator.next();\n      }\n    }\n    return { toArray, add, remove };\n  } else {\n    const connections: Dexie[] = [];\n    const toArray = (): ReadonlyArray<Dexie> => connections;\n    const add = (db: Dexie) => {\n      connections.push(db._novip);\n    };\n    const remove = (db: Dexie | undefined) => {\n      if (!db) return;\n      const index = connections.indexOf(db._novip);\n      if (index !== -1) {\n        connections.splice(index, 1);\n      }\n    };\n    return { toArray, add, remove };\n  }\n}\n\n"
  },
  {
    "path": "src/globals/constants.ts",
    "content": "export const DEXIE_VERSION = '{version}'; // Replaced by build-script.\nexport const maxString = String.fromCharCode(65535);\nexport const minKey = -Infinity; // minKey can be constant. maxKey must be a prop of Dexie (_maxKey)\nexport const INVALID_KEY_ARGUMENT =\n  \"Invalid key provided. Keys must be of type string, number, Date or Array<string | number | Date>.\";\nexport const STRING_EXPECTED = \"String expected.\";\nexport const DEFAULT_MAX_CONNECTIONS = 1000;\nexport const dexieStackFrameFilter = frame => !/(dexie\\.js|dexie\\.min\\.js)/.test(frame);\nexport const DBNAMES_DB = '__dbnames';\nexport const READONLY = 'readonly';\nexport const READWRITE = 'readwrite';\n\n"
  },
  {
    "path": "src/globals/global-events.ts",
    "content": "import Events from '../helpers/Events';\nimport { GlobalDexieEvents } from '../public/types/db-events';\n\nexport const DEXIE_STORAGE_MUTATED_EVENT_NAME = 'storagemutated' as 'storagemutated';\n\n// Name of the global event fired using DOM dispatchEvent (if not in node).\n// Reason for propagating this as a DOM event is for getting reactivity across\n// multiple versions of Dexie within the same app (as long as they are\n// compatible with regards to the event data).\n// If the ObservabilitySet protocol change in a way that would not be backward\n// compatible, make sure also update the event name to a new number at the end\n// so that two Dexie instances of different versions continue to work together\n//  - maybe not able to communicate but won't fail due to unexpected data in\n// the detail property of the CustomEvent. If so, also make sure to udpate\n// docs and explain at which Dexie version the new name and format of the event\n// is being used.\nexport const STORAGE_MUTATED_DOM_EVENT_NAME = 'x-storagemutated-1';\n\nexport const globalEvents = Events(null, DEXIE_STORAGE_MUTATED_EVENT_NAME) as GlobalDexieEvents;\n"
  },
  {
    "path": "src/globals/global.ts",
    "content": "declare var global;\nexport const _global: any =\n    typeof globalThis !== 'undefined' ? globalThis :\n    typeof self !== 'undefined' ? self :\n    typeof window !== 'undefined' ? window :\n    global;\n"
  },
  {
    "path": "src/helpers/Events.js",
    "content": "import {keys, isArray, asap} from '../functions/utils';\nimport {nop, mirror, reverseStoppableEventChain} from '../functions/chaining-functions';\nimport {exceptions} from '../errors';\n\nexport default function Events(ctx) {\n    var evs = {};\n    var rv = function (eventName, subscriber) {\n        if (subscriber) {\n            // Subscribe. If additional arguments than just the subscriber was provided, forward them as well.\n            var i = arguments.length, args = new Array(i - 1);\n            while (--i) args[i - 1] = arguments[i];\n            evs[eventName].subscribe.apply(null, args);\n            return ctx;\n        } else if (typeof (eventName) === 'string') {\n            // Return interface allowing to fire or unsubscribe from event\n            return evs[eventName];\n        }\n    };\n    rv.addEventType = add;\n    \n    for (var i = 1, l = arguments.length; i < l; ++i) {\n        add(arguments[i]);\n    }\n    \n    return rv;\n\n    function add(eventName, chainFunction, defaultFunction) {\n        if (typeof eventName === 'object') return addConfiguredEvents(eventName);\n        if (!chainFunction) chainFunction = reverseStoppableEventChain;\n        if (!defaultFunction) defaultFunction = nop;\n\n        var context = {\n            subscribers: [],\n            fire: defaultFunction,\n            subscribe: function (cb) {\n                if (context.subscribers.indexOf(cb) === -1) {\n                    context.subscribers.push(cb);\n                    context.fire = chainFunction(context.fire, cb);\n                }\n            },\n            unsubscribe: function (cb) {\n                context.subscribers = context.subscribers.filter(function (fn) { return fn !== cb; });\n                context.fire = context.subscribers.reduce(chainFunction, defaultFunction);\n            }\n        };\n        evs[eventName] = rv[eventName] = context;\n        return context;\n    }\n\n    function addConfiguredEvents(cfg) {\n        // events(this, {reading: [functionChain, nop]});\n        keys(cfg).forEach(function (eventName) {\n            var args = cfg[eventName];\n            if (isArray(args)) {\n                add(eventName, cfg[eventName][0], cfg[eventName][1]);\n            } else if (args === 'asap') {\n                // Rather than approaching event subscription using a functional approach, we here do it in a for-loop where subscriber is executed in its own stack\n                // enabling that any exception that occur wont disturb the initiator and also not nescessary be catched and forgotten.\n                var context = add(eventName, mirror, function fire() {\n                    // Optimazation-safe cloning of arguments into args.\n                    var i = arguments.length, args = new Array(i);\n                    while (i--) args[i] = arguments[i];\n                    // All each subscriber:\n                    context.subscribers.forEach(function (fn) {\n                        asap(function fireEvent() {\n                            fn.apply(null, args);\n                        });\n                    });\n                });\n            } else throw new exceptions.InvalidArgument(\"Invalid event config\");\n        });\n    }\n}\n"
  },
  {
    "path": "src/helpers/database-enumerator.ts",
    "content": "import { Dexie } from \"../classes/dexie/dexie\";\nimport { Table } from \"../public/types/table\";\nimport { DBNAMES_DB } from \"../globals/constants\";\nimport { DexieDOMDependencies } from \"../public/types/dexie-dom-dependencies\";\nimport { nop } from \"../functions/chaining-functions\";\n\ntype IDBKeyNamesVar = typeof IDBKeyRange;\n\nfunction getDbNamesTable(indexedDB: IDBFactory, IDBKeyRange: IDBKeyNamesVar) {\n  let dbNamesDB = indexedDB[\"_dbNamesDB\"];\n  if (!dbNamesDB) {\n    dbNamesDB = indexedDB[\"_dbNamesDB\"] = new Dexie(DBNAMES_DB, {\n      addons: [],\n      indexedDB,\n      IDBKeyRange,\n    });\n    dbNamesDB.version(1).stores({ dbnames: \"name\" });\n  }\n  return dbNamesDB.table(\"dbnames\") as Table<{ name: string }, string>;\n}\n\nfunction hasDatabasesNative(indexedDB: IDBFactory) {\n  return indexedDB && typeof indexedDB.databases === \"function\";\n}\n\nexport function getDatabaseNames({\n  indexedDB,\n  IDBKeyRange,\n}: DexieDOMDependencies) {\n  return hasDatabasesNative(indexedDB)\n    ? Promise.resolve(indexedDB.databases()).then((infos) =>\n        infos\n          // Select name prop of infos:\n          .map((info) => info.name)\n          // Filter out DBNAMES_DB as previous Dexie or browser version would not have included it in the result.\n          .filter((name) => name !== DBNAMES_DB)\n      )\n    : getDbNamesTable(indexedDB, IDBKeyRange).toCollection().primaryKeys();\n}\n\nexport function _onDatabaseCreated(\n  { indexedDB, IDBKeyRange }: DexieDOMDependencies,\n  name: string\n) {\n  !hasDatabasesNative(indexedDB) &&\n    name !== DBNAMES_DB &&\n    getDbNamesTable(indexedDB, IDBKeyRange).put({name}).catch(nop);\n}\n\nexport function _onDatabaseDeleted(\n  { indexedDB, IDBKeyRange }: DexieDOMDependencies,\n  name: string\n) {\n  !hasDatabasesNative(indexedDB) &&\n    name !== DBNAMES_DB &&\n    getDbNamesTable(indexedDB, IDBKeyRange).delete(name).catch(nop);\n}\n"
  },
  {
    "path": "src/helpers/debug.ts",
    "content": "// By default, debug will be true only if platform is a web platform and its page is served from localhost.\n// When debug = true, error's stacks will contain asyncronic long stacks.\nexport var debug = typeof location !== 'undefined' &&\n        // By default, use debug mode if served from localhost.\n        /^(http|https):\\/\\/(localhost|127\\.0\\.0\\.1)/.test(location.href);\n\nexport function setDebug(value, filter) {\n    debug = value;\n}\n\nexport function deprecated<T> (what: string, fn: (...args)=>T) {\n    return function () {\n        console.warn(`${what} is deprecated. See https://dexie.org/docs/Deprecations}`);\n        return fn.apply(this, arguments);\n    } as (...args)=>T\n}\n"
  },
  {
    "path": "src/helpers/index-spec.ts",
    "content": "import { IndexSpec } from '../public/types/index-spec';\n\nexport function createIndexSpec(\n  name: string,\n  keyPath: string | string[],\n  unique: boolean,\n  multi: boolean,\n  auto: boolean,\n  compound: boolean,\n  isPrimKey: boolean,\n  type?: string\n): IndexSpec {\n  return {\n    name,\n    keyPath,\n    unique,\n    multi,\n    auto,\n    compound,\n    src: (unique && !isPrimKey ? '&' : '') + (multi ? '*' : '') + (auto ? \"++\" : \"\") + nameFromKeyPath(keyPath),\n    type\n  }\n}\n\nexport function nameFromKeyPath (keyPath?: string | string[]): string {\n  return typeof keyPath === 'string' ?\n    keyPath :\n    keyPath ? ('[' + [].join.call(keyPath, '+') + ']') : \"\";\n}\n"
  },
  {
    "path": "src/helpers/promise.d.ts",
    "content": "\nimport {PromiseExtended, PromiseExtendedConstructor} from '../public/types/promise-extended';\n\nexport interface DexiePromise<T=any> extends PromiseExtended<T> {\n  then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): DexiePromise<TResult1 | TResult2>;\n  catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): DexiePromise<T | TResult>;\n  catch<TResult = never>(ErrorConstructor: Function, onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): DexiePromise<T | TResult>;\n  catch<TResult = never>(errorName: string, onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): DexiePromise<T | TResult>;\n  finally<U>(onFinally?: () => U | PromiseLike<U>): DexiePromise<T>;\n  timeout (ms: number, msg?: string): DexiePromise<T>;\n  _state: null | true | false;\n  _lib?: boolean;\n}\n\nexport interface DexiePromiseConstructor extends PromiseExtendedConstructor {\n  new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): DexiePromise<T>;\n  follow (fn: Function, zoneProps?): DexiePromise<void>;\n  PSD,\n  newPSD<R> (zoneProps, fn: (...args)=>R, ...args) : R;\n  usePSD<R> (psd, fn: (...args)=>R, ...args): R;\n  rejectionMapper: (e?: any) => Error;\n}\n\nexport const NativePromise : PromiseConstructor;\nexport var globalPSD;\nexport var PSD;\nexport function wrap<F extends Function>(f: F, errorCatcher?: (err) => void) : F;\nexport function newScope<T> (fn: (...args)=>T, props?, a1?, a2?) : T;\nexport function usePSD<T> (psd, fn: (...args)=>T, a1?, a2?, a3?) : T;\nexport function rejection (failure: any) : DexiePromise<never>;\nexport function incrementExpectedAwaits(): number;\nexport function decrementExpectedAwaits(sourceTaskId?: number): void;\nexport function beginMicroTickScope(): boolean;\nexport function endMicroTickScope(): void;\nexport function execInGlobalContext(cb: Function): void;\nexport declare var DexiePromise : DexiePromiseConstructor;\n\nexport default DexiePromise;\n"
  },
  {
    "path": "src/helpers/promise.js",
    "content": "/*\n * Copyright (c) 2014-2017 David Fahlander\n * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/LICENSE-2.0\n */\nimport { _global } from '../globals/global';\nimport {tryCatch, props, setProp,\n    getPropertyDescriptor, getArrayOf, extend, getProto} from '../functions/utils';\nimport {nop, callBoth, mirror} from '../functions/chaining-functions';\nimport {debug} from './debug';\nimport {exceptions} from '../errors';\n\n//\n// Promise and Zone (PSD) for Dexie library\n//\n// I started out writing this Promise class by copying promise-light (https://github.com/taylorhakes/promise-light) by\n// https://github.com/taylorhakes - an A+ and ECMASCRIPT 6 compliant Promise implementation.\n//\n// In previous versions this was fixed by not calling setTimeout when knowing that the resolve() or reject() came from another\n// tick. In Dexie v1.4.0, I've rewritten the Promise class entirely. Just some fragments of promise-light is left. I use\n// another strategy now that simplifies everything a lot: to always execute callbacks in a new micro-task, but have an own micro-task\n// engine that is indexedDB compliant across all browsers.\n// Promise class has also been optimized a lot with inspiration from bluebird - to avoid closures as much as possible.\n//\n// Specific non-standard features of this Promise class:\n// * Custom zone support (a.k.a. PSD) with ability to keep zones also when using native promises as well as\n//   native async / await.\n// * Promise.follow() method built upon the custom zone engine, that allows user to track all promises created from current stack frame\n//   and below + all promises that those promises creates or awaits.\n// * Detect any unhandled promise in a PSD-scope (PSD.onunhandled). \n//\n// David Fahlander, https://github.com/dfahlander\n//\n\n// Just a pointer that only this module knows about.\n// Used in Promise constructor to emulate a private constructor.\nvar INTERNAL = {};\n\nconst\n    ZONE_ECHO_LIMIT = 100,\n    [resolvedNativePromise, nativePromiseProto, resolvedGlobalPromise] = typeof Promise === 'undefined' ?\n        [] :\n        (()=>{\n            let globalP = Promise.resolve();\n            if (typeof crypto === 'undefined' || !crypto.subtle)\n                return [globalP, getProto(globalP), globalP];\n            // Generate a native promise (as window.Promise may have been patched)\n            const nativeP = crypto.subtle.digest(\"SHA-512\", new Uint8Array([0]));\n            return [\n                nativeP,\n                getProto(nativeP),\n                globalP\n            ];\n        })(),\n    nativePromiseThen = nativePromiseProto && nativePromiseProto.then;\n\nexport const NativePromise = resolvedNativePromise && resolvedNativePromise.constructor;\nconst patchGlobalPromise = !!resolvedGlobalPromise;\n\n/* The default function used only for the very first promise in a promise chain.\n   As soon as then promise is resolved or rejected, all next tasks will be executed in micro ticks\n   emulated in this module. For indexedDB compatibility, this means that every method needs to \n   execute at least one promise before doing an indexedDB operation. Dexie will always call \n   db.ready().then() for every operation to make sure the indexedDB event is started in an\n   indexedDB-compatible emulated micro task loop.\n*/\nfunction schedulePhysicalTick() {\n    queueMicrotask(physicalTick);\n}\n\n// Configurable through Promise.scheduler.\n// Don't export because it would be unsafe to let unknown\n// code call it unless they do try..catch within their callback.\n// This function can be retrieved through getter of Promise.scheduler though,\n// but users must not do Promise.scheduler = myFuncThatThrowsException\nvar asap = function (callback, args) {\n    microtickQueue.push([callback, args]);\n    if (needsNewPhysicalTick) {\n        schedulePhysicalTick();\n        needsNewPhysicalTick = false;\n    }\n};\n\nvar isOutsideMicroTick = true, // True when NOT in a virtual microTick.\n    needsNewPhysicalTick = true, // True when a push to microtickQueue must also schedulePhysicalTick()\n    unhandledErrors = [], // Rejected promises that has occured. Used for triggering 'unhandledrejection'.\n    rejectingErrors = [], // Tracks if errors are being re-rejected during onRejected callback.\n    rejectionMapper = mirror; // Remove in next major when removing error mapping of DOMErrors and DOMExceptions\n    \nexport var globalPSD = {\n    id: 'global',\n    global: true,\n    ref: 0,\n    unhandleds: [],\n    onunhandled: nop,\n    pgp: false,\n    env: {},\n    finalize: nop\n};\n\nexport var PSD = globalPSD;\n\nexport var microtickQueue = []; // Callbacks to call in this or next physical tick.\nexport var numScheduledCalls = 0; // Number of listener-calls left to do in this physical tick.\nexport var tickFinalizers = []; // Finalizers to call when there are no more async calls scheduled within current physical tick.\n\nexport default function DexiePromise(fn) {\n    if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');    \n    this._listeners = [];\n    \n    // A library may set `promise._lib = true;` after promise is created to make resolve() or reject()\n    // execute the microtask engine implicitely within the call to resolve() or reject().\n    // To remain A+ compliant, a library must only set `_lib=true` if it can guarantee that the stack\n    // only contains library code when calling resolve() or reject().\n    // RULE OF THUMB: ONLY set _lib = true for promises explicitely resolving/rejecting directly from\n    // global scope (event handler, timer etc)!\n    this._lib = false;\n    // Current async scope\n    var psd = (this._PSD = PSD);\n    \n    if (typeof fn !== 'function') {\n        if (fn !== INTERNAL) throw new TypeError('Not a function');\n        // Private constructor (INTERNAL, state, value).\n        // Used internally by Promise.resolve() and Promise.reject().\n        this._state = arguments[1];\n        this._value = arguments[2];\n        if (this._state === false)\n            handleRejection(this, this._value); // Map error, set stack and addPossiblyUnhandledError().\n        return;\n    }\n    \n    this._state = null; // null (=pending), false (=rejected) or true (=resolved)\n    this._value = null; // error or result\n    ++psd.ref; // Refcounting current scope\n    executePromiseTask(this, fn);\n}\n\n// Prepare a property descriptor to put onto Promise.prototype.then\nconst thenProp = {\n    get: function() {\n        var psd = PSD, microTaskId = totalEchoes;\n\n        function then (onFulfilled, onRejected) {\n            var possibleAwait = !psd.global && (psd !== PSD || microTaskId !== totalEchoes);\n            const cleanup = possibleAwait && !decrementExpectedAwaits();\n            var rv = new DexiePromise((resolve, reject) => {\n                propagateToListener(this, new Listener(\n                    nativeAwaitCompatibleWrap(onFulfilled, psd, possibleAwait, cleanup),\n                    nativeAwaitCompatibleWrap(onRejected, psd, possibleAwait, cleanup),\n                    resolve,\n                    reject,\n                    psd));\n            });\n            if (this._consoleTask) rv._consoleTask = this._consoleTask;\n            return rv;\n        }\n\n        then.prototype = INTERNAL; // For idempotense, see setter below.\n\n        return then;\n    },\n    // Be idempotent and allow another framework (such as zone.js or another instance of a Dexie.Promise module) to replace Promise.prototype.then\n    // and when that framework wants to restore the original property, we must identify that and restore the original property descriptor.\n    set: function (value) {\n        setProp (this, 'then', value && value.prototype === INTERNAL ?\n            thenProp : // Restore to original property descriptor.\n            {\n                get: function(){\n                    return value; // Getter returning provided value (behaves like value is just changed)\n                },\n                set: thenProp.set // Keep a setter that is prepared to restore original.\n            }\n        );\n    }\n};\n\nprops(DexiePromise.prototype, {\n    then: thenProp, // Defined above.\n    _then: function (onFulfilled, onRejected) {\n        // A little tinier version of then() that don't have to create a resulting promise.\n        propagateToListener(this, new Listener(null, null, onFulfilled, onRejected, PSD));        \n    },\n\n    catch: function (onRejected) {\n        if (arguments.length === 1) return this.then(null, onRejected);\n        // First argument is the Error type to catch\n        var type = arguments[0],\n            handler = arguments[1];\n        return typeof type === 'function' ? this.then(null, err =>\n            // Catching errors by its constructor type (similar to java / c++ / c#)\n            // Sample: promise.catch(TypeError, function (e) { ... });\n            err instanceof type ? handler(err) : PromiseReject(err))\n        : this.then(null, err =>\n            // Catching errors by the error.name property. Makes sense for indexedDB where error type\n            // is always DOMError but where e.name tells the actual error type.\n            // Sample: promise.catch('ConstraintError', function (e) { ... });\n            err && err.name === type ? handler(err) : PromiseReject(err));\n    },\n\n    finally: function (onFinally) {\n        return this.then(value => {\n            return DexiePromise.resolve(onFinally()).then(()=>value);\n        }, err => {\n            return DexiePromise.resolve(onFinally()).then(()=>PromiseReject(err));\n        });\n    },\n    \n    timeout: function (ms, msg) {\n        return ms < Infinity ?\n            new DexiePromise((resolve, reject) => {\n                var handle = setTimeout(() => reject(new exceptions.Timeout(msg)), ms);\n                this.then(resolve, reject).finally(clearTimeout.bind(null, handle));\n            }) : this;\n    }\n});\n\nif (typeof Symbol !== 'undefined' && Symbol.toStringTag)\n    setProp(DexiePromise.prototype, Symbol.toStringTag, 'Dexie.Promise');\n\n// Now that Promise.prototype is defined, we have all it takes to set globalPSD.env.\n// Environment globals snapshotted on leaving global zone\nglobalPSD.env = snapShot();\n\nfunction Listener(onFulfilled, onRejected, resolve, reject, zone) {\n    this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;\n    this.onRejected = typeof onRejected === 'function' ? onRejected : null;\n    this.resolve = resolve;\n    this.reject = reject;\n    this.psd = zone;\n}\n\n// Promise Static Properties\nprops (DexiePromise, {\n    all: function () {\n        var values = getArrayOf.apply(null, arguments) // Supports iterables, implicit arguments and array-like.\n            .map(onPossibleParallellAsync); // Handle parallell async/awaits \n        return new DexiePromise(function (resolve, reject) {\n            if (values.length === 0) resolve([]);\n            var remaining = values.length;\n            values.forEach((a,i) => DexiePromise.resolve(a).then(x => {\n                values[i] = x;\n                if (!--remaining) resolve(values);\n            }, reject));\n        });\n    },\n    \n    resolve: value => {\n        if (value instanceof DexiePromise) return value;\n        if (value && typeof value.then === 'function') return new DexiePromise((resolve, reject)=>{\n            value.then(resolve, reject);\n        });\n        var rv = new DexiePromise(INTERNAL, true, value);\n        return rv;\n    },\n    \n    reject: PromiseReject,\n    \n    race: function () {\n        var values = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync);\n        return new DexiePromise((resolve, reject) => {\n            values.map(value => DexiePromise.resolve(value).then(resolve, reject));\n        });\n    },\n\n    PSD: {\n        get: ()=>PSD,\n        set: value => PSD = value\n    },\n\n    totalEchoes: {get: ()=>totalEchoes},\n\n    //task: {get: ()=>task},\n    \n    newPSD: newScope,\n    \n    usePSD: usePSD,\n    \n    scheduler: {\n        get: () => asap,\n        set: value => {asap = value}\n    },\n    \n    rejectionMapper: {\n        get: () => rejectionMapper,\n        set: value => {rejectionMapper = value;} // Map reject failures\n    },\n            \n    follow: (fn, zoneProps) => {\n        return new DexiePromise((resolve, reject) => {\n            return newScope((resolve, reject) => {\n                var psd = PSD;\n                psd.unhandleds = []; // For unhandled standard- or 3rd party Promises. Checked at psd.finalize()\n                psd.onunhandled = reject; // Triggered directly on unhandled promises of this library.\n                psd.finalize = callBoth(function () {\n                    // Unhandled standard or 3rd part promises are put in PSD.unhandleds and\n                    // examined upon scope completion while unhandled rejections in this Promise\n                    // will trigger directly through psd.onunhandled\n                    run_at_end_of_this_or_next_physical_tick(()=>{\n                        this.unhandleds.length === 0 ? resolve() : reject(this.unhandleds[0]);\n                    });\n                }, psd.finalize);\n                fn();\n            }, zoneProps, resolve, reject);\n        });\n    }\n});\n\nif (NativePromise) {\n    if (NativePromise.allSettled) setProp (DexiePromise, \"allSettled\", function() {\n        const possiblePromises = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync);\n        return new DexiePromise(resolve => {\n            if (possiblePromises.length === 0) resolve([]);\n            let remaining = possiblePromises.length;\n            const results = new Array(remaining);\n            possiblePromises.forEach((p, i) => DexiePromise.resolve(p).then(\n                value => results[i] = {status: \"fulfilled\", value},\n                reason => results[i] = {status: \"rejected\", reason})\n                .then(()=>--remaining || resolve(results)));\n        });\n    });\n    if (NativePromise.any && typeof AggregateError !== 'undefined') setProp(DexiePromise, \"any\", function() {\n        const possiblePromises = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync);\n        return new DexiePromise((resolve, reject) => {\n            if (possiblePromises.length === 0) reject(new AggregateError([]));\n            let remaining = possiblePromises.length;\n            const failures = new Array(remaining);\n            possiblePromises.forEach((p, i) => DexiePromise.resolve(p).then(\n                value => resolve(value),\n                failure => {\n                    failures[i] = failure;\n                    if (!--remaining) reject(new AggregateError(failures));\n                }));\n        });\n    });\n    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers\n    if (NativePromise.withResolvers) DexiePromise.withResolvers = NativePromise.withResolvers;\n}\n\n/**\n* Take a potentially misbehaving resolver function and make sure\n* onFulfilled and onRejected are only called once.\n*\n* Makes no guarantees about asynchrony.\n*/\nfunction executePromiseTask (promise, fn) {\n    // Promise Resolution Procedure:\n    // https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure\n    try {\n        fn(value => {\n            if (promise._state !== null) return; // Already settled\n            if (value === promise) throw new TypeError('A promise cannot be resolved with itself.');\n            var shouldExecuteTick = promise._lib && beginMicroTickScope();\n            if (value && typeof value.then === 'function') {\n                executePromiseTask(promise, (resolve, reject) => {\n                    value instanceof DexiePromise ?\n                        value._then(resolve, reject) :\n                        value.then(resolve, reject);\n                });\n            } else {\n                promise._state = true;\n                promise._value = value;\n                propagateAllListeners(promise);\n            }\n            if (shouldExecuteTick) endMicroTickScope();\n        }, handleRejection.bind(null, promise)); // If Function.bind is not supported. Exception is handled in catch below\n    } catch (ex) {\n        handleRejection(promise, ex);\n    }\n}\n\nfunction handleRejection (promise, reason) {\n    rejectingErrors.push(reason);\n    if (promise._state !== null) return;\n    var shouldExecuteTick = promise._lib && beginMicroTickScope();\n    reason = rejectionMapper(reason);\n    promise._state = false;\n    promise._value = reason;\n    // Add the failure to a list of possibly uncaught errors\n    addPossiblyUnhandledError(promise);\n    propagateAllListeners(promise);\n    if (shouldExecuteTick) endMicroTickScope();\n}\n\nfunction propagateAllListeners (promise) {\n    //debug && linkToPreviousPromise(promise);\n    var listeners = promise._listeners;\n    promise._listeners = [];\n    for (var i = 0, len = listeners.length; i < len; ++i) {\n        propagateToListener(promise, listeners[i]);\n    }\n    var psd = promise._PSD;\n    --psd.ref || psd.finalize(); // if psd.ref reaches zero, call psd.finalize();\n    if (numScheduledCalls === 0) {\n        // If numScheduledCalls is 0, it means that our stack is not in a callback of a scheduled call,\n        // and that no deferreds where listening to this rejection or success.\n        // Since there is a risk that our stack can contain application code that may\n        // do stuff after this code is finished that may generate new calls, we cannot\n        // call finalizers here.\n        ++numScheduledCalls;\n        asap(()=>{\n            if (--numScheduledCalls === 0) finalizePhysicalTick(); // Will detect unhandled errors\n        }, []);\n    }\n}\n\nfunction propagateToListener(promise, listener) {\n    if (promise._state === null) {\n        promise._listeners.push(listener);\n        return;\n    }\n\n    var cb = promise._state ? listener.onFulfilled : listener.onRejected;\n    if (cb === null) {\n        // This Listener doesnt have a listener for the event being triggered (onFulfilled or onReject) so lets forward the event to any eventual listeners on the Promise instance returned by then() or catch()\n        return (promise._state ? listener.resolve : listener.reject) (promise._value);\n    }\n    ++listener.psd.ref;\n    ++numScheduledCalls;\n    asap (callListener, [cb, promise, listener]);\n}\n\nfunction callListener (cb, promise, listener) {\n    try {\n        // Call callback and resolve our listener with it's return value.\n        var ret, value = promise._value;\n            \n        if (!promise._state && rejectingErrors.length) rejectingErrors = [];\n        // cb is onResolved\n        ret = debug && promise._consoleTask ? promise._consoleTask.run(()=>cb (value)) : cb (value);\n        if (!promise._state && rejectingErrors.indexOf(value) === -1) {\n            markErrorAsHandled(promise); // Callback didnt do Promise.reject(err) nor reject(err) onto another promise.\n        }\n        listener.resolve(ret);\n    } catch (e) {\n        // Exception thrown in callback. Reject our listener.\n        listener.reject(e);\n    } finally {\n        if (--numScheduledCalls === 0) finalizePhysicalTick();\n        --listener.psd.ref || listener.psd.finalize();\n    }\n}\n\n/* The callback to schedule with queueMicrotask().\n   It runs a virtual microtick and executes any callback registered in microtickQueue.\n */\nfunction physicalTick() {\n    usePSD(globalPSD, ()=>{\n        // Make sure to reset the async context to globalPSD before\n        // executing any of the microtick subscribers.\n        beginMicroTickScope() && endMicroTickScope();\n    });\n}\n\nexport function beginMicroTickScope() {\n    var wasRootExec = isOutsideMicroTick;\n    isOutsideMicroTick = false;\n    needsNewPhysicalTick = false;\n    return wasRootExec;\n}\n\n/* Executes micro-ticks without doing try..catch.\n   This can be possible because we only use this internally and\n   the registered functions are exception-safe (they do try..catch\n   internally before calling any external method). If registering\n   functions in the microtickQueue that are not exception-safe, this\n   would destroy the framework and make it instable. So we don't export\n   our asap method.\n*/\nexport function endMicroTickScope() {\n    var callbacks, i, l;\n    do {\n        while (microtickQueue.length > 0) {\n            callbacks = microtickQueue;\n            microtickQueue = [];\n            l = callbacks.length;\n            for (i = 0; i < l; ++i) {\n                var item = callbacks[i];\n                item[0].apply(null, item[1]);\n            }\n        }\n    } while (microtickQueue.length > 0);\n    isOutsideMicroTick = true;\n    needsNewPhysicalTick = true;\n}\n\nfunction finalizePhysicalTick() {\n    var unhandledErrs = unhandledErrors;\n    unhandledErrors = [];\n    unhandledErrs.forEach(p => {\n        p._PSD.onunhandled.call(null, p._value, p);\n    });\n    var finalizers = tickFinalizers.slice(0); // Clone first because finalizer may remove itself from list.\n    var i = finalizers.length;\n    while (i) finalizers[--i]();    \n}\n\nfunction run_at_end_of_this_or_next_physical_tick (fn) {\n    function finalizer() {\n        fn();\n        tickFinalizers.splice(tickFinalizers.indexOf(finalizer), 1);\n    }\n    tickFinalizers.push(finalizer);\n    ++numScheduledCalls;\n    asap(()=>{\n        if (--numScheduledCalls === 0) finalizePhysicalTick();\n    }, []);\n}\n\nfunction addPossiblyUnhandledError(promise) {\n    // Only add to unhandledErrors if not already there. The first one to add to this list\n    // will be upon the first rejection so that the root cause (first promise in the\n    // rejection chain) is the one listed.\n    if (!unhandledErrors.some(p => p._value === promise._value))\n        unhandledErrors.push(promise);\n}\n\nfunction markErrorAsHandled(promise) {\n    // Called when a reject handled is actually being called.\n    // Search in unhandledErrors for any promise whos _value is this promise_value (list\n    // contains only rejected promises, and only one item per error)\n    var i = unhandledErrors.length;\n    while (i) if (unhandledErrors[--i]._value === promise._value) {\n        // Found a promise that failed with this same error object pointer,\n        // Remove that since there is a listener that actually takes care of it.\n        unhandledErrors.splice(i, 1);\n        return;\n    }\n}\n\nfunction PromiseReject (reason) {\n    return new DexiePromise(INTERNAL, false, reason);\n}\n\nexport function wrap (fn, errorCatcher) {\n    var psd = PSD;\n    return function() {\n        var wasRootExec = beginMicroTickScope(),\n            outerScope = PSD;\n\n        try {\n            switchToZone(psd, true);\n            return fn.apply(this, arguments);\n        } catch (e) {\n            errorCatcher && errorCatcher(e);\n        } finally {\n            switchToZone(outerScope, false);\n            if (wasRootExec) endMicroTickScope();\n        }\n    };\n}\n\n\n//\n// variables used for native await support\n//\nconst task = { awaits: 0, echoes: 0, id: 0}; // The ongoing macro-task when using zone-echoing.\nvar taskCounter = 0; // ID counter for macro tasks.\nvar zoneStack = []; // Stack of left zones to restore asynchronically.\nvar zoneEchoes = 0; // When > 0, zoneLeaveEcho is queued. When 0 and task.echoes is also 0, nothing is queued.\nvar totalEchoes = 0; // ID counter for micro-tasks. Used to detect possible native await in our Promise.prototype.then.\n\n\nvar zone_id_counter = 0;\nexport function newScope (fn, props, a1, a2) {\n    var parent = PSD,\n        psd = Object.create(parent);\n    psd.parent = parent;\n    psd.ref = 0;\n    psd.global = false;\n    psd.id = ++zone_id_counter;\n    // Prepare for promise patching (done in usePSD):\n    var globalEnv = globalPSD.env;\n    psd.env = patchGlobalPromise ? {\n        Promise: DexiePromise, // Changing window.Promise could be omitted for Chrome and Edge, where IDB+Promise plays well!\n        PromiseProp: {value: DexiePromise, configurable: true, writable: true},\n        all: DexiePromise.all,\n        race: DexiePromise.race,\n        allSettled: DexiePromise.allSettled,\n        any: DexiePromise.any,\n        resolve: DexiePromise.resolve,\n        reject: DexiePromise.reject,\n    } : {};\n    if (props) extend(psd, props);\n    \n    // unhandleds and onunhandled should not be specifically set here.\n    // Leave them on parent prototype.\n    // unhandleds.push(err) will push to parent's prototype\n    // onunhandled() will call parents onunhandled (with this scope's this-pointer though!)\n    ++parent.ref;\n    psd.finalize = function () {\n        --this.parent.ref || this.parent.finalize();\n    }\n    var rv = usePSD (psd, fn, a1, a2);\n    if (psd.ref === 0) psd.finalize();\n    return rv;\n}\n\n// Function to call if scopeFunc returns NativePromise\n// Also for each NativePromise in the arguments to Promise.all()\nexport function incrementExpectedAwaits() {\n    if (!task.id) task.id = ++taskCounter;\n    ++task.awaits;\n    task.echoes += ZONE_ECHO_LIMIT;\n    return task.id;\n}\n\n// Function to call when 'then' calls back on a native promise where onAwaitExpected() had been called.\n// Also call this when a native await calls then method on a promise. In that case, don't supply\n// sourceTaskId because we already know it refers to current task.\nexport function decrementExpectedAwaits() {\n    if (!task.awaits) return false;\n    if (--task.awaits === 0) task.id = 0;\n    task.echoes = task.awaits * ZONE_ECHO_LIMIT; // Will reset echoes to 0 if awaits is 0.\n    return true;\n}\n\nif ((''+nativePromiseThen).indexOf('[native code]') === -1) {\n    // If the native promise' prototype is patched, we cannot rely on zone echoing.\n    // Disable that here:\n    incrementExpectedAwaits = decrementExpectedAwaits = nop;\n}\n\n// Call from Promise.all() and Promise.race()\nexport function onPossibleParallellAsync (possiblePromise) {\n    if (task.echoes && possiblePromise && possiblePromise.constructor === NativePromise) {\n        incrementExpectedAwaits(); \n        return possiblePromise.then(x => {\n            decrementExpectedAwaits();\n            return x;\n        }, e => {\n            decrementExpectedAwaits();\n            return rejection(e);\n        });\n    }\n    return possiblePromise;\n}\n\nfunction zoneEnterEcho(targetZone) {\n    ++totalEchoes;\n    //console.log(\"Total echoes \", totalEchoes);\n    //if (task.echoes === 1) console.warn(\"Cancelling echoing of async context.\");\n    if (!task.echoes || --task.echoes === 0) {\n        task.echoes = task.awaits = task.id = 0; // Cancel echoing.\n    }\n\n    zoneStack.push(PSD);\n    switchToZone(targetZone, true);\n}\n\nfunction zoneLeaveEcho() {\n    var zone = zoneStack[zoneStack.length-1];\n    zoneStack.pop();\n    switchToZone(zone, false);\n}\n\nfunction switchToZone (targetZone, bEnteringZone) {\n    var currentZone = PSD;\n    if (bEnteringZone ? task.echoes && (!zoneEchoes++ || targetZone !== PSD) : zoneEchoes && (!--zoneEchoes || targetZone !== PSD)) {\n        // Enter or leave zone asynchronically as well, so that tasks initiated during current tick\n        // will be surrounded by the zone when they are invoked.\n        queueMicrotask(bEnteringZone ? zoneEnterEcho.bind(null, targetZone) : zoneLeaveEcho);\n    }\n    if (targetZone === PSD) return;\n\n    PSD = targetZone; // The actual zone switch occurs at this line.\n\n    // Snapshot on every leave from global zone.\n    if (currentZone === globalPSD) globalPSD.env = snapShot();\n\n    if (patchGlobalPromise) {\n        // Let's patch the global and native Promises (may be same or may be different)\n        var GlobalPromise = globalPSD.env.Promise;\n        // Swich environments (may be PSD-zone or the global zone. Both apply.)\n        var targetEnv = targetZone.env;\n\n        if (currentZone.global || targetZone.global) {\n            // Leaving or entering global zone. It's time to patch / restore global Promise.\n\n            // Set this Promise to window.Promise so that transiled async functions will work on Firefox, Safari and IE, as well as with Zonejs and angular.\n            Object.defineProperty(_global, 'Promise', targetEnv.PromiseProp);\n\n            // Support Promise.all() etc to work indexedDB-safe also when people are including es6-promise as a module (they might\n            // not be accessing global.Promise but a local reference to it)\n            GlobalPromise.all = targetEnv.all;\n            GlobalPromise.race = targetEnv.race;\n            GlobalPromise.resolve = targetEnv.resolve;\n            GlobalPromise.reject = targetEnv.reject;\n            if (targetEnv.allSettled) GlobalPromise.allSettled = targetEnv.allSettled;\n            if (targetEnv.any) GlobalPromise.any = targetEnv.any;\n        }\n    }\n}\n\nfunction snapShot () {\n    var GlobalPromise = _global.Promise;\n    return patchGlobalPromise ? {\n        Promise: GlobalPromise,\n        PromiseProp: Object.getOwnPropertyDescriptor(_global, \"Promise\"),\n        all: GlobalPromise.all,\n        race: GlobalPromise.race,\n        allSettled: GlobalPromise.allSettled,\n        any: GlobalPromise.any,\n        resolve: GlobalPromise.resolve,\n        reject: GlobalPromise.reject,\n    } : {};\n}\n\nexport function usePSD (psd, fn, a1, a2, a3) {\n    var outerScope = PSD;\n    try {\n        switchToZone(psd, true);\n        return fn(a1, a2, a3);\n    } finally {\n        switchToZone(outerScope, false);\n    }\n}\n\nfunction nativeAwaitCompatibleWrap(fn, zone, possibleAwait, cleanup) {\n    return typeof fn !== 'function' ? fn : function () {\n        var outerZone = PSD;\n        if (possibleAwait) incrementExpectedAwaits();\n        switchToZone(zone, true);\n        try {\n            return fn.apply(this, arguments);\n        } finally {\n            switchToZone(outerZone, false);\n            if (cleanup) queueMicrotask(decrementExpectedAwaits);\n        }\n    };\n}\n\n/** Execute callback in global context */\nexport function execInGlobalContext(cb) {\n    if (Promise === NativePromise && task.echoes === 0) {\n        if (zoneEchoes === 0) {\n            cb();\n        } else {\n            enqueueNativeMicroTask(cb);\n        }\n    } else {\n        setTimeout(cb, 0);\n    }\n}\n\nexport var rejection = DexiePromise.reject;\n\nexport {DexiePromise};\n"
  },
  {
    "path": "src/helpers/prop-modification.ts",
    "content": "import { isArray } from \"../functions/utils\";\nimport { PropModSpec } from \"../public/types/prop-modification\";\n\n/** Consistent change propagation across offline synced data.\n * \n * This class is executed client- and server side on sync, making\n * an operation consistent across sync for full consistency and accuracy.\n * \n * Example: An object represents a bank account with a balance.\n * One offline user adds $ 1.00 to the balance.\n * Another user (online) adds $ 2.00 to the balance.\n * When first user syncs, the balance becomes the sum of every operation (3.00).\n * \n * -- initial: balance is 0\n * 1. db.bankAccounts.update(1, { balance: new ProdModification({add: 100})}) // user 1 (offline)\n * 2. db.bankAccounts.update(1, { balance: new ProdModification({add: 200})}) // user 2 (online)\n * -- before user 1 syncs, balance is 200 (representing money with integers * 100 to avoid rounding issues)\n * <user 1 syncs>\n * -- balance is 300\n * \n * When new operations are added, they need to be added to:\n * 1. PropModSpec interface\n * 2. Here in PropModification with the logic they represent\n * 3. (Optionally) a sugar function for it, such as const mathAdd = (amount: number | BigInt) => new PropModification({mathAdd: amount})\n */\nexport class PropModification {\n  [\"@@propmod\"]: PropModSpec;\n  execute(value: any): any {\n    const spec = this[\"@@propmod\"]\n    // add (mathematical or set-wise)\n    if (spec.add !== undefined) {\n      const term = spec.add;\n      // Set-addition on array representing a set of primitive types (strings, numbers)\n      if (isArray(term)) {\n        return [...(isArray(value) ? value : []), ...term].sort();\n      }\n      // Mathematical addition:\n      if (typeof term === 'number') return (Number(value) || 0) + term; // if value is not convertible to number, return 0 + term\n      if (typeof term === 'bigint') {\n        try {\n          return BigInt(value) + term;\n        } catch {\n          return BigInt(0) + term; // Unlike Number(value) that can return NaN, BigInt(value) throws if value is not BigInt, Number or numeric string\n        }\n      }\n      throw new TypeError(`Invalid term ${term}`);\n    }\n\n    // remove (mathematical or set-wise)\n    if (spec.remove !== undefined) {\n      const subtrahend = spec.remove;\n      // Set-addition on array representing a set of primitive types (strings, numbers)\n      if (isArray(subtrahend)) {\n        return isArray(value) ? value.filter(item => !subtrahend.includes(item)).sort() : [];\n      }        \n      // Mathematical addition:\n      if (typeof subtrahend === 'number') return Number(value) - subtrahend;\n      if (typeof subtrahend === 'bigint') {\n        try {\n          return BigInt(value) - subtrahend;\n        } catch {\n          return BigInt(0) - subtrahend; // Unlike Number(value) that can return NaN, BigInt(value) throws if value is not BigInt, Number or numeric string\n        }\n      }\n      throw new TypeError(`Invalid subtrahend ${subtrahend}`);\n    }\n\n    // Replace a prefix:\n    const prefixToReplace = spec.replacePrefix?.[0];\n    if (prefixToReplace && typeof value === 'string' && value.startsWith(prefixToReplace)) {\n      return spec.replacePrefix[1] + value.substring(prefixToReplace.length);\n    }\n    return value;\n  }\n\n  constructor(spec: PropModSpec) {\n    this[\"@@propmod\"] = spec;\n  }\n}\n"
  },
  {
    "path": "src/helpers/rangeset.ts",
    "content": "import { cmp } from \"../functions/cmp\";\nimport { extend, iteratorSymbol, props } from '../functions/utils';\nimport { IndexableType } from '../public';\nimport {\n  EmptyRange,\n  IntervalTree,\n  IntervalTreeNode,\n  RangeSetConstructor,\n  RangeSetPrototype,\n} from \"../public/types/rangeset\";\n\n/* An interval tree implementation to efficiently detect overlapping ranges of queried indexes.\n *\n * https://en.wikipedia.org/wiki/Interval_tree\n * \n */\n\nfunction isEmptyRange(node: IntervalTree | {from: IndexableType, to: IndexableType}): node is EmptyRange {\n  return !(\"from\" in node);\n}\n\nexport type RangeSet = RangeSetPrototype & IntervalTree;\n\nexport const RangeSet = function(fromOrTree: any, to?: any) {\n  if (this) {\n    // Called with new()\n    extend(this, arguments.length ? {d:1, from: fromOrTree, to: arguments.length > 1 ? to : fromOrTree} : {d:0});\n  } else {\n    // Called without new()\n    const rv = new RangeSet();\n    if (fromOrTree && (\"d\" in fromOrTree)) {\n      extend(rv, fromOrTree);\n    }\n    return rv;\n  }\n} as RangeSetConstructor;\n\nprops(RangeSet.prototype, {\n  add(rangeSet: IntervalTree | {from: IndexableType, to: IndexableType}) {\n    mergeRanges(this, rangeSet);\n    return this;\n  },\n  addKey(key: IndexableType) {\n    addRange(this, key, key);\n    return this;\n  },\n  addKeys(keys: IndexableType[]) {\n    keys.forEach(key => addRange(this, key, key));\n    return this;\n  },\n  hasKey(key: IndexableType) {\n    const node = getRangeSetIterator(this).next(key).value;\n    return node && cmp(node.from, key) <= 0 && cmp(node.to, key) >= 0;\n  },\n\n  [iteratorSymbol](): Iterator<IntervalTreeNode, undefined, IndexableType | undefined> {\n    return getRangeSetIterator(this);\n  }\n});\n\nfunction addRange(target: IntervalTree, from: IndexableType, to: IndexableType) {\n  const diff = cmp(from, to);\n  // cmp() returns NaN if one of the args are IDB-invalid keys.\n  // Avoid storing invalid keys in rangeset:\n  if (isNaN(diff)) return;\n\n  // Caller is trying to add a range where from is greater than to:\n  if (diff > 0) throw RangeError();\n  \n  if (isEmptyRange(target)) return extend(target, { from, to, d: 1 });\n  const left = target.l;\n  const right = target.r;\n  if (cmp(to, target.from) < 0) {\n    left\n      ? addRange(left, from, to)\n      : (target.l = { from, to, d: 1, l: null, r: null });\n    return rebalance(target);\n  }\n  if (cmp(from, target.to) > 0) {\n    right\n      ? addRange(right, from, to)\n      : (target.r = { from, to, d: 1, l: null, r: null });\n    return rebalance(target);\n  }\n  // Now we have some kind of overlap. We will be able to merge the new range into the node or let it be swallowed.\n\n  // Grow left?\n  if (cmp(from, target.from) < 0) {\n    target.from = from;\n    target.l = null; // Cut off for now. Re-add later.\n    target.d = right ? right.d + 1 : 1;\n  }\n  // Grow right?\n  if (cmp(to, target.to) > 0) {\n    target.to = to;\n    target.r = null; // Cut off for now. Re-add later.\n    target.d = target.l ? target.l.d + 1 : 1;\n  }\n  const rightWasCutOff = !target.r;\n  // Re-add left?\n  if (left && !target.l) {\n    //Ranges to the left may be swallowed. Cut it of and re-add all.\n    //Could probably be done more efficiently!\n    mergeRanges(target, left);\n  }\n  // Re-add right?\n  if (right && rightWasCutOff) {\n    //Ranges to the right may be swallowed. Cut it of and re-add all.\n    //Could probably be done more efficiently!\n    mergeRanges(target, right);\n  }\n}\n\nexport function mergeRanges(target: IntervalTree, newSet: IntervalTree | {from: IndexableType, to: IndexableType}) {\n  function _addRangeSet(\n    target: IntervalTree,\n    { from, to, l, r }: IntervalTreeNode | {from: IndexableType, to: IndexableType, l?: undefined, r?: undefined}\n  ) {\n    addRange(target, from, to);\n    if (l) _addRangeSet(target, l);\n    if (r) _addRangeSet(target, r);\n  }\n\n  if(!isEmptyRange(newSet)) _addRangeSet(target, newSet);\n}\n\nexport function rangesOverlap(\n  rangeSet1: IntervalTree,\n  rangeSet2: IntervalTree\n): boolean {\n    // Start iterating other from scratch.\n    const i1 = getRangeSetIterator(rangeSet2);\n    let nextResult1 = i1.next();\n    if (nextResult1.done) return false;\n    let a = nextResult1.value;\n\n    // Start iterating this from start of other\n    const i2 = getRangeSetIterator(rangeSet1);\n    let nextResult2 = i2.next(a.from); // Start from beginning of other range\n    let b = nextResult2.value;\n\n    while (!nextResult1.done && !nextResult2.done) {\n      if (cmp(b!.from, a.to) <= 0 && cmp(b!.to, a.from) >= 0) return true;\n      cmp(a.from, b!.from) < 0\n        ? (a = (nextResult1 = i1.next(b!.from)).value!) // a is behind. forward it to beginning of next b-range\n        : (b = (nextResult2 = i2.next(a.from)).value); // b is behind. forward it to beginning of next a-range\n    }\n  return false;\n}\n\ntype RangeSetIteratorState =\n  | {\n      up?: RangeSetIteratorState;\n      n: IntervalTreeNode;\n      s: 0 | 1 | 2 | 3;\n    }\n  | undefined\n  | null;\nexport function getRangeSetIterator(\n  node: EmptyRange | IntervalTreeNode\n): Generator<IntervalTreeNode, undefined, IndexableType | undefined> {\n  let state: RangeSetIteratorState = isEmptyRange(node) ? null : { s: 0, n: node };\n\n  return {\n    next(key?) {\n      const keyProvided = arguments.length > 0;\n      while (state) {\n        switch (state.s) {\n          case 0:\n            // Initial state for node.\n            // Fast forward to leftmost node.\n            state.s = 1;\n            if (keyProvided) {\n              while (state.n.l && cmp(key, state.n.from) < 0)\n                state = { up: state, n: state.n.l, s: 1 };\n            } else {\n              while (state.n.l) state = { up: state, n: state.n.l, s: 1 };\n            }\n          // intentionally fall into case 1:\n          case 1:\n            // We're on a node where it's left part is already handled or does not exist.\n            state.s = 2;\n            if (!keyProvided || cmp(key, state.n.to) <= 0)\n              return { value: state.n, done: false };\n          case 2:\n            // We've emitted our node and should continue with the right part or let parent take over from it's state 1\n            if (state.n.r) {\n              state.s = 3; // So when child is done, we know we're done.\n              state = { up: state, n: state.n.r, s: 0 };\n              continue; // Will fall in to case 0 with fast forward to left leaf of this subtree.\n            }\n          // intentionally fall into case 3:\n          case 3:\n            state = state.up;\n        }\n      }\n      return { done: true };\n    },\n  } as Generator<IntervalTreeNode, undefined, IndexableType>;\n}\n\nfunction rebalance(target: IntervalTreeNode) {\n  const diff = (target.r?.d || 0) - (target.l?.d || 0);\n  const r = diff > 1 ? \"r\" : diff < -1 ? \"l\" : \"\";\n  if (r) {\n\n    // Rotate (https://en.wikipedia.org/wiki/Tree_rotation)\n    //\n    // \n    //                    [OLDROOT]\n    //       [OLDROOT.L]            [NEWROOT]\n    //                        [NEWROOT.L] [NEWROOT.R]\n    //\n    // Is going to become:\n    //\n    // \n    //                    [NEWROOT]\n    //        [OLDROOT]             [NEWROOT.R]\n    // [OLDROOT.L] [NEWROOT.L]  \n\n    // * clone now has the props of OLDROOT\n    // Plan:\n    // * target must be given the props of NEWROOT\n    // * target[l] must point to a new OLDROOT\n    // * target[r] must point to NEWROOT.R\n    // * OLDROOT[r] must point to NEWROOT.L\n    const l = r === \"r\" ? \"l\" : \"r\"; // Support both left/right rotation\n    const rootClone = { ...target };\n    // We're gonna copy props from target's right node into target so that target will\n    // have same range as old target[r] (instead of changing pointers, we copy values.\n    // that way we do not need to adjust pointers in parents).\n    const oldRootRight = target[r]; \n    target.from = oldRootRight.from;\n    target.to = oldRootRight.to;\n    target[r] = oldRootRight[r];\n    rootClone[r] = oldRootRight[l];\n    target[l] = rootClone;\n    rootClone.d = computeDepth(rootClone);\n  }\n  target.d = computeDepth(target);\n}\n\nfunction computeDepth({ r, l }: Pick<IntervalTreeNode, \"l\" | \"r\">) {\n  return (r ? (l ? Math.max(r.d, l.d) : r.d) : l ? l.d : 0) + 1;\n}\n"
  },
  {
    "path": "src/helpers/table-schema.ts",
    "content": "import { IndexSpec } from '../public/types/index-spec';\nimport { TableSchema } from '../public/types/table-schema';\nimport { arrayToObject } from '../functions/utils';\n\nexport function createTableSchema(\n  name: string,\n  primKey: IndexSpec,\n  indexes: IndexSpec[],\n): TableSchema {\n  return {\n    name,\n    primKey,\n    indexes,\n    mappedClass: null,\n    idxByName: arrayToObject(indexes, (index) => [index.name, index]),\n  };\n}\n"
  },
  {
    "path": "src/helpers/vipify.ts",
    "content": "import { type Dexie } from \"../classes/dexie\";\nimport { type Table } from \"../classes/table\";\nimport { type Transaction } from \"../classes/transaction\";\n\nexport function vipify<T extends Table | Transaction>(\n  target: T,\n  vipDb: Dexie\n): T {\n  return new Proxy(target, {\n    get (target, prop, receiver) {\n      // The \"db\" prop of the table or transaction is the only one we need to\n      // override. The rest of the props can be accessed from the original\n      // object.\n      if (prop === 'db') return vipDb;\n      return Reflect.get(target, prop, receiver);\n    }\n  });\n}\n"
  },
  {
    "path": "src/helpers/yield-support.ts",
    "content": "import { isArray } from '../functions/utils';\n\nexport function awaitIterator (iterator: Iterator<any>) {\n  var callNext = result => iterator.next(result),\n      doThrow = error => iterator.throw(error),\n      onSuccess = step(callNext),\n      onError = step(doThrow);\n\n  function step(getNext: (any)=>any) {\n      return (val?) => {\n          var next = getNext(val),\n              value = next.value;\n\n          return next.done ? value :\n              (!value || typeof value.then !== 'function' ?\n                  isArray(value) ? Promise.all(value).then(onSuccess, onError) : onSuccess(value) :\n                  value.then(onSuccess, onError));\n      };\n  }\n\n  return step(callNext)();\n}\n"
  },
  {
    "path": "src/hooks/hooks-middleware.ts",
    "content": "import {\n  DBCore,\n  DBCoreTable,\n  DBCoreMutateResponse,\n  DBCoreDeleteRangeRequest,\n  DBCoreAddRequest,\n  DBCorePutRequest,\n  DBCoreDeleteRequest,\n  DBCoreTransaction,\n  DBCoreKeyRange\n} from \"../public/types/dbcore\";\nimport { nop } from '../functions/chaining-functions';\nimport { hasOwn, setByKeyPath } from '../functions/utils';\nimport { getObjectDiff } from \"../functions/get-object-diff\";\nimport { PSD } from '../helpers/promise';\n//import { LockableTableMiddleware } from '../dbcore/lockable-table-middleware';\nimport { getEffectiveKeys } from '../dbcore/get-effective-keys';\nimport { Middleware } from '../public/types/middleware';\nimport { Transaction } from '../classes/transaction';\n\nexport const hooksMiddleware: Middleware<DBCore>  = {\n  stack: \"dbcore\",\n  name: \"HooksMiddleware\",\n  level: 2,\n  create: (downCore: DBCore) => ({\n    ...downCore,\n    table(tableName: string) {\n      const downTable = downCore.table(tableName);\n      const {primaryKey} = downTable.schema;\n  \n      const tableMiddleware: DBCoreTable = {\n        ...downTable,\n        mutate(req):Promise<DBCoreMutateResponse> {\n          const dxTrans = PSD.trans as Transaction;\n          // Hooks can be transaction-bound. Need to grab them from transaction.table and not\n          // db.table!\n          const {deleting, creating, updating} = dxTrans.table(tableName).hook;\n          switch (req.type) {\n            case 'add':\n              if (creating.fire === nop) break;\n              return dxTrans._promise('readwrite', ()=>addPutOrDelete(req), true);\n            case 'put':\n              if (creating.fire === nop && updating.fire === nop) break;\n              return dxTrans._promise('readwrite', ()=>addPutOrDelete(req), true);\n            case 'delete':\n              if (deleting.fire === nop) break;\n              return dxTrans._promise('readwrite', ()=>addPutOrDelete(req), true);\n            case 'deleteRange':\n              if (deleting.fire === nop) break;\n              return dxTrans._promise('readwrite', ()=>deleteRange(req), true);\n          }\n          // Any of the breaks above happened (no hooks) - do the default:\n          return downTable.mutate(req);\n\n\n          function addPutOrDelete(req: DBCoreAddRequest | DBCorePutRequest | DBCoreDeleteRequest): Promise<DBCoreMutateResponse> {\n            const dxTrans = PSD.trans;\n            const keys = req.keys || getEffectiveKeys(primaryKey, req);\n            if (!keys) throw new Error(\"Keys missing\");\n            // Clone Request and set keys arg\n            req = req.type === 'add' || req.type === 'put' ?\n              {...req, keys} :\n              {...req};\n            if (req.type !== 'delete') req.values = [...req.values];\n            if (req.keys) req.keys = [...req.keys];\n  \n            return getExistingValues(downTable, req, keys).then (existingValues => {\n              const contexts = keys.map((key, i) => {\n                const existingValue = existingValues[i];\n                const ctx = { onerror: null, onsuccess: null };\n                if (req.type === 'delete') {\n                  // delete operation\n                  deleting.fire.call(ctx, key, existingValue, dxTrans);\n                } else if (req.type === 'add' || existingValue === undefined) {\n                  // The add() or put() resulted in a create\n                  const generatedPrimaryKey = creating.fire.call(ctx, key, req.values[i], dxTrans);\n                  if (key == null && generatedPrimaryKey != null) {\n                    key = generatedPrimaryKey;\n                    req.keys[i] = key;\n                    if (!primaryKey.outbound) {\n                      setByKeyPath(req.values[i], primaryKey.keyPath, key);\n                    }\n                  }\n                } else {\n                  // The put() operation resulted in an update\n                  const objectDiff = getObjectDiff(existingValue, req.values[i]);\n                  const additionalChanges = updating.fire.call(ctx, objectDiff, key, existingValue, dxTrans);\n                  if (additionalChanges) {\n                    const requestedValue = req.values[i];\n                    Object.keys(additionalChanges).forEach(keyPath => {\n                      if (hasOwn(requestedValue, keyPath)) {\n                        // keyPath is already present as a literal property of the object\n                        requestedValue[keyPath] = additionalChanges[keyPath];\n                      } else {\n                        // keyPath represents a new or existing path into the object\n                        setByKeyPath(requestedValue, keyPath, additionalChanges[keyPath]);\n                      }\n                    });\n                  }\n                }\n                return ctx;\n              });\n              return downTable.mutate(req).then(({failures, results, numFailures, lastResult}) => {\n                for (let i=0; i<keys.length; ++i) {\n                  const primKey = results ? results[i] : keys[i];\n                  const ctx = contexts[i];\n                  if (primKey == null) {\n                    ctx.onerror && ctx.onerror(failures[i]);\n                  } else {\n                    ctx.onsuccess && ctx.onsuccess(\n                      req.type === 'put' && existingValues[i] ? // the put resulted in an update\n                        req.values[i] : // update hooks expects existing value\n                        primKey // create hooks expects primary key\n                    );\n                  }\n                }\n                return {failures, results, numFailures, lastResult};\n              }).catch(error => {\n                contexts.forEach(ctx => ctx.onerror && ctx.onerror(error));\n                return Promise.reject(error);\n              });\n            });\n          }\n  \n          function deleteRange(req: DBCoreDeleteRangeRequest): Promise<DBCoreMutateResponse> {\n            return deleteNextChunk(req.trans, req.range, 10000);\n          }\n  \n          function deleteNextChunk(trans: DBCoreTransaction, range: DBCoreKeyRange, limit: number) {\n            // Query what keys in the DB within the given range\n            return downTable.query({trans, values: false, query: {index: primaryKey, range}, limit})\n            .then(({result}) => {\n              // Given a set of keys, bulk delete those using the same procedure as in addPutOrDelete().\n              // This will make sure that deleting hook is called.\n              return addPutOrDelete({type: 'delete', keys: result, trans}).then(res => {\n                if (res.numFailures > 0) return Promise.reject(res.failures[0]);\n                if (result.length < limit) {\n                  return {failures: [], numFailures: 0, lastResult: undefined} as DBCoreMutateResponse;\n                } else {\n                  return deleteNextChunk(trans, {...range, lower: result[result.length - 1], lowerOpen: true}, limit);\n                }\n              });\n            })\n          }\n        }\n      };\n      //const {lock, lockableMiddleware} = LockableTableMiddleware(tableMiddleware);\n\n      return tableMiddleware;\n    },\n  }) as DBCore\n};\n\nfunction getExistingValues(\n  table: DBCoreTable,\n  req: DBCoreAddRequest | DBCorePutRequest | DBCoreDeleteRequest,\n  effectiveKeys: any[]\n) {\n  return req.type === \"add\"\n    ? Promise.resolve([])\n    : table.getMany({ trans: req.trans, keys: effectiveKeys, cache: \"immutable\" });\n}\n"
  },
  {
    "path": "src/index-umd.ts",
    "content": "// Issue #1127. Need another index.ts for the UMD module with only a default export\n// like it was before.\n// In practice though, the UMD export will also export the named export in \n// https://github.com/dfahlander/Dexie.js/blob/c9187ae60c0d7a424f85bab3af179fbbc9901c8e/src/classes/dexie/dexie-static-props.ts#L223-L228\nimport Dexie from \"./index\";\nimport * as namedExports from \"./index\";\nimport { __assign } from 'tslib';\n__assign(Dexie, namedExports, {default: Dexie});\nexport default Dexie;\n"
  },
  {
    "path": "src/index.ts",
    "content": "import { Dexie } from './classes/dexie';\nimport { DexieConstructor } from './public/types/dexie-constructor';\nimport { DexiePromise } from './helpers/promise';\nimport { mapError } from './errors';\nimport * as Debug from './helpers/debug';\nimport { dexieStackFrameFilter, DEFAULT_MAX_CONNECTIONS } from './globals/constants';\n\n// Generate all static properties such as Dexie.maxKey etc\n// (implement interface DexieConstructor):\nimport './classes/dexie/dexie-static-props';\nimport './live-query/enable-broadcast';\nimport './support-bfcache';\nimport { liveQuery } from './live-query/live-query';\nimport { Entity } from './classes/entity/Entity';\nimport { cmp } from './functions/cmp';\nimport { PropModification } from './helpers/prop-modification';\nimport { replacePrefix, add, remove } from './functions/propmods';\n\n\n// Set rejectionMapper of DexiePromise so that it generally tries to map\n// DOMErrors and DOMExceptions to a DexieError instance with same name but with\n// async stack support and with a prototypal inheritance from DexieError and Error.\n// of Map DOMErrors and DOMExceptions to corresponding Dexie errors.\nDexiePromise.rejectionMapper = mapError;\n\n// Let the async stack filter focus on app code and filter away frames from dexie.min.js:\nDebug.setDebug(Debug.debug, dexieStackFrameFilter);\n\nexport { RangeSet, mergeRanges, rangesOverlap } from \"./helpers/rangeset\";\nexport { Dexie, liveQuery, DEFAULT_MAX_CONNECTIONS }; // Comply with public/index.d.ts.\nexport { Entity };\nexport { cmp };\nexport { PropModification, replacePrefix, add, remove };\nexport default Dexie;\n"
  },
  {
    "path": "src/live-query/cache/adjust-optimistic-request-from-failures.ts",
    "content": "import { delArrayItem, isArray } from '../../functions/utils';\nimport { TblQueryCache } from '../../public/types/cache';\nimport {\n  DBCoreMutateRequest,\n  DBCoreMutateResponse,\n} from '../../public/types/dbcore';\n\nexport function adjustOptimisticFromFailures(\n  tblCache: TblQueryCache,\n  req: DBCoreMutateRequest,\n  res: DBCoreMutateResponse\n): DBCoreMutateRequest {\n  if (res.numFailures === 0) return req;\n  if (req.type === 'deleteRange') {\n    // numFailures > 0 means the deleteRange operation failed in its whole.\n    return null;\n  }\n\n  const numBulkOps = req.keys\n    ? req.keys.length\n    : 'values' in req && req.values\n    ? req.values.length\n    : 1;\n  if (res.numFailures === numBulkOps) {\n    // Same number of failures as the number of ops. This means that all ops failed.\n    return null;\n  }\n\n  const clone: DBCoreMutateRequest = { ...req };\n\n  if (isArray(clone.keys)) {\n    clone.keys = clone.keys.filter((_, i) => !(i in res.failures));\n  }\n  if ('values' in clone && isArray(clone.values)) {\n    clone.values = clone.values.filter((_, i) => !(i in res.failures));\n  }\n  return clone;\n}\n"
  },
  {
    "path": "src/live-query/cache/apply-optimistic-ops.ts",
    "content": "import { cmp } from '../../functions/cmp';\nimport { isArray } from '../../functions/utils';\nimport { RangeSet } from '../../helpers/rangeset';\nimport { CacheEntry } from '../../public/types/cache';\nimport {\n  DBCoreMutateRequest,\n  DBCoreQueryRequest,\n  DBCoreTable,\n} from '../../public/types/dbcore';\nimport { isWithinRange } from './is-within-range';\n\nexport function applyOptimisticOps(\n  result: any[],\n  req: DBCoreQueryRequest,\n  ops: DBCoreMutateRequest[] | undefined,\n  table: DBCoreTable,\n  cacheEntry: CacheEntry,\n  immutable: boolean\n): any[] {\n  if (!ops || ops.length === 0) return result;\n  const index = req.query.index;\n  const { multiEntry } = index;\n  const queryRange = req.query.range;\n  const primaryKey = table.schema.primaryKey;\n  const extractPrimKey = primaryKey.extractKey!;\n  const extractIndex = index.extractKey!;\n  const extractLowLevelIndex = (index.lowLevelIndex || index).extractKey!;\n\n  let finalResult = ops.reduce((result, op) => {\n    let modifedResult = result;\n    const includedValues: any[] = [];\n    if (op.type === 'add' || op.type === 'put') {\n      const includedPKs = new RangeSet(); // For ignoring duplicates\n      for (let i = op.values.length - 1; i >= 0; --i) {\n        // backwards to prioritize last value of same PK\n        const value = op.values[i];\n        const pk = extractPrimKey(value);\n        if (includedPKs.hasKey(pk)) continue;\n        const key = extractIndex(value);\n        if (\n          multiEntry && isArray(key)\n            ? key.some((k) => isWithinRange(k, queryRange))\n            : isWithinRange(key, queryRange)\n        ) {\n          includedPKs.addKey(pk);\n          includedValues.push(value);\n        }\n      }\n    }\n    switch (op.type) {\n      case 'add': {\n        const existingKeys = new RangeSet().addKeys(\n          req.values ? result.map((v) => extractPrimKey(v)) : result\n        );\n\n        modifedResult = result.concat(\n          req.values\n            ? includedValues.filter((v) => {\n                const key = extractPrimKey(v);\n                if (existingKeys.hasKey(key)) return false;\n                existingKeys.addKey(key);\n                return true;\n              })\n            : includedValues\n                .map((v) => extractPrimKey(v))\n                .filter((k) => {\n                  if (existingKeys.hasKey(k)) return false;\n                  existingKeys.addKey(k);\n                  return true;\n                })\n        );\n        break;\n      }\n      case 'put': {\n        const keySet = new RangeSet().addKeys(\n          op.values.map((v) => extractPrimKey(v))\n        );\n        modifedResult = result\n          .filter(\n            // Remove all items that are being replaced\n            (item) => !keySet.hasKey(req.values ? extractPrimKey(item) : item)\n          )\n          .concat(\n            // Add all items that are being put (sorting will be done later)\n            req.values\n              ? includedValues\n              : includedValues.map((v) => extractPrimKey(v))\n          );\n        break;\n      }\n      case 'delete':\n        const keysToDelete = new RangeSet().addKeys(op.keys);\n        modifedResult = result.filter(\n          (item) =>\n            !keysToDelete.hasKey(req.values ? extractPrimKey(item) : item)\n        );\n\n        break;\n      case 'deleteRange':\n        const range = op.range;\n        modifedResult = result.filter(\n          (item) => !isWithinRange(extractPrimKey(item), range)\n        );\n        break;\n    }\n    return modifedResult;\n  }, result);\n\n  // If no changes were made, we can return the original result.\n  if (finalResult === result) return result;\n\n  // Sort the result on sortIndex:\n  const sorter: (a: any, b: any) => number = (a, b) =>\n    cmp(extractLowLevelIndex(a), extractLowLevelIndex(b)) ||\n    cmp(extractPrimKey(a), extractPrimKey(b));\n  \n  // If direction is 'prev' or 'prevunique', sort in descending order\n  finalResult.sort(req.direction === 'prev' || req.direction === 'prevunique'\n    ? (a, b) => sorter(b, a)\n    : sorter\n  );\n\n  // If we have a limit we need to respect it:\n  if (req.limit && req.limit < Infinity) {\n    if (finalResult.length > req.limit) {\n      finalResult.length = req.limit; // Cut of any extras after sorting correctly.\n    } else if (result.length === req.limit && finalResult.length < req.limit) {\n      // We're missing some items because of the limit. We need to add them back.\n      // The easiest way is to mark the cache entry as dirty, which will cause\n      // it to be requeried after the write-transaction successfully completes.\n      cacheEntry.dirty = true;\n    }\n  }\n  return immutable ? Object.freeze(finalResult) as any[] : finalResult;\n}\n"
  },
  {
    "path": "src/live-query/cache/are-ranges-equal.ts",
    "content": "import { cmp } from '../../functions/cmp';\nimport { DBCoreKeyRange } from '../../public/types/dbcore';\n\nexport function areRangesEqual(r1: DBCoreKeyRange, r2: DBCoreKeyRange) {\n  return (\n    cmp(r1.lower, r2.lower) === 0 &&\n    cmp(r1.upper, r2.upper) === 0 &&\n    !!r1.lowerOpen === !!r2.lowerOpen &&\n    !!r1.upperOpen === !!r2.upperOpen\n  );\n}\n"
  },
  {
    "path": "src/live-query/cache/cache-middleware.ts",
    "content": "import { LiveQueryContext } from '..';\nimport type { Transaction } from '../../classes/transaction';\nimport { getEffectiveKeys } from '../../dbcore/get-effective-keys';\nimport { deepClone, delArrayItem, setByKeyPath } from '../../functions/utils';\nimport DexiePromise, { PSD } from '../../helpers/promise';\nimport { ObservabilitySet } from '../../public/types/db-events';\nimport {\n  DBCore, DBCoreMutateRequest, DBCoreMutateResponse, DBCoreQueryRequest,\n  DBCoreQueryResponse\n} from '../../public/types/dbcore';\nimport { Middleware } from '../../public/types/middleware';\nimport { obsSetsOverlap } from '../obs-sets-overlap';\nimport { adjustOptimisticFromFailures } from './adjust-optimistic-request-from-failures';\nimport { applyOptimisticOps } from './apply-optimistic-ops';\nimport { cache } from './cache';\nimport { findCompatibleQuery } from './find-compatible-query';\nimport { isCachableContext } from './is-cachable-context';\nimport { isCachableRequest } from './is-cachable-request';\nimport { signalSubscribersLazily } from './signalSubscribers';\nimport { subscribeToCacheEntry } from './subscribe-cachentry';\n\nexport const cacheMiddleware: Middleware<DBCore> = {\n  stack: 'dbcore',\n  level: 0,\n  name: 'Cache',\n  create: (core) => {\n    const dbName = core.schema.name;\n    const coreMW: DBCore = {\n      ...core,\n      transaction: (stores, mode, options) => {\n        const idbtrans = core.transaction(\n          stores,\n          mode,\n          options\n        ) as IDBTransaction & {\n          mutatedParts?: ObservabilitySet;\n          _explicit?: boolean;\n        };\n        // Maintain TblQueryCache.ops array when transactions commit or abort\n        if (mode === 'readwrite') {\n          const ac = new AbortController();\n          const { signal } = ac;\n          const endTransaction = (wasCommitted: boolean) => () => {\n            ac.abort();\n            if (mode === 'readwrite') {\n              // Collect which subscribers to notify:\n              const affectedSubscribers = new Set<()=>void>();\n\n              // Go through all tables in transaction and check if they have any optimistic updates\n              for (const storeName of stores) {\n                const tblCache = cache[`idb://${dbName}/${storeName}`];\n                if (tblCache) {\n                  const table = core.table(storeName);\n                  // Pick optimistic ops that are part of this transaction\n                  const ops = tblCache.optimisticOps.filter(\n                    (op) => op.trans === idbtrans\n                  );\n                  // Transaction was marked as _explicit in enterTransactionScope(), transaction-helpers.ts.\n                  if (idbtrans._explicit && wasCommitted && idbtrans.mutatedParts) {\n                    // Invalidate all queries that overlap with the mutated parts and signal their subscribers\n                    for (const entries of Object.values(\n                      tblCache.queries.query\n                    )) {\n                      for (const entry of entries.slice()) {\n                        if (obsSetsOverlap(entry.obsSet, idbtrans.mutatedParts)) {\n                          delArrayItem(entries, entry); // Remove the entry from the cache so it can be refreshed\n                          entry.subscribers.forEach((requery) => affectedSubscribers.add(requery));\n                        }\n                      }\n                    }\n                  } else if (ops.length > 0) {\n                    // Remove them from the optimisticOps array\n                    tblCache.optimisticOps = tblCache.optimisticOps.filter(\n                      (op) => op.trans !== idbtrans\n                    );\n                    // Commit or abort the optimistic updates\n                    for (const entries of Object.values(\n                      tblCache.queries.query\n                    )) {\n                      for (const entry of entries.slice()) {\n                        if (\n                          entry.res != null && // if entry.promise but not entry.res, we're fine. Query will resume now and get the result.\n                          idbtrans.mutatedParts/* &&\n                          obsSetsOverlap(entry.obsSet, idbtrans.mutatedParts)*/\n                        ) {\n                          if (wasCommitted && !entry.dirty) {\n                            const freezeResults = Object.isFrozen(entry.res);\n                            const modRes = applyOptimisticOps(\n                              entry.res as any[],\n                              entry.req,\n                              ops,\n                              table,\n                              entry,\n                              freezeResults\n                            );\n                            if (entry.dirty) {\n                              // Found out at this point that the entry is dirty - not to rely on!\n                              delArrayItem(entries, entry);\n                              entry.subscribers.forEach((requery) => affectedSubscribers.add(requery));\n                            } else if (modRes !== entry.res) {\n                              entry.res = modRes;\n                              // Update promise\n                              entry.promise = DexiePromise.resolve({result: modRes} satisfies DBCoreQueryResponse);\n                              \n                              // No need to notify subscribers. They already have this value.\n                              // We have just updated the value of the cache without having to\n                              // requery the database - because we know the result for this\n                              // query based on computing the operations and applying them\n                              // to the previous result.\n                            }\n                          } else {\n                            if (entry.dirty) {\n                              // If the entry is dirty we need to get rid of it so that\n                              // a new entry will be created when the query is run again.\n                              delArrayItem(entries, entry);\n                            }\n                            // If we're not committing, we need to notify subscribers that the\n                            // optimistic updates are no longer valid.\n                            entry.subscribers.forEach((requery) => affectedSubscribers.add(requery));\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              }\n              affectedSubscribers.forEach((requery) => requery());\n            }\n          };\n          idbtrans.addEventListener('abort', endTransaction(false), {\n            signal,\n          });\n          idbtrans.addEventListener('error', endTransaction(false), {\n            signal,\n          });\n          idbtrans.addEventListener('complete', endTransaction(true), {\n            signal,\n          });\n        }\n        return idbtrans;\n      },\n      table(tableName: string) {\n        const downTable = core.table(tableName);\n        const primKey = downTable.schema.primaryKey;\n        const tableMW = {\n          ...downTable,\n          mutate(req: DBCoreMutateRequest): Promise<DBCoreMutateResponse> {\n            const trans = PSD.trans as Transaction;\n            if (\n              primKey.outbound || // Non-inbound tables are harded to apply optimistic updates on because we can't know primary key of results\n              trans.db._options.cache === 'disabled' || // User has opted-out from caching\n              trans.explicit || // It's an explicit write transaction being made. Don't affect cache until transaction commits.\n              trans.idbtrans.mode !== 'readwrite' // We only handle 'readwrite' in our transaction override. 'versionchange' transactions don't use cache (from populate or upgraders).\n            ) {\n              // Just forward the request to the core.\n              return downTable.mutate(req);\n            }\n            // Find the TblQueryCache for this table:\n            const tblCache = cache[`idb://${dbName}/${tableName}`];\n            if (!tblCache) return downTable.mutate(req);\n\n            const promise = downTable.mutate(req);\n            if ((req.type === 'add' || req.type === 'put') && (req.values.length >= 50 || getEffectiveKeys(primKey, req).some(key => key == null))) {\n              // There are some autoIncremented keys not set yet. Need to wait for completion before we can reliably enqueue the operation.\n              // (or there are too many objects so we lazy out to avoid performance bottleneck for large bulk inserts)\n              promise.then((res) => { // We need to extract result keys and generate cloned values with the keys set (so that applyOptimisticOps can work)\n                // But we have a problem! The req.mutatedParts is still not complete so we have to actively add the keys to the unsignaledParts set manually.\n                const reqWithResolvedKeys = {\n                  ...req,\n                  values: req.values.map((value, i) => {\n                    if (res.failures[i]) return value; // No need to rewrite a failing value\n                    const valueWithKey = primKey.keyPath?.includes('.')\n                      ? deepClone(value)\n                      : {\n                        ...value,\n                      };\n                    setByKeyPath(valueWithKey, primKey.keyPath, res.results![i]);\n                    return valueWithKey;\n                  })\n                };\n                const adjustedReq = adjustOptimisticFromFailures(tblCache, reqWithResolvedKeys, res);\n                tblCache.optimisticOps.push(adjustedReq);\n                // Signal subscribers after the observability middleware has complemented req.mutatedParts with the new keys.\n                // We must queue the task so that we get the req.mutatedParts updated by observability middleware first.\n                // If we refactor the dependency between observability middleware and this middleware we might not need to queue the task.\n                queueMicrotask(()=>req.mutatedParts && signalSubscribersLazily(req.mutatedParts)); // Reason for double laziness: in user awaits put and then does another put, signal once.\n              });\n            } else {\n              // Enque the operation immediately\n              tblCache.optimisticOps.push(req);\n              // Signal subscribers that there are mutated parts\n              req.mutatedParts && signalSubscribersLazily(req.mutatedParts);\n              promise.then((res) => {\n                if (res.numFailures > 0) {\n                  // In case the operation failed, we need to remove it from the optimisticOps array.\n                  delArrayItem(tblCache.optimisticOps, req);\n                  const adjustedReq = adjustOptimisticFromFailures(tblCache, req, res);\n                  if (adjustedReq) {\n                    tblCache.optimisticOps.push(adjustedReq);\n                  }\n                  req.mutatedParts && signalSubscribersLazily(req.mutatedParts); // Signal the rolling back of the operation.\n                }\n              });\n              promise.catch(()=> {\n                // In case the operation failed, we need to remove it from the optimisticOps array.\n                delArrayItem(tblCache.optimisticOps, req);\n                req.mutatedParts && signalSubscribersLazily(req.mutatedParts); // Signal the rolling back of the operation.\n              });\n            }\n            return promise;\n          },\n          query(req: DBCoreQueryRequest): Promise<DBCoreQueryResponse> {\n            if (!isCachableContext(PSD, downTable) || !isCachableRequest(\"query\", req)) return downTable.query(req);\n            const freezeResults =\n              (PSD as LiveQueryContext).trans?.db._options.cache === 'immutable';\n            const { requery, signal } = PSD as LiveQueryContext;\n            let [cacheEntry, exactMatch, tblCache, container] =\n              findCompatibleQuery(dbName, tableName, 'query', req);\n            if (cacheEntry && exactMatch) {\n              cacheEntry.obsSet = req.obsSet!; // So that optimistic result is monitored.\n              // How? - because observability-middleware will track result where optimistic\n              // mutations are applied and record it in the cacheEntry.\n              // TODO: CHANGE THIS! The difference is resultKeys only.\n              // Wanted behavior:\n              //  * cacheEntry obsSet should represent the obsSet without optimistic updates (so it can be checked when merging ops in tx commit)\n              //  * cacheEntry optimisticObsSet should represent the obsSet with current optimistic updates. It should be updated when adding an op\n              //    by adding the primary keys of the put/add/delete operation to the set.\n              //  * observability-middleware should stop recording req.obsSet when a cache entry exact match is found because it won't be used anyway.\n              // I'm thinking of merging observability-middleware with cache-middleware into one single middleware because the dependencies are too\n              // tight between them.\n            } else {\n              // --> TODO here: If not exact match, check if we have a superset to extract\n              // the data from.\n\n              // No cached result found. We need to query the database and cache the result.\n              const promise = downTable.query(req).then((res) => {\n                // Freeze or clone results\n                const result = res.result;\n                if (cacheEntry) cacheEntry.res = result;\n                if (freezeResults) {\n                  // For performance reasons don't deep freeze.\n                  // Only freeze the top-level array and its items.\n                  // This is good enough to teach users that the result must be treated as immutable\n                  // without enforcing it recursively on the entire result (which is not even possible\n                  // for things like Date objects and typed arrays)\n                  for (let i = 0, l = result.length; i < l; ++i) {\n                    Object.freeze(result[i]);\n                  }\n                  Object.freeze(result);\n                } else {\n                  // If not frozen, we need to clone the result to avoid user mutating the cache\n                  // When we do this, user's must feel conformable with the fact that the result\n                  // can be mutated deeply - user is not expected to have any respect for immutability.\n                  res.result = deepClone(result);\n                }\n                return res;\n              }).catch(error => {\n                // In case the query operation failed, we need to remove it from the cache\n                // so that subsequent calls does not get the same error but re-evaluate\n                // the query.\n                if (container && cacheEntry) delArrayItem(container, cacheEntry);\n                return Promise.reject(error);\n              });\n              cacheEntry = {\n                obsSet: req.obsSet!,\n                promise,\n                subscribers: new Set(),\n                type: 'query',\n                req,\n                dirty: false,\n              };\n              if (container) {\n                container.push(cacheEntry);\n              } else {\n                container = [cacheEntry];\n                if (!tblCache) {\n                  tblCache = cache[`idb://${dbName}/${tableName}`] = {\n                    queries: {\n                      query: {},\n                      count: {},\n                    },\n                    objs: new Map(),\n                    optimisticOps: [],\n                    unsignaledParts: {}\n                  };\n                }\n                tblCache.queries.query[req.query.index.name || ''] = container;\n              }\n            }\n            subscribeToCacheEntry(cacheEntry, container!, requery, signal);\n            return cacheEntry.promise.then((res: DBCoreQueryResponse) => {\n              return {\n                result: applyOptimisticOps(\n                  res.result,\n                  req,\n                  tblCache?.optimisticOps,\n                  downTable,\n                  cacheEntry!,\n                  freezeResults\n                ) as any[], // readonly any[]\n              };\n            });\n          },\n        };\n        return tableMW;\n      },\n    };\n    return coreMW;\n  },\n};\n\n\n"
  },
  {
    "path": "src/live-query/cache/cache.ts",
    "content": "import { type GlobalQueryCache } from \"../../public/types/cache\";\n\nexport const cache: GlobalQueryCache = {}\n"
  },
  {
    "path": "src/live-query/cache/does-ranges-overlap.ts",
    "content": "import { DBCoreKeyRange } from '../../public/types/dbcore';\nimport { isBelowUpper } from './is-within-range';\n\n\nexport function doesRangesOverlap(r1: DBCoreKeyRange, r2: DBCoreKeyRange) {\n  return isBelowUpper(r1.lower, r2) && isBelowUpper(r2.lower, r1);\n}\n"
  },
  {
    "path": "src/live-query/cache/find-compatible-query.ts",
    "content": "import { CacheEntry, TblQueryCache } from '../../public/types/cache';\nimport {\n  DBCoreCountRequest,\n  DBCoreQueryRequest,\n} from '../../public/types/dbcore';\nimport { areRangesEqual } from './are-ranges-equal';\nimport { cache } from './cache';\nimport { isSuperRange } from './is-super-range';\n\nexport function findCompatibleQuery(\n  dbName: string,\n  tableName: string,\n  type: 'query',\n  req: DBCoreQueryRequest\n): [] | [CacheEntry, boolean, TblQueryCache, CacheEntry[]];\nexport function findCompatibleQuery(\n  dbName: string,\n  tableName: string,\n  type: 'count',\n  req: DBCoreCountRequest\n): [] | [CacheEntry, boolean, TblQueryCache, CacheEntry[]];\nexport function findCompatibleQuery(\n  dbName: string,\n  tableName: string,\n  type: 'query' | 'count',\n  req: Partial<DBCoreQueryRequest> & Partial<DBCoreCountRequest>\n): [] | [CacheEntry, boolean, TblQueryCache, CacheEntry[]] {\n  const tblCache = cache[`idb://${dbName}/${tableName}`];\n  if (!tblCache) return [];\n  const queries = tblCache.queries[type];\n  if (!queries) return [null, false, tblCache, null];\n  const indexName = req.query ? req.query.index.name : null;\n  const entries = queries[indexName || ''];\n  if (!entries) return [null, false, tblCache, null];\n\n  switch (type) {\n    case 'query':\n      // Normalize direction to improve cache hit rate for forward queries\n      // (undefined and 'next' are equivalent)\n      const reqDirection = req.direction ?? 'next';\n      const equalEntry = entries.find(\n        (entry) =>\n          (entry.req as DBCoreQueryRequest).limit === req.limit &&\n          (entry.req as DBCoreQueryRequest).values === req.values &&\n          ((entry.req as DBCoreQueryRequest).direction ?? 'next') === reqDirection &&\n          areRangesEqual(entry.req.query.range, req.query.range)\n      );\n      if (equalEntry)\n        return [\n          equalEntry,\n          true, // exact match\n          tblCache,\n          entries,\n        ];\n      const superEntry = entries.find((entry) => {\n        const limit = 'limit' in entry.req ? entry.req.limit : Infinity;\n        return (\n          limit >= req.limit &&\n          ((entry.req as DBCoreQueryRequest).direction ?? 'next') === reqDirection &&\n          (req.values ? (entry.req as DBCoreQueryRequest).values : true) &&\n          isSuperRange(entry.req.query.range, req.query.range)\n        );\n      });\n      return [superEntry, false, tblCache, entries];\n    case 'count':\n      const countQuery = entries.find((entry) =>\n        areRangesEqual(entry.req.query.range, req.query.range)\n      );\n      return [countQuery, !!countQuery, tblCache, entries];\n  }\n}\n"
  },
  {
    "path": "src/live-query/cache/is-cachable-context.ts",
    "content": "import { DBCore, DBCoreTable } from '../../public/types/dbcore';\nimport { LiveQueryContext } from '../live-query';\n\nexport function isCachableContext(ctx: LiveQueryContext, table: DBCoreTable) {\n  return (\n    ctx.trans.mode === 'readonly' &&\n    !!ctx.subscr &&\n    !ctx.trans.explicit &&\n    ctx.trans.db._options.cache !== 'disabled' &&\n    !table.schema.primaryKey.outbound\n  );\n}\n\n"
  },
  {
    "path": "src/live-query/cache/is-cachable-request.ts",
    "content": "import { DBCoreCountRequest, DBCoreGetManyRequest, DBCoreGetRequest, DBCoreOpenCursorRequest, DBCoreQueryRequest } from '../../public/types/dbcore';\n\n\nexport function isCachableRequest(type: string, req: Partial<DBCoreQueryRequest & DBCoreCountRequest & DBCoreGetManyRequest & DBCoreGetRequest & DBCoreOpenCursorRequest>) {\n  switch (type) {\n    case 'query':\n      return req.values && !req.unique;\n    case 'get':\n      return false;\n    case 'getMany':\n      return false;\n    case 'count':\n      return false;\n    case 'openCursor':\n      return false;\n  }\n}\n"
  },
  {
    "path": "src/live-query/cache/is-super-range.ts",
    "content": "import { cmp } from '../../functions/cmp';\nimport { DBCoreKeyRange } from '../../public/types/dbcore';\n\nexport function compareLowers(lower1: any, lower2: any, lowerOpen1: boolean, lowerOpen2: boolean) {\n  if (lower1 === undefined) return lower2 !== undefined ? -1 : 0;\n  if (lower2 === undefined) return 1; // since lower1 !== undefined\n  const c = cmp(lower1, lower2);\n  if (c === 0) {\n    if (lowerOpen1 && lowerOpen2) return 0;\n    if (lowerOpen1) return 1\n    if (lowerOpen2) return -1;\n  }\n  return c;\n}\n\nexport function compareUppers(upper1: any, upper2: any, upperOpen1: boolean, upperOpen2: boolean) {\n  if (upper1 === undefined) return upper2 !== undefined ? 1 : 0;\n  if (upper2 === undefined) return -1; // since upper1 !== undefined\n  const c = cmp(upper1, upper2);\n  if (c === 0) {\n    if (upperOpen1 && upperOpen2) return 0;\n    if (upperOpen1) return -1\n    if (upperOpen2) return 1;\n  }\n  return c;\n}\n\nexport function isSuperRange(r1: DBCoreKeyRange, r2: DBCoreKeyRange) {\n  return (\n    compareLowers(r1.lower, r2.lower, r1.lowerOpen, r2.lowerOpen) <= 0 &&\n    compareUppers(r1.upper, r2.upper, r1.upperOpen, r2.upperOpen) >= 0\n  );\n}"
  },
  {
    "path": "src/live-query/cache/is-within-range.ts",
    "content": "import { cmp } from '../../functions/cmp';\nimport { IndexableType } from '../../public';\nimport { DBCoreKeyRange } from '../../public/types/dbcore';\n\nexport function isAboveLower(key: IndexableType, range: DBCoreKeyRange) {\n  return range.lower === undefined\n    ? true // lower is less than anything because it is undefined\n    : range.lowerOpen\n    ? cmp(key, range.lower) > 0 // lowerOpen: Exclude lower bound\n    : cmp(key, range.lower) >= 0; // !lowerOpen: Include lower bound\n}\n\nexport function isBelowUpper(key: IndexableType, range: DBCoreKeyRange) {\n  return range.upper === undefined\n    ? true // upper is greater than anything because it is undefined\n    : range.upperOpen\n    ? cmp(key, range.upper) < 0 // upperOpen: Exclude upper bound\n    : cmp(key, range.upper) <= 0; // !upperOpen: Include upper bound\n}\n\nexport function isWithinRange(key: IndexableType, range: DBCoreKeyRange) {\n  return isAboveLower(key, range) && isBelowUpper(key, range);\n}\n"
  },
  {
    "path": "src/live-query/cache/signalSubscribers.ts",
    "content": "import { CacheEntry, TblQueryCache } from '../../public/types/cache';\nimport { ObservabilitySet } from '../../public/types/db-events';\nimport { extendObservabilitySet } from '../extend-observability-set';\nimport { obsSetsOverlap } from '../obs-sets-overlap';\nimport { cache } from './cache';\n\nlet unsignaledParts: ObservabilitySet = {};\nlet isTaskEnqueued = false;\n\nexport function signalSubscribersLazily(part: ObservabilitySet, optimistic = false) {\n  extendObservabilitySet(unsignaledParts, part);\n  if (!isTaskEnqueued) {\n    isTaskEnqueued = true;\n    setTimeout(() => {\n      isTaskEnqueued = false;\n      const parts = unsignaledParts;\n      unsignaledParts = {};\n      signalSubscribersNow(parts, false);\n    }, 0);\n  }\n}\n\nexport function signalSubscribersNow(\n  updatedParts: ObservabilitySet,\n  deleteAffectedCacheEntries = false\n) {\n  const queriesToSignal = new Set<() => void>();\n  if (updatedParts.all) {\n    // Signal all subscribers to requery.\n    for (const tblCache of Object.values(cache)) {\n      collectTableSubscribers(\n        tblCache,\n        updatedParts,\n        queriesToSignal,\n        deleteAffectedCacheEntries\n      );\n    }\n  } else {\n    for (const key in updatedParts) {\n      const parts = /^idb\\:\\/\\/(.*)\\/(.*)\\//.exec(key);\n      if (parts) {\n        const [, dbName, tableName] = parts;\n        const tblCache = cache[`idb://${dbName}/${tableName}`];\n        if (tblCache)\n          collectTableSubscribers(\n            tblCache,\n            updatedParts,\n            queriesToSignal,\n            deleteAffectedCacheEntries\n          );\n      }\n    }\n  }\n  // Now when affected cache entries are removed, signal collected subscribers to requery.\n  queriesToSignal.forEach((requery) => requery());\n}\n\nfunction collectTableSubscribers(\n  tblCache: TblQueryCache,\n  updatedParts: ObservabilitySet,\n  outQueriesToSignal: Set<() => void>,\n  deleteAffectedCacheEntries: boolean\n) {\n  const updatedEntryLists: [string, CacheEntry[]][] = [];\n  for (const [indexName, entries] of Object.entries(tblCache.queries.query)) {\n    const filteredEntries: CacheEntry[] = [];\n    for (const entry of entries) {\n      if (obsSetsOverlap(updatedParts, entry.obsSet)) {\n        // This query is affected by the mutation. Remove it from cache\n        // and signal all subscribers to requery.\n        entry.subscribers.forEach((requery) => outQueriesToSignal.add(requery));\n      } else if (deleteAffectedCacheEntries) {\n        filteredEntries.push(entry);\n      }\n    }\n    // Collect cache entries to be updated\n    if (deleteAffectedCacheEntries)\n      updatedEntryLists.push([indexName, filteredEntries]);\n  }\n  if (deleteAffectedCacheEntries) {\n    for (const [indexName, filteredEntries] of updatedEntryLists) {\n      tblCache.queries.query[indexName] = filteredEntries;\n    }\n  }\n}\n"
  },
  {
    "path": "src/live-query/cache/subscribe-cachentry.ts",
    "content": "import { delArrayItem } from \"../../functions/utils\";\nimport { CacheEntry } from \"../../public/types/cache\";\n\nexport function subscribeToCacheEntry(cacheEntry: CacheEntry, container: CacheEntry[], requery: ()=>void, signal: AbortSignal) {\n  cacheEntry.subscribers.add(requery);\n  signal.addEventListener(\"abort\", () => {\n    cacheEntry.subscribers.delete(requery);\n    if (cacheEntry.subscribers.size === 0) {\n      enqueForDeletion(cacheEntry, container);\n    }\n  });\n}\n\n\nfunction enqueForDeletion(cacheEntry: CacheEntry, container: CacheEntry[]) {\n  setTimeout(() => {\n    if (cacheEntry.subscribers.size === 0) { // Still empty (no new subscribers readded after grace time)\n      delArrayItem(container, cacheEntry);\n    }\n  }, 3000);\n}\n"
  },
  {
    "path": "src/live-query/enable-broadcast.ts",
    "content": "import {\n  globalEvents,\n  STORAGE_MUTATED_DOM_EVENT_NAME,\n  DEXIE_STORAGE_MUTATED_EVENT_NAME,\n} from '../globals/global-events';\nimport { propagateLocally, propagatingLocally } from './propagate-locally';\n\nexport let bc: BroadcastChannel;\n\nexport let createBC = ()=>{};\n\nif (typeof BroadcastChannel !== 'undefined') {\n  createBC = () => {\n    bc = new BroadcastChannel(STORAGE_MUTATED_DOM_EVENT_NAME);\n    bc.onmessage = ev => ev.data && propagateLocally(ev.data);\n  }\n  createBC();\n\n  /**\n   * The Node.js BroadcastChannel will prevent the node process from exiting\n   * if the BroadcastChannel is not closed.\n   * Therefore we have to call unref() which allows the process to finish\n   * properly even when the BroadcastChannel is never closed.\n   * @link https://nodejs.org/api/worker_threads.html#broadcastchannelunref\n   * @link https://github.com/dexie/Dexie.js/pull/1576\n   */\n  if (typeof (bc as any).unref === 'function') {\n    (bc as any).unref();\n  }\n  \n  //\n  // Propagate local changes to remote tabs, windows and workers via BroadcastChannel\n  //\n  globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, (changedParts) => {\n    if (!propagatingLocally) {\n      bc.postMessage(changedParts);\n    }\n  });\n}\n"
  },
  {
    "path": "src/live-query/extend-observability-set.ts",
    "content": "import { cloneSimpleObjectTree, deepClone, keys, objectIsEmpty } from \"../functions/utils\";\nimport { mergeRanges, RangeSet } from \"../helpers/rangeset\";\nimport { ObservabilitySet } from \"../public/types/db-events\";\n\nexport function extendObservabilitySet(\n  target: ObservabilitySet,\n  newSet: ObservabilitySet\n): ObservabilitySet {\n  keys(newSet).forEach(part => {\n    if (target[part]) mergeRanges(target[part], newSet[part]);\n    else target[part] = cloneSimpleObjectTree(newSet[part]); // Somewhat faster\n  });\n  return target;\n}\n"
  },
  {
    "path": "src/live-query/index.ts",
    "content": "export * from './live-query';\n"
  },
  {
    "path": "src/live-query/live-query.ts",
    "content": "import { _global, isAsyncFunction, keys, objectIsEmpty } from '../functions/utils';\nimport {\n  globalEvents,\n  DEXIE_STORAGE_MUTATED_EVENT_NAME,\n} from '../globals/global-events';\nimport {\n  beginMicroTickScope,\n  decrementExpectedAwaits,\n  endMicroTickScope,\n  execInGlobalContext,\n  incrementExpectedAwaits,\n  NativePromise,\n  newScope,\n  PSD,\n  usePSD,\n} from '../helpers/promise';\nimport { ObservabilitySet } from '../public/types/db-events';\nimport {\n  Observable as IObservable,\n  Subscription,\n} from '../public/types/observable';\nimport { Observable } from '../classes/observable/observable';\nimport { extendObservabilitySet } from './extend-observability-set';\nimport { rangesOverlap } from '../helpers/rangeset';\nimport { domDeps } from '../classes/dexie/dexie-dom-dependencies';\nimport { Transaction } from '../classes/transaction';\nimport { obsSetsOverlap } from './obs-sets-overlap';\n\nexport interface LiveQueryContext {\n  subscr: ObservabilitySet;\n  signal: AbortSignal;\n  requery: () => void;\n  trans: null | Transaction;\n  querier: Function; // For debugging purposes and Error messages\n}\n\nexport function liveQuery<T>(querier: () => T | Promise<T>): IObservable<T> {\n  let hasValue = false;\n  let currentValue: T;\n  const observable = new Observable<T>((observer) => {\n    const scopeFuncIsAsync = isAsyncFunction(querier);\n    function execute(ctx: LiveQueryContext) {\n      const wasRootExec = beginMicroTickScope(); // Performance: Avoid starting a new microtick scope within the async context.\n      try {\n        if (scopeFuncIsAsync) {\n          incrementExpectedAwaits();\n        }\n        let rv = newScope(querier, ctx);\n        if (scopeFuncIsAsync) {\n          // Make sure to set rv = rv.finally in order to wait to after decrementExpectedAwaits() has been called.\n          // This fixes zone leaking issue that the liveQuery zone can leak to observer's next microtask.\n          rv = (rv as Promise<any>).finally(decrementExpectedAwaits);\n        }\n        return rv;\n      } finally {\n        wasRootExec && endMicroTickScope(); // Given that we created the microtick scope, we must also end it.\n      }\n    }\n\n    let closed = false;\n    let abortController: AbortController;\n\n    let accumMuts: ObservabilitySet = {};\n    let currentObs: ObservabilitySet = {};\n\n    const subscription: Subscription = {\n      get closed() {\n        return closed;\n      },\n      unsubscribe: () => {\n        if (closed) return;\n        closed = true;\n        if (abortController) abortController.abort();\n        if (startedListening) globalEvents.storagemutated.unsubscribe(mutationListener);\n      },\n    };\n\n    observer.start && observer.start(subscription); // https://github.com/tc39/proposal-observable\n\n    let startedListening = false;\n\n    const doQuery = () => execInGlobalContext(_doQuery);\n\n    function shouldNotify() {\n      return obsSetsOverlap(currentObs, accumMuts);\n    }\n\n    const mutationListener = (parts: ObservabilitySet) => {\n      extendObservabilitySet(accumMuts, parts);\n      if (shouldNotify()) {\n        doQuery();\n      }\n    };\n\n    const _doQuery = () => {\n      if (\n        closed || // closed - don't run!\n        !domDeps.indexedDB) // SSR in sveltekit, nextjs etc\n      {\n        return;\n      }\n      accumMuts = {};\n      const subscr: ObservabilitySet = {};\n      // Abort signal fill three purposes:\n      // 1. Abort the query if the observable is unsubscribed.\n      // 2. Abort the query if a new query is made before the previous one has completed.\n      // 3. For cached queries to know if they should remain in memory or could be enqued for being freed up.\n      //    (they will remain in memory for a short time and if noone needs them again, they will eventually be freed up)\n      if (abortController) abortController.abort(); // Cancel previous query. Last query will be cancelled on unsubscribe().\n      abortController = new AbortController();\n      \n      const ctx: LiveQueryContext = {\n        subscr,\n        signal: abortController.signal,\n        requery: doQuery,\n        querier,\n        trans: null // Make the scope transactionless (don't reuse transaction from outer scope of the caller of subscribe())\n      }\n      const ret = execute(ctx);\n      Promise.resolve(ret).then(\n        (result) => {\n          hasValue = true;\n          currentValue = result;\n          if (closed || ctx.signal.aborted) {\n            // closed - no subscriber anymore.\n            // signal.aborted - new query was made before this one completed and\n            // the querier might have catched AbortError and return successful result.\n            // If so, we should not rely in that result because we know we have aborted\n            // this run, which means there's another run going on that will handle accumMuts\n            // and we must not base currentObs on the half-baked subscr.\n            return;\n          }\n          accumMuts = {};\n          // Update what we are subscribing for based on this last run:\n          currentObs = subscr;\n          if (!objectIsEmpty(currentObs) && !startedListening) {\n            globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, mutationListener);\n            startedListening = true;\n          }\n          execInGlobalContext(()=>!closed && observer.next && observer.next(result));\n        },\n        (err) => {\n          hasValue = false;\n          if (!['DatabaseClosedError', 'AbortError'].includes(err?.name)) {\n            if (!closed) execInGlobalContext(()=>{\n              if (closed) return;\n              observer.error && observer.error(err);\n            });\n          }\n        }\n      );\n    };\n\n    // Use setTimeot here to guarantee execution in a private macro task before and\n    // after. The helper executeInGlobalContext(_doQuery) is not enough here because\n    // caller of `subscribe()` could be anything, such as a frontend framework that will\n    // continue in the same tick after subscribe() is called and call other\n    // eftects, that could involve dexie operations such as writing to the DB.\n    // If that happens, the private zone echoes from a live query tast started here\n    // could still be ongoing when the other operations start and make them inherit\n    // the async context from a live query.\n    setTimeout(doQuery, 0);\n    return subscription;\n  });\n  observable.hasValue = () => hasValue;\n  observable.getValue = () => currentValue;\n  return observable;\n}\n"
  },
  {
    "path": "src/live-query/obs-sets-overlap.ts",
    "content": "import { rangesOverlap } from '../helpers/rangeset';\nimport { ObservabilitySet } from '../public/types/db-events';\n\nexport function obsSetsOverlap(os1: ObservabilitySet, os2: ObservabilitySet) {\n  return os1.all || os2.all || Object.keys(os1).some(\n    (key) => os2[key] && rangesOverlap(os2[key], os1[key])\n  );\n}\n"
  },
  {
    "path": "src/live-query/observability-middleware.ts",
    "content": "import { LiveQueryContext } from \".\";\nimport { getFromTransactionCache } from \"../dbcore/cache-existing-values-middleware\";\nimport { getEffectiveKeys } from \"../dbcore/get-effective-keys\";\nimport { exceptions } from \"../errors\";\nimport { cmp } from \"../functions/cmp\";\nimport { isArray, keys } from \"../functions/utils\";\nimport { PSD } from \"../helpers/promise\";\nimport { RangeSet } from \"../helpers/rangeset\";\nimport { ObservabilitySet } from \"../public/types/db-events\";\nimport {\n  DBCore,\n  DBCoreCountRequest,\n  DBCoreCursor,\n  DBCoreGetManyRequest,\n  DBCoreGetRequest,\n  DBCoreIndex,\n  DBCoreOpenCursorRequest,\n  DBCoreQueryRequest,\n  DBCoreQueryResponse,\n  DBCoreTable,\n  DBCoreTableSchema,\n  DBCoreTransaction,\n} from \"../public/types/dbcore\";\nimport { Middleware } from \"../public/types/middleware\";\nimport { isCachableContext } from \"./cache/is-cachable-context\";\nimport { isCachableRequest } from \"./cache/is-cachable-request\";\nimport { extendObservabilitySet } from \"./extend-observability-set\";\n\nexport const observabilityMiddleware: Middleware<DBCore> = {\n  stack: \"dbcore\",\n  level: 0,\n  name: \"Observability\",\n  create: (core) => {\n    const dbName = core.schema.name;\n    const FULL_RANGE = new RangeSet(core.MIN_KEY, core.MAX_KEY);\n\n    return {\n      ...core,\n      transaction: (stores, mode, options) => {\n        if (PSD.subscr && mode !== 'readonly') {\n          throw new exceptions.ReadOnly(`Readwrite transaction in liveQuery context. Querier source: ${(PSD as LiveQueryContext).querier}`);\n        }\n        return core.transaction(stores, mode, options);\n      },\n      table: (tableName) => {\n        const table = core.table(tableName);\n        const { schema } = table;\n        const { primaryKey, indexes } = schema;\n        const { extractKey, outbound } = primaryKey;\n        const indexesWithAutoIncPK = primaryKey.autoIncrement && indexes.filter(\n          (index) => index.compound && (index.keyPath as string[]).includes(primaryKey.keyPath as string)\n        );\n        const tableClone: DBCoreTable = {\n          ...table,\n          mutate: (req) => {\n            const trans = req.trans as DBCoreTransaction & {\n              mutatedParts?: ObservabilitySet;\n            };\n            const mutatedParts = req.mutatedParts || (req.mutatedParts = {});\n            const getRangeSet = (indexName: string) => {\n              const part = `idb://${dbName}/${tableName}/${indexName}`;\n              return (mutatedParts[part] ||\n                (mutatedParts[part] = new RangeSet())) as RangeSet;\n            };\n            const pkRangeSet = getRangeSet(\"\");\n            const delsRangeSet = getRangeSet(\":dels\");\n\n            const { type } = req;\n            let [keys, newObjs] =\n              req.type === \"deleteRange\"\n                ? [req.range] // keys will be an DBCoreKeyRange object - transformed later on to a [from,to]-style range.\n                : req.type === \"delete\"\n                ? [req.keys] // keys known already here. newObjs will be undefined.\n                : req.values.length < 50\n                ? [getEffectiveKeys(primaryKey, req).filter(id => id), req.values] // keys except autoIncremented - they will be added later on.\n                : []; // keys and newObjs will both be undefined - changeSpec will become true (changed for entire table)\n\n            const oldCache = req.trans[\"_cache\"];\n\n            // Add the mutated table and optionally keys to the mutatedTables set on the transaction.\n            // Used by subscribers to txcommit event and for Collection.prototype.subscribe().\n            if (isArray(keys)) {\n              // keys is an array - delete, add or put of less than 50 rows.\n              // Individual keys (add put or delete)\n              pkRangeSet.addKeys(keys);\n              // Only get oldObjs if they have been cached recently\n              // (This applies to Collection.modify() only, but also if updating/deleting hooks have subscribers)\n              const oldObjs = type === 'delete' || keys.length === newObjs.length ? getFromTransactionCache(keys, oldCache) : null;\n\n              // Supply detailed values per index for both old and new objects:\n              if (!oldObjs) {\n                // add, delete or put and we don't know old values.\n                // Indicate this in the \":dels\" part, for the sake of count() and primaryKeys() queries only!\n                delsRangeSet.addKeys(keys);\n              }\n              if (oldObjs || newObjs) {\n                // No matter if knowning oldObjs or not, track the indices if it's a put, add or delete.\n                trackAffectedIndexes(getRangeSet, schema, oldObjs, newObjs);\n              }\n            } else if (keys) {\n              // keys is a DBCoreKeyRange object. Transform it to [from,to]-style range.\n              // As we can't know deleted index ranges, mark index-based subscriptions must trigger.\n              // (above/below-style ranges are not supported in RangeSet.ts, so we must replace open ends\n              // with core.MIN_KEY and core.MAX_KEY respectively. This is what solves issue #2067!\n              const range = {\n                from: keys.lower ?? core.MIN_KEY,\n                to: keys.upper ?? core.MAX_KEY\n              };\n              delsRangeSet.add(range);\n              // deleteRange. keys is a DBCoreKeyRange objects. Transform it to [from,to]-style range.\n              pkRangeSet.add(range);\n            } else {\n              // Too many requests to record the details without slowing down write performance.\n              // Let's just record a generic large range on primary key, the virtual :dels index and\n              // all secondary indices:\n              pkRangeSet.add(FULL_RANGE);\n              delsRangeSet.add(FULL_RANGE);\n              schema.indexes.forEach(idx => getRangeSet(idx.name).add(FULL_RANGE));\n            }\n\n            return table.mutate(req).then((res) => {\n              // Merge the mutated parts from the request into the transaction's mutatedParts\n              // now when the request went fine.\n              if (keys && (req.type === 'add' || req.type === 'put')) {\n                // Less than 50 requests (keys truthy) (otherwise we've added full range anyway)\n                // autoincrement means we might not have got all keys until now\n                pkRangeSet.addKeys(res.results);\n                if (indexesWithAutoIncPK) {\n                  // Dexie Issue 1946:\n                  // If an auto-incremented primary key is part of a compound index,\n                  // we need to compute the resulting value of that index after inserting\n                  // the rows.\n                  indexesWithAutoIncPK.forEach(idx => {\n                    // Extract values of this compound index where primary key is not yet set:\n                    const idxVals = req.values.map(v => idx.extractKey(v));\n                    // Find the position of the primary key in the index:\n                    const pkPos = (idx.keyPath as string[]).findIndex(prop => prop === primaryKey.keyPath);\n                    // Update idxVals with the resulting primary keys to complete the index value:\n                    for (let i = 0, len = res.results!.length; i < len; ++i) {\n                      idxVals[i][pkPos] = res.results![i];\n                    }\n                    // Add the updated index to the rangeset:\n                    getRangeSet(idx.name).addKeys(idxVals);\n                  });\n                }\n              }\n              trans.mutatedParts = extendObservabilitySet (\n                trans.mutatedParts || {},\n                mutatedParts\n              );\n              return res;\n            });\n          },\n        };\n\n        const getRange: (req: any) => [DBCoreIndex, RangeSet] = ({\n          query: { index, range },\n        }:\n          | DBCoreQueryRequest\n          | DBCoreCountRequest\n          | DBCoreOpenCursorRequest) => [\n          index,\n          new RangeSet(range.lower ?? core.MIN_KEY, range.upper ?? core.MAX_KEY),\n        ];\n\n        const readSubscribers: {[method in\n          Exclude<keyof DBCoreTable, \"name\" | \"schema\" | \"mutate\">]: \n          (req: any) => [DBCoreIndex, RangeSet]\n        } = {\n          get: (req) => [primaryKey, new RangeSet(req.key)],\n          getMany: (req) => [primaryKey, new RangeSet().addKeys(req.keys)],\n          count: getRange,\n          query: getRange,\n          openCursor: getRange,\n        }\n\n        keys(readSubscribers).forEach((method: 'get' | 'getMany' | 'count' | 'query' | 'openCursor') => {\n          tableClone[method] = function (\n            req:\n              | DBCoreGetRequest\n              | DBCoreGetManyRequest\n              | DBCoreQueryRequest\n              | DBCoreCountRequest\n              | DBCoreOpenCursorRequest\n          ) {\n            const { subscr } = PSD as LiveQueryContext;\n            const isLiveQuery = !!subscr;\n            let cachable = isCachableContext(PSD as LiveQueryContext, table) && isCachableRequest(method, req);\n            const obsSet = cachable\n              ? req.obsSet = {} // Implicit read transaction - track changes for this query only for the request's duration\n              : subscr; // Explicit read transaction - track changes across entire live query\n\n            if (isLiveQuery) {\n              // Current zone want's to track all queries so they can be subscribed to.\n              // (The query is executed within a \"liveQuery\" zone)\n              // Check whether the query applies to a certain set of ranges:\n              // Track what we should be observing:\n              const getRangeSet = (indexName: string) => {\n                const part = `idb://${dbName}/${tableName}/${indexName}`;\n                return (obsSet[part] ||\n                  (obsSet[part] = new RangeSet())) as RangeSet;\n              };\n              const pkRangeSet = getRangeSet(\"\");\n              const delsRangeSet = getRangeSet(\":dels\");\n              const [queriedIndex, queriedRanges] = readSubscribers[method](req);\n              // A generic rule here: queried ranges should always be subscribed to.\n              if (method === 'query' && queriedIndex.isPrimaryKey && !(req as DBCoreQueryRequest).values) {\n                // A pure primay-key based Collection where only .primaryKeys() is requested. Don't wakeup on other changes than added or deleted primary keys within queried range.\n                delsRangeSet.add(queriedRanges);\n              } else {\n                getRangeSet(queriedIndex.name || \"\").add(queriedRanges);\n              }\n              if (!queriedIndex.isPrimaryKey) {\n                // Only count(), query() and openCursor() operates on secondary indices.\n                // Since put(), delete() and deleteRange() mutations may happen without knowing oldObjs,\n                // the mutate() method will be missing what secondary indices that are being deleted from\n                // the subscribed range. We are working around this issue by recording all the resulting\n                // primary keys from the queries. This only works for those kinds of queries where we can\n                // derive the primary key from the result.\n                // In this block we are accomplishing this using various strategies depending on the properties\n                // of the query result.\n\n                if (method === \"count\") {\n                  // We've got a problem! Delete and put mutations happen without known the oldObjs.\n                  // Those mutation could change the count.\n                  // Solution: Dedicated \":dels\" url represends a subscription to all mutations without oldObjs\n                  // (specially triggered in the mutators put(), delete() and deleteRange() when they don't know oldObject)\n                  delsRangeSet.add(FULL_RANGE);\n                } else {\n                  // openCursor() or query()\n\n                  // Prepare a keysPromise in case the we're doing an IDBIndex.getAll() on a store with outbound keys.\n                  const keysPromise =\n                    method === \"query\" &&\n                    outbound &&\n                    (req as DBCoreQueryRequest).values &&\n                    table.query({\n                      ...(req as DBCoreQueryRequest),\n                      values: false,\n                    });\n\n                  return table[method].apply(this, arguments).then((res) => {\n                    if (method === \"query\") {\n                      if (outbound && (req as DBCoreQueryRequest).values) {\n                        // If keys are outbound, we can't use extractKey to map what keys to observe.\n                        // We've queried an index (like 'dateTime') on an outbound table\n                        // and retrieve a list of objects\n                        // from who we cannot know their primary keys.\n                        // \"Luckily\" though, we've prepared the keysPromise to assist us in exact this condition.\n                        return keysPromise.then(\n                          ({ result: resultingKeys }: DBCoreQueryResponse) => {\n                            pkRangeSet.addKeys(resultingKeys);\n                            return res;\n                          }\n                        );\n                      }\n                      // query() inbound values, keys or outbound keys. Secondary indexes only since\n                      // for primary keys we would only add results within the already registered range.\n                      const pKeys = (req as DBCoreQueryRequest).values\n                        ? (res as DBCoreQueryResponse).result.map(extractKey)\n                        : (res as DBCoreQueryResponse).result;\n                      if ((req as DBCoreQueryRequest).values) {\n                        // Subscribe to any mutation made on the returned keys,\n                        // so that we detect both deletions and updated properties.\n                        pkRangeSet.addKeys(pKeys);\n                      } else {\n                        // Subscribe only to mutations on the returned keys\n                        // in case the mutator was unable to know oldObjs.\n                        // If it has oldObj, the mutator won't put anything in \":dels\" because\n                        // it can more fine-grained put the exact removed and added index value in the correct\n                        // index range that we subscribe to in the queried range sets.\n                        // We don't load values so a change on a property outside our index will not\n                        // require us to re-execute the query.\n                        delsRangeSet.addKeys(pKeys);\n                      }\n                    } else if (method === \"openCursor\") {\n                      // Caller requests a cursor.\n                      // For the same reason as when method===\"query\", we only need to observe\n                      // those keys whose values are possibly used or rendered - which could\n                      // only happen on keys where they get the cursor's key, primaryKey or value.\n                      const cursor: DBCoreCursor | null = res;\n                      const wantValues = (req as DBCoreOpenCursorRequest).values;\n                      return (\n                        cursor &&\n                        Object.create(cursor, {\n                          key: {\n                            get() {\n                              delsRangeSet.addKey(cursor.primaryKey);\n                              return cursor.key;\n                            },\n                          },\n                          primaryKey: {\n                            get() {\n                              const pkey = cursor.primaryKey;\n                              delsRangeSet.addKey(pkey);\n                              return pkey;\n                            },\n                          },\n                          value: {\n                            get() {\n                              wantValues && pkRangeSet.addKey(cursor.primaryKey);\n                              return cursor.value;\n                            },\n                          },\n                        })\n                      );\n                    }\n                    return res;\n                  });\n                }\n              }\n            }\n            return table[method].apply(this, arguments);\n          };\n        });\n        return tableClone;\n      },\n    };\n  },\n};\n\nfunction trackAffectedIndexes(\n  getRangeSet: (index: string) => RangeSet,\n  schema: DBCoreTableSchema,\n  oldObjs: readonly any[] | undefined,\n  newObjs: readonly any[] | undefined\n) {\n  function addAffectedIndex(ix: DBCoreIndex) {\n    const rangeSet = getRangeSet(ix.name || \"\");\n    function extractKey(obj: any) {\n      return obj != null ? ix.extractKey(obj) : null;\n    }\n    const addKeyOrKeys = (key: any) => ix.multiEntry && isArray(key)\n      // multiEntry and the old property was an array - add each array entry to the rangeSet:\n      ? key.forEach(key => rangeSet.addKey(key))\n      // Not multiEntry or the old property was not an array - add each array entry to the rangeSet:\n      : rangeSet.addKey(key);\n\n    (oldObjs || newObjs).forEach((_, i) => {\n      const oldKey = oldObjs && extractKey(oldObjs[i]);\n      const newKey = newObjs && extractKey(newObjs[i]);\n      if (cmp(oldKey, newKey) !== 0) {\n        // The index has changed. Add both old and new value of the index.\n        if (oldKey != null) addKeyOrKeys(oldKey); // If oldKey is invalid key, addKey() will be a noop.\n        if (newKey != null) addKeyOrKeys(newKey); // If newKey is invalid key, addKey() will be a noop.\n      }\n    });\n  }\n  schema.indexes.forEach(addAffectedIndex);\n}\n"
  },
  {
    "path": "src/live-query/propagate-locally.ts",
    "content": "import { globalEvents, DEXIE_STORAGE_MUTATED_EVENT_NAME, STORAGE_MUTATED_DOM_EVENT_NAME } from '../globals/global-events';\nimport { ObservabilitySet } from \"../public/types/db-events\";\nimport { signalSubscribersNow } from './cache/signalSubscribers';\n\nif (typeof dispatchEvent !== 'undefined' && typeof addEventListener !== 'undefined') {\n  globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, updatedParts => {\n    if (!propagatingLocally) {\n      let event: CustomEvent<ObservabilitySet>;\n      event = new CustomEvent(STORAGE_MUTATED_DOM_EVENT_NAME, {\n        detail: updatedParts\n      });\n      propagatingLocally = true;\n      dispatchEvent(event);\n      propagatingLocally = false;\n    }\n  });\n  addEventListener(STORAGE_MUTATED_DOM_EVENT_NAME, ({detail}: CustomEvent<ObservabilitySet>) => {\n    if (!propagatingLocally) {\n      propagateLocally(detail);\n    }\n  });\n}\n\n/** Called from listeners to BroadcastChannel and DOM event to\n * propagate the event locally into dexie's storagemutated event\n * and invalidate cached queries.\n * \n * This function is only called when the event is not originating\n * from this same Dexie module - either from another redundant dexie import\n * or from a foreign tab or worker. That's why we need to invalidate\n * the cache when this happens.\n */\nexport function propagateLocally(updateParts: ObservabilitySet) {\n  let wasMe = propagatingLocally;\n  try {\n    propagatingLocally = true;\n    // Fire the \"storagemutated\" event.\n    globalEvents.storagemutated.fire(updateParts);\n    // Invalidate cached queries and signal subscribers to requery.\n    signalSubscribersNow(updateParts, true);\n  } finally {\n    propagatingLocally = wasMe;\n  }\n}\n\nexport let propagatingLocally = false;\n"
  },
  {
    "path": "src/public/index.d.ts",
    "content": "/* public/index.d.ts - The source of dexie.d.ts\n * \n * We're using dts-bundle-generator to bundle this file to the\n * final version.\n * \n * We're separating public from internal types as a way to keep package-private\n * properties and types without exposing them to the public API.\n * \n * All internal types extends their corresponding public type though, so that\n * we still get the benefit from the tsc compilator checking type correctness.\n */\n\nimport { DexieConstructor} from './types/dexie-constructor';\ndeclare var Dexie: DexieConstructor;\n\n/* For backard compatibility, define a \"module\" Dexie and put types\n * such as Table in there as well. This must be in order for existing\n * samples and app code out there to work without modifications. Which\n * is very important as it would otherwise impose a lot of work for app\n * devs upgrading.\n */\nimport { Table } from './types/table';\nimport { Collection } from './types/collection';\nimport { PromiseExtended } from './types/promise-extended';\nimport { Observable } from './types/observable';\nimport { IntervalTree, RangeSetConstructor } from './types/rangeset';\nimport { Dexie, TableProp } from './types/dexie';\nexport type { TableProp };\nimport { PropModification, PropModSpec } from './types/prop-modification';\nexport { PropModification, PropModSpec };\nexport * from './types/entity';\nexport * from './types/entity-table';\nexport { UpdateSpec } from './types/update-spec';\nexport * from './types/insert-type';\n\n// Alias of Table and Collection in order to be able to refer them from module below...\ninterface _Table<T, TKey, TInsertType> extends Table<T, TKey, TInsertType> {}\ninterface _Collection<T,TKey> extends Collection<T,TKey> {}\n\n// Besides being the only exported value, let Dexie also be\n// a namespace for types...\ndeclare namespace Dexie {\n  // The \"Dexie.Promise\" type.\n  type Promise<T=any> = PromiseExtended<T> // Because many samples have been Dexie.Promise.\n  // The \"Dexie.Table\" interface. Same as named exported interface Table.\n  interface Table<T=any,Key=any,TInsertType=T> extends _Table<T,Key,TInsertType> {} // Because all samples have been Dexie.Table<...>\n  // The \"Dexie.Collection\" interface. Same as named exported interface Collection.\n  interface Collection<T=any,Key=any> extends _Collection<T, Key> {} // Because app-code may declare it.\n}\n\n/** Explicitely export IndexableType. Mostly for backward compatibility.*/\nexport { IndexableType } from './types/indexable-type';\n\n/** 'Dexie' is the only value export (non-type). Export it both by name and as a default export.\n * API user may choose whether to use the named or defualt export. All samples\n * have been using the default export until version 2.0.1.\n*/\nexport { Dexie };\nexport function liveQuery<T>(querier: () => T | Promise<T>): Observable<T>;\nexport function mergeRanges(target: IntervalTree, newSet: IntervalTree): void;\nexport function rangesOverlap(\n  rangeSet1: IntervalTree,\n  rangeSet2: IntervalTree\n): boolean;\ndeclare var RangeSet: RangeSetConstructor;\nexport function cmp(a: any, b: any): number;\nexport function replacePrefix(a: string, b: string): PropModification;\nexport function add(num: number | bigint | any[]): PropModification;\nexport function remove(num: number | bigint | any[]): PropModification;\n\nexport { RangeSet };\n\n/** Exporting 'Dexie' as the default export.\n **/\nexport default Dexie;\n"
  },
  {
    "path": "src/public/types/_insert-type.d.ts",
    "content": "import { IsStrictlyAny } from \"./is-strictly-any\";\n\n/** Extract the union of literal method names in T\n */\n type NonInsertProps<T> = {\n  [P in keyof T]: IsStrictlyAny<T[P]> extends true\n    ? never // Plain property of type any (not method)\n    : T[P] extends (...args: any[]) => any\n    ? P // a function (method)\n    : T[P] extends {on:any,subdocs:any,gc:any,isSynced:any,isLoaded:any,shouldLoad:any}\n    ? P // an YDoc property - should be generated by dexie and not by user\n    : never; // Not a non-insert property (don't omit it from InsertType)\n}[keyof T];\n\n/** Default insert type of T is a subset of T where:\n *    * given optional props (such as an auto-generated primary key) are made optional\n *    * methods are omitted\n */\n export type InsertType<T, OptionalProps extends keyof T> = Omit<T, OptionalProps | NonInsertProps<T>> & {[P in OptionalProps]?: T[P]};\n"
  },
  {
    "path": "src/public/types/cache.d.ts",
    "content": "import { ObservabilitySet } from './db-events';\nimport {\n  DBCoreCountRequest,\n  DBCoreGetManyRequest,\n  DBCoreGetRequest,\n  DBCoreKeyRange,\n  DBCoreMutateRequest,\n  DBCoreQueryRequest,\n} from './dbcore';\nimport { IntervalTree } from './rangeset';\n\n\nexport type GlobalQueryCache = {\n  // TODO: Change to parts: {[part: string]: TblQueryCache}\n  //       och unsignaledParts: ObservabilitySet;\n  [part: string]: TblQueryCache; // part is `idb://${dbName}/${tableName}`\n};\n\nexport interface TblQueryCache {\n  queries: {\n    query: {[indexName: string]: CacheEntry[]};\n    count: {[indexName: string]: CacheEntry[]};\n  },\n  objs: Map<string | number, object>;\n  optimisticOps: DBCoreMutateRequest[];\n  unsignaledParts: ObservabilitySet;\n}\n\ninterface CacheEntryCommon {\n  subscribers: Set<() => void>;\n  obsSet: ObservabilitySet;\n  //txObsSet: ObservabilitySet;\n  promise: Promise<any>;\n  dirty: boolean;\n}\n\nexport type CacheEntry = CacheEntryCommon &\n  (\n    | {\n        type: 'query';\n        req: DBCoreQueryRequest;\n        res?: readonly any[];\n      }\n    | {\n        type: 'count';\n        req: DBCoreCountRequest;\n        res?: number;\n      }\n  );\n"
  },
  {
    "path": "src/public/types/collection.d.ts",
    "content": "import { ThenShortcut } from \"./then-shortcut\";\nimport { IndexableTypeArray } from \"./indexable-type\";\nimport { WhereClause } from \"./where-clause\";\nimport { PromiseExtended } from \"./promise-extended\";\nimport { IndexableType } from \"./indexable-type\";\nimport { Dexie } from \"./dexie\";\nimport { UpdateSpec } from \"./update-spec\";\n\nexport interface Collection<T=any, TKey=IndexableType, TInsertType=T> {\n  db: Dexie;\n  and(filter: (x: T) => boolean): Collection<T, TKey, TInsertType>;\n  clone(props?: Object): Collection<T, TKey, TInsertType>;\n  count(): PromiseExtended<number>;\n  count<R>(thenShortcut: ThenShortcut<number, R>): PromiseExtended<R>;\n  distinct(): Collection<T, TKey, TInsertType>;\n  each(callback: (obj: T, cursor: {key: IndexableType, primaryKey: TKey}) => any): PromiseExtended<void>;\n  eachKey(callback: (key: IndexableType, cursor: {key: IndexableType, primaryKey: TKey}) => any): PromiseExtended<void>;\n  eachPrimaryKey(callback: (key: TKey, cursor: {key: IndexableType, primaryKey: TKey}) => any): PromiseExtended<void>;\n  eachUniqueKey(callback: (key: IndexableType, cursor: {key: IndexableType, primaryKey: TKey}) => any): PromiseExtended<void>;\n  filter<S extends T>(filter: (x: T) => x is S): Collection<S, TKey>;\n  filter(filter: (x: T) => boolean): Collection<T, TKey, TInsertType>;\n  first(): PromiseExtended<T | undefined>;\n  first<R>(thenShortcut: ThenShortcut<T | undefined, R>): PromiseExtended<R>;\n  firstKey(): PromiseExtended<IndexableType | undefined>;\n  keys(): PromiseExtended<IndexableTypeArray>;\n  keys<R>(thenShortcut: ThenShortcut<IndexableTypeArray, R>): PromiseExtended<R>;\n  primaryKeys(): PromiseExtended<TKey[]>;\n  primaryKeys<R>(thenShortcut: ThenShortcut<TKey[], R>): PromiseExtended<R>;\n  last(): PromiseExtended<T | undefined>;\n  last<R>(thenShortcut: ThenShortcut<T | undefined, R>): PromiseExtended<R>;\n  lastKey(): PromiseExtended<IndexableType | undefined>;\n  limit(n: number): Collection<T, TKey, TInsertType>;\n  offset(n: number): Collection<T, TKey, TInsertType>;\n  or(indexOrPrimayKey: string): WhereClause<T, TKey, TInsertType>;\n  raw(): Collection<T, TKey, TInsertType>;\n  reverse(): Collection<T, TKey, TInsertType>;\n  sortBy(keyPath: string): PromiseExtended<T[]>;\n  sortBy<R>(keyPath: string, thenShortcut: ThenShortcut<T[], R>) : PromiseExtended<R>;\n  toArray(): PromiseExtended<Array<T>>;\n  toArray<R>(thenShortcut: ThenShortcut<T[], R>) : PromiseExtended<R>;\n  uniqueKeys(): PromiseExtended<IndexableTypeArray>;\n  uniqueKeys<R>(thenShortcut: ThenShortcut<IndexableTypeArray, R>): PromiseExtended<R>;\n  until(filter: (value: T) => boolean, includeStopEntry?: boolean): Collection<T, TKey, TInsertType>;\n  // Mutating methods\n  delete(): PromiseExtended<number>;\n  modify(changeCallback: (obj: T, ctx:{value: TInsertType}) => void | boolean): PromiseExtended<number>;\n  modify(changes: UpdateSpec<TInsertType>): PromiseExtended<number>;\n}\n"
  },
  {
    "path": "src/public/types/db-events.d.ts",
    "content": "import { DexieEventSet } from \"./dexie-event-set\";\nimport { DexieEvent } from \"./dexie-event\";\nimport { Transaction } from \"./transaction\";\nimport { Dexie } from \"./dexie\";\nimport { IntervalTree } from \"./rangeset\";\nexport interface DexieOnReadyEvent {\n  subscribe(fn: (vipDb: Dexie) => any, bSticky: boolean): void;\n  unsubscribe(fn: (vipDb: Dexie) => any): void;\n  fire(vipDb: Dexie): any;\n}\n\nexport interface DexieVersionChangeEvent {\n  subscribe(fn: (event: IDBVersionChangeEvent) => any): void;\n  unsubscribe(fn: (event: IDBVersionChangeEvent) => any): void;\n  fire(event: IDBVersionChangeEvent): any;\n}\n\nexport interface DexiePopulateEvent {\n  subscribe(fn: (trans: Transaction) => any): void;\n  unsubscribe(fn: (trans: Transaction) => any): void;\n  fire(trans: Transaction): any;\n}\n\nexport interface DexieCloseEvent {\n  subscribe(fn: (event: Event) => any): void;\n  unsubscribe(fn: (event: Event) => any): void;\n  fire(event: Event): any;\n}\n\nexport interface DbEventFns {\n  (eventName: 'populate', subscriber: (trans: Transaction) => any): void;\n  (eventName: 'blocked', subscriber: (event: IDBVersionChangeEvent) => any): void;\n  (eventName: 'versionchange', subscriber: (event: IDBVersionChangeEvent) => any): void;\n  (eventName: 'close', subscriber: (event: Event) => any): void;\n}\nexport interface DbEvents extends DbEventFns, DexieEventSet {\n  (eventName: 'ready', subscriber: (vipDb: Dexie) => any, bSticky?: boolean): void;\n  ready: DexieOnReadyEvent;\n  populate: DexiePopulateEvent;\n  blocked: DexieEvent;\n  versionchange: DexieVersionChangeEvent;\n  close: DexieCloseEvent;\n}\n\n/** Set of mutated parts of the database\n */\nexport type ObservabilitySet = {\n  /** Database part having been mutated.\n   * \n   * This structure is produced in observability-middleware.ts\n   * and consumed in live-query.ts.\n   * \n   * Format of 'part':\n   * \n   *   `idb://${dbName}/${tableName}/${indexName}`\n   * \n   * * dbName is the database name\n   * * tableName is the table name\n   * * indexName is any of:\n   *    1. An empty string - represents the primary keys of the affected objs\n   *    2. \":dels\" - represents primary keys of deleted objects in the table\n   *    3. The keyPath of an index, such as \"name\", \"age\" or \"address.city\" -\n   *       represents indexes that, if used in a query, might affect the\n   *       result of that query.\n   * \n   * IntervalTree\n   *    * See definition of IntervalTree type in rangeset.d.ts\n   *    * See rangesOverlap() in rangeset.ts that can be used to compare two\n   *      IntervalTrees and detect collissions.\n   *    * See RangeSet class that can be used to create an IntervalTree and add\n   *      ranges to it.\n   */\n  [part: string]: IntervalTree;\n};\n\nexport interface DexieOnStorageMutatedEvent {\n  subscribe(fn: (parts: ObservabilitySet) => any): void;\n  unsubscribe(fn: (parts: ObservabilitySet) => any): void;\n  fire(parts: ObservabilitySet): any;\n}\n\nexport interface GlobalDexieEvents extends DexieEventSet {\n  (eventName: 'storagemutated', subscriber: (parts: ObservabilitySet) => any): void;\n  storagemutated: DexieOnStorageMutatedEvent;\n}\n"
  },
  {
    "path": "src/public/types/db-schema.d.ts",
    "content": "import { TableSchema } from \"./table-schema\";\n\nexport type DbSchema = {[tableName: string]: TableSchema};\n"
  },
  {
    "path": "src/public/types/dbcore.d.ts",
    "content": "// For public interface\n\nimport { ObservabilitySet } from \"./db-events\";\nimport {ChromeTransactionDurability} from \"./dexie-constructor\";\n\nexport const enum DBCoreRangeType {\n  Equal = 1,\n  Range = 2,\n  Any = 3,\n  Never = 4\n}\n\nexport interface DBCoreKeyRange {\n  readonly type: DBCoreRangeType;\n  readonly lower: any;\n  readonly lowerOpen?: boolean;\n  readonly upper: any;\n  readonly upperOpen?: boolean;\n  //includes (key: Key) : boolean; Despite IDBKeyRange api - it's no good to have this as a method. Benefit from using a more functional approach.\n}\n\nexport interface DBCoreTransaction {\n  abort(): void;\n}\n\ninterface DbCoreTransactionOptions {\n  durability: ChromeTransactionDurability\n}\n\nexport type DBCoreMutateRequest = DBCoreAddRequest | DBCorePutRequest | DBCoreDeleteRequest | DBCoreDeleteRangeRequest;\n\nexport interface DBCoreMutateResponse {\n  numFailures: number,\n  failures: {[operationNumber: number]: Error};\n  lastResult: any;\n  results?: any[]; // Always present on responses to AddRequest and PutRequest.\n}\n\nexport interface DBCoreAddRequest {\n  type: 'add';\n  trans: DBCoreTransaction;\n  values: readonly any[];\n  keys?: any[];\n  mutatedParts?: ObservabilitySet\n  /** @deprecated Will always get results since 3.1.0-alpha.5 */\n  wantResults?: boolean;\n}\n\nexport interface DBCorePutRequest {\n  type: 'put';\n  trans: DBCoreTransaction;\n  values: readonly any[];\n  keys?: any[];\n  mutatedParts?: ObservabilitySet\n  upsert?: boolean; // If true, will insert the object if it does not exist. If false, will only update existing objects using the 'updates' property.\n  criteria?: {\n    index: string | null;\n    range: DBCoreKeyRange;\n  };\n  changeSpec?: {[keyPath: string]: any}; // Common changeSpec for each key\n  isAdditionalChunk?: boolean;\n  updates?: {\n    keys: any[],\n    changeSpecs: {[keyPath: string]: any}[]; // changeSpec per key.  \n  },\n  /** @deprecated Will always get results since 3.1.0-alpha.5 */\n  wantResults?: boolean;\n}\n\nexport interface DBCoreDeleteRequest {\n  type: 'delete';\n  trans: DBCoreTransaction;\n  keys: any[];\n  mutatedParts?: ObservabilitySet\n  criteria?: {\n    index: string | null;\n    range: DBCoreKeyRange;\n  };\n  isAdditionalChunk?: boolean;\n}\n\nexport interface DBCoreDeleteRangeRequest {\n  type: 'deleteRange';\n  trans: DBCoreTransaction;\n  range: DBCoreKeyRange;\n  mutatedParts?: ObservabilitySet\n}\n\nexport interface DBCoreGetManyRequest {\n  trans: DBCoreTransaction;\n  keys: any[];\n  cache?: \"immutable\" | \"clone\"\n  obsSet?: ObservabilitySet\n}\n\nexport interface DBCoreGetRequest {\n  trans: DBCoreTransaction;\n  key: any;\n  obsSet?: ObservabilitySet\n}\n\nexport interface DBCoreQuery {\n  index: DBCoreIndex;//keyPath: null | string | string[]; // null represents primary key. string a property, string[] several properties.\n  range: DBCoreKeyRange;\n}\n\nexport interface DBCoreQueryRequest {\n  trans: DBCoreTransaction;\n  values?: boolean;\n  limit?: number;\n  query: DBCoreQuery;\n  direction?: \"next\" | \"nextunique\" | \"prev\" | \"prevunique\";\n  obsSet?: ObservabilitySet\n\n}\n\nexport interface DBCoreQueryResponse {\n  result: any[];\n}\n\nexport interface DBCoreOpenCursorRequest {\n  trans: DBCoreTransaction;\n  values?: boolean;\n  unique?: boolean;\n  reverse?: boolean;\n  query: DBCoreQuery;\n  obsSet?: ObservabilitySet\n}\n\nexport interface DBCoreCountRequest {\n  trans: DBCoreTransaction;\n  query: DBCoreQuery;\n  obsSet?: ObservabilitySet\n}\n\nexport interface DBCoreCursor {\n  readonly trans: DBCoreTransaction;\n  readonly key: any;\n  readonly primaryKey: any;\n  readonly value?: any;\n  readonly done?: boolean;\n  continue(key?: any): void;\n  continuePrimaryKey(key: any, primaryKey: any): void;\n  advance(count: number): void;\n  start(onNext: ()=>void): Promise<any>\n  stop(value?: any | Promise<any>): void;\n  next(): Promise<DBCoreCursor>;\n  fail(error: Error): void;\n}\n\nexport interface DBCoreSchema {\n  name: string;\n  tables: DBCoreTableSchema[];\n}\nexport interface DBCoreTableSchema {\n  readonly name: string;\n  readonly primaryKey: DBCoreIndex;\n  readonly indexes: DBCoreIndex[];\n  readonly getIndexByKeyPath: (keyPath: null | string | string[]) => DBCoreIndex | undefined;\n}\n\nexport interface DBCoreIndex {\n  /** Name of the index, or null for primary key */\n  readonly name: string | null;\n  /** True if this index represents the primary key */\n  readonly isPrimaryKey?: boolean;\n  /** True if this index represents the primary key and is not inbound (https://dexie.org/docs/inbound) */\n  readonly outbound?: boolean; \n  /** True if and only if keyPath is an array (https://dexie.org/docs/Compound-Index) */\n  readonly compound?: boolean;\n  /** keyPath, null for primary key, string for single-property indexes, Array<string> for compound indexes */\n  readonly keyPath: null | string | string[];\n  /** Auto-generated primary key (does not apply to secondary indexes) */\n  readonly autoIncrement?: boolean;\n  /** Whether index is unique. Also true if index is primary key. */\n  readonly unique?: boolean;\n  /** Whether index is multiEntry. */\n  readonly multiEntry?: boolean;\n  /** Extract (using keyPath) a key from given value (object). Null for outbound primary keys */\n  readonly extractKey: ((value: any) => any) | null;\n  /** If this index is a virtual index, lowLevelIndex represents the actual IndexedDB index behind it */\n  readonly lowLevelIndex?: DBCoreIndex;\n}\nexport interface DBCore {\n  stack: \"dbcore\";\n  // Transaction and Object Store\n  transaction(stores: string[], mode: 'readonly' | 'readwrite', options?: DbCoreTransactionOptions): DBCoreTransaction;\n\n  // Utility methods\n  readonly MIN_KEY: any;\n  readonly MAX_KEY: any;\n  readonly schema: DBCoreSchema;\n  table(name: string): DBCoreTable;\n}\n\nexport interface DBCoreTable {\n  readonly name: string;\n  readonly schema: DBCoreTableSchema;\n\n  mutate(req: DBCoreMutateRequest): Promise<DBCoreMutateResponse>;\n  get(req: DBCoreGetRequest): Promise<any>;\n  getMany(req: DBCoreGetManyRequest): Promise<any[]>;\n  query(req: DBCoreQueryRequest): Promise<DBCoreQueryResponse>;\n  openCursor(req: DBCoreOpenCursorRequest): Promise<DBCoreCursor | null>;\n  count(req: DBCoreCountRequest): Promise<number>;\n}\n\n// Type aliases for backward compatibility against v3.0.0:\nexport type Key = any;\nexport type RangeType = DBCoreRangeType;\nexport type KeyRange = DBCoreKeyRange;\nexport type MutateRequest = DBCoreMutateRequest;\nexport type AddRequest = DBCoreAddRequest;\nexport type PutRequest = DBCorePutRequest;\nexport type DeleteRequest = DBCoreDeleteRequest;\nexport type DeleteRangeRequest = DBCoreDeleteRangeRequest;\nexport type MutateResponse = DBCoreMutateResponse;\nexport type DBCoreTransactionMode = 'readonly' | 'readwrite';\n"
  },
  {
    "path": "src/public/types/dbquerycore.d.ts",
    "content": "import {\n  DBCoreCountRequest,\n  DBCoreCursor,\n  DBCoreGetManyRequest,\n  DBCoreGetRequest,\n  DBCoreMutateRequest,\n  DBCoreMutateResponse,\n  DBCoreOpenCursorRequest,\n  DBCoreQueryRequest,\n  DBCoreQueryResponse,\n  DBCoreSchema,\n  DBCoreTableSchema,\n  DBCoreTransaction,\n  DbCoreTransactionOptions,\n} from './dbcore';\n\nexport interface DBQueryCore {\n  stack: 'dbquerycore';\n  // Transaction and Object Store\n  transaction(\n    stores: string[],\n    mode: 'readonly' | 'readwrite',\n    options?: DbCoreTransactionOptions\n  ): DBCoreTransaction;\n\n  // Utility methods\n  readonly MIN_KEY: any;\n  readonly MAX_KEY: any;\n  readonly schema: DBCoreSchema;\n  table(name: string): DBQueryCoreTable;\n}\n\nexport interface DBQueryCoreTable {\n  readonly name: string;\n  readonly schema: DBCoreTableSchema;\n\n  mutate(req: DBQueryCoreMutateRequest): Promise<DBCoreMutateResponse>;\n  get(req: DBCoreGetRequest): Promise<any>;\n  getMany(req: DBCoreGetManyRequest): Promise<any[]>;\n  query(req: DBCoreQueryRequest): Promise<DBCoreQueryResponse>;\n  openCursor(req: DBCoreOpenCursorRequest): Promise<DBCoreCursor | null>;\n  count(req: DBCoreCountRequest): Promise<number>;\n}\n\nexport type DBQueryCoreMutateRequest = DBCoreMutateRequest | DBQueryCoreUpdateRequest\n\nexport interface DBQueryCoreUpdateRequest {\n  type: 'update';\n  trans: DBCoreTransaction;\n  keys: readonly any[];\n  changeSpecs: {[keyPath: string]: any}[]; // changeSpec per key.\n}\n"
  },
  {
    "path": "src/public/types/dexie-constructor.d.ts",
    "content": "import { Dexie } from \"./dexie\";\nimport { Transaction } from \"./transaction\";\nimport { ThenShortcut } from \"./then-shortcut\";\nimport { TableSchema } from \"./table-schema\";\nimport { IndexSpec } from \"./index-spec\";\nimport { DexieExceptionClasses, DexieErrors } from \"./errors\";\nimport { PromiseExtendedConstructor } from \"./promise-extended\";\nimport { DexieEventSet } from \"./dexie-event-set\";\nimport { DexieDOMDependencies } from \"./dexie-dom-dependencies\";\nimport { GlobalDexieEvents, ObservabilitySet } from \"./db-events\";\nimport { Observable } from \"./observable\";\nimport { GlobalQueryCache } from \"./cache\";\n\nexport type ChromeTransactionDurability = 'default' | 'strict' | 'relaxed'\n\nexport interface DexieOptions {\n  addons?: Array<(db: Dexie) => void>;\n  autoOpen?: boolean;\n  indexedDB?: {open: Function};\n  IDBKeyRange?: {bound: Function, lowerBound: Function, upperBound: Function};\n  allowEmptyDB?: boolean;\n  modifyChunkSize?: number | { [key: string]: number };\n  chromeTransactionDurability?: ChromeTransactionDurability;\n  cache?: 'immutable' | 'cloned' | 'disabled';\n  maxConnections?: number;\n}\n\nexport interface DexieConstructor extends DexieExceptionClasses {\n  new(databaseName: string, options?: DexieOptions) : Dexie;\n  prototype: any;\n\n  addons: Array<(db: Dexie) => void>;\n  version: number;\n  semVer: string;\n  currentTransaction: Transaction;\n  waitFor<T> (promise: PromiseLike<T> | T, timeoutMilliseconds?: number) : Promise<T>;\n\n  getDatabaseNames(): Promise<string[]>;\n  getDatabaseNames<R>(thenShortcut: ThenShortcut<string[],R>): Promise<R>;\n\n  vip<U>(scopeFunction: () => U): U;\n  ignoreTransaction<U>(fn: ()=> U) : U;\n  disableBfCache?: boolean;\n  liveQuery<T>(fn: () => T | Promise<T>): Observable<T>;\n  extendObservabilitySet (target: ObservabilitySet, newSet: ObservabilitySet): ObservabilitySet;\n  override<F> (origFunc:F, overridedFactory: (fn:any)=>any) : F; // ?\n  getByKeyPath(obj: Object, keyPath: string | string[]): any;\n  setByKeyPath(obj: Object, keyPath: string | string[], value: any): void;\n  delByKeyPath(obj: Object, keyPath: string | string[]): void;\n  shallowClone<T> (obj: T): T;\n  deepClone<T>(obj: T): T;\n  asap(fn: Function) : void; //?\n  maxKey: Array<Array<void>> | string;\n  minKey: number;\n  exists(dbName: string) : Promise<boolean>;\n  delete(dbName: string): Promise<void>;\n  dependencies: DexieDOMDependencies;\n  default: Dexie; // Work-around for different build tools handling default imports differently.\n  cache: GlobalQueryCache;\n  debug: false | true | 'dexie';\n\n  Promise: PromiseExtendedConstructor;\n  //TableSchema: {}; // Deprecate!\n  //IndexSpec: {new():IndexSpec}; //? Deprecate\n  Events: (ctx?: any)=>DexieEventSet;\n  on: GlobalDexieEvents;\n\n  errnames: DexieErrors;\n}\n"
  },
  {
    "path": "src/public/types/dexie-dom-dependencies.d.ts",
    "content": "export interface DexieDOMDependencies {\n  indexedDB: IDBFactory;\n  IDBKeyRange: typeof IDBKeyRange;\n}\n"
  },
  {
    "path": "src/public/types/dexie-event-set.d.ts",
    "content": "import { DexieEvent } from \"./dexie-event\";\n\nexport interface DexieEventSet {\n  (eventName: string): DexieEvent; // To be able to unsubscribe.\n\n  addEventType (\n      eventName: string,\n      chainFunction?: (f1:Function,f2:Function)=>Function,\n      defaultFunction?: Function): DexieEvent;\n  addEventType (\n      events: {[eventName:string]: ('asap' | [(f1:Function,f2:Function)=>Function, Function])})\n      : DexieEvent;    \n}\n"
  },
  {
    "path": "src/public/types/dexie-event.d.ts",
    "content": "export interface DexieEvent {\n  subscribers: Function[];\n  fire(...args:any[]): any;\n  subscribe(fn: (...args:any[]) => any): void;\n  unsubscribe(fn: (...args:any[]) => any): void;\n}\n"
  },
  {
    "path": "src/public/types/dexie.d.ts",
    "content": "import { Table } from './table';\nimport { ExtendableVersion, Version } from './version';\nimport { DbEvents, DbEventFns } from './db-events';\nimport { TransactionMode } from './transaction-mode';\nimport { Transaction } from './transaction';\nimport { WhereClause } from './where-clause';\nimport { Collection } from './collection';\nimport { DbSchema } from './db-schema';\nimport { DexieOptions } from './dexie-constructor';\nimport { PromiseExtended } from './promise-extended';\nimport { IndexableType } from './indexable-type';\nimport { DBCore } from './dbcore';\nimport { Middleware, DexieStacks } from './middleware';\n\nexport type TableProp<DX> = {\n  [K in keyof DX]: DX[K] extends {schema: any, get: any, put: any, add: any, where: any} ? K : never;\n}[keyof DX] & string;\n\ntype TXWithTables<DX extends Dexie> = Dexie extends DX\n? Transaction // If not subclassed, just expect a Transaction without table props\n: Transaction & { [P in TableProp<DX>]: DX[P] };\n\n\nexport interface Dexie {\n  readonly name: string;\n  readonly tables: Table[];\n  readonly verno: number;\n  readonly vip: Dexie;\n\n  readonly _allTables: { [name: string]: Table<any, IndexableType> };\n  readonly _options: DexieOptions;\n\n  readonly core: DBCore;\n\n  _createTransaction: (\n    this: Dexie,\n    mode: IDBTransactionMode,\n    storeNames: ArrayLike<string>,\n    dbschema: DbSchema,\n    parentTransaction?: Transaction | null\n  ) => Transaction;\n\n  readonly _novip: Dexie;\n\n  _dbSchema: DbSchema;\n\n  version(versionNumber: number): Version;\n\n  on: DbEvents;\n\n  once: DbEventFns;\n\n  open(): PromiseExtended<Dexie>;\n\n  table<T = any, TKey = IndexableType, TInsertType=T>(tableName: string): Table<T, TKey, TInsertType>;\n\n  transaction<U>(\n    mode: TransactionMode,\n    tables: readonly (string | Table)[],\n    scope: (\n      trans: TXWithTables<this>\n    ) => PromiseLike<U> | U\n  ): PromiseExtended<U>;\n\n  transaction<U>(\n    mode: TransactionMode,\n    table: string | Table,\n    scope: (trans: TXWithTables<this>) => PromiseLike<U> | U\n  ): PromiseExtended<U>;\n  transaction<U>(\n    mode: TransactionMode,\n    table: string | Table,\n    table2: string | Table,\n    scope: (trans: TXWithTables<this>) => PromiseLike<U> | U\n  ): PromiseExtended<U>;\n  transaction<U>(\n    mode: TransactionMode,\n    table: string | Table,\n    table2: string | Table,\n    table3: string | Table,\n    scope: (trans: TXWithTables<this>) => PromiseLike<U> | U\n  ): PromiseExtended<U>;\n  transaction<U>(\n    mode: TransactionMode,\n    table: string | Table,\n    table2: string | Table,\n    table3: string | Table,\n    table4: string | Table,\n    scope: (trans: TXWithTables<this>) => PromiseLike<U> | U\n  ): PromiseExtended<U>;\n  transaction<U>(\n    mode: TransactionMode,\n    table: string | Table,\n    table2: string | Table,\n    table3: string | Table,\n    table5: string | Table,\n    scope: (trans: TXWithTables<this>) => PromiseLike<U> | U\n  ): PromiseExtended<U>;\n  \n  close(closeOptions?: {disableAutoOpen: boolean}): void;\n\n  delete(closeOptions?: {disableAutoOpen: boolean}): PromiseExtended<void>;\n\n  isOpen(): boolean;\n\n  hasBeenClosed(): boolean;\n\n  hasFailed(): boolean;\n\n  dynamicallyOpened(): boolean;\n\n  backendDB(): IDBDatabase;\n\n  use(middleware: Middleware<DBCore>): this;\n  // Add more supported stacks here... : use(middleware: Middleware<HookStack>): this;\n  unuse({ stack, create }: Middleware<{ stack: keyof DexieStacks }>): this;\n  unuse({ stack, name }: { stack: keyof DexieStacks; name: string }): this;\n\n  // Make it possible to touch physical class constructors where they reside - as properties on db instance.\n  // For example, checking if (x instanceof db.Table). Can't do (x instanceof Dexie.Table because it's just a virtual interface)\n  Table: { prototype: Table };\n  WhereClause: { prototype: WhereClause };\n  Version: Function & { prototype: ExtendableVersion };\n  Transaction: { prototype: Transaction };\n  Collection: { prototype: Collection };\n}\n"
  },
  {
    "path": "src/public/types/entity-table.d.ts",
    "content": "import { IndexableType } from \"..\";\nimport { InsertType } from \"./insert-type\";\nimport { IsStrictlyAny } from \"./is-strictly-any\";\nimport { Table } from \"./table\";\n\n/** IDType extract the actual type of the primary key:\n *  * If TKey is a literal type that names a property of T, extract the type using T[TKey]\n *  * Else, use TKey as is.\n */\nexport type IDType<T, TKeyPropNameOrKeyType> = IsStrictlyAny<T> extends true\n  ? TKeyPropNameOrKeyType\n  : TKeyPropNameOrKeyType extends string\n  ? TKeyPropNameOrKeyType extends keyof T\n    ? T[TKeyPropNameOrKeyType]\n    : TKeyPropNameOrKeyType\n  : TKeyPropNameOrKeyType;\n\nexport type EntityTable<T, TKeyPropName extends keyof T = never, TInsertType = InsertType<T, TKeyPropName>> = Table<T, IDType<T, TKeyPropName>, TInsertType>;\n\n"
  },
  {
    "path": "src/public/types/entity.d.ts",
    "content": "import { Dexie, TableProp } from './dexie';\n\nexport class Entity<TDexieSubClass extends Dexie=Dexie> {\n  protected constructor();\n  protected readonly db: TDexieSubClass;\n  table(): TableProp<TDexieSubClass>;\n}\n"
  },
  {
    "path": "src/public/types/errors.d.ts",
    "content": "import { IndexableTypeArrayReadonly } from \"./indexable-type\";\n\n/** DexieError\n * \n * Common base class for all errors originating from Dexie.js except TypeError,\n * SyntaxError and RangeError.\n * \n * https://dexie.org/docs/DexieErrors/DexieError\n * \n */\nexport interface DexieError extends Error {\n  name: string;\n  message: string;\n  stack: string;\n  inner: any;\n  toString(): string;\n}\n\n/**\n * List of the names of auto-generated error classes that extends DexieError\n * and shares the interface of DexieError.\n * \n * Each error should be documented at https://dexie.org/docs/DexieErrors/Dexie.<errname>\n * \n * The generic type DexieExceptionClasses is a map of full error name to\n * error constructor. The DexieExceptionClasses is mixed in into Dexie,\n * so that it is always possible to throw or catch certain errors via\n * Dexie.ErrorName. Example:\n * \n * try {\n *   throw new Dexie.InvalidTableError(\"Invalid table foo\", innerError?);\n * } catch (err) {\n *   if (err instanceof Dexie.InvalidTableError) {\n *     // Could also have check for err.name === \"InvalidTableError\", or\n *     // err.name === Dexie.errnames.InvalidTableError.\n *     console.log(\"Seems to be an invalid table here...\");\n *   } else {\n *     throw err;\n *   }\n * }\n */\nexport type DexieErrors = {\n  // https://dexie.org/docs/DexieErrors/Dexie.OpenFailedError\n  OpenFailed: 'OpenFailedError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.VersionChangeError\n  VersionChange: 'VersionChangeError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.SchemaError\n  Schema: 'SchemaError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.UpgradeError\n  Upgrade: 'UpgradeError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.InvalidTableError\n  InvalidTable: 'InvalidTableError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.MissingAPIError\n  MissingAPI: 'MissingAPIError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.NoSuchDatabaseError\n  NoSuchDatabase: 'NoSuchDatabaseError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.InvalidArgumentError\n  InvalidArgument: 'InvalidArgumentError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.SubTransactionError\n  SubTransaction: 'SubTransactionError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.UnsupportedError\n  Unsupported: 'UnsupportedError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.InternalError\n  Internal: 'InternalError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.DatabaseClosedError\n  DatabaseClosed: 'DatabaseClosedError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.PrematureCommitError\n  PrematureCommit: 'PrematureCommitError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.ForeignAwaitError\n  ForeignAwait: 'ForeignAwaitError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.UnknownError\n  Unknown: 'UnknownError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.ConstraintError\n  Constraint: 'ConstraintError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.DataError\n  Data: 'DataError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.TransactionInactiveError\n  TransactionInactive: 'TransactionInactiveError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.ReadOnlyError\n  ReadOnly: 'ReadOnlyError',\n  \n  // https://dexie.org/docs/DexieErrors/Dexie.VersionError\n  Version: 'VersionError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.NotFoundError\n  NotFound: 'NotFoundError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.InvalidStateError\n  InvalidState: 'InvalidStateError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.InvalidAccessError\n  InvalidAccess: 'InvalidAccessError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.AbortError\n  Abort: 'AbortError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.TimeoutError\n  Timeout: 'TimeoutError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.QuotaExceededError\n  QuotaExceeded: 'QuotaExceededError',\n\n  // https://dexie.org/docs/DexieErrors/Dexie.DataCloneError\n  DataClone: 'DataCloneError'\n}\n\n/** ModifyError\n * \n * https://dexie.org/docs/DexieErrors/Dexie.ModifyError\n */\nexport interface ModifyError extends DexieError {\n  failures: Array<any>;\n  failedKeys: IndexableTypeArrayReadonly;\n  successCount: number;\n}\n\n/** BulkError\n * \n * https://dexie.org/docs/DexieErrors/Dexie.BulkError\n */\nexport interface BulkError extends DexieError {\n  failures: Error[];\n  failuresByPos: {[operationNumber: number]: Error};\n}\n\nexport interface DexieErrorConstructor {\n  new(msg?: string, inner?: Object) : DexieError;\n  new(inner: Object): DexieError;\n  prototype: DexieError;\n}\n\nexport interface ModifyErrorConstructor {\n  new (\n    msg?:string,\n    failures?: any[],\n    successCount?: number,\n    failedKeys?: IndexableTypeArrayReadonly) : ModifyError;\n  prototype: ModifyError;\n}\n\nexport interface BulkErrorConstructor {\n  new (msg?:string, failures?: {[operationNumber: number]: Error}) : BulkError;\n  prototype: BulkError;\n}\n\nexport type ExceptionAliasSet = {[ShortName in keyof DexieErrors]: DexieErrorConstructor} & {\n  Dexie: DexieErrorConstructor,\n  Modify: ModifyErrorConstructor;\n  Bulk: BulkErrorConstructor;\n}\n\nexport type ExceptionSet = {[P in DexieErrors[keyof DexieErrors]]: DexieErrorConstructor};\n\nexport type DexieExceptionClasses = ExceptionSet & {\n  DexieError: DexieErrorConstructor,\n  ModifyError: ModifyErrorConstructor;\n  BulkError: BulkErrorConstructor;\n}\n"
  },
  {
    "path": "src/public/types/global.d.ts",
    "content": "/**\n * Patching in types for Chrome's extended transaction API\n * Corresponds to this change: https://chromium.googlesource.com/chromium/src/+/d762124e2b4090a7985cddc5438678de7900fcc4/third_party/blink/renderer/modules/indexeddb/idb_transaction_options.idl\n */\ntype ChromeTransactionDurability = 'default' | 'strict' | 'relaxed'\n\ninterface IDBTransactionOptions {\n    durability?: ChromeTransactionDurability\n}\n\ninterface IDBDatabase {\n    transaction(storeNames: string | string[], mode?: IDBTransactionMode, options?: IDBTransactionOptions): IDBTransaction\n}\n\n/**\n * Type definitions for IndexedDB 3.0 features (Interop 2026)\n * - getAll(options) and getAllKeys(options) with direction parameter\n * - getAllRecords() for retrieving records with keys\n * @see https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex/getAll\n * @see https://w3c.github.io/IndexedDB/\n */\ninterface IDBGetAllOptions {\n    query?: IDBKeyRange | IDBValidKey | null;\n    count?: number;\n    direction?: IDBCursorDirection;\n}\n\ninterface IDBRecord<T = any> {\n    key: IDBValidKey;\n    primaryKey: IDBValidKey;\n    value: T;\n}\n\ninterface IDBObjectStore {\n    // IDB 3.0: getAll/getAllKeys with options object including direction\n    getAll(options: IDBGetAllOptions): IDBRequest<any[]>;\n    getAllKeys(options: IDBGetAllOptions): IDBRequest<IDBValidKey[]>;\n    // getAllRecords is used for feature detection\n    getAllRecords?(options?: IDBGetAllOptions): IDBRequest<IDBRecord[]>;\n}\n\ninterface IDBIndex {\n    // IDB 3.0: getAll/getAllKeys with options object including direction\n    getAll(options: IDBGetAllOptions): IDBRequest<any[]>;\n    getAllKeys(options: IDBGetAllOptions): IDBRequest<IDBValidKey[]>;\n    // getAllRecords is used for feature detection  \n    getAllRecords?(options?: IDBGetAllOptions): IDBRequest<IDBRecord[]>;\n}"
  },
  {
    "path": "src/public/types/index-spec.d.ts",
    "content": "export interface IndexSpec {\n  name: string;\n  keyPath: string | Array<string> | undefined;\n  unique: boolean | undefined;\n  multi: boolean | undefined;\n  auto: boolean | undefined;\n  compound: boolean | undefined;\n  src: string;\n  type?: string | undefined;\n}\n"
  },
  {
    "path": "src/public/types/indexable-type.d.ts",
    "content": "export type IndexableTypePart =\nstring | number | Date | ArrayBuffer | ArrayBufferView | DataView | Array<Array<void>>;\n\nexport type IndexableTypeArray = Array<IndexableTypePart>;\nexport type IndexableTypeArrayReadonly = ReadonlyArray<IndexableTypePart>;\nexport type IndexableType = IndexableTypePart | IndexableTypeArrayReadonly;\n\n"
  },
  {
    "path": "src/public/types/insert-type.d.ts",
    "content": "import { IsStrictlyAny } from \"./is-strictly-any\";\n\n/** Extract the union of literal method names in T\n */\n type NonInsertProps<T> = {\n  [P in keyof T]: IsStrictlyAny<T[P]> extends true\n    ? never // Plain property of type any (not method)\n    : T[P] extends (...args: any[]) => any\n    ? P // a function (method)\n    : T[P] extends {on:any,subdocs:any,gc:any,isSynced:any,isLoaded:any,shouldLoad:any}\n    ? P // an YDoc property - should be generated by dexie and not by user\n    : never; // Not a non-insert property (don't omit it from InsertType)\n}[keyof T];\n\n/** Default insert type of T is a subset of T where:\n *    * given optional props (such as an auto-generated primary key) are made optional\n *    * methods are omitted\n */\n export type InsertType<T, OptionalProps extends keyof T> = Omit<T, OptionalProps | NonInsertProps<T>> & {[P in OptionalProps]?: T[P]};\n"
  },
  {
    "path": "src/public/types/is-strictly-any.d.ts",
    "content": "export type IsStrictlyAny<T> = (T extends never ? true : false) extends false ? false : true;\n"
  },
  {
    "path": "src/public/types/keypaths.d.ts",
    "content": "type KeyPathIgnoreObject =\n  | ArrayBuffer\n  | ArrayBufferView\n  | RegExp\n  | Blob\n  | FileList\n  | FileSystemFileHandle\n  | FileSystemDirectoryHandle\n  | DataView\n  | ImageBitmap\n  | ImageData\n  | Map<any, any>\n  | Set<any>\n  | CryptoKey\n  | Promise<any>\n  | ReadableStream<any>\n  | ReadableStreamDefaultReader<any>\n  | ReadableStreamDefaultController<any>\n  | { whenLoaded: Promise<any> }; // Y.Doc\n\nexport type KeyPaths<T, MAXDEPTH = 'II', CURRDEPTH extends string = ''> = {\n  [P in keyof T]: P extends string\n    ? CURRDEPTH extends MAXDEPTH\n      ? P\n      : T[P] extends Array<infer K>\n      ? K extends any[] // Array of arrays (issue #2026)\n        ? P | `${P}.${number}` | `${P}.${number}.${number}`\n        : K extends object // only drill into the array element if it's an object\n        ? P | `${P}.${number}` | `${P}.${number}.${KeyPaths<Required<K>>}`\n        : P | `${P}.${number}`\n      : T[P] extends (...args: any[]) => any // Method\n      ? never\n      : T[P] extends KeyPathIgnoreObject // Not valid in update spec or where clause (+ avoid circular reference)\n      ? P\n      : T[P] extends object\n      ? P | `${P}.${KeyPaths<Required<T[P]>, MAXDEPTH, `${CURRDEPTH}I`>}`\n      : P\n    : never;\n}[keyof T];\n\nexport type KeyPathValue<T, PATH> = PATH extends `${infer R}.${infer S}`\n  ? R extends keyof T\n    ? KeyPathValue<Required<T[R]>, S>\n    : T extends any[]\n    ? PATH extends `${number}.${infer S}`\n      ? KeyPathValue<Required<T[number]>, S>\n      : void\n    : void\n  : PATH extends `${number}`\n  ? T extends any[]\n    ? T[number]\n    : void\n  : PATH extends keyof T\n  ? T[PATH]\n  : any;\n"
  },
  {
    "path": "src/public/types/middleware.d.ts",
    "content": "import { DBCore } from \"./dbcore\";\n\nexport interface Middleware<TStack extends {stack: string}> {\n  stack: TStack[\"stack\"],\n  create: (down: TStack) => Partial<TStack>;\n  level?: number;\n  name?: string;\n}\n\nexport interface DexieStacks {\n  dbcore: DBCore;\n}\n"
  },
  {
    "path": "src/public/types/observable.d.ts",
    "content": "// There typings are extracted from https://github.com/tc39/proposal-observable\n\ndeclare global {\n  interface SymbolConstructor {\n    readonly observable: symbol;\n  }\n}\n\ninterface Subscribable<T> {\n  subscribe(observer: Partial<Observer<T>>): Unsubscribable;\n}\ninterface Unsubscribable {\n  unsubscribe(): void;\n}\nexport interface Observable<T = any> {\n  subscribe(observerOrNext?: Observer<T> | ((value: T) => void)): Subscription;\n  subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n  getValue?(): T;\n  hasValue?(): boolean;\n\n\t[Symbol.observable]: () => Subscribable<T>;\n}\n\nexport interface Subscription {\n  unsubscribe(): void;\n  readonly closed: boolean;\n}\n\nexport interface Observer<T = any> {\n  start?: (subscription: Subscription) => void;\n  next?: (value: T) => void;\n  error?: (error: any) => void;\n  complete?: () => void;\n}\n"
  },
  {
    "path": "src/public/types/promise-extended.d.ts",
    "content": "export interface PromiseExtendedConstructor extends PromiseConstructor {\n  readonly prototype: PromiseExtended;\n  new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): PromiseExtended<T>;\n  all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): PromiseExtended<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;\n  all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): PromiseExtended<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;\n  all<T1, T2, T3, T4, T5, T6, T7, T8>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): PromiseExtended<[T1, T2, T3, T4, T5, T6, T7, T8]>;\n  all<T1, T2, T3, T4, T5, T6, T7>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): PromiseExtended<[T1, T2, T3, T4, T5, T6, T7]>;\n  all<T1, T2, T3, T4, T5, T6>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): PromiseExtended<[T1, T2, T3, T4, T5, T6]>;\n  all<T1, T2, T3, T4, T5>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>]): PromiseExtended<[T1, T2, T3, T4, T5]>;\n  all<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>]): PromiseExtended<[T1, T2, T3, T4]>;\n  all<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): PromiseExtended<[T1, T2, T3]>;\n  all<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): PromiseExtended<[T1, T2]>;\n  all<T>(values: (T | PromiseLike<T>)[]): PromiseExtended<T[]>;\n  race<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): PromiseExtended<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10>;\n  race<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): PromiseExtended<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9>;\n  race<T1, T2, T3, T4, T5, T6, T7, T8>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): PromiseExtended<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8>;\n  race<T1, T2, T3, T4, T5, T6, T7>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): PromiseExtended<T1 | T2 | T3 | T4 | T5 | T6 | T7>;\n  race<T1, T2, T3, T4, T5, T6>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): PromiseExtended<T1 | T2 | T3 | T4 | T5 | T6>;\n  race<T1, T2, T3, T4, T5>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>]): PromiseExtended<T1 | T2 | T3 | T4 | T5>;\n  race<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>]): PromiseExtended<T1 | T2 | T3 | T4>;\n  race<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): PromiseExtended<T1 | T2 | T3>;\n  race<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): PromiseExtended<T1 | T2>;\n  race<T>(values: (T | PromiseLike<T>)[]): PromiseExtended<T>;\n  reject(reason: any): PromiseExtended<never>;\n  reject<T>(reason: any): PromiseExtended<T>;\n  resolve<T>(value: T | PromiseLike<T>): PromiseExtended<T>;\n  resolve(): PromiseExtended<void>;\n}\n\n/** The interface of Dexie.Promise, which basically extends standard Promise with methods:\n *  \n *  finally() - also subject for standardization\n *  timeout() - set a completion timeout\n *  catch(ErrorClass, handler) - java style error catching\n *  catch(errorName, handler) - cross-domain safe type error catching (checking error.name instead of instanceof)\n * \n */\nexport interface PromiseExtended<T=any> extends Promise<T> {\n  then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseExtended<TResult1 | TResult2>;\n  catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): PromiseExtended<T | TResult>;\n  catch<TResult = never>(ErrorConstructor: Function, onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): PromiseExtended<T | TResult>;\n  catch<TResult = never>(errorName: string, onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): PromiseExtended<T | TResult>;\n  finally<U>(onFinally?: () => U | PromiseLike<U>): PromiseExtended<T>;\n  timeout (ms: number, msg?: string): PromiseExtended<T>;\n}\n\n"
  },
  {
    "path": "src/public/types/prop-modification.d.ts",
    "content": "export type PropModSpec = {\n  replacePrefix?: [string, string];\n  add?: number | bigint | Array<string | number>;\n  remove?: number | bigint | Array<string | number>;\n}\n\nexport class PropModification {\n  [\"@@propmod\"]: PropModSpec;\n  constructor(spec: PropModSpec);\n  execute<T>(value: T): T;\n}\n"
  },
  {
    "path": "src/public/types/rangeset.d.ts",
    "content": "import { IndexableType } from \"./indexable-type\";\n\nexport type IntervalTree = IntervalTreeNode | EmptyRange;\nexport interface IntervalTreeNode {\n  from: IndexableType; // lower bound\n  to: IndexableType; // upper bound\n  l?: IntervalTreeNode | null; // left\n  r?: IntervalTreeNode | null; // right\n  d: number; // depth\n}\nexport interface EmptyRange {\n  d: 0\n}\n\nexport interface RangeSetPrototype {\n  add(rangeSet: IntervalTree | {from: IndexableType, to: IndexableType}): RangeSet;\n  addKey(key: IndexableType): RangeSet;\n  addKeys(keys: IndexableType[]): RangeSet;\n  hasKey(key: IndexableType): boolean;\n  [Symbol.iterator](): Iterator<IntervalTreeNode, undefined, IndexableType | undefined>;\n}\n\nexport type RangeSet = RangeSetPrototype & IntervalTree;\n\nexport interface RangeSetConstructor {\n  (tree: IntervalTree): RangeSet;\n  new (): RangeSet;\n  new (from: IndexableType, to?: IndexableType): RangeSet;\n}\n"
  },
  {
    "path": "src/public/types/table-hooks.d.ts",
    "content": "import { DexieEventSet } from \"./dexie-event-set\";\nimport { DexieEvent } from \"./dexie-event\";\nimport { Transaction } from \"./transaction\";\nimport { IndexableType } from \"./indexable-type\";\n\ninterface CreatingHookContext<T,Key> {\n  onsuccess?: (primKey: Key) => void;\n  onerror?: (err: any) => void;\n}\n\ninterface UpdatingHookContext<T,Key> {\n  onsuccess?: (updatedObj: T) => void;\n  onerror?: (err: any) => void;\n}\n\ninterface DeletingHookContext<T,Key> {\n  onsuccess?: () => void;\n  onerror?: (err: any) => void;\n}\n\ninterface TableHooks<T=any,TKey=IndexableType,TInsertType=T> extends DexieEventSet {\n  (eventName: 'creating', subscriber: (this: CreatingHookContext<T,TKey>, primKey:TKey, obj:T, transaction:Transaction) => void | undefined | TKey): void;\n  (eventName: 'reading', subscriber: (obj:T) => T | any): void;\n  (eventName: 'updating', subscriber: (this: UpdatingHookContext<T,TKey>, modifications:Object, primKey:TKey, obj:T, transaction:Transaction) => any): void;\n  (eventName: 'deleting', subscriber: (this: DeletingHookContext<T,TKey>, primKey:TKey, obj:T, transaction:Transaction) => any): void;\n  creating: DexieEvent;\n  reading: DexieEvent;\n  updating: DexieEvent;\n  deleting: DexieEvent;\n}\n"
  },
  {
    "path": "src/public/types/table-schema.d.ts",
    "content": "import { IndexSpec } from \"./index-spec\";\n\nexport interface TableSchema {\n  name: string;\n  primKey: IndexSpec;\n  indexes: IndexSpec[];\n  yProps?: {prop: string, updatesTable: string}[]; // Available if y-dexie addon is used and schema defines Y.Doc properties.\n  mappedClass: Function;\n  idxByName: {[name: string]: IndexSpec};\n  readHook?: (x:any) => any\n}\n"
  },
  {
    "path": "src/public/types/table.d.ts",
    "content": "import { TableSchema } from \"./table-schema\";\nimport { IndexableTypeArrayReadonly } from \"./indexable-type\";\nimport { TableHooks } from \"./table-hooks\";\nimport { Collection } from \"./collection\";\nimport { ThenShortcut } from \"./then-shortcut\";\nimport { WhereClause } from \"./where-clause\";\nimport { PromiseExtended } from \"./promise-extended\";\nimport { IndexableType } from \"./indexable-type\";\nimport { DBCoreTable } from \"./dbcore\";\nimport { Dexie } from \"./dexie\";\nimport { UpdateSpec } from \"./update-spec\";\n\nexport interface Table<T=any, TKey=any, TInsertType=T> {\n  db: Dexie;\n  name: string;\n  schema: TableSchema;\n  hook: TableHooks<T, TKey, TInsertType>;\n  core: DBCoreTable;\n\n  get(key: TKey): PromiseExtended<T | undefined>;\n  get<R>(key: TKey, thenShortcut: ThenShortcut<T | undefined,R>): PromiseExtended<R>;\n  get(equalityCriterias: {[key:string]:any}): PromiseExtended<T | undefined>;\n  get<R>(equalityCriterias: {[key:string]:any}, thenShortcut: ThenShortcut<T | undefined, R>): PromiseExtended<R>;\n  where(index: string | string[]): WhereClause<T, TKey, TInsertType>;\n  where(equalityCriterias: {[key:string]:any}): Collection<T, TKey, TInsertType>;\n\n  filter(fn: (obj: T) => boolean): Collection<T, TKey, TInsertType>;\n\n  count(): PromiseExtended<number>;\n  count<R>(thenShortcut: ThenShortcut<number, R>): PromiseExtended<R>;\n\n  offset(n: number): Collection<T, TKey, TInsertType>;\n\n  limit(n: number): Collection<T, TKey, TInsertType>;\n\n  each(callback: (obj: T, cursor: {key: any, primaryKey: TKey}) => any): PromiseExtended<void>;\n\n  toArray(): PromiseExtended<Array<T>>;\n  toArray<R>(thenShortcut: ThenShortcut<T[], R>): PromiseExtended<R>;\n\n  toCollection(): Collection<T, TKey, TInsertType>;\n  orderBy(index: string | string[]): Collection<T, TKey, TInsertType>;\n  reverse(): Collection<T, TKey, TInsertType>;\n  mapToClass(constructor: Function): Function;\n  add(item: TInsertType, key?: TKey): PromiseExtended<TKey>;\n  update(\n    key: TKey | T,\n    changes: UpdateSpec<TInsertType> | ((obj: T, ctx:{value: any, primKey: IndexableType}) => void | boolean)): PromiseExtended<number>;\n  upsert(\n    key: TKey | T,\n    changes: UpdateSpec<TInsertType>): PromiseExtended<boolean>;\n  put(item: TInsertType, key?: TKey): PromiseExtended<TKey>;\n  delete(key: TKey): PromiseExtended<void>;\n  clear(): PromiseExtended<void>;\n  bulkGet(keys: TKey[]): PromiseExtended<(T | undefined)[]>;\n\n  bulkAdd<B extends boolean>(items: readonly TInsertType[], keys: IndexableTypeArrayReadonly, options: { allKeys: B }): PromiseExtended<B extends true ? TKey[] : TKey>;\n  bulkAdd<B extends boolean>(items: readonly TInsertType[], options: { allKeys: B }): PromiseExtended<B extends true ? TKey[] : TKey>;\n  bulkAdd(items: readonly TInsertType[], keys?: IndexableTypeArrayReadonly, options?: { allKeys: boolean }): PromiseExtended<TKey>;\n\n  bulkPut<B extends boolean>(items: readonly TInsertType[], keys: IndexableTypeArrayReadonly, options: { allKeys: B }): PromiseExtended<B extends true ? TKey[] : TKey>;\n  bulkPut<B extends boolean>(items: readonly TInsertType[], options: { allKeys: B }): PromiseExtended<B extends true ? TKey[] : TKey>;\n  bulkPut(items: readonly TInsertType[], keys?: IndexableTypeArrayReadonly, options?: { allKeys: boolean }): PromiseExtended<TKey>;\n\n  bulkUpdate(keysAndChanges: ReadonlyArray<{key: TKey, changes: UpdateSpec<T>}>): PromiseExtended<number>;\n\n  bulkDelete(keys: TKey[]): PromiseExtended<void>;\n}\n"
  },
  {
    "path": "src/public/types/then-shortcut.d.ts",
    "content": "export type ThenShortcut<T,TResult> =  (value: T) => TResult | PromiseLike<TResult>;\n"
  },
  {
    "path": "src/public/types/transaction-events.d.ts",
    "content": "import { DexieEvent } from \"./dexie-event\";\nimport { DexieEventSet } from \"./dexie-event-set\";\n\nexport interface TransactionEvents extends DexieEventSet {\n  (eventName: 'complete', subscriber: () => any): void;\n  (eventName: 'abort', subscriber: () => any): void;\n  (eventName: 'error', subscriber: (error:any) => any): void;\n  complete: DexieEvent;\n  abort: DexieEvent;\n  error: DexieEvent;\n}    \n"
  },
  {
    "path": "src/public/types/transaction-mode.d.ts",
    "content": "export type TransactionMode = 'readonly' | 'readwrite' | 'r' | 'r!' | 'r?' | 'rw' | 'rw!' | 'rw?';\n"
  },
  {
    "path": "src/public/types/transaction.d.ts",
    "content": "import { Table } from \"./table\";\nimport { Dexie } from \"./dexie\";\nimport { TransactionEvents } from \"./transaction-events\";\n\nexport interface Transaction {\n  db: Dexie;\n  active: boolean;\n  mode: IDBTransactionMode;\n  idbtrans: IDBTransaction;\n  //tables: { [type: string]: Table<any, any> }; Deprecated since 2.0. Obsolete from v3.0.\n  storeNames: Array<string>;\n  explicit?: boolean;\n  parent?: Transaction;\n  on: TransactionEvents;\n  abort(): void;\n  table(tableName: string): Table<any, any>;\n  table<T>(tableName: string): Table<T, any>;\n  table<T, Key>(tableName: string): Table<T, Key>;\n  table<T, Key, TInsertType>(tableName: string): Table<T, Key, TInsertType>;\n}\n"
  },
  {
    "path": "src/public/types/update-spec.d.ts",
    "content": "import { KeyPaths, KeyPathValue } from \"./keypaths\";\nimport { PropModification } from \"./prop-modification\";\n\nexport type UpdateSpec<T> = { [KP in KeyPaths<Required<T>>]?: KeyPathValue<Required<T>, KP> | PropModification };\n"
  },
  {
    "path": "src/public/types/version.d.ts",
    "content": "import { DbSchema } from \"./db-schema\";\nimport { Dexie } from \"./dexie\";\nimport { IndexSpec } from \"./index-spec\";\nimport { TableSchema } from \"./table-schema\";\nimport { Transaction } from \"./transaction\";\n\nexport interface Version {\n  stores(schema: { [tableName: string]: string | null }): Version;\n  upgrade(fn: (trans: Transaction) => PromiseLike<any> | void): Version;\n}\n\nexport interface ExtendableVersion extends Version {\n  db: Dexie;\n  _parseStoresSpec(\n    stores: { [tableName: string]: string | null },\n    outSchema: DbSchema\n  ): void;\n  _createTableSchema(\n    tableName: string,\n    primKey: IndexSpec,\n    indexes: IndexSpec[],\n  ): TableSchema;\n  _parseIndexSyntax(primKeyAndIndexes: string): IndexSpec[];\n}\n"
  },
  {
    "path": "src/public/types/where-clause.d.ts",
    "content": "import { IndexableTypeArray, IndexableTypeArrayReadonly } from \"./indexable-type\";\nimport { Collection } from \"./collection\";\nimport { IndexableType } from \"./indexable-type\";\n\nexport interface WhereClause<T=any, TKey=IndexableType, TInsertType=T> {\n  above(key: any): Collection<T, TKey, TInsertType>;\n  aboveOrEqual(key: any): Collection<T, TKey, TInsertType>;\n  anyOf(keys: ReadonlyArray<IndexableType>): Collection<T, TKey, TInsertType>;\n  anyOf(...keys: Array<IndexableType>): Collection<T, TKey, TInsertType>;\n  anyOfIgnoreCase(keys: string[]): Collection<T, TKey, TInsertType>;\n  anyOfIgnoreCase(...keys: string[]): Collection<T, TKey, TInsertType>;\n  below(key: any): Collection<T, TKey, TInsertType>;\n  belowOrEqual(key: any): Collection<T, TKey, TInsertType>;\n  between(lower: any, upper: any, includeLower?: boolean, includeUpper?: boolean): Collection<T, TKey, TInsertType>;\n  equals(key: IndexableType): Collection<T, TKey, TInsertType>;\n  equalsIgnoreCase(key: string): Collection<T, TKey, TInsertType>;\n  inAnyRange(ranges: ReadonlyArray<{0: any, 1: any}>, options?: { includeLowers?: boolean, includeUppers?: boolean }): Collection<T, TKey, TInsertType>;\n  startsWith(key: string): Collection<T, TKey, TInsertType>;\n  startsWithAnyOf(prefixes: string[]): Collection<T, TKey, TInsertType>;\n  startsWithAnyOf(...prefixes: string[]): Collection<T, TKey, TInsertType>;\n  startsWithIgnoreCase(key: string): Collection<T, TKey, TInsertType>;\n  startsWithAnyOfIgnoreCase(prefixes: string[]): Collection<T, TKey, TInsertType>;\n  startsWithAnyOfIgnoreCase(...prefixes: string[]): Collection<T, TKey, TInsertType>;\n  noneOf(keys: ReadonlyArray<IndexableType>): Collection<T, TKey, TInsertType>;\n  notEqual(key: IndexableType): Collection<T, TKey, TInsertType>;\n}\n"
  },
  {
    "path": "src/support-bfcache.ts",
    "content": "import { Dexie } from \"./classes/dexie\";\nimport { connections } from \"./globals/connections\";\nimport { debug } from \"./helpers/debug\";\nimport { RangeSet } from \"./helpers/rangeset\";\nimport { bc, createBC } from \"./live-query/enable-broadcast\";\nimport { propagateLocally } from \"./live-query/propagate-locally\";\n\n\nif (typeof addEventListener !== 'undefined') {\n  addEventListener('pagehide', (event) => {\n    if (!Dexie.disableBfCache && event.persisted) {\n      if (debug) console.debug('Dexie: handling persisted pagehide');\n      bc?.close();\n      for (const db of connections.toArray()) {\n        db.close({disableAutoOpen: false});\n      }\n    }\n  });\n  addEventListener('pageshow', (event) => {\n    if (!Dexie.disableBfCache && event.persisted) {\n      if (debug) console.debug('Dexie: handling persisted pageshow');\n      createBC();\n      propagateLocally({all: new RangeSet(-Infinity, [[]])}); // Trigger all queries to requery\n    }\n  });\n}\n"
  },
  {
    "path": "src/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"target\": \"es5\",\n    \"module\": \"es2015\",\n    \"importHelpers\": true,\n    \"strictNullChecks\": false,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": false,\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"es2021\", \"dom\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"outDir\": \"../tools/tmp/src/\",\n    \"sourceMap\": true\n  },\n  \"dirs\": [\n    \".\"\n  ]\n}\n"
  },
  {
    "path": "test/.eslintrc.json",
    "content": "{\n  \"parserOptions\": {\n    \"ecmaVersion\": 2020,\n    \"sourceType\": \"module\",\n    \"ecmaFeatures\": {\n    }\n  },\n  \"env\": {\n    \"browser\": true,\n    \"node\": true,\n    \"es6\": true\n  },\n  \"rules\": {\n    \"no-undef\": [\"error\"]\n  },\n  \"globals\": {\n    \"Promise\": true\n  }\n}\n"
  },
  {
    "path": "test/.gitignore",
    "content": "/bundle.js\n/bundle.js.map\nintegrations/**/dist\n"
  },
  {
    "path": "test/data.json",
    "content": "{\n  \"formatName\": \"dexie\",\n  \"formatVersion\": 1,\n  \"data\": {\n    \"databaseName\": \"dexie-export-import-basic-tests\",\n    \"databaseVersion\": 1,\n    \"tables\": [\n      {\n        \"name\": \"outbound\",\n        \"schema\": \"null\",\n        \"rowCount\": 3\n      },\n      {\n        \"name\": \"inbound\",\n        \"schema\": \"id\",\n        \"rowCount\": 3\n      }\n    ],\n    \"data\": [\n      {\n        \"tableName\": \"outbound\",\n        \"inbound\": false,\n        \"rows\": [\n          [\n            2,\n            {\n              \"foo\": \"bar\"\n            }\n          ],\n          {\n            \"$\": [\n              1,\n              {\n                \"date\": 1,\n                \"blob\": {\n                  \"type\": \"\",\n                  \"stringContents\": \"潳敭桴湩\"\n                },\n                \"binary\": {\n                  \"encoded\": \"AQID\",\n                  \"byteOffset\": 0,\n                  \"length\": 3\n                },\n                \"text\": \"foo\",\n                \"check\": false\n              }\n            ],\n            \"$types\": {\n              \"$\": {\n                \"0\": \"date\",\n                \"1.date\": \"date\",\n                \"1.blob\": \"blob\",\n                \"1.binary\": \"uint8array\"\n              }\n            }\n          },\n          [\n            \"3\",\n            {\n              \"bar\": \"foo\"\n            }\n          ]\n        },\n        {\n          \"tableName\": \"inbound\",\n          \"inbound\": true,\n          \"rows\": [\n            {\n              \"id\": 1,\n              \"date\": 1,\n              \"blob\": {\n                \"type\": \"\",\n                \"stringContents\": \"潳敭桴湩\"\n              },\n              \"binary\": {\n                \"encoded\": \"AQID\",\n                \"byteOffset\": 0,\n                \"length\": 3\n              },\n              \"text\": \"foo\",\n              \"check\": false,\n              \"$types\": {\n                \"date\": \"date\",\n                \"blob\": \"blob\",\n                \"binary\": \"uint8array\"\n              }\n            },\n            {\n              \"id\": 2,\n              \"foo\": \"bar\"\n            },\n            {\n              \"id\": 3,\n              \"bar\": \"foo\"\n            ]\n          }\n        ]\n      }\n    }"
  },
  {
    "path": "test/deepEqual.js",
    "content": "import { equal } from 'QUnit';\nimport sortedJSON from \"sorted-json\";\nimport { deepClone } from '../src/functions/utils';\n\nexport function deepEqual(actual, expected, description) {\n  actual = JSON.parse(JSON.stringify(actual));\n  expected = JSON.parse(JSON.stringify(expected));\n  actual = sortedJSON.sortify(actual, { sortArray: false });\n  expected = sortedJSON.sortify(expected, { sortArray: false });\n  equal(JSON.stringify(actual, null, 2), JSON.stringify(expected, null, 2), description);\n}\nexport function isDeepEqual(actual, expected, allowedExtra, prevActual) {\n  actual = deepClone(actual);\n  expected = deepClone(expected);\n  if (allowedExtra) Array.isArray(allowedExtra) ? allowedExtra.forEach(key => {\n    if (actual[key]) expected[key] = deepClone(prevActual[key]);\n  }) : Object.keys(allowedExtra).forEach(key => {\n    if (actual[key]) expected[key] = deepClone(allowedExtra[key]);\n  });\n\n  actual = sortedJSON.sortify(actual, { sortArray: false });\n  expected = sortedJSON.sortify(expected, { sortArray: false });\n  return JSON.stringify(actual, null, 2) === JSON.stringify(expected, null, 2);\n}\n"
  },
  {
    "path": "test/dexie-unittest-utils.js",
    "content": "﻿import Dexie from 'dexie';\nimport {ok, start, test, config} from 'QUnit';\n\n// Custom QUnit config options.\nconfig.urlConfig.push(/*{\n    id: \"polyfillIE\", // Remarked because has no effect anymore. Find out why.\n\tlabel: \"Include IE Polyfill\",\n    tooltip: \"Enabling this will include the idb-iegap polyfill that makes\" +\n    \" IE10&IE11 support multiEntry and compound indexes as well as compound\" +\n    \" primary keys\"\n}, {\n    id: \"indexedDBShim\", // Remarked because has no effect anymore. Need to find out why. Should invoke the shim if set!\n    label: \"IndexedDBShim (UseWebSQL as backend)\",\n    tooltip: \"Enable this in Safari browsers without indexedDB support or\" +\n    \" with poor indexedDB support\"\n},*/ {\n    id: \"dontoptimize\",\n    label: \"Dont optimize tests\",\n    tooltip: \"Always delete and recreate the DB between each test\"\n});\n\nvar no_optimize = window.no_optimize || window.location.search.indexOf('dontoptimize') !== -1;\n\nconst ArrayBuffer = window.ArrayBuffer;\n\nfunction stringify (idbKey) {\n    var res = '' + (idbKey && idbKey.constructor && idbKey.constructor === ArrayBuffer ?\n        new Uint8Array(idbKey) : idbKey);\n    return res;\n}\n\nexport function resetDatabase(db) {\n    /// <param name=\"db\" type=\"Dexie\"></param>\n    var Promise = Dexie.Promise;\n    return no_optimize || !db._hasBeenCreated ?\n        // Full Database recreation. Takes much time!\n        db.delete().then(function () {\n            return db.open().then(function() {\n                if (!no_optimize) {\n                    db._hasBeenCreated = true;\n                    var initialState = (db._initialState = {});\n                    // Now, snapshot the database how it looks like initially (what on.populate did)\n                    return db.transaction('r', db.tables, function() {\n                        var trans = Dexie.currentTransaction;\n                        return Promise.all(trans.storeNames.filter(function(tableName) {\n                            // Don't clear 'meta tables'\n                            return tableName[0] != '_' && tableName[0] != '$';\n                        }).map(function (tableName) {\n                            var items = {};\n                            initialState[tableName] = items;\n                            return db.table(tableName).each(function(item, cursor) {\n                                items[stringify(cursor.primaryKey)] = { key: cursor.primaryKey, value: item };\n                            });\n                        }));\n                    });\n                }\n            });\n        })\n\n        :\n\n        // Optimize: Don't delete and recreate database. Instead, just clear all object stores,\n        // and manually run db.on.populate\n        db.transaction('rw!', db.tables, function() {\n            // Got to do an operation in order for backend transaction to be created.\n            var trans = Dexie.currentTransaction;\n            var initialState = db._initialState;\n            return Promise.all(trans.storeNames.filter(function(tableName) {\n                // Don't clear 'meta tables'\n                return tableName[0] != '_' && tableName[0] != '$';\n            }).map(function(tableName) {\n                // Read current state\n                var items = {};\n                return db.table(tableName).each(function(item, cursor) {\n                    items[stringify(cursor.primaryKey)] = { key: cursor.primaryKey, value: item };\n                }).then(function() {\n                    // Diff from initialState\n                    // Go through initialState and diff with current state\n                    var initialItems = initialState[tableName];\n                    return Promise.all(Object.keys(initialItems).map(key => {\n                        var item = items[key];\n                        var initialItem = initialItems[key];\n                        if (!item || JSON.stringify(item.value) != JSON.stringify(initialItem.value))\n                            return (db.table(tableName).schema.primKey.keyPath ? db.table(tableName).put(initialItem.value) :\n                                db.table(tableName).put(initialItem.value, initialItem.key));\n                        return Promise.resolve();\n                    }));\n                }).then(function() {\n                    // Go through current state and diff with initialState\n                    var initialItems = initialState[tableName];\n                    var keysToDelete = Object.keys(items)\n                        .filter(key => !initialItems[key])\n                        .map(key => items[key].key);\n\n                    if (keysToDelete.length > 0) {\n                        return db.table(tableName).bulkDelete(keysToDelete);\n                    }\n                });\n            }));\n        });\n}\n\nexport function deleteDatabase(db) {\n    var Promise = Dexie.Promise;\n    return no_optimize ? db.delete() : db.transaction('rw!', db.tables, function() {\n        // Got to do an operation in order for backend transaction to be created.\n        var trans = Dexie.currentTransaction;\n        return Promise.all(trans.storeNames.filter(function(tableName) {\n            // Don't clear 'meta tables'\n            return tableName[0] != '_' && tableName[0] != '$';\n        }).map(function(tableName) {\n            // Clear all tables\n            return db.table(tableName).clear();\n        }));\n    });\n}\n\nexport const isIE = !(window.ActiveXObject) && \"ActiveXObject\" in window;\nexport const isEdge = /Edge\\/\\d+/.test(navigator.userAgent);\nexport const isChrome = !!window.chrome;\nvar hasPolyfillIE = [].slice.call(document.getElementsByTagName(\"script\")).some(\n    s => s.src.indexOf(\"idb-iegap\") !== -1);\n    \nexport const isSafari = typeof navigator !== 'undefined' &&\n    /Safari\\//.test(navigator.userAgent) &&\n    !/Chrom(e|ium)\\/|Edge\\//.test(navigator.userAgent);\n\n// Safari private mode are being used on LambdaTest's servers and even if Safari does a good job to\n// support IndexedDB in private mode, it comes with some issues. One of them is that it doesn't\n// seem to respect when doing preventDefault() on IDB request error events, which dexie does in order\n// to override the default cancelling of transactions on error events in case they were catched explicitely.\n// We use this vars to omit certain unit tests from the suite so that they don't fail for Safari private mode\n// which is being used in LambdaTest's servers.\nexport const isSafariPrivateMode = isSafari; // Sorry there's no way to distinguish private mode from non-private in modern Safari. Still keep it as separate variable for exposing the purpose where it's being used.\n\nexport function supports (features) {\n    return features.split('+').reduce((result,feature)=>{\n        switch (feature.toLowerCase()) {\n            case \"compound\":\n                return result && Array.isArray(Dexie.maxKey);\n            case \"multientry\":\n                return result && (hasPolyfillIE || (!isIE && !isEdge)); // Should add Safari to\n            case \"deleteobjectstoreafterread\":\n                return result && (!isIE && !isEdge);\n            case \"versionchange\":\n                return result;\n                //return result && (!isIE && !isEdge); // Should add Safari to\n            case \"binarykeys\":\n                try {\n                    return result && Array.isArray(Dexie.maxKey) && indexedDB.cmp(new Uint8Array([1]), new Uint8Array([1])) === 0;\n                } catch (e) {\n                    return false;\n                }\n            case \"domevents\":\n                return typeof window === 'object' && window.addEventListener;\n\n            default:\n                throw new Error (\"Unknown feature: \" + feature);\n        }\n    }, true);\n}\n\nexport function spawnedTest (name, num, promiseGenerator) {\n    if (!promiseGenerator) {\n        promiseGenerator = num;\n        test(name, function(assert) {\n            let done = assert.async();\n            Dexie.spawn(promiseGenerator)\n                .catch(e => ok(false, e.stack || e))\n                .then(done);\n        });\n    } else {\n        test(name, num, function(assert) {\n            let done = assert.async();\n            Dexie.spawn(promiseGenerator)\n                .catch(e => ok(false, e.stack || e))\n                .then(done);\n        });\n    }\n}\n\nexport function promisedTest (name, num, asyncFunction) {\n    if (!asyncFunction) {\n        asyncFunction = num;\n        test(name, (assert) => {\n            let done = assert.async();\n            Promise.resolve().then(asyncFunction)\n              .catch(e => ok(false, e.stack || e))\n              .then(done);\n        });\n    } else {\n        test(name, num, (assert) => {\n            let done = assert.async();\n            Promise.resolve().then(asyncFunction)\n              .catch(e => ok(false, e.stack || e))\n              .then(done);\n        });\n    }\n}\n"
  },
  {
    "path": "test/gh-actions.sh",
    "content": "#!/bin/bash -e\npnpm run test:typings\nif [ \"$LAMBDATEST\" == \"true\" ]; then\n  pnpm run test:ltcloud\nelse\n  pnpm run test:unit\nfi\n"
  },
  {
    "path": "test/integrations/test-dexie-relationships/basic-tests.js",
    "content": "import Dexie from 'dexie';\nimport dexieRelationships from 'dexie-relationships';\nimport {resetDatabase, promisedTest} from '../../dexie-unittest-utils';\nimport {module, asyncTest, start, stop, strictEqual, deepEqual, ok} from 'QUnit';\n\nconst assert = ok;\n\n//\n// Define DB and schema\n//\nvar db = new Dexie('bands-simple', {addons: [dexieRelationships]});\ndb.version(1).stores({\n    genres: `\n            id,\n            name`,\n    bands: `\n            id,\n            name,\n            genreId -> genres.id`,\n    albums: `\n            id,\n            name,\n            bandId -> bands.id,\n            year`\n});\n\n//\n// Populate Database\n//\ndb.on('populate', () => {\n    // Genres\n    db.genres.bulkAdd([{\n        id: 1,\n        name: \"Rock\"\n    },{\n        id: 2,\n        name: \"Schlager\"\n    }])\n\n    // Bands\n    db.bands.bulkAdd([{\n        id: 1,\n        name: 'Beatles',\n        genreId: 1\n    },{\n        id: 2,\n        name: 'Abba',\n        genreId: 2\n    }])\n\n    // Albums\n    db.albums.bulkAdd([{\n        id: 1,\n        name: 'Abbey Road',\n        year: 1969,\n        bandId: 1\n    }, {\n        id: 2,\n        name: 'Let It Be',\n        year: 1970,\n        bandId: 1\n    }, {\n        id: 3,\n        name: 'Super Trouper',\n        bandId: 2,\n        year: 1980\n    }, {\n        id: 4,\n        name: 'Waterloo',\n        bandId: 2,\n        year: 1974\n    }]);\n});\n\n//\n// Test Module setup script\n//\nmodule('dexie-relationships-basics', {\n    setup: () => {\n        stop();\n        resetDatabase(db).catch(e => {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).then(()=>start());\n    }\n});\n\n//\n// Tests goes here...\n//\n\npromisedTest ('many-to-one - should be possible to retrieve an entity with a collection of referring entities attached to it', async () => {\n    // Query\n    const bands = await db.bands.where('name').equals('Beatles').with({\n        albums: 'albums'\n    });\n\n    // Assertions\n    assert(bands.length == 1, \"Should be one Beatles\");\n    let beatles = bands[0]\n    assert(!!beatles.albums, \"Should have got the foreign albums collection\")\n    assert(beatles.albums.length === 2, \"Should have 2 albums in this db\")\n    assert(beatles.albums[0].name === \"Abbey Road\", \"First albums should be 'Abbey Roead'\")\n    assert(beatles.albums[1].name === \"Let It Be\", \"Second album should be 'Let It Be'\")\n});\n\npromisedTest('one-to-one - should be possible to retrieve entity with a foreign key to expand that foreign key', async () => {\n    const albums = await db.albums.where('year').between(1970, 1974, true, true).with ({\n        band: 'bandId'\n    });\n\n    assert (albums.length === 2, \"Should retrieve two albums between 1970 to 1974\")\n    const [letItBe, waterloo] = albums;\n\n    assert (letItBe.name === \"Let It Be\", \"First album should be 'Let It Be'\")\n    assert (!!letItBe.band, \"Should get the band resolved with the query\")\n    assert (letItBe.band.name === \"Beatles\", \"The band should be Beatles\")\n\n    assert (waterloo.name === \"Waterloo\", \"Second album should be 'Waterloo'\")\n    assert (!!waterloo.band, \"Should get the band resolved with the query\")\n    assert (waterloo.band.name === \"Abba\", \"The band should be Abba\")\n});\n\npromisedTest('Multiple foreign keys of different kind - Should be possible to retrieve entities with oneToOne as well as manyToOne relations', async () => {\n    const bands = await db.bands.where('name').equals('Beatles').with({\n        albums: 'albums',\n        genre: 'genreId'\n    });\n    assert(bands.length == 1, \"Should be one Beatles\")\n    let beatles = bands[0]\n    assert(!!beatles.albums, \"Should have got the foreign albums collection\")\n    assert(beatles.albums.length === 2, \"Should have 2 albums in this db\")\n    assert(beatles.albums[0].name === \"Abbey Road\", \"First albums should be 'Abbey Roead'\")\n    assert(beatles.albums[1].name === \"Let It Be\", \"Second album should be 'Let It Be'\")\n    assert(!!beatles.genre, \"Should have got the foreign genre entity\")\n    assert(beatles.genre.name === \"Rock\", \"The genre should be 'Rock' (even though that could be questionable)\");\n});\n\npromisedTest('Navigation properties should be non-enumerable', async () => {\n    console.log('should be possible to put back an object to indexedDB after ' +\n      'having retrieved it with navigation properties ' +\n      'without storing the navigation properties redundantly');\n    \n    const bands = await db.bands.where('name').equals('Abba').with({albums: 'albums', genre: 'genreId'});\n\n    assert(bands.length === 1, \"Should be one Abba\");\n    let abba = bands[0]\n    assert (!!abba.albums, \"Abba should have its 'albums' foreign collection\")\n    assert (!!abba.genre, \"Abba should have its 'genre' foreign property\")\n    abba.customProperty = \"Hello world\";\n\n    await db.bands.put(abba);\n\n    abba = db.bands.where('name').equals('Abba').first();\n\n    assert(!abba.albums, \"Abba should not have the 'albums' foreign collection stored redundantly\")\n    assert(!abba.genre, \"Abba should not have the 'genre' foreign property stored redundantly\")\n});\n\npromisedTest('Sample from README - should be possible to copy and paste the sample from README', async () => {\n    let rows = await db.bands\n        .where('name').startsWithAnyOf('A', 'B')\n        .with({albums: 'albums', genre: 'genreId'}); // Resolves foreign keys into props\n\n    assert (true, \"Promise resolved and no exception occured\");\n\n    // Print the result:\n    rows.forEach (band => {\n        console.log (`Band Name: ${band.name}`)\n        console.log (`Genre: ${band.genre.name}`)\n        console.log (`Albums: ${JSON.stringify(band.albums, null, 4)}`)\n    });\n});\n"
  },
  {
    "path": "test/integrations/test-dexie-relationships/gh-actions.sh",
    "content": "#!/bin/bash -e\npnpm install\npnpm install webpack # Need to do all the time - bug in pnpm probably.\npnpm test\n"
  },
  {
    "path": "test/integrations/test-dexie-relationships/index.js",
    "content": "import \"./basic-tests\";\n"
  },
  {
    "path": "test/integrations/test-dexie-relationships/karma.conf.js",
    "content": "// Include common configuration\nconst {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../karma.common');\n\nmodule.exports = function (config) {\n  const browserMatrixOverrides = {\n    // Be fine with testing on local travis firefox for both pull requests and pushs.\n    ci: [\"remote_chrome\"],\n    // Be fine with chrome for this particular integration test.\n    pre_npm_publish: ['Chrome']\n  };\n\n  const cfg = getKarmaConfig(browserMatrixOverrides, {\n    // Base path should point at dexie root\n    basePath: '../../../',\n    // The files needed to apply dexie-observable to the standard dexie unit tests.\n    files: karmaCommon.files.concat([\n      'dist/dexie.js', // Dexie\n      'test/integrations/test-dexie-relationships/node_modules/dexie-relationships/dist/index.js', // dexieRelationships\n      'test/integrations/test-dexie-relationships/dist/test-bundle.js',\n      { pattern: 'test/integrations/test-dexie-relationships/dist/test-bundle.js.map', included: false },\n      { pattern: 'test/integrations/test-dexie-relationships/node_modules/dexie-relationships/dist/*.map', included: false },\n    ])\n  });\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "test/integrations/test-dexie-relationships/package.json",
    "content": "{\n  \"name\": \"test-dexie-relationships\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"private\": true,\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"webpack --mode development && pnpm run test:karma\",\n    \"test:karma\": \"../../../node_modules/karma/bin/karma start karma.conf.js --single-run\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"dexie-relationships\": \"^1.2.11\",\n    \"webpack\": \"^5.74.0\",\n    \"webpack-cli\": \"^4.5.0\"\n  },\n  \"devDependencies\": {\n    \"qunit\": \"2.10.0\",\n    \"qunitjs\": \"1.23.1\"\n  }\n}\n"
  },
  {
    "path": "test/integrations/test-dexie-relationships/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  entry: './index.js',\n  resolve: {\n    alias: {\n      dexie: path.resolve(__dirname, '../../../dist/dexie.js')\n    }\n  },\n  externals: {\n    QUnit: 'QUnit',\n    dexie: 'Dexie'\n  },\n  output: {\n    filename: 'test-bundle.js',\n    path: path.resolve(__dirname, './dist'),\n  },\n};"
  },
  {
    "path": "test/is-idb-and-promise-compatible.js",
    "content": "import Dexie from 'dexie';\nimport {NativePromise} from '../src/helpers/promise';\n\nvar _resolve = NativePromise.resolve.bind(NativePromise);\nvar _then = NativePromise.prototype.then;\n\nexport class IdbPromiseIncompatibleError extends Error {\n    constructor() {\n        super(\"IndexedDB and Promise are incompatible on this browser\");\n        this.name = \"IdbPromiseIncompatibleError\";\n    }\n}\n\nexport async function isIdbAndPromiseCompatible() {\n    let db = new Dexie(\"idbPromiseCompatTest\");\n    db.version(1).stores({foo:'bar'});\n    await db.delete();\n    await db.open();\n    return await db.transaction('r', db.foo, async ()=>{\n        let x = await db.foo.count();\n        let p = _resolve(0);\n        for (let i=0;i<10;++i) {\n            p = _then.call(p, x => x + 1);\n        }\n        let result = await p;\n        console.log(\"Result: \"+ result + \" (should be 10\");\n        try {\n            await db.foo.count();\n            db.close();\n            return true;\n        } catch (ex) {\n            db.close();\n            throw new IdbPromiseIncompatibleError();\n        }\n    });\n}\n"
  },
  {
    "path": "test/karma-env.js",
    "content": "QUnit.config.autostart = false;\nwindow.workerImports = ['../dist/dexie.js'];\nwindow.workerSource = 'base/test/worker.js';\n\n// QUnit 1.x exports asyncTest, test, etc. as global functions\n// but rollup expects them as properties of the QUnit object when using named imports\n// Make them accessible both ways\nif (typeof asyncTest !== 'undefined' && !QUnit.asyncTest) {\n    QUnit.asyncTest = asyncTest;\n}\nif (typeof test !== 'undefined' && !QUnit.test) {\n    QUnit.test = test;\n}\nif (typeof module !== 'undefined' && !QUnit.module) {\n    QUnit.module = module;\n}\nif (typeof ok !== 'undefined' && !QUnit.ok) {\n    QUnit.ok = ok;\n}\nif (typeof equal !== 'undefined' && !QUnit.equal) {\n    QUnit.equal = equal;\n}\nif (typeof notEqual !== 'undefined' && !QUnit.notEqual) {\n    QUnit.notEqual = notEqual;\n}\nif (typeof deepEqual !== 'undefined' && !QUnit.deepEqual) {\n    QUnit.deepEqual = deepEqual;\n}\nif (typeof notDeepEqual !== 'undefined' && !QUnit.notDeepEqual) {\n    QUnit.notDeepEqual = notDeepEqual;\n}\nif (typeof strictEqual !== 'undefined' && !QUnit.strictEqual) {\n    QUnit.strictEqual = strictEqual;\n}\nif (typeof notStrictEqual !== 'undefined' && !QUnit.notStrictEqual) {\n    QUnit.notStrictEqual = notStrictEqual;\n}\nif (typeof start !== 'undefined' && !QUnit.start) {\n    QUnit.start = start;\n}\nif (typeof stop !== 'undefined' && !QUnit.stop) {\n    QUnit.stop = stop;\n}\n"
  },
  {
    "path": "test/karma.browsers.matrix.d.ts",
    "content": "export let local: string[];\nexport let ciLocal: string[];\nexport let ci: string[];\nexport let pre_npm_publish: string[];\n"
  },
  {
    "path": "test/karma.browsers.matrix.js",
    "content": "/** This module comprises the list of browsers\n * to run tests on depending on environment.\n *\n * \"remote...\" browsers listed here must also be defined in\n * karma.lambdatest.js\n */\n\nmodule.exports = {\n    // On developers machines, Chrome is most likely to be installed.\n    // When running as root (e.g. CI containers), use ChromeNoSandbox instead.\n    local: [process.getuid && process.getuid() === 0 ? 'ChromeNoSandbox' : 'Chrome'],\n\n    // When Lambdatest credentials aren't available, use Chrome and Firefox on Github Actions:\n    ciLocal: ['Chrome', 'Firefox'],\n\n    // Continous Integration on every push\n    ci: [\n        'remote_chrome',\n        'remote_safari',\n        'remote_firefox'\n    ],\n\n    // Test matrix used before every npm publish.\n    // Note: The script tools/release.sh will run the tests\n    // locally on Chrome. However, this is just an\n    // extra safety check as all tests must anyway have been successful\n    // on the CI that tests on all configured browsers in Lambdatest.\n    pre_npm_publish: [\n        'Chrome',\n    ]\n}\n\n"
  },
  {
    "path": "test/karma.common.d.ts",
    "content": "export namespace karmaCommon {\n    let hostname: string;\n    let frameworks: string[];\n    let reporters: string[];\n    namespace client {\n        let captureConsole: boolean;\n    }\n    let colors: boolean;\n    let browserNoActivityTimeout: number;\n    let browserDisconnectTimeout: number;\n    let processKillTimeout: number;\n    let browserSocketTimeout: number;\n    let plugins: string[];\n    let files: (string | {\n        pattern: string;\n        watched: boolean;\n        included: boolean;\n        served: boolean;\n    })[];\n}\n/**\n * @param browserMatrixOverrides {{full: string[], ci: string[]}}\n *  Map between browser suite and array of browser to test.\n * @param configOverrides {Object} configOverrides to the common template\n */\nexport function getKarmaConfig(browserMatrixOverrides: {\n    full: string[];\n    ci: string[];\n}, configOverrides: any): any;\nexport const browserSuiteToUse: \"pre_npm_publish\" | \"ci\" | \"ciLocal\" | \"local\";\nimport defaultBrowserMatrix = require(\"./karma.browsers.matrix\");\nexport { defaultBrowserMatrix };\n"
  },
  {
    "path": "test/karma.common.js",
    "content": "const { configureLambdaTest } = require('./karma.lambdatest');\n\n/* Base karma configurations to require and extend from other karma.conf.js\n */\nconst karmaCommon = {\n  hostname: 'localhost.lambdatest.com',\n  // Use qunitjs 1.23.1 instead of qunit 2.x\n  // We load it manually in files array instead of using the 'qunit' framework\n  frameworks: [],\n\n  reporters: ['mocha'],\n\n  client: {\n    captureConsole: false,\n  },\n\n  colors: true,\n\n  browserNoActivityTimeout: 2 * 60 * 1000,\n  browserDisconnectTimeout: 10000,\n  processKillTimeout: 10000,\n  browserSocketTimeout: 20000,\n\n  customLaunchers: {\n    ChromeNoSandbox: {\n      base: 'Chrome',\n      flags: ['--no-sandbox', '--disable-setuid-sandbox', '--headless', '--disable-gpu'],\n    },\n  },\n\n  plugins: [\n    'karma-qunit',\n    'karma-mocha-reporter',\n    'karma-chrome-launcher',\n    'karma-firefox-launcher',\n    // karma-webdriver-launcher depends on 'wd' which runs a build script (node\n    // scripts/build-browser-scripts) during install. pnpm v10+ skips arbitrary\n    // install scripts by default, so 'wd/build/' won't exist on a fresh install\n    // and the plugin will fail to load. Only include it when actually running\n    // remote WebDriver tests (LambdaTest CI).\n    ...(process.env.LT_USERNAME ? ['karma-webdriver-launcher'] : []),\n  ],\n\n  files: [\n    'test/babel-polyfill/polyfill.min.js',\n    // Load qunitjs 1.23.1 manually (creates global QUnit, asyncTest, test, etc.)\n    'node_modules/qunitjs/qunit/qunit.js',\n    // karma-qunit adapter (expects window.QUnit to exist)\n    'node_modules/karma-qunit/lib/adapter.js',\n    'test/karma-env.js',\n    {\n      pattern: 'test/worker.js',\n      watched: true,\n      included: false,\n      served: true,\n    },\n    {\n      pattern: '!(node_modules|tmp)*/*.map',\n      watched: false,\n      included: false,\n      served: true,\n    },\n  ],\n};\n\nconfigureLambdaTest(karmaCommon);\n\nconst browserSuiteToUse = process.env.NODE_ENV === 'release'\n  ? 'pre_npm_publish' // When run by tools/release.sh\n  : process.env.LT_USERNAME && process.env.GH_ACTIONS\n  ? \"ci\" // Automated CI\n  : process.env.GH_ACTIONS\n  ? \"ciLocal\" // \"ci\" when not having the credentials (= forks of the dexie repo)\n  : 'local'; // Developer local machine\n\nconsole.log('LT_TUNNEL_NAME', process.env.LT_TUNNEL_NAME);\n\nconst defaultBrowserMatrix = require('./karma.browsers.matrix');\n\n/**\n * @param browserMatrixOverrides {{full: string[], ci: string[]}}\n *  Map between browser suite and array of browser to test.\n * @param configOverrides {Object} configOverrides to the common template\n */\nfunction getKarmaConfig(browserMatrixOverrides, configOverrides) {\n  console.log('Browser-suite: ' + browserSuiteToUse);\n  browserMatrixOverrides = Object.assign(\n    {},\n    defaultBrowserMatrix,\n    browserMatrixOverrides\n  );\n  const browsers = browserMatrixOverrides[browserSuiteToUse];\n  console.log('Browsers to test: ' + browsers.join(', '));\n  const finalConfig = Object.assign({}, karmaCommon, configOverrides, {\n    browsers,\n  });\n  return finalConfig;\n}\n\nmodule.exports = {\n  karmaCommon,\n  getKarmaConfig,\n  browserSuiteToUse,\n  defaultBrowserMatrix,\n};\n"
  },
  {
    "path": "test/karma.conf.js",
    "content": "const {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('./karma.common');\n\nmodule.exports = function (config) {\n  const cfg = getKarmaConfig (defaultBrowserMatrix, {\n    // Base path should point at the root \n    basePath: '..',\n    // Files to include\n    files: karmaCommon.files.concat([\n      'dist/dexie.js',\n      'test/bundle.js',\n      { watched: true, included: false, served: true, pattern: 'test/worker.js' },\n    ])\n  });\n\n  config.set(cfg);\n}\n"
  },
  {
    "path": "test/karma.lambdatest.d.ts",
    "content": "export function configureLambdaTest(karmaCommon: any): void;\n"
  },
  {
    "path": "test/karma.lambdatest.js",
    "content": "\nconst ltBrowsers = {\n  remote_firefox: {\n    browserName: 'firefox',\n    browserVersion: '118',\n    'LT:Options': {\n      platformName: 'Windows 10'\n    }\n  },\n  remote_edge: {\n    browserName: 'Edge',\n    browserVersion: '118',\n    'LT:Options': {\n      platformName: 'Windows 10'\n    }\n  },\n  remote_chrome: {\n    browserName: \"Chrome\",\n    browserVersion: \"144\",\n    'LT:Options': {\n      platformName: 'Windows 11'\n    }\n  },\n  remote_safari: {\n    browserName: \"Safari\",\n    browserVersion: \"16\",\n    'LT:Options': {\n      platformName: 'MacOS Ventura'\n    }\n  }\n};\n\nconst webdriverConfig = {\n  hostname: 'hub.lambdatest.com',\n  port: 80,\n};\n\nconst webdriverConfigMobile = {\n  hostname: 'mobile-hub.lambdatest.com',\n  port: 80,\n};\n\nfor (const key of Object.keys(ltBrowsers)) {\n  ltBrowsers[key].base = 'WebDriver';\n  if (ltBrowsers[key].isRealMobile) {\n    ltBrowsers[key].config = webdriverConfigMobile;\n    ltBrowsers[key].user = process.env.LT_USERNAME;\n    ltBrowsers[key].accessKey = process.env.LT_ACCESS_KEY;\n    ltBrowsers[key].tunnel = true;\n    ltBrowsers[key].console = true;\n    ltBrowsers[key].network = true;\n    ltBrowsers[key].tunnelName = process.env.LT_TUNNEL_NAME || 'jasmine';\n    ltBrowsers[key].pseudoActivityInterval = 5000; // 5000 ms heartbeat\n  } else {\n    ltBrowsers[key].config = webdriverConfig;\n    ltBrowsers[key]['LT:Options'].username = process.env.LT_USERNAME;\n    ltBrowsers[key]['LT:Options'].accessKey = process.env.LT_ACCESS_KEY;\n    ltBrowsers[key]['LT:Options'].tunnel = true;\n    ltBrowsers[key]['LT:Options'].console = true;\n    ltBrowsers[key]['LT:Options'].network = true;\n    ltBrowsers[key]['LT:Options'].tunnelName =\n      process.env.LT_TUNNEL_NAME || 'jasmine';\n    ltBrowsers[key]['LT:Options'].pseudoActivityInterval = 5000; // 5000 ms heartbeat\n  }\n\n  ltBrowsers[key].retryLimit = 2;\n}\n\nfunction configureLambdaTest(karmaCommon) {\n  karmaCommon.hostname = 'localhost.lambdatest.com';\n  karmaCommon.customLaunchers = {\n    ...karmaCommon.customLaunchers,\n    ...ltBrowsers\n  }\n}\n\nmodule.exports = {\n  configureLambdaTest\n}\n"
  },
  {
    "path": "test/lt-local.js",
    "content": "/* eslint-env node */\n/* eslint-disable no-console */\n\n'use strict'\nconst process = require('node:process')\nconst lambdaTunnel = require('@lambdatest/node-tunnel')\nconst fs = require('fs');\n\nconst tunnelInstance = new lambdaTunnel() // eslint-disable-line new-cap\nconst tunnelArguments = {\n  user: process.env.LT_USERNAME,\n  key: process.env.LT_ACCESS_KEY,\n  tunnelName: process.env.LT_TUNNEL_NAME || 'jasmine',\n  logFile: 'local.log'\n}\nif (process.env.LAMBDATEST === 'true') {\n  (async () => {\n    try {\n      await tunnelInstance.start(tunnelArguments)\n      await new Promise(res => setTimeout(res, 5000))\n      await fs.writeFileSync('tunnel.pid', tunnelInstance.proc.pid.toString());\n    } catch (error) {\n      console.log(error.message)\n    }\n  })()\n}\n"
  },
  {
    "path": "test/rebalance.md",
    "content": "\n\n```js\n  { from: 63, to: 71 },  // 0. Tree: [63-71]\n  { from: 99, to: 102 }, // 1. Tree: [63-71]\n                         //      (null)  [99-102]\n  { from: 90, to: 92 },  // 2. Tree: [99-102]\n                         //       [63-71]  (null)\n                         //    [90-92] [90-92]\n                         // WOW: Here both left and right leafs are the same node!\n```\n\n```\nTree:\n        [63-71]\n    (null)  [99-102]\n\nInsert [90-92]:\n\n        [63-71]\n  (null)      [99-102]\n          [90-92]    (null)\n\nRotation:\n       [99-102]\n  [63-72]       (null)\n(null)   [90-92]\n\nRebalance root (as it SHOULD be):\n\n          [90-92]\n  [63-71]        [99-102]\n(null) (null)  (null)  (null)\n```"
  },
  {
    "path": "test/run-unit-tests.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Dexie Unit tests</title>\n  <link rel=\"stylesheet\" href=\"../node_modules/qunitjs/qunit/qunit.css\">\n</head>\n<body>\n    <div id=\"qunit\"></div>\n    <div id=\"qunit-fixture\"></div>\n    <!-- <script src=\"https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js\"></script> -->\n    <script src=\"../node_modules/qunitjs/qunit/qunit.js\"></script>\n    <script src=\"../dist/dexie.js\"></script>\n    <!-- <script> if (typeof Promise === 'undefined') Promise = Dexie.Promise </script> -->\n    <script src=\"bundle.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "test/tests-all.js",
    "content": "﻿import Dexie from 'dexie';\nDexie.test = true; // Improve code coverage\nimport \"./tests-cmp.js\";\nimport \"./tests-table.js\";\nimport \"./tests-chrome-transaction-durability.js\";\nimport \"./tests-collection.js\";\nimport \"./tests-whereclause.js\";\nimport \"./tests-transaction.js\";\nimport \"./tests-open.js\";\nimport \"./tests-yield\";\nimport \"./tests-asyncawait.js\";\nimport \"./tests-exception-handling.js\";\nimport \"./tests-upgrading.js\";\nimport \"./tests-misc\";\nimport \"./tests-promise.js\";\nimport \"./tests-extendability.js\";\nimport \"./tests-crud-hooks\";\nimport \"./tests-blobs\";\nimport \"./tests-binarykeys\";\nimport \"./tests-live-query\";\nimport \"./tests-rangeset\";\nimport \"./tests-idb30\";\nimport \"./tests-max-connections.js\";\n//import \"./tests-performance.js\"; Not required. Should make other performance tests separately instead.\n"
  },
  {
    "path": "test/tests-asyncawait.js",
    "content": "import Dexie from 'dexie';\nimport {module, test, equal, ok} from 'QUnit';\nimport {resetDatabase, spawnedTest, promisedTest} from './dexie-unittest-utils';\nimport {isIdbAndPromiseCompatible} from './is-idb-and-promise-compatible';\n\nlet idbAndPromiseCompatible;\n\nconst hasNativeAsyncFunctions = false;\ntry {\n    hasNativeAsyncFunctions = !!new Function(`return (async ()=>{})();`)().then;\n} catch (e) {}\n\nvar db = new Dexie(\"TestDBTranx\");\ndb.version(1).stores({\n    items: \"id\"\n});\n\nmodule(\"asyncawait\", {\n    setup: function (assert) {\n        // Execute this promise when needed:\n        if (idbAndPromiseCompatible === undefined) {\n            // Initialize this promise.\n            idbAndPromiseCompatible = isIdbAndPromiseCompatible();\n        }\n        let done = assert.async();\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(done);\n    },\n    teardown: function () {\n    }\n});\n\ntest(\"Should be able to use global Promise within transaction scopes\", function(assert) {\n    let done = assert.async();\n    db.transaction('rw', db.items, trans => {\n        return window.Promise.resolve().then(()=> {\n            ok(Dexie.currentTransaction == trans, \"Transaction scopes should persist through Promise.resolve()\");\n            return db.items.add({ id: \"foobar\" });\n        }).then(()=>{\n            return Promise.resolve();\n        }).then(()=>{\n            ok(Dexie.currentTransaction == trans, \"Transaction scopes should persist through Promise.resolve()\");\n            return db.items.get('foobar');\n        });\n    }).then (function(foobar){\n        equal(foobar.id, 'foobar', \"Transaction should have lived throughout the Promise.resolve() chain\");\n    }).catch (e => {\n        ok(false, `Error: ${e.stack || e}`);\n    }).finally(done);\n});\n\ntest(\"Should be able to use native async await\", function(assert) {\n    let done = assert.async();\n    Dexie.Promise.resolve(idbAndPromiseCompatible).then(()=>{\n        let f = new Function('ok','equal', 'Dexie', 'db', `return db.transaction('rw', db.items, async ()=>{\n            let trans = Dexie.currentTransaction;\n            ok(!!trans, \"Should have a current transaction\");\n            await db.items.add({id: 'foo'});\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of Dexie.Promise\");\n            await Dexie.Promise.resolve();\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of Dexie.Promise synch\");\n            await window.Promise.resolve();\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of global Promise\");\n            await 3;\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of primitive(!)\");\n            await db.transaction('r', db.items, async innerTrans => {\n                ok(!!innerTrans, \"SHould have inner transaction\");\n                equal(Dexie.currentTransaction, innerTrans, \"Inner transaction should be there\");\n                equal(innerTrans.parent, trans, \"Parent transaction should be correct\");\n                let x = await db.items.get(1);\n                ok(Dexie.currentTransaction === innerTrans, \"Transaction persisted in inner transaction\");\n            });\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of sub transaction\");\n            await (async ()=>{\n                return await db.items.get(1);\n            })();\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of async function\");\n            await (async ()=>{\n                await Promise.all([db.transaction('r', db.items, async() => {\n                    await db.items.get(1);\n                    await db.items.get(2);\n                }), db.transaction('r', db.items, async() => {\n                    return await db.items.get(1);\n                })]);\n            })();\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of async function 2\");\n\n            await window.Promise.resolve().then(()=>{\n                ok(Dexie.currentTransaction === trans, \"Transaction persisted after window.Promise.resolve().then()\");\n                return (async ()=>{})(); // Resolve with native promise\n            }).then(()=>{\n                ok(Dexie.currentTransaction === trans, \"Transaction persisted after native promise completion\");\n                return window.Promise.resolve();\n            }).then(()=>{\n                ok(Dexie.currentTransaction === trans, \"Transaction persisted after window.Promise.resolve().then()\");\n                return (async ()=>{})();\n            });\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of mixed promises\");\n            \n            try {\n                let foo = await db.items.get('foo');\n                ok(true, \"YOUR BROWSER HAS COMPATIBILITY BETWEEN NATIVE PROMISES AND INDEXEDDB!\");\n            } catch (e) {\n                ok(true, \"Browser has no compatibility between native promises and indexedDB.\");\n            }\n        })`);\n        return f(ok, equal, Dexie, db);\n    }).catch('IdbPromiseIncompatibleError', e => {\n        ok (true, `Promise and IndexedDB is incompatible on this browser. Native async await fails in idb transaction by reality`)\n    }).catch(e => {\n        if (hasNativeAsyncFunctions)\n            ok(false, `Error: ${e.stack || e}`);\n        else \n            ok(true, `This browser does not support native async functions`);\n    }).then(done);\n});\n\ntest(\"Should be able to use native async await from upgrade handler (issue #612)\", function(assert) {\n    let done = assert.async();\n    Dexie.Promise.resolve(idbAndPromiseCompatible).then(()=>{\n        let f = new Function('ok','equal', 'Dexie', `\n        return Dexie.delete('issue612').then(async ()=>{\n          const log = [];\n          const db = new Dexie('issue612');\n          db.version(1).stores({foo: 'id'});\n          await db.open();\n          await db.foo.add({id: 1, name: \"Foo Bar\"});\n          db.close();\n          db.version(2).stores({foo: 'id, firstName, lastName'}).upgrade(async tx => {\n            log.push(\"2:1\");\n            await tx.foo.toCollection().modify(x => {\n                const [firstName, lastName] = x.name.split(' ');\n                x.firstName = firstName;\n                x.lastName = lastName;\n                ++x.v\n            });\n            log.push(\"2:2\");\n          });\n          db.version(3).upgrade(async tx => {\n            log.push(\"3:1\");\n            await tx.foo.toArray();\n            log.push(\"3:2\");\n          });\n          await db.open();\n          const count = await db.foo.where({firstName: 'Foo'}).count();\n          equal(count, 1, \"Should find base on the upgraded index\");\n          equal(log.join(','), \"2:1,2:2,3:1,3:2\", \"Execution order of upgraders should be correct\");\n          db.close();\n        });`);\n        return f(ok, equal, Dexie);\n    }).catch('IdbPromiseIncompatibleError', e => {\n        ok (true, `Promise and IndexedDB is incompatible on this browser. Native async await fails in idb transaction by reality`)\n    }).catch(e => {\n        if (hasNativeAsyncFunctions)\n            ok(false, `Error: ${e.stack || e}`);\n        else \n            ok(true, `This browser does not support native async functions`);\n    }).then(()=>{\n        return Dexie.delete(\"issue612\");\n    }).then(done);\n});\n\n\nconst NativePromise = (()=>{\n    try {\n        return new Function(\"return (async ()=>{})().constructor\")();\n    } catch(e) {\n        return window.Promise; \n    }\n})();\n\ntest(\"Must not leak PSD zone\", async function(assert) {\n    let done = assert.async();\n    let compatiblity = await idbAndPromiseCompatible.catch(e=>{\n        return false;\n    });\n    if (!compatiblity) {\n        ok (true, `Promise and IndexedDB is incompatible on this browser. Native async await fails \"by design\"`);\n        done();\n        return;\n    }\n    if (!hasNativeAsyncFunctions) {\n        ok(true, \"Browser doesnt support native async-await\");\n        done();\n        return;\n    }\n    let F = new Function('ok','equal', 'Dexie', 'db', `\n        ok(Dexie.currentTransaction === null, \"Should not have an ongoing transaction to start with\");\n        var trans1, trans2;\n        var p1 = db.transaction('r', db.items, async ()=> {\n            var trans = trans1 = Dexie.currentTransaction;\n            await db.items.get(1); // Just to prohibit IDB bug in Safari - must use transaction in initial tick!\n            await 3;\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 1.0 - after await 3\");\n            await 4;\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 1.0 - after await 4\");\n            await 5;\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 1.0 - after await 5\");\n            await db.items.get(1);\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 1.1 - after db.items.get(1)\");\n            await 6;\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 1.1 - after await 6\");\n            await subFunc(1);\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 1.2 - after async subFunc()\");\n            await Promise.all([subFunc(11), subFunc(12), subFunc(13)]);\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 1.3 - after Promise.all()\");\n            await subFunc2_syncResult();\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 1.4 - after async subFunc_syncResult()\");\n            await Promise.all([subFunc2_syncResult(), subFunc2_syncResult(), subFunc2_syncResult()]);\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 1.5 - after Promise.all(sync results)\");\n        });\n        var p2 = db.transaction('r', db.items, async ()=> {\n            var trans = trans2 = Dexie.currentTransaction;\n            await db.items.get(1); // Just to prohibit IDB bug in Safari - must use transaction in initial tick!\n            ok(trans1 !== trans2, \"Parallell transactions must be different from each other\");\n            await 3;\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 2.0 - after await 3\");\n            await db.items.get(1);\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 2.1 - after db.items.get(1)\");\n            await subFunc(2);\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 2.2 - after async subFunc()\");\n            await Promise.all([subFunc(21), subFunc(22), subFunc(23)]);\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 2.3 - after Promise.all()\");\n            await subFunc2_syncResult();\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 2.4 - after async subFunc_syncResult()\");\n            await Promise.all([subFunc2_syncResult(), subFunc2_syncResult(), subFunc2_syncResult()]);\n            ok(Dexie.currentTransaction === trans, \"Should still be in same transaction 2.5 - after Promise.all(sync results)\");\n        });\n        //var p2 = Promise.resolve();\n        ok(Dexie.currentTransaction === null, \"Should not have an ongoing transaction after transactions\");\n\n        async function subFunc(n) {\n            await 3;\n            let result = await db.items.get(2);\n            return result;\n        }\n\n        async function subFunc2_syncResult() {\n            let result = await 3;\n            return result;\n        }\n        \n        return Promise.all([p1, p2]);\n    `);\n    F(ok, equal, Dexie, db).catch(e => ok(false, e.stack || e)).then(done);\n});\n\ntest(\"Must not leak PSD zone2\", async function(assert) {\n    let done = assert.async();\n    ok(Dexie.currentTransaction === null, \"Should not have an ongoing transaction to start with\");\n    \n\n    db.transaction('rw', db.items, ()=>{\n        let trans = Dexie.currentTransaction;\n        ok(trans !== null, \"Should have a current transaction\");\n        let otherZonePromise;\n        Dexie.ignoreTransaction(()=>{\n            ok(Dexie.currentTransaction == null, \"No Transaction in this zone\");\n            function promiseFlow () {\n                return NativePromise.resolve().then(()=>{\n                    if(Dexie.currentTransaction !== null) ok(false, \"PSD zone leaked\");\n                    return new NativePromise(resolve => NativePromise.resolve().then(resolve));\n                });\n            };\n            otherZonePromise = promiseFlow();\n            for (let i=0;i<100;++i) {\n                otherZonePromise = otherZonePromise.then(promiseFlow);\n            }\n        });\n        // In parallell with the above 2*100 async tasks are being executed and verified,\n        // maintain the transaction zone below:        \n        return db.items.get(1).then(()=>{ // Just to prohibit IDB bug in Safari - must use transaction in initial tick!\n            return idbAndPromiseCompatible;\n        }).then(()=> {\n            ok(Dexie.currentTransaction === trans, \"Still same transaction 1\");\n            // Make sure native async functions maintains the zone:\n            let f = new Function('ok', 'equal', 'Dexie', 'trans','NativePromise', 'db',\n            `return (async ()=>{\n                ok(Dexie.currentTransaction === trans, \"Still same transaction 1.1\");\n                await Promise.resolve();\n                ok(Dexie.currentTransaction === trans, \"Still same transaction 1.2\");\n                await Dexie.Promise.resolve();\n                ok(Dexie.currentTransaction === trans, \"Still same transaction 1.3\");\n                await window.Promise.resolve();\n                ok(Dexie.currentTransaction === trans, \"Still same transaction 1.4\");\n                await db.items.get(1);\n            })()`);\n            return f(ok, equal, Dexie, trans, NativePromise, db);\n        }).catch (e => {\n            // Could not test native async functions in this browser.\n            if (e.name === 'IdbPromiseIncompatibleError') {\n                ok (true, `Promise and IndexedDB is incompatible on this browser. Native async await fails \"by design\" for indexedDB transactions`);\n            } else if (hasNativeAsyncFunctions)\n            ok(false, `Error: ${e.stack || e}`);\n            else \n                ok(true, `This browser does not support native async functions`);\n        }).then(()=>{\n            // NativePromise\n            ok(Dexie.currentTransaction === trans, \"Still same transaction 2\");\n            return Promise.resolve();\n        }).then(()=>{\n            // window.Promise\n            ok(Dexie.currentTransaction === trans, \"Still same transaction 3\");\n            return Dexie.Promise.resolve();\n        }).then(()=>{\n            // Dexie.Promise\n            ok(Dexie.currentTransaction === trans, \"Still same transaction 4\");\n            return otherZonePromise; // wait for the foreign zone promise to complete.\n        }).then(()=>{\n            ok(Dexie.currentTransaction === trans, \"Still same transaction 5\");\n        });\n    }).catch(e => {\n        ok(false, `Error: ${e.stack || e}`);\n    }).then(done);\n});\n\ntest(\"Should be able to await Promise.all()\", async (assert) => {\n    let done = assert.async();\n    if (!hasNativeAsyncFunctions) {\n        ok(true, \"Browser doesnt support native async-await\");\n        done();\n        return;\n    }    \n    let compatible = await idbAndPromiseCompatible.catch(()=>false);\n    if (!compatible) {\n        ok (true, `Promise and IndexedDB is incompatible on this browser. Native async await fails \"by design\" for indexedDB transactions`);\n        done();\n        return;\n    }\n    (new Function('ok', 'equal', 'Dexie', 'db',\n    `return db.transaction('r', db.items, async (trans)=>{\n        ok(Dexie.currentTransaction === trans, \"Correct initial transaction.\");\n        await db.items.get(1); // Just to prohibit IDB bug in Safari - must use transaction in initial tick!\n        var promises = [];\n        for (var i=0; i<50; ++i) {\n            promises.push(subAsync1(trans));\n        }\n        for (var i=0; i<50; ++i) {\n            promises.push(subAsync2(trans));\n        }\n        await Promise.all(promises);\n        ok(Dexie.currentTransaction === trans, \"Still same transaction 1 - after await Promise.all([100 promises...]);\");\n        await Promise.all([1,2,3, db.items.get(2)]);\n        ok(Dexie.currentTransaction === trans, \"Still same transaction 2 - after Promise.all(1,2,3,db.items.get(2))\");\n        await db.items.get(1);\n        ok(Dexie.currentTransaction === trans, \"Still same transaction 3 - after await db.items.get(1);\");\n        await 3;\n        ok(Dexie.currentTransaction === trans, \"Still same transaction 4 - after await 3;\");\n    });\n\n    async function subAsync1 (trans) {\n        await 1;\n        await 2;\n        await 3;\n        if (Dexie.currentTransaction !== trans) ok(false, \"Not in transaction\");\n    }\n\n    async function subAsync2 (trans) {\n        await 1;\n        await 2;\n        if (Dexie.currentTransaction !== trans) ok(false, \"Not in transaction 2\");\n        await db.items.get(1);\n    }\n    `))(ok, equal, Dexie, db)\n    .catch(e => {\n        ok(false, e.stack || e);\n    }).then(done);\n});\n\nspawnedTest(\"Should use Promise.all where applicable\", function* (){\n    yield db.transaction('rw', db.items, function* () {\n        let x = yield Promise.resolve(3);\n        yield db.items.bulkAdd([{id: 'a'}, {id: 'b'}]);\n        let all = yield Promise.all([db.items.get('a'), db.items.get('b')]);\n        equal (all.length, 2);\n        equal (all[0].id, 'a');\n        equal (all[1].id, 'b');\n        all = yield Promise.all([db.items.get('a'), db.items.get('b')]);\n        equal (all.length, 2);\n        equal (all[0].id, 'a');\n        equal (all[1].id, 'b');\n    });\n});\n\nspawnedTest(\"Even when keeping a reference to global Promise, still maintain PSD zone states\", function* (){\n   let Promise = window.Promise;\n   yield db.transaction('rw', db.items, () => {\n       var trans = Dexie.currentTransaction;\n       ok (trans !== null, \"Have a transaction\");\n       return Promise.resolve().then(()=>{\n           ok (Dexie.currentTransaction === trans, \"Still have the same current transaction.\");\n           return Promise.resolve().then(()=>Promise.resolve());\n       }).then(()=>{\n           ok (Dexie.currentTransaction === trans, \"Still have the same current transaction after multiple global.Promise.resolve() calls\");\n       });\n   });\n});\n\nspawnedTest (\"Sub Transactions with async await\", function*() {\n    try {\n        let compatible = yield idbAndPromiseCompatible.catch(()=>false);\n        if (!compatible) {\n            ok (true, `Promise and IndexedDB is incompatible on this browser. Native async await fails \"by design\" for indexedDB transactions`);\n            return;\n        }\n\n        yield new Function ('equal', 'ok', 'Dexie', 'db', `return (async ()=>{\n            await db.items.bulkAdd([{id: 1}, {id:2}, {id: 3}]);\n            let result = await db.transaction('rw', db.items, async ()=>{\n                let items = await db.items.toArray();\n                let numItems = await db.transaction('r', db.items, async ()=>{\n                    equal(await db.items.count(), await db.items.count(), \"Two awaits of count should equal\");\n                    equal(await db.items.count(), 3, \"Should be 3 items\");\n                    return await db.items.count();\n                });\n                let numItems2 = await db.transaction('r', db.items, async ()=>{\n                    equal(await db.items.count(), await db.items.count(), \"Two awaits of count should equal\");\n                    equal(await db.items.count(), 3, \"Should be 3 items\");\n                    return await db.items.count();\n                });\n                equal (numItems, numItems2, \"The total two inner transactions should be possible to run after each other\");\n                return numItems;\n            });\n            equal (result, 3, \"Result should be 3\");\n        })();`)(equal, ok, Dexie, db);\n    } catch (e) {\n        ok(e.name === 'SyntaxError', \"No support for native async functions in this browser\");        \n    }\n});\n\npromisedTest (\"Should patch global Promise within transaction scopes but leave them intact outside\", async() => {\n    ok(Promise !== Dexie.Promise, \"At global scope. Promise should not be Dexie.Promise\");\n    ok(window.Promise !== Dexie.Promise, \"At global scope. Promise should not be Dexie.Promise\");\n    var GlobalPromise = window.Promise;\n    await db.transaction('rw', db.items, async() =>{\n        ok(Promise === Dexie.Promise, \"Within transaction scope, Promise should be Dexie.Promise.\");\n        ok(window.Promise === Dexie.Promise, \"Within transaction scope, window.Promise should be Dexie.Promise.\");\n        ok(GlobalPromise !== Promise, \"Promises are different\");\n        ok(GlobalPromise.resolve === Promise.resolve, \"If holding a reference to the real global promise and doing Promise.resolve() it should be Dexie.Promise.resolve withing transaction scopes\")   \n        ok(GlobalPromise.reject === Promise.reject, \"If holding a reference to the real global promise and doing Promise.reject() it should be Dexie.Promise.reject withing transaction scopes\")\n        ok(GlobalPromise.all === Promise.all, \"If holding a reference to the real global promise and doing Promise.all() it should be Dexie.Promise.all withing transaction scopes\")\n        ok(GlobalPromise.race === Promise.race, \"If holding a reference to the real global promise and doing Promise.race() it should be Dexie.Promise.race withing transaction scopes\")\n    });\n});\n\npromisedTest (\"Should be able to use transpiled async await\", async () => {\n    await db.transaction('rw', db.items, async ()=>{\n        let trans = Dexie.currentTransaction;\n        ok(!!trans, \"Should have a current transaction\");\n        await db.items.add({id: 'foo'});\n        ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of Dexie.Promise\");\n        await Promise.resolve();\n        ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of Promise.resolve()\");\n        await 3;\n        ok(Dexie.currentTransaction === trans, \"Transaction persisted after await 3\");\n        await db.transaction('r', db.items, async (innerTrans) => {\n            ok(!!innerTrans, \"Should have inner transaction\");\n            equal(Dexie.currentTransaction, innerTrans, \"Inner transaction should be there\");\n            equal(innerTrans.parent, trans, \"Parent transaction should be correct\");\n            let x = await db.items.get(1);\n            ok(Dexie.currentTransaction === innerTrans, \"Transaction persisted in inner transaction\");\n        });\n        ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of sub transaction\");\n        await (async ()=>{\n            return await db.items.get(1);\n        })();\n        ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of async function\");\n        await (async ()=>{\n            await Promise.all([db.transaction('r', db.items, async() => {\n                await db.items.get(1);\n                await db.items.get(2);\n            }), db.transaction('r', db.items, async() => {\n                return await db.items.get(1);\n            })]);\n        })();\n        ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of async function 2\");\n\n        await Promise.resolve().then(()=>{\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted after window.Promise.resolve().then()\");\n            return (async ()=>{})(); // Resolve with native promise\n        }).then(()=>{\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted after native promise completion\");\n            return Promise.resolve();\n        }).then(()=>{\n            ok(Dexie.currentTransaction === trans, \"Transaction persisted after window.Promise.resolve().then()\");\n            return (async ()=>{})();\n        });\n        ok(Dexie.currentTransaction === trans, \"Transaction persisted between await calls of mixed promises\");\n\n    }).catch ('PrematureCommitError', ()=> {\n        ok(true, \"PROMISE IS INCOMPATIBLE WITH INDEXEDDB (https://github.com/dexie/Dexie.js/issues/317). Ignoring test.\");\n    })\n});\n\npromisedTest (\"Should be able to use some simpe native async await even without zone echoing \", async () => {\n    if (!hasNativeAsyncFunctions) {\n        ok(true, \"Browser doesnt support native async-await\");\n        return;\n    }\n    \n    let compatible = await idbAndPromiseCompatible.catch(()=>false);\n    if (!compatible) {\n        ok (true, `Promise and IndexedDB is incompatible on this browser. Native async await fails \"by design\" for indexedDB transactions`);\n        return;\n    }\n\n    await (new Function('ok', 'equal', 'Dexie', 'db',\n    `return db.transaction('r', db.items, trans=> (async (trans) => {\n        ok(Dexie.currentTransaction === trans, \"Correct initial transaction.\");\n        await Promise.all([1,2,3, db.items.get(2), Promise.resolve()]);\n        ok(Dexie.currentTransaction === trans, \"Still same transaction 1 - after Promise.all(1,2,3,db.items.get(2))\");\n        await db.items.get(1);\n        ok(Dexie.currentTransaction === trans, \"Still same transaction 2 - after await db.items.get(1);\");\n    })(trans));`))(ok, equal, Dexie, db)\n});\n\nconst GlobalPromise = window.Promise;\npromisedTest (\"Should behave outside transactions as well\", async () => {\n    if (!hasNativeAsyncFunctions) {\n        ok(true, \"Browser doesnt support native async-await\");\n        return;\n    }\n    let compatible = await idbAndPromiseCompatible.catch(()=>false);\n    if (!compatible) {\n        ok (true, `Promise and IndexedDB is incompatible on this browser. Native async await fails \"by design\" for indexedDB transactions`);\n        return;\n    }\n    \n    await (new Function('ok', 'equal', 'Dexie', 'db', 'GlobalPromise',\n    `async function doSomething() {\n        ok(!Dexie.currentTransaction, \"Should be at global scope.\");\n        ok(window.Promise !== Dexie.Promise, \"window.Promise should be original\");\n        ok(window.Promise === GlobalPromise, \"window.Promise should be original indeed\");\n        await db.items.get(1);\n        ok(!Dexie.currentTransaction, \"Should be at global scope.\");\n        await 3;\n        ok(!Dexie.currentTransaction, \"Should be at global scope.\");\n        await db.items.put({id:1, aj: \"aj\"});\n        ok(true, \"Could put an item\");\n        await db.items.update(1, {aj: \"oj\"});\n        ok(true, \"Could query an item\");\n        ok(!Dexie.currentTransaction, \"Should be at global scope.\");\n        await 4;\n        ok(!Dexie.currentTransaction, \"Should be at global scope.\");\n    }\n\n    return doSomething();\n    `))(ok, equal, Dexie, db, GlobalPromise)\n});\n\n"
  },
  {
    "path": "test/tests-binarykeys.js",
    "content": "import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {resetDatabase, supports, promisedTest} from './dexie-unittest-utils';\n\nvar db = new Dexie(\"TestDBBinaryKeys\");\ndb.version(1).stores({\n    items: \"id,data\"\n});\n\ndb.on('populate', ()=> {\n    db.items.bulkAdd([\n        {id: 'Uint8Array', data: new Uint8Array([1,2,3])},\n        {id: 'ArrayBuffer', data: new Uint8Array([4,5,6]).buffer},\n    ]);\n});\n\nmodule(\"binarykeys\", {\n    setup: function () {\n        stop();\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: function () {\n    }\n});\n\npromisedTest('Binary Primary Key (Int32Array)', async () => {\n    if (!supports(\"binarykeys\")) {\n        ok(true, \"This browser does not support IndexedDB 2.0\");\n        return;\n    }\n    try {\n        const id = new Int32Array([4, 2]);\n        equal (id[0], 4, \"Sanity check 1\");\n        equal (id[1], 2, \"Sanity check 2\");\n        \n        await db.items.add({id, data: \"string\"});\n\n        let back = await db.items.where({id: new Int32Array([4, 2])}).first();\n        equal (back.data, \"string\", \"Should retrieve an object by its binary primary key\");\n        equal (back.id[0], 4, \"Should get correct value 4\");\n        equal (back.id[1], 2, \"Should get correcg value 2\");\n    } finally {\n        // Cleanup. This is only needed because of a firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1395071\n        await db.items.clear();\n    }\n});\n\npromisedTest('Binary Primary Key (Float32Array)', async () => {\n    if (!supports(\"binarykeys\")) {\n        ok(true, \"This browser does not support IndexedDB 2.0\");\n        return;\n    }\n    try {\n        const id = new Float32Array([4.3, 2.5]);\n        equal (Math.round(id[0] * 100), 4.3 * 100, \"Sanity check 1\");\n        equal (Math.round(id[1] * 100), 2.5 * 100, \"Sanity check 2\");\n        \n        await db.items.add({id, data: \"string\"});\n\n        let back = await db.items // avoiding db.items.get(key) because it triggers bug in Firefox 55.\n            .where({id: new Float32Array([4.3, 2.5])})\n            .first();\n        equal (back.data, \"string\", \"Should retrieve an object by its binary primary key\");\n        equal (Math.round(back.id[0] * 100), 4.3 * 100, \"Should get correct float value 4.3\");\n        equal (Math.round(back.id[1] * 100), 2.5 * 100, \"Should get correcg float value 2.5\");\n    } finally {\n        // Cleanup. This is only needed because of a firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1395071\n        await db.items.clear();        \n    }\n});\n\n\npromisedTest('Binary Index', async () => {\n    if (!supports(\"binarykeys\")) {\n        ok(true, \"This browser does not support IndexedDB 2.0\");\n        return;\n    }\n\n    equal (await db.items.where('data').equals(new Uint8Array([1,2,3])).count(), 1, \"Should be able to query on binary key\");\n    let x = await db.items.where('data')\n        .anyOf([new Uint8Array([1,2,3]), new Uint8Array([4,5,6])])\n        .toArray();\n    equal (x.length, 2, \"Should find both keys even though the second has another binary type (IndexedDB should not distinguish them)\");\n});\n\npromisedTest('OR-query', async () => {\n    if (!supports(\"binarykeys\")) {\n        ok(true, \"This browser does not support IndexedDB 2.0\");\n        return;\n    }\n    try {\n        await db.items.bulkAdd([\n            {\n                id: new Float32Array([6.3, 10.5]),\n                data: \"something\"\n            },\n            {\n                id: new Uint8Array([1,2,3]),\n                data: \"somethingelse\"\n            }\n        ]);\n        \n\n        let a = await db.items.where('data').equals(\"something\")\n            .or('id').equals(new Uint8Array([1,2,3]))\n            .toArray();\n        \n        equal (a.length, 2, \"Should get two entries\");\n        ok (a.some(x => x.data === \"something\"), \"Should get 'something' in the result\");\n        ok (a.some(x => x.data === \"somethingelse\"), \"Should get 'somethingelse' in the result\");\n    } finally {\n        // Cleanup. This is only needed because of a firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1395071\n        await db.items.clear();        \n    }\n});"
  },
  {
    "path": "test/tests-blobs.js",
    "content": "import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {resetDatabase, promisedTest, isSafariPrivateMode} from './dexie-unittest-utils';\n\nvar db = new Dexie(\"TestDBBinary\");\ndb.version(1).stores({\n    items: \"id\"\n});\n\nmodule(\"blobs\", {\n    setup: function () {\n        stop();\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: function () {\n    }\n});\n\nfunction readBlob (blob) {\n    return new Promise ((resolve, reject) => {\n        let reader = new FileReader();\n        reader.onloadend = ev => resolve (ev.target.result);\n        reader.onerror = ev => reject(ev.target.error);\n        reader.onabort = ev => reject(new Error(\"Blob Aborted\"));\n        reader.readAsArrayBuffer(blob);\n    });\n}\n\nfunction arraysAreEqual (a1, a2) {\n    let length = a1.length;\n    if (a2.length !== length) return false;\n    for (var i=0; i<length; ++i) {\n        if (a1[i] !== a2[i]) return false;\n    }\n    return true;\n}\n\npromisedTest (`Test blobs`, async ()=>{\n    if (isSafariPrivateMode) {\n        ok(true, \"Safari private mode does not support Blobs\");\n        return;\n    }\n    let binaryData = new Uint8Array([1,2,3,4]);\n    let blob = new Blob([binaryData], {type: 'application/octet-binary'});\n    await db.items.add ({id: 1, blob: blob });\n    let back = await db.items.get(1);\n    let arrayBuffer = await readBlob(back.blob);\n    let resultBinaryData = new Uint8Array(arrayBuffer);\n    ok(arraysAreEqual(resultBinaryData, binaryData), \"Arrays should be equal\");\n});\n\npromisedTest (`Test blob with creating hook applied`, async ()=>{\n    if (isSafariPrivateMode) {\n        ok(true, \"Safari private mode does not support Blobs\");\n        return;\n    }\n    function updatingHook (modifications, primKey, obj, trans) {\n        ok (modifications.blob instanceof Blob, \"When hook is called, the modifications should point to a Blob object\");\n    }\n    try {\n        db.items.hook('updating', updatingHook);\n        let binaryData = new Uint8Array([1,2,3,4]);\n        let blob = new Blob([binaryData], {type: 'application/octet-binary'});\n        await db.items.add ({id: 1 });\n        await db.items.put ({id: 1, blob: blob });\n        let back = await db.items.get(1);\n        let arrayBuffer = await readBlob(back.blob);\n        let resultBinaryData = new Uint8Array(arrayBuffer);\n        ok(arraysAreEqual(resultBinaryData, binaryData), \"Arrays should be equal\");\n    } finally {\n        db.items.hook('updating').unsubscribe(updatingHook);\n    }\n});\n\n"
  },
  {
    "path": "test/tests-chrome-transaction-durability.js",
    "content": "import Dexie from 'dexie';\nimport {start, asyncTest, ok} from 'QUnit';\nimport {isChrome, resetDatabase} from './dexie-unittest-utils';\n\n\"use strict\";\n\nmodule(\"chrome-transaction-durability\", {\n    setup: function () {\n    },\n    teardown: function () {\n    }\n});\n\nasyncTest(\"Transaction should use relaxed durability if specified\", function() {\n    if (!isChrome) {\n        ok(true, \"This browser does not support Chrome transaction durability\");\n        start();\n        return;\n    }\n\n    const db = setupDb('relaxed')\n    db.transaction('rw', db.users, trans => {\n        if (trans.idbtrans.durability === void 0) {\n            ok(true, \"This version of Chromium does not support transaction durability\");\n        } else {\n            ok(trans.idbtrans.durability === 'relaxed', \"Transaction has relaxed durability\");\n        }\n    }).catch(function (err) {\n        ok(false, err);\n    }).finally(function () {\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    });\n});\n\n\nasyncTest(\"Transaction should use strict durability if specified\", function() {\n    if (!isChrome) {\n        ok(true, \"This browser does not support Chrome transaction durability\");\n        start();\n        return;\n    }\n\n    const db = setupDb('strict')\n    db.transaction('rw', db.users, trans => {\n        if (trans.idbtrans.durability === void 0) {\n            ok(true, \"This version of Chromium does not support transaction durability\");\n        } else {\n            ok(trans.idbtrans.durability === 'strict', \"Transaction has strict durability\");\n        }\n    }).catch(function (err) {\n        ok(false, err);\n    }).finally(function () {\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    });\n});\n\n\nasyncTest(\"Transaction should use default durability if not specified\", function() {\n    if (!isChrome) {\n        ok(true, \"This browser does not support Chrome transaction durability\");\n        start();\n        return;\n    }\n\n    const db = setupDb()\n    db.transaction('rw', db.users, trans => {\n        if (trans.idbtrans.durability === void 0) {\n            ok(true, \"This version of Chromium does not support transaction durability\");\n        } else {\n            ok(trans.idbtrans.durability === 'default', \"Transaction has default durability\");\n        }\n    }).catch(function (err) {\n        ok(false, err);\n    }).finally(function () {\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    });\n});\n\nconst setupDb = (chromeTransactionDurability) => {\n    const db = new Dexie(\"TestDBTrans\", { chromeTransactionDurability });\n    db.version(1).stores({\n        users: \"username\",\n    });\n    return db;\n}"
  },
  {
    "path": "test/tests-cmp.js",
    "content": "import Dexie from 'dexie';\nimport { module, test, equal, ok } from 'QUnit';\n\nfunction fillArrayBuffer(ab, val) {\n  const view = new Uint8Array(ab);\n  for (let i = 0; i < view.byteLength; ++i) {\n    view[i] = val;\n  }\n}\n\nmodule('cmp');\n\nconst { cmp } = Dexie;\n\ntest('it should support indexable types', () => {\n  // numbers\n  ok(cmp(1, 1) === 0, 'Equal numbers should return 0');\n  ok(cmp(1, 2) === -1, 'Less than numbers should return -1');\n  ok(cmp(-1, -2000) === 1, 'Greater than numbers should return 1');\n  // strings\n  ok(cmp('A', 'A') === 0, 'Equal strings should return 0');\n  ok(cmp('A', 'B') === -1, 'Less than strings should return -1');\n  ok(cmp('C', 'A') === 1, 'Greater than strings should return 1');\n  // Dates\n  ok(cmp(new Date(1), new Date(1)) === 0, 'Equal dates should return 0');\n  ok(cmp(new Date(1), new Date(2)) === -1, 'Less than dates should return -1');\n  ok(\n    cmp(new Date(1000), new Date(500)) === 1,\n    'Greater than dates should return 1'\n  );\n  // Arrays\n  ok(cmp([1, 2, '3'], [1, 2, '3']) === 0, 'Equal arrays should return 0');\n  ok(cmp([-1], [1]) === -1, 'Less than arrays should return -1');\n  ok(cmp([1], [-1]) === 1, 'Greater than arrays should return 1');\n  ok(cmp([1], [1, 0]) === -1, 'If second array is longer with same leading entries, return -1');\n  ok(cmp([1, 0], [1]) === 1, 'If first array is longer with same leading entries, return 1');\n  ok(cmp([1], [0,0]) === 1, 'If first array is shorter but has greater leading entries, return 1');\n  ok(cmp([0,0], [1]) === -1, 'If second array is shorter but has greater leading entries, return -1');\n\n  /* Binary types\n  | DataView\n  | Uint8ClampedArray\n  | Uint8Array\n  | Int8Array\n  | Uint16Array\n  | Int16Array\n  | Uint32Array\n  | Int32Array\n  | Float32Array\n  | Float64Array;\n*/\n  const viewTypes = [\n    'DataView',\n    'Uint8ClampedArray',\n    'Uint8Array',\n    'Int8Array',\n    'Uint16Array',\n    'Uint32Array',\n    'Int32Array',\n    'Float32Array',\n    'Float64Array',\n  ]\n    .map((typeName) => [typeName, self[typeName]])\n    .filter(([_, ctor]) => !!ctor); // Don't try to test types not supported by the browser\n\n  const zeroes1 = new ArrayBuffer(16);\n  const zeroes2 = new ArrayBuffer(16);\n  const ones = new ArrayBuffer(16);\n  fillArrayBuffer(zeroes1, 0);\n  fillArrayBuffer(zeroes2, 0);\n  fillArrayBuffer(ones, 1);\n\n  for (const [typeName, ArrayBufferView] of viewTypes) {\n    // Equals\n    let v1 = new ArrayBufferView(zeroes1);\n    let v2 = new ArrayBufferView(zeroes2);\n    ok(cmp(v1, v2) === 0, `Equal ${typeName}s should return 0`);\n    // Less than\n    v1 = new ArrayBufferView(zeroes1);\n    v2 = new ArrayBufferView(ones);\n    ok(cmp(v1, v2) === -1, `Less than ${typeName}s should return -1`);\n    // Less than\n    v1 = new ArrayBufferView(ones);\n    v2 = new ArrayBufferView(zeroes1);\n    ok(cmp(v1, v2) === 1, `Greater than ${typeName}s should return 1`);\n  }\n});\ntest(\"it should respect IndexedDB's type order\", () => {\n  const zoo = [\n    'meow',\n    1,\n    new Date(),\n    Infinity,\n    -Infinity,\n    new ArrayBuffer(1),\n    [[]],\n  ];\n  const [minusInfinity, num, infinity, date, string, binary, array] =\n    zoo.sort(cmp);\n  equal(minusInfinity, -Infinity, 'Minus infinity is sorted first');\n  equal(num, 1, 'Numbers are sorted second');\n  equal(infinity, Infinity, 'Infinity is sorted third');\n  ok(date instanceof Date, 'Date is sorted fourth');\n  ok(typeof string === 'string', 'strings are sorted fifth');\n  ok(binary instanceof ArrayBuffer, 'binaries are sorted sixth');\n  ok(Array.isArray(array), 'Arrays are sorted seventh');\n});\n\ntest('it should return NaN on invalid types', () => {\n  ok(\n    isNaN(cmp(1, { foo: 'bar' })),\n    'Comparing a number against an object returns NaN (would throw in indexedDB)'\n  );\n  ok(\n    isNaN(cmp({ foo: 'bar' }, 1)),\n    'Comparing an object against a number returns NaN also'\n  );\n});\n\ntest('it should treat different binary types as if they were equal', () => {\n  const viewTypes = [\n    'DataView',\n    'Uint8ClampedArray',\n    'Uint8Array',\n    'Int8Array',\n    'Uint16Array',\n    'Uint32Array',\n    'Int32Array',\n    'Float32Array',\n    'Float64Array',\n  ]\n    .map((typeName) => [typeName, self[typeName]])\n    .filter(([_, ctor]) => !!ctor); // Don't try to test types not supported by the browser\n\n  const zeroes1 = new ArrayBuffer(16);\n  const zeroes2 = new ArrayBuffer(16);\n  fillArrayBuffer(zeroes1, 0);\n  fillArrayBuffer(zeroes2, 0);\n\n  for (const [typeName, ArrayBufferView] of viewTypes) {\n    let v1 = new ArrayBufferView(zeroes1);\n    ok(cmp(v1, zeroes1) === 0, `Comparing ${typeName} with ArrayBuffer should return 0 if they have identical data`);\n  }\n});\n\ntest('it should return NaN if comparing arrays where any item or sub array item includes an invalid key', ()=> {\n  ok(cmp([1, [[2, \"3\"]]], [1,[[2, \"3\"]]]) === 0, \"It can deep compare arrays with valid keys (equals)\");\n  ok(cmp([1, [[2, \"3\"]]], [1,[[2, 3]]]) === 1, \"It can deep compare arrays with valid keys (greater than)\");\n  ok(isNaN(cmp([1, [[2, 3]]], [1,[[{foo: \"bar\"}, 3]]])), \"It returns NaN when any item in the any of the arrays are invalid keys\");\n});\n"
  },
  {
    "path": "test/tests-collection.js",
    "content": "﻿import Dexie, {replacePrefix, add, remove} from 'dexie';\nimport {module, stop, start, test, asyncTest, equal, ok} from 'QUnit';\nimport {resetDatabase, supports, spawnedTest, promisedTest} from './dexie-unittest-utils';\nimport { deepEqual } from './deepEqual';\n\nvar db = new Dexie(\"TestDBCollection\");\ndb.version(1).stores({ users: \"id,first,last,[foo+bar],&username,*&email,*pets,parentPath\" });\n\nvar User = db.users.defineClass({\n    id:         Number,\n    first:      String,\n    last:       String,\n    username:   String,\n    email:      [String],\n    pets:       [String],\n});\ndb.on(\"populate\", function () {\n    db.users.add({ id: 1, first: \"David\", last: \"Fahlander\", username: \"dfahlander\", email: [\"david@awarica.com\", \"daw@thridi.com\"], pets: [\"dog\"] });\n    db.users.add({ id: 2, first: \"Karl\", last: \"Cedersköld\", username: \"kceder\", email: [\"karl@ceder.what\"], pets: [] });\n});\n\nmodule(\"collection\", {\n    setup: function () {\n        stop();\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: function () {\n    }\n});\n\nspawnedTest(\"and with values\", function*(){\n    let array = yield db.users.where(\"last\").inAnyRange([[\"a\",\"g\"],[\"A\",\"G\"]])\n        .and(user => user.username === \"dfahlander\")\n        .toArray();\n    equal (array.length, 1, \"Should find one user with given criteria\");\n});\n\nspawnedTest(\"and with keys\", function*(){\n    let keys = yield db.users.where(\"last\").inAnyRange([[\"a\",\"g\"],[\"A\",\"G\"]])\n        .and(user => user.username === \"dfahlander\")\n        .keys();\n    equal (keys.length, 1, \"Should find one user with given criteria\");\n});\n\nspawnedTest(\"and with delete\", function*() {\n    yield db.users.orderBy('username')\n        .and(u => ok(!!u, \"User should exist here\"))\n        .delete();\n});\n\nasyncTest(\"each\", 3, function () {\n    var array = [];\n    db.users.orderBy(\"id\").each(function (user) {\n        array.push(user);\n    }).then(function () {\n        equal(array.length, 2, \"Got two users\");\n        equal(array[0].first, \"David\", \"First is David\");\n        equal(array[1].first, \"Karl\", \"Second is Karl\");\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\nasyncTest(\"count\", 1, function () {\n    db.users.count(function (count) {\n        equal(count, 2, \"Two objects in table\");\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\nasyncTest(\"toArray\", 3, function () {\n    db.users.orderBy(\"last\").toArray(function (a) {\n        equal(a.length, 2, \"Array length is 2\");\n        equal(a[0].first, \"Karl\", \"First is Karl\");\n        equal(a[1].first, \"David\", \"Second is David\");\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\nasyncTest(\"limit\", 6, function () {\n    db.transaction(\"r\", db.users, function () {\n        db.users.orderBy(\"last\").limit(1).toArray(function (a) {\n            equal(a.length, 1, \"Array length is 1\");\n            equal(a[0].first, \"Karl\", \"First is Karl\");\n        });\n\n        db.users.orderBy(\"last\").limit(10).toArray(function (a) {\n            equal(a.length, 2, \"Array length is 2\");\n        });\n\n        db.users.orderBy(\"last\").limit(0).toArray(function (a) {\n            equal(a.length, 0, \"Array length is 0\");\n        });\n\n        db.users.orderBy(\"last\").limit(-1).toArray(function (a) {\n            equal(a.length, 0, \"Array length is 0\");\n        });\n\n        db.users.orderBy(\"id\").limit(-1).toArray(function (a) {\n            equal(a.length, 0, \"Array length is 0\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"offset().limit() with advanced combinations\", 22, function () {\n    db.transaction(\"rw\", db.users, function () {\n        for (var i = 0; i < 10; ++i) {\n            db.users.add({ id: 3+i, first: \"First\" + i, last: \"Last\" + i, username: \"user\" + i, email: [\"user\" + i + \"@abc.se\"] });\n        }\n\n        // Using algorithm + count()\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").count(function (count) {\n            equal(count, 10, \"Counting all 10\");\n        });\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").limit(5).count(function (count) {\n            equal(count, 5, \"algorithm + count(): limit(5).count()\");\n        });\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").offset(7).count(function (count) {\n            equal(count, 3, \"algorithm + count(): offset(7).count()\");\n        });\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").offset(6).limit(4).count(function (count) {\n            equal(count, 4, \"algorithm + count(): offset(6).limit(4)\");\n        });\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").offset(7).limit(4).count(function (count) {\n            equal(count, 3, \"algorithm + count(): offset(7).limit(4)\");\n        });\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").offset(17).limit(4).count(function (count) {\n            equal(count, 0, \"algorithm + count(): offset(17).limit(4)\");\n        });\n        // Using algorithm + toArray()\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").limit(5).toArray(function (a) {\n            equal(a.length, 5, \"algorithm + toArray(): limit(5)\");\n        });\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").offset(7).toArray(function (a) {\n            equal(a.length, 3, \"algorithm + toArray(): offset(7)\");\n        });\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").offset(6).limit(4).toArray(function (a) {\n            equal(a.length, 4, \"algorithm + toArray(): offset(6).limit(4)\");\n        });\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").offset(7).limit(4).toArray(function (a) {\n            equal(a.length, 3, \"algorithm + toArray(): offset(7).limit(4)\");\n        });\n        db.users.where(\"first\").startsWithIgnoreCase(\"first\").offset(17).limit(4).toArray(function (a) {\n            equal(a.length, 0, \"algorithm + toArray(): offset(17).limit(4)\");\n        });\n        // Using IDBKeyRange + count()\n        db.users.where(\"first\").startsWith(\"First\").count(function (count) {\n            equal(count, 10, \"IDBKeyRange + count() - count all 10\");\n        });\n        db.users.where(\"first\").startsWith(\"First\").limit(5).count(function (count) {\n            equal(count, 5, \"IDBKeyRange + count(): limit(5)\");\n        });\n        db.users.where(\"first\").startsWith(\"First\").offset(7).count(function (count) {\n            equal(count, 3, \"IDBKeyRange + count(): offset(7)\");\n        });\n        db.users.where(\"first\").startsWith(\"First\").offset(6).limit(4).count(function (count) {\n            equal(count, 4, \"IDBKeyRange + count(): offset(6)\");\n        });\n        db.users.where(\"first\").startsWith(\"First\").offset(7).limit(4).count(function (count) {\n            equal(count, 3, \"IDBKeyRange + count(): offset(7).limit(4)\");\n        });\n        db.users.where(\"first\").startsWith(\"First\").offset(17).limit(4).count(function (count) {\n            equal(count, 0, \"IDBKeyRange + count(): offset(17).limit(4)\");\n        });\n        // Using IDBKeyRange + toArray()\n        db.users.where(\"first\").startsWith(\"First\").limit(5).toArray(function (a) {\n            equal(a.length, 5, \"IDBKeyRange + toArray(): limit(5)\");\n        });\n        db.users.where(\"first\").startsWith(\"First\").offset(7).toArray(function (a) {\n            equal(a.length, 3, \"IDBKeyRange + toArray(): offset(7)\");\n        });\n        db.users.where(\"first\").startsWith(\"First\").offset(6).limit(4).toArray(function (a) {\n            equal(a.length, 4, \"IDBKeyRange + toArray(): offset(6).limit(4)\");\n        });\n        db.users.where(\"first\").startsWith(\"First\").offset(7).limit(4).toArray(function (a) {\n            equal(a.length, 3, \"IDBKeyRange + toArray(): offset(7).limit(4)\");\n        });\n        db.users.where(\"first\").startsWith(\"First\").offset(17).limit(4).toArray(function (a) {\n            equal(a.length, 0, \"IDBKeyRange + toArray(): offset(17).limit(4)\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"first\", 1, function () {\n    db.users.orderBy(\"last\").first(function (karlCeder) {\n        equal(karlCeder.first, \"Karl\", \"Got Karl\");\n    }).finally(start);\n});\nasyncTest(\"last\", function () {\n    db.users.orderBy(\"last\").last(function (david) {\n        equal(david.first, \"David\", \"Got David\");\n    }).finally(start);\n});\nasyncTest(\"and\", 2, function () {\n    db.transaction(\"r\", db.users, function () {\n\n        db.users.where(\"first\")\n            .equalsIgnoreCase(\"david\")\n            .and(function (user) {\n                return user.email.indexOf(\"apa\") >= 0\n            })\n            .first(function (user) {\n                equal(user, null, \"Found no user with first name 'david' and email 'apa'\");\n            });\n\n        db.users.where(\"first\")\n            .equalsIgnoreCase(\"david\")\n            .and(function (user) {\n                return user.email.indexOf(\"daw@thridi.com\") >= 0\n            })\n            .first(function (user) {\n                equal(user.first, \"David\", \"Found user with first name 'david' and email 'daw@thridi.com'\");\n            });\n\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"reverse\", function () {\n    db.transaction(\"r\", db.users, function () {\n        db.users.orderBy(\"first\").reverse().first(function (user) {\n            equal(user.first, \"Karl\", \"Got Karl\");\n        });\n\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nif (!supports(\"multiEntry\")) {\n    test(\"distinct\", ()=>ok(true, \"SKIPPED - MULTIENTRY UNSUPPORTED\"));\n} else {\n    asyncTest(\"distinct\", function () {\n        db.transaction(\"r\", db.users, function () {\n\n            db.users.where(\"email\").startsWithIgnoreCase(\"d\").toArray(function (a) {\n                equal(a.length, 2, \"Got two duplicates of David since he has two email addresses starting with 'd' (Fails on IE10, IE11 due to not supporting multivalued array indexes)\");\n            });\n            db.users.where(\"email\").startsWithIgnoreCase(\"d\").distinct().toArray(function (a) {\n                equal(a.length, 1, \"Got single instance of David since we used the distinct() method. (Fails on IE10, IE11 due to not supporting multivalued array indexes)\");\n            });\n\n        }).catch(function (e) {\n            ok(false, e);\n        }).finally(start);\n    });\n}\n\nasyncTest(\"modify\", function () {\n    db.transaction(\"rw\", db.users, function () {\n        var currentTime = new Date();\n        db.users.toCollection().modify({\n            lastUpdated: currentTime\n        }).then(function (count) {\n            equal(count, 2, \"Promise supplied the number of modifications made\");\n        });\n        db.users.toArray(function (a) {\n            equal(a.length, 2, \"Length ok\");\n            equal(a[0].first, \"David\", \"First is David\");\n            equal(a[0].lastUpdated.getTime(), currentTime.getTime(), \"Could set new member lastUpdated on David\");\n            equal(a[1].lastUpdated.getTime(), currentTime.getTime(), \"Could set new member lastUpdated on Karl\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"modify-using-function\", function () {\n    db.transaction(\"rw\", db.users, function () {\n        var currentTime = new Date();\n        db.users.toCollection().modify(function(user) {\n            user.fullName = user.first + \" \" + user.last;\n            user.lastUpdated = currentTime;\n        });\n        db.users.toArray(function (a) {\n            equal(a.length, 2);\n            equal(a[0].first, \"David\");\n            equal(a[0].fullName, \"David Fahlander\", \"Could modify David with a getter function\");\n            equal(a[0].lastUpdated.getTime(), currentTime.getTime(), \"Could set new member lastUpdated on David\");\n            equal(a[1].lastUpdated.getTime(), currentTime.getTime(), \"Could set new member lastUpdated on Karl\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"modify-causing-error\", 2, function () {\n    db.transaction(\"rw\", db.users, function () {\n        var currentTime = new Date();\n        db.users.toCollection().modify(function (user) {\n            user.id = 1;\n            user.fullName = user.first + \" \" + user.last;\n            user.lastUpdated = currentTime;\n        });\n        db.users.toArray(function (a) {\n            ok(false, \"Should not come here, beacuse we should get error when setting all primkey to 1\");\n        });\n    }).catch(Dexie.ModifyError, function (e) {\n        ok(true, \"Got ModifyError: \" + e);\n        equal(e.successCount, 1, \"Succeeded with the first entry but not the second\");\n    }).catch(function (e) {\n        ok(false, \"Another error than the expected was thrown: \" + e);\n    }).finally(start);\n});\n\npromisedTest(\"modify-primary-key\", async ()=>{\n    await db.users.add({id: 87, first: \"Olle\"});\n    const res = await db.users.where({id: 87}).modify(user => user.id = 88);\n    ok(res, \"Successfully modified the primary key of a user\");\n    const user88 = await db.users.get(88);\n    ok(!!user88, \"Should be able to retrieve user 88 using db.users.get(88)\");\n    equal(user88.first, \"Olle\", \"Retrieved user should be David\");\n});\n\n//\n// Issue #594 (A Safari issue)\n//\npromisedTest(\"modify-with-where(issue-594)\", async ()=>{\n    db.users.add({ id: 3, first: \"David\", last: \"Fahlander2\", username: \"dfahlander2\", email: [\"david2@awarica.com\"], pets: [] });\n    db.users.add({ id: 4, first: \"David\", last: \"Fahlander3\", username: \"dfahlander3\", email: [\"david3@awarica.com\"], pets: [] });\n    const numDavids = (await db.users.where('first').equals(\"David\").toArray()).length;\n    equal(numDavids, 3, \"There should be 3 Davids\");\n    const numModifications = await db.users.where('first').equals(\"David\").modify((object) => { object.anotherProperty = 'test'; });\n    equal(numModifications, 3, \"There should have been 3 modifications\");\n    // Also verify that the modification did really happen:\n    const davids = await db.users.where({first: 'David'}).toArray();\n    ok(davids.every(david => david.anotherProperty === 'test'), \"All Davids where modified indeed\");\n});\n\n\nasyncTest(\"delete\", 2, function () {\n    db.users.orderBy(\"id\").delete().then(function (count) {\n        equal(count, 2, \"All two records deleted\");\n        return db.users.count(function (count) {\n            equal(count, 0, \"No users in collection anymore\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"delete(2)\", 3, function () {\n    db.transaction(\"rw\", db.users, function () {\n        db.users.add({ id: 3, first: \"dAvid\", last: \"Helenius\", username: \"dahel\" });\n        db.users.where(\"first\").equalsIgnoreCase(\"david\").delete().then(function (deleteCount) {\n            equal(deleteCount, 2, \"Two items deleted (Both davids)\");\n        });\n        db.users.toArray(function (a) {\n            equal(a.length, 1, \"Deleted one user\");\n            equal(a[0].first, \"Karl\", \"Only Karl is there now\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"delete(3, combine with OR)\", 3, function () {\n    db.transaction(\"rw\", db.users, function () {\n        db.users.add({ id: 3, first: \"dAvid\", last: \"Helenius\", username: \"dahel\" });\n        db.users.where(\"first\").equals(\"dAvid\").or(\"username\").equals(\"kceder\").delete().then(function (deleteCount) {\n            equal(deleteCount, 2, \"Two items deleted (Both dAvid Helenius and Karl Cedersköld)\");\n        });\n        db.users.toArray(function (a) {\n            equal(a.length, 1, \"Only one item left since dAvid and Karl have been deleted\");\n            equal(a[0].first, \"David\", \"Only David Fahlander is there now!\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\n\nasyncTest(\"keys\", 3, function () {\n    db.users.orderBy(\"first\").keys(function(a) {\n        equal(a.length, 2, \"There should be two results\");\n        equal(a[0], \"David\", \"First is David\");\n        equal(a[1], \"Karl\", \"Second is Karl\");\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"uniqueKeys\", 7, function () {\n    db.transaction(\"rw\", db.users, function () {\n        db.users.add({ id: 3, first: \"David\", last: \"Helenius\", username: \"dahel\" });\n        db.users.orderBy(\"first\").keys(function (a) {\n            equal(a.length, 3, \"When not using uniqueKeys, length is 3\");\n            equal(a[0], \"David\", \"First is David\");\n            equal(a[1], \"David\", \"Second is David\");\n            equal(a[2], \"Karl\", \"Third is Karl\");\n        });\n        db.users.orderBy(\"first\").uniqueKeys(function (a) {\n            equal(a.length, 2, \"When using uniqueKeys, length is 2\");\n            equal(a[0], \"David\", \"First is David\");\n            equal(a[1], \"Karl\", \"Second is Karl\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"primaryKeys\", 3, function () {\n    db.users.orderBy(\"last\").primaryKeys(function(a) {\n        equal(a.length, 2, \"There should be two results\");\n        equal(a[0], 2, \"Second is Karl\");\n        equal(a[1], 1, \"First is David\");\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"primaryKeys2\", 3, function () {\n    db.users.orderBy(\"first\").primaryKeys(function(a) {\n        equal(a.length, 2, \"There should be two results\");\n        equal(a[0], 1, \"First is David\");\n        equal(a[1], 2, \"Second is Karl\");\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"eachKey and eachUniqueKey\", function () {\n    db.transaction(\"rw\", db.users, function () {\n        db.users.add({ id: 3, first: \"Ylva\", last: \"Fahlander\", username: \"yfahlander\" });\n        var a = [];\n        db.users.orderBy(\"last\").eachKey(function (lastName) {\n            a.push(lastName);\n        }).then(function () {\n            equal(a.length, 3, \"When using eachKey, number of keys are 3\");\n        });\n        var a2 = [];\n        db.users.orderBy(\"last\").eachUniqueKey(function (lastName) {\n            a2.push(lastName);\n        }).then(function () {\n            equal(a2.length, 2, \"When using eachUniqueKey, number of keys are 2\");\n        });\n\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"or\", 14, function () {\n    db.transaction(\"rw\", db.users, function () {\n        db.users.add({ id: 3, first: \"Apan\", last: \"Japan\", username: \"apanjapan\" });\n        db.users.where(\"first\").equalsIgnoreCase(\"david\").or(\"last\").equals(\"Japan\").sortBy(\"first\", function (a) {\n            equal(a.length, 2, \"Got two users\");\n            equal(a[0].first, \"Apan\", \"First is Apan\");\n            equal(a[1].first, \"David\", \"Second is David\");\n        });\n        db.users.where(\"first\").equalsIgnoreCase(\"david\").or(\"last\").equals(\"Japan\").or(\"id\").equals(2).sortBy(\"id\", function (a) {\n            equal(a.length, 3, \"Got three users\");\n            equal(a[0].first, \"David\", \"First is David\");\n            equal(a[1].first, \"Karl\", \"Second is Karl\");\n            equal(a[2].first, \"Apan\", \"Third is Apan\");\n        });\n        var userArray = [];\n        db.users.where(\"id\").anyOf(1, 2, 3, 4).or(\"username\").anyOf(\"dfahlander\", \"kceder\", \"apanjapan\").each(function (user) {\n            ok(true, \"Found: \" + JSON.stringify(user));\n            userArray.push(user);\n        }).then(function () {\n            equal(userArray.length, 3, \"Got all three users\");\n            ok(userArray.some(function (user) { return user.first === \"David\" }), \"David was found\");\n            ok(userArray.some(function (user) { return user.first === \"Karl\" }), \"Karl was found\");\n            ok(userArray.some(function (user) { return user.first === \"Apan\" }), \"Apan was found\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"or-issue#15-test\", function () {\n    var db = new Dexie(\"MyDB_issue15\");\n    db.version(1).stores({\n        phones: \"++id, additionalFeatures, android, availability, battery, camera, connectivity, description, display, hardware, id, images, name, sizeAndWeight, storage\"\n    });\n    db.on('populate', function () {\n        ok(true, \"on(populate) called\");\n        for (var i = 0; i < 100; ++i) {\n            db.phones.add({ id: 3 + i, name: \"Name\" + randomString(16), additionalFeatures: [randomString(10)], android: 1, availability: 0, battery: 1, camera: 1 });\n        }\n\n        var seed = 1;\n        function pseudoRandom() {\n            var x = Math.sin(seed++) * 10000;\n            return x - Math.floor(x);\n        }\n\n        function randomString(count) {\n            var ms = [];\n            for (var i = 0; i < count; ++i) {\n                ms.push(String.fromCharCode(32 + Math.floor(pseudoRandom() * 95)));\n            }\n            return ms.join('');\n        }\n    });\n\n    db.open().catch(function (err) {\n        ok(false, \"DB ERROR: \" + err);\n    });\n\n\n    var numRuns = 10;\n\n    for (var i = 0; i < numRuns; ++i) {\n\n        db.phones.where(\"name\").startsWithIgnoreCase(\"name\").or(\"id\").below(50).toArray(function (a) {\n\n            equal(a.length, 100, \"Found 100 phones\");\n\n        }).catch(function (err) {\n\n            ok(false, \"error:\" + err.stack);\n\n        }).finally(function () {\n            if (--numRuns == 0) {\n                // All test runs finished. Delete DB and exit unit test.\n                db.delete();\n                start();\n            }\n        });\n    }\n\n});\n\nasyncTest(\"until\", function () {\n    db.transaction(\"rw\", db.users, function () {\n        db.users.add({ id: 3, first: \"Apa1\", username: \"apa1\" });\n        db.users.add({ id: 4, first: \"Apa2\", username: \"apa2\" });\n        db.users.add({ id: 5, first: \"Apa3\", username: \"apa3\" });\n\n        // Checking that it stops immediately when first item is the stop item:\n        db.users.orderBy(\":id\").until(function (user) { return user.first == \"David\" }).toArray(function (a) {\n            equal(0, a.length, \"Stopped immediately because David has ID 1\");\n        });\n\n        // Checking that specifying includeStopEntry = true will include the stop entry.\n        db.users.orderBy(\":id\").until(function (user) { return user.first == \"David\" }, true).toArray(function (a) {\n            equal(1, a.length, \"Got the stop entry when specifying includeStopEntry = true\");\n            equal(\"David\", a[0].first, \"Name is David\");\n        });\n\n        // Checking that when sorting on first name and stopping on David, we'll get the apes.\n        db.users.orderBy(\"first\").until(function (user) { return user.first == \"David\" }).toArray(function (a) {\n            equal(3, a.length, \"Got 3 users only (3 apes) because the Apes comes before David and Karl when ordering by first name\");\n            equal(\"apa1\", a[0].username, \"First is apa1\");\n            equal(\"apa2\", a[1].username, \"Second is apa2\");\n            equal(\"apa3\", a[2].username, \"Third is apa3\");\n        });\n\n        // Checking that reverse() affects the until() method as expected:\n        db.users.orderBy(\"first\").reverse().until(function (user) { return user.username == \"apa2\" }).toArray(function (a) {\n            equal(3, a.length, \"Got 3 users only (David, Karl and Apa3)\");\n            equal(\"Karl\", a[0].first, \"When reverse(), First is Karl.\");\n            equal(\"David\", a[1].first, \"When reverse(), Second is David\");\n            equal(\"Apa3\", a[2].first, \"When reverse(), Third is Apa3\");\n        });\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n    }).finally(start);\n});\n\nasyncTest(\"firstKey\", function () {\n    db.users.orderBy('last').firstKey(function (key) {\n        equal(\"Cedersköld\", key, \"First lastName is Cedersköld\");\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n    }).finally(function () {\n        start();\n    });\n});\n\nasyncTest(\"lastKey\", function () {\n    db.users.orderBy('last').lastKey(function (key) {\n        equal(\"Fahlander\", key, \"Last lastName is Fahlander\");\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n    }).finally(function () {\n        start();\n    });\n});\n\nasyncTest(\"firstKey on primary key\", function () {\n    db.users.toCollection().firstKey(function (key) {\n        equal(key, 1, \"First key is 1\");\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n    }).finally(function () {\n        start();\n    });\n});\n\nasyncTest(\"lastKey on primary key\", function () {\n    db.users.toCollection().lastKey(function (key) {\n        equal(key, 2, \"lastKey is 2\");\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n    }).finally(function () {\n        start();\n    });\n});\n\nasyncTest(\"Promise chain from within each() operation\", 2, function () {\n    db.transaction('r', db.users, function() {\n        db.users.each(function(user) {\n            db.users.where('id').equals(user.id).first(function(usr) {\n                return db.users.where('id').equals(usr.id).first();\n            }).then(function(u) {\n                return u;\n            }).then(function(u2) {\n                equal(u2.id, user.id, \"Could get the same user after some chains of Promise.resolve()\");\n            });\n        });\n    }).catch(function(err) {\n        ok(false, err.stack || err);\n    }).finally(start);\n});\n\npromisedTest(\"Issue 1381: Collection.filter().primaryKeys() on virtual index\", async () => {\n    if (!supports(\"compound\")) {\n        ok(true, \"Skipping this test as the browser does not support compound indexes\");\n        return;\n    }\n    // The original repro: https://jsitor.com/qPJXVESEcb failed when using Collection.delete().\n    // Debugging it led me to that there is a general problem with virtual cursor's primaryKey property.\n    // So that's what we're testing here:\n    await db.users.add({id: 1000, foo: \"A\", bar: \"B\"});\n    const ids = await db.users.where({foo: \"A\"}).filter(x => true).primaryKeys();\n    ok(ids.length === 1, \"Theres one id there\");\n    equal(ids[0], 1000, \"The ID is 1000\");\n});\n\npromisedTest(\"Collection.modify() with replace\", async () => {\n    await db.users.bulkAdd([\n        {id: 1000, foo: \"A\", parentPath: \"A/B/C\"},\n        {id: 1001, foo: \"B\", parentPath: \"A/B/C\"},\n        {id: 1002, foo: \"C\", parentPath: \"A/B\"}\n    ]);\n    await db.users.where('parentPath').startsWith(\"A/B\").modify({\n        parentPath: replacePrefix(\"A/B\", \"X\")\n    });\n    let users = await db.users.where('id').between(1000, 1003).toArray();\n    deepEqual(users, [\n        {id: 1000, foo: \"A\", parentPath: \"X/C\"},\n        {id: 1001, foo: \"B\", parentPath: \"X/C\"},\n        {id: 1002, foo: \"C\", parentPath: \"X\"}\n    ], \"All three have moved from A/B to X\");\n    await db.users.where('parentPath').startsWith(\"X/C\").modify({\n        parentPath: replacePrefix(\"X/C\", \"Y\")\n    });\n    users = await db.users.where('id').between(1000, 1003).toArray();\n    deepEqual(users, [\n        {id: 1000, foo: \"A\", parentPath: \"Y\"},\n        {id: 1001, foo: \"B\", parentPath: \"Y\"},\n        {id: 1002, foo: \"C\", parentPath: \"X\"}\n    ], \"The first two have moved from X/C to Y\");\n    await db.users.toCollection().modify({\n        parentPath: replacePrefix(\"X\", \"Z\")\n    });\n    users = await db.users.where('id').between(1000, 1003).toArray();\n    deepEqual(users, [\n        {id: 1000, foo: \"A\", parentPath: \"Y\"},\n        {id: 1001, foo: \"B\", parentPath: \"Y\"},\n        {id: 1002, foo: \"C\", parentPath: \"Z\"}\n    ], \"Omitting where-criteria will still check the prefix before replacing\");\n});\n\npromisedTest(\"Collection.modify() with add / remove\", async () => {\n    await db.users.bulkAdd([\n        {id: 1100, foo: \"A\", age: 18, pets: [\"dog\", \"cat\"], balance: 100},\n        {id: 1101, foo: \"A\", age: 18, pets: [\"cat\", \"dog\"], balance: 100},\n    ]);\n\n    // Mathematical add:\n    db.users.update(1100, {\n        age: add(1)\n    });\n    await db.users.update(1100, {\n        age: add(1)\n    });\n    let user = await db.users.get(1100);\n    equal(user.age, 20, \"Both add operations have taken place\");\n\n    // Money transaction\n    await db.transaction('rw', db.users, ()=> {\n        db.users.update(1100, {balance: add(100)});\n        db.users.update(1101, {balance: add(-100)});\n    });\n    let [user1, user2] = await db.users.bulkGet([1100, 1101]);\n    equal(user1.balance, 200, \"User 1 has 200 after transaction\");\n    equal(user2.balance, 0, \"User 2 has 0 after transaction\");\n\n    // Add to set\n    await db.users.update(1100, { pets: add([\"elephant\"])});\n    user = await db.users.get(1100);\n    deepEqual(user.pets, [\"cat\", \"dog\", \"elephant\"], \"Elephant was added and pets where sorted\");\n    await db.users.update(1100, { pets: remove([\"dog\"])});\n    user = await db.users.get(1100);\n    deepEqual(user.pets, [\"cat\", \"elephant\"], \"Dog was removed\");\n\n    // Multiple operations\n    await db.users.update(1100, {\n        pets: add([\"rabbit\", \"cow\"]),\n        age: remove(1),\n        balance: add(1_000_000)\n    });\n    user = await db.users.get(1100);\n    deepEqual(user.pets, [\"cat\", \"cow\", \"elephant\", \"rabbit\"], \"rabbit and cow where added\");\n    equal(user.age, 19, \"Age was decremented from 20 to 19\");\n    equal(user.balance, 1_000_200, \"Balance was increased with a million\");\n});\n\n"
  },
  {
    "path": "test/tests-crud-hooks.js",
    "content": "/**\n * Created by David on 3/31/2016.\n */\nimport Dexie from 'dexie';\nimport {module, stop, start, test, asyncTest, equal, ok} from 'QUnit';\nimport {resetDatabase, supports, spawnedTest, promisedTest} from './dexie-unittest-utils';\n\nlet Promise = Dexie.Promise,\n    all = Promise.all,\n    async = Dexie.async,\n    spawn = Dexie.spawn;\n\nvar db = new Dexie(\"TestDBCrudHooks\");\ndb.version(1).stores({\n    table1: \"id,idx\",\n    table2: \",&idx\",\n    table3: \"++id,&idx\",\n    table4: \"++,&idx\",\n    table5: \"\"\n});\n\nvar ourTables = [db.table1, db.table2, db.table3, db.table4, db.table5];\n\nvar opLog = [],\n    successLog = [],\n    errorLog = [],\n    watchSuccess = false,\n    watchError = false,\n    deliverKeys = [],\n    deliverModifications = null,\n    deliverKeys2 = [],\n    deliverModifications2 = null,\n    opLog2 = [],\n    successLog2 = [],\n    errorLog2 = [],\n    transLog = [];\n\nfunction unsubscribeHooks() {\n    ourTables.forEach(table => {\n        table.hook('creating').unsubscribe(creating2);\n        table.hook('creating').unsubscribe(creating1);\n        table.hook('reading').unsubscribe(reading1);\n        table.hook('reading').unsubscribe(reading2);\n        table.hook('updating').unsubscribe(updating1);\n        table.hook('updating').unsubscribe(updating2);\n        table.hook('deleting').unsubscribe(deleting2);\n        table.hook('deleting').unsubscribe(deleting1);\n    });\n}\n\nfunction subscrubeHooks() {\n    ourTables.forEach(table => {\n        table.hook('creating', creating1);\n        table.hook('creating', creating2);\n        table.hook('reading', reading1);\n        table.hook('reading', reading2);\n        table.hook('updating', updating1);\n        table.hook('updating', updating2);\n        table.hook('deleting', deleting1);\n        table.hook('deleting', deleting2);\n    });\n}\nconst reset = async(function* reset () {\n    unsubscribeHooks();\n    yield all(ourTables.map(table => table.clear()));\n    subscrubeHooks();\n    opLog = [];\n    successLog = [];\n    errorLog = [];\n    watchSuccess = false;\n    watchError = false;\n    deliverKeys = [];\n    deliverModifications = null;\n    deliverKeys2 = [];\n    deliverModifications2 = null;\n    opLog2 = [];\n    successLog2 = [];\n    errorLog2 = [];\n    transLog = [];\n});\n\n/*function stack() {\n    if (Error.captureStackTrace) {\n        let obj = {};\n        Error.captureStackTrace(obj, stack);\n        return obj.stack;\n    }\n    var e = new Error(\"\");\n    if (e.stack) return e.stack;\n    try{throw e}catch(ex){return ex.stack || \"\";}\n}*/\n\nfunction nop(){}\n\nfunction creating1 (primKey, obj, transaction) {\n    // You may do additional database operations using given transaction object.\n    // You may also modify given obj\n    // You may set this.onsuccess = function (primKey){}. Called when autoincremented key is known.\n    // You may set this.onerror = callback if create operation fails.\n    // If returning any value other than undefined, the returned value will be used as primary key\n    transLog.push({trans: transaction, current: Dexie.currentTransaction});\n    let op = {\n        op: \"create\",\n        key: primKey,\n        value: Dexie.deepClone(obj)\n    };\n    opLog.push(op);\n\n    if (watchSuccess) {\n        this.onsuccess = primKey => successLog.push(primKey);\n    }\n    if (watchError) {\n        this.onerror = e => errorLog.push(e);\n    }\n    if (deliverKeys[opLog.length-1])\n        return deliverKeys[opLog.length-1];\n}\n\n\n// Check that chaining several hooks works\nfunction creating2 (primKey, obj, transaction) {\n    let op = {\n        op: \"create\",\n        key: primKey,\n        value: Dexie.deepClone(obj)\n    };\n    opLog2.push(op);\n\n    if (watchSuccess) {\n        this.onsuccess = primKey => successLog2.push(primKey);\n    }\n    if (watchError) {\n        this.onerror = e => errorLog2.push(e);\n    }\n    if (deliverKeys2[opLog2.length-1])\n        return deliverKeys2[opLog2.length-1];\n}\n\nfunction reading1 (obj) {\n    opLog.push({\n        op: \"read\",\n        obj: Dexie.deepClone(obj)\n    });\n    return {theObject: obj};\n}\n\nfunction reading2 (obj) {\n    opLog2.push({\n        op: \"read\",\n        obj: Dexie.deepClone(obj)\n    });\n    return obj.theObject;\n}\n\nfunction updating1 (modifications, primKey, obj, transaction) {\n    // You may use transaction to do additional database operations.\n    // You may not do any modifications on any of the given arguments.\n    // You may set this.onsuccess = function (updatedObj){} when update operation completes.\n    // You may set this.onerror = callback if update operation fails.\n    // If you want to make additional modifications, return another modifications object\n    // containing the additional or overridden modifications to make. Any returned\n    // object will be merged to the given modifications object.\n    transLog.push({trans: transaction, current: Dexie.currentTransaction});\n    let op = {\n        op: \"update\",\n        key: primKey,\n        obj: Dexie.deepClone(obj),\n        mods: Dexie.shallowClone(modifications),\n    };\n    opLog.push(op);\n\n    if (watchSuccess) {\n        this.onsuccess = (updatedObj) => successLog.push(updatedObj);\n    }\n    if (watchError) {\n        this.onerror = e => errorLog.push(e);\n    }\n    if (deliverModifications) return deliverModifications;\n}\n\n// Chaining:\nfunction updating2 (modifications, primKey, obj, transaction) {\n    // You may use transaction to do additional database operations.\n    // You may not do any modifications on any of the given arguments.\n    // You may set this.onsuccess = function (updatedObj){} when update operation completes.\n    // You may set this.onerror = callback if update operation fails.\n    // If you want to make additional modifications, return another modifications object\n    // containing the additional or overridden modifications to make. Any returned\n    // object will be merged to the given modifications object.\n    let op = {\n        op: \"update\",\n        key: primKey,\n        obj: Dexie.deepClone(obj),\n        mods: Dexie.shallowClone(modifications)\n    };\n    opLog2.push(op);\n\n    if (watchSuccess) {\n        this.onsuccess = (updatedObj) => successLog2.push(updatedObj);\n    }\n    if (watchError) {\n        this.onerror = e => errorLog2.push(e);\n    }\n    if (deliverModifications2) return deliverModifications2;\n}\n\nfunction deleting1 (primKey, obj, transaction) {\n    // You may do additional database operations using given transaction object.\n    // You may set this.onsuccess = callback when delete operation completes.\n    // You may set this.onerror = callback if delete operation fails.\n    // Any modification to obj is ignored.\n    // Any return value is ignored.\n    // throwing exception will make the db operation fail.\n    transLog.push({trans: transaction, current: Dexie.currentTransaction});\n    let op = {\n        op: \"delete\",\n        key: primKey,\n        obj: obj\n    };\n    opLog.push(op);\n    if (watchSuccess) {\n        this.onsuccess = () => successLog.push(undefined);\n    }\n    if (watchError) {\n        this.onerror = e => errorLog.push(e);\n    }\n}\n\n// Chaining:\nfunction deleting2 (primKey, obj, transaction) {\n    // You may do additional database operations using given transaction object.\n    // You may set this.onsuccess = callback when delete operation completes.\n    // You may set this.onerror = callback if delete operation fails.\n    // Any modification to obj is ignored.\n    // Any return value is ignored.\n    // throwing exception will make the db operation fail.\n    let op = {\n        op: \"delete\",\n        key: primKey,\n        obj: obj\n    };\n    opLog2.push(op);\n    if (watchSuccess) {\n        this.onsuccess = () => successLog2.push(undefined);\n    }\n    if (watchError) {\n        this.onerror = e => errorLog2.push(e);\n    }\n}\n\nmodule(\"crud-hooks\", {\n    setup: function () {\n        stop();\n        resetDatabase(db).then(()=>reset()).catch(e => {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: function () {\n        unsubscribeHooks();\n    }\n});\n\nconst expect = async (function* (expected, modifyer) {\n    yield reset();\n    yield modifyer();\n    equal(JSON.stringify(opLog, null, 2), JSON.stringify(\n        expected.map((x) => ({...x, updatedObj: undefined})), null, 2), \"Expected oplog: \" + JSON.stringify(expected));\n    ok(transLog.every(x => x.trans && x.current === x.trans), \"transaction argument is valid and same as Dexie.currentTransaction\");\n    yield reset();\n    watchSuccess = true;\n    watchError = true;\n    yield modifyer();\n    equal (errorLog.length + errorLog2.length, 0, \"No errors should have been registered\");\n    equal (successLog.length, expected.filter(op => op.op !== 'read').length, \"First hook got success events\");\n    equal (successLog2.length, expected.filter(op => op.op !== 'read').length, \"Second hook got success events\");\n    expected.forEach((x, i) => {\n        if (x.op === \"create\" && x.key !== undefined) {\n            equal(successLog[i], x.key, \"Success events got the correct key\");\n            equal(successLog2[i], x.key, \"Success events got the correct key (2)\");\n        }\n        if (x.op === \"update\") {\n            equal(JSON.stringify(successLog[i]), JSON.stringify(x.updatedObj),\n                \"Success events got the updated object\");\n            equal(JSON.stringify(successLog2[i]), JSON.stringify(x.updatedObj),\n                \"Success events got the updated object (2)\");\n        }\n    });\n\n    if (expected.some(x => x.op === \"create\" && x.key === undefined)) {\n        // Test to deliver prim key from both hooks and expect the second hook's key to win.\n        yield reset();\n        deliverKeys = expected.map((x,i)=>\"Hook1Key\"+ i);\n        deliverKeys2 = expected.map((x,i)=>\"Hook2Key\"+ i);\n        watchSuccess = true;\n        watchError = true;\n        yield modifyer();\n        equal (errorLog.length + errorLog2.length, 0, \"No errors should have been registered\");\n        expected.forEach((x, i) => {\n            if (x.op === \"create\" && x.key === undefined) {\n                equal(opLog[i].key, expected[i].key, \"First hook got expected key delivered\");\n                equal(opLog2[i].key, deliverKeys[i], \"Second hook got key delivered from first hook\");\n                equal(successLog[i], deliverKeys2[i], \"Success event got delivered key from hook2\");\n                equal(successLog2[i], deliverKeys2[i], \"Success event got delivered key from hook2 (2)\");\n            }\n        });\n    }\n\n    if (expected.some(x => x.op === \"update\")) {\n        yield reset();\n        deliverModifications = {\"someProp.someSubProp\": \"someValue\"};\n        yield modifyer();\n        expected.forEach((x, i) => {\n           if (x.op === \"update\") {\n               equal(\n                   JSON.stringify(opLog[i].obj),\n                   JSON.stringify(opLog2[i].obj),\n                   \"Object has not yet been changed in hook2\");\n               ok(Object.keys(opLog[i].mods).every(prop =>\n                    JSON.stringify(opLog[i].mods[prop]) ===\n                    JSON.stringify(opLog2[i].mods[prop])),\n                   \"All mods that were originally sent to hook1, are also sent to hook2\");\n               ok(\"someProp.someSubProp\" in opLog2[i].mods, \"oplog2 got first hook's additional modifications\");\n           }\n        });\n    }\n});\n\nconst verifyErrorFlows = async (function* (modifyer) {\n    yield reset();\n    ok(true, \"Verifying ERROR flows\");\n    watchSuccess = true;\n    watchError = true;\n    yield modifyer();\n    equal (opLog.length, opLog2.length, \"Number of ops same for hook1 and hook2: \" + opLog.length);\n    equal (successLog.length + errorLog.length, opLog.length, \"Either onerror or onsuccess must have been called for every op. onerror: \" +\n        errorLog.length + \". onsuccess: \" + successLog.length + \". opLog: \" + JSON.stringify(opLog));\n    equal (successLog2.length + errorLog2.length, opLog2.length, \"Either onerror or onsuccess must have been called for every op (hook2). onerror: \" +\n        errorLog2.length + \". onsuccess: \" + successLog2.length+ \". opLog: \" + JSON.stringify(opLog2));\n});\n\n\n\n\n\n//\n//\n//   Tests goes here...\n//\n//\n\n//\n// CREATING hook tests...\n//\n// Ways to produce CREATEs:\n//  Table.add()\n//  Table.put()\n//  Table.bulkAdd()\n//  Table.bulkPut()\n\nspawnedTest(\"creating using Table.add()\", function*() {\n\n    yield expect([{\n        op: \"create\",\n        key: 1,\n        value: {id: 1, idx: 11}\n    }, {\n        op: \"create\",\n        key: 2,\n        value: {idx: 12}\n    }, {\n        op: \"create\",\n        value: {idx: 13}\n    }, {\n        op: \"create\",\n        value: {idx: 14}\n    }], () => db.transaction('rw', db.tables, ()=> {\n        db.table1.add({id: 1, idx: 11});\n        db.table2.add({idx: 12}, 2);\n        db.table3.add({idx: 13});\n        db.table4.add({idx: 14});\n    }));\n\n    yield verifyErrorFlows(()=>db.transaction('rw', db.tables, function* () {\n        yield db.table1.add({id: 1}); // success\n        yield db.table1.add({id: 1}).catch(nop); // Trigger error event (constraint)\n        yield db.table2.add({}, 1); // sucesss\n        yield db.table2.add({}, 1).catch(nop); // Trigger error event (constraint)\n        yield db.table1.add({id: {}}).catch(nop);// Trigger direct exception (invalid key type)\n    }).catch(nop));\n});\n\nspawnedTest(\"creating using Table.put()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id: 1, idx: 11}\n    },{\n        op: \"create\",\n        key: 2,\n        value: {idx: 12}\n    },{\n        op: \"create\",\n        value: {idx: 13}\n    },{\n        op: \"create\",\n        value: {idx: 14}\n    }], () => db.transaction('rw', db.tables, function* () {\n        //console.log(\"Putting {id:1, idx: 11} into table1\");\n        yield db.table1.put({id:1, idx:11});\n        //console.log(\"Putting {idx: 12} for key 2 into table2\");\n        yield db.table2.put({idx:12}, 2);\n        //console.log(\"Putting {idx: 13} into table3\");\n        yield db.table3.put({idx:13});\n        //console.log(\"Putting {idx: 14} into table4\");\n        yield db.table4.put({idx:14});\n    }));\n\n    yield verifyErrorFlows(()=>db.transaction('rw', db.tables, function* () {\n        yield db.table3.put({idx: 1}); // success\n        yield db.table3.put({idx: 1}).catch(nop); // Trigger error event (constraint)\n        yield db.table2.put({}, 1); // sucesss\n        yield db.table2.put({}, 1).catch(nop); // Trigger error event (constraint)\n        yield db.table3.put({id: {}}).catch(nop);// Trigger direct exception (invalid key type)\n    }).catch(nop));\n});\n\nspawnedTest(\"creating using Table.bulkAdd()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id: 1, idx: 11}\n    },{\n        op: \"create\",\n        key: 1.2,\n        value: {id: 1.2, idx: 11.2}\n    },{\n        op: \"create\",\n        key: 2,\n        value: {idx: 12}\n    },{\n        op: \"create\",\n        key: 2.2,\n        value: {idx: 12.2}\n    },{\n        op: \"create\",\n        value: {idx: 13}\n    },{\n        op: \"create\",\n        value: {idx: 13.2}\n    },{\n        op: \"create\",\n        value: {idx: 14}\n    },{\n        op: \"create\",\n        value: {idx: 14.2}\n    }], () => db.transaction('rw', db.tables, function* () {\n        db.table1.bulkAdd([{id: 1, idx: 11},{id: 1.2, idx: 11.2}]);\n        db.table2.bulkAdd([{idx: 12},{idx: 12.2}], [2, 2.2]);\n        db.table3.bulkAdd([{idx: 13},{idx: 13.2}]);\n        db.table4.bulkAdd([{idx: 14},{idx: 14.2}]);\n    }));\n\n    yield verifyErrorFlows(()=>db.transaction('rw', db.tables, function* () {\n        yield db.table1.bulkAdd([{id:1},{id:1}]).catch(nop); // 1. success, 2. error event.\n        yield db.table1.bulkAdd([{id:2},{id:2},{id:3}]).catch(nop); // 1. success, 2. error event., 3. success\n        yield db.table2.bulkAdd([{}, {}], [1,1]).catch(nop); // 1. success, 2. error event.\n        yield db.table2.bulkAdd([{}, {}, {}], [2,2,3]).catch(nop); // 1. success, 2. error event. 3. success.\n        yield db.table1.bulkAdd([{id:{}}]).catch(nop);// Trigger direct exception (invalid key type)\n    }).catch(nop));\n});\n\nspawnedTest(\"creating using Table.bulkPut()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id: 1, idx: 11}\n    },{\n        op: \"create\",\n        key: 1.2,\n        value: {id: 1.2, idx: 11.2}\n    },{\n        op: \"create\",\n        key: 2,\n        value: {idx: 12}\n    },{\n        op: \"create\",\n        key: 2.2,\n        value: {idx: 12.2}\n    },{\n        op: \"create\",\n        value: {idx: 13}\n    },{\n        op: \"create\",\n        value: {idx: 13.2}\n    },{\n        op: \"create\",\n        value: {idx: 14}\n    },{\n        op: \"create\",\n        value: {idx: 14.2}\n    }], () => db.transaction('rw', db.tables, function* () {\n        yield db.table1.bulkPut([{id: 1, idx: 11},{id: 1.2, idx: 11.2}]);\n        yield db.table2.bulkPut([{idx: 12},{idx: 12.2}], [2, 2.2]);\n        yield db.table3.bulkPut([{idx: 13},{idx: 13.2}]);\n        yield db.table4.bulkPut([{idx: 14},{idx: 14.2}]);\n    }));\n\n    yield verifyErrorFlows(()=>db.transaction('rw', db.tables, function* () {\n        yield db.table3.bulkPut([{idx:1},{idx:1}]).catch(nop); // 1. success, 2. error event.\n        yield db.table3.bulkPut([{idx:2},{idx:2},{idx:3}]).catch(nop); // 1. success, 2. error event., 3. success\n        yield db.table1.bulkPut([{id:{}}]).catch(nop);// Trigger direct exception (invalid key type)\n    }).catch(nop));\n});\n\n//\n// READING hooks test\n// Ways to produce READs:\n//  Table.get()\n//  Table.bulkGet()\n//  Collection.toArray()\n//  Collection.each()\n//  Collection.first()\n//  Collection.last()\n// But not:\n//  Table.filter() / Collection.and()\n\nspawnedTest(\"reading tests\", function* (){\n    yield expect([{\n        op: \"create\",\n        key: 1,\n        value: {foo: \"bar\"}\n    },{\n        op: \"create\",\n        key: 2,\n        value: {fee: \"bore\"}\n    },{\n        op: \"read\", // toArray() (1)\n        obj: {foo: \"bar\"}\n    },{\n        op: \"read\", // toArray() (2)\n        obj: {fee: \"bore\"}\n    },{\n        op: \"read\", // reverse.each() (1)\n        obj: {fee: \"bore\"}\n    },{\n        op: \"read\", // reverse.each() (2)\n        obj: {foo: \"bar\"}\n    },{\n        op: \"read\", // first()\n        obj: {foo: \"bar\"}\n    },{\n        op: \"read\", // last()\n        obj: {fee: \"bore\"}\n    },{\n        op: \"read\", // bulkGet() (1)\n        obj: {foo: \"bar\"}\n    },{\n        op: \"read\", // bulkGet() (2)\n        obj: {fee: \"bore\"}\n    }], ()=> db.transaction('rw', 'table5', function*(){\n        yield db.table5.bulkAdd([{foo: \"bar\"}, {fee: \"bore\"}], [1, 2]);\n        yield db.table5.toArray();\n        yield db.table5.reverse().each(x => {});\n        yield db.table5.orderBy(':id').first();\n        yield db.table5.orderBy(':id').last();\n        yield db.table5.bulkGet([1, 2]);\n        yield db.table5.filter(x => false).toArray();\n    }));\n\n    let readOps = opLog.filter(o => o.op === 'read'),\n        readOps2 = opLog2.filter(o => o.op === 'read');\n\n    ok (readOps.every ((o,i)=>\n        JSON.stringify(readOps2[i].obj.theObject) === JSON.stringify(o.obj)),\n        \"hook2 should have got hook1's return value\");\n\n});\n\n//\n// UPDATING hooks test\n// Ways to produce UPDATEs:\n//  Table.put()\n//  Table.bulkPut()\n//  Table.update()\n//  Collection.modify()\n\nspawnedTest(\"updating using Table.put()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id:1, address: {city: 'A'}}\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1, address: {city: 'A'}},\n        mods: {\"address.city\": \"B\"},\n        updatedObj: {id:1, address: {city: 'B'}},\n    }], ()=>db.transaction('rw', db.tables, function* (){\n        db.table1.put({id:1, address: {city: 'A'}}); // create\n        db.table1.put({id:1, address: {city: 'B'}}); // update\n    }));\n\n    yield verifyErrorFlows(()=>db.transaction('rw', db.tables, function* () {\n        yield db.table3.add({id:1, idx:1});\n        yield db.table3.put({id:2, idx:1}).catch(nop); // error event (constraint)\n        yield db.table3.put({id:{}}).catch(nop); // Trigger direct exception (invalid key type)\n    }).catch(nop));\n});\n\nspawnedTest(\"updating using Table.bulkPut()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id:1, address: {city: 'A'}}\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1, address: {city: 'A'}},\n        mods: {\"address.city\": \"B\"},\n        updatedObj: {id:1, address: {city: 'B'}},\n    }], ()=>db.transaction('rw', db.tables, function* (){\n        db.table1.put({id:1, address: {city: 'A'}}); // create\n        db.table1.put({id:1, address: {city: 'B'}}); // update\n    }));\n\n    yield verifyErrorFlows(()=>db.transaction('rw', db.tables, function* () {\n        yield db.table4.add({idx:1}, 1);\n        yield db.table4.bulkPut([{idx:1}], [2]).catch(nop); // error event (DataError)\n        yield db.table3.bulkPut([{}],[{}]).catch(nop); // Trigger direct exception (invalid key type)\n    }).catch(nop));\n});\n\nspawnedTest(\"updating using Table.update()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id:1, address: {city: 'A'}}\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1, address: {city: 'A'}},\n        mods: {\"address.city\": \"B\"},\n        updatedObj: {id:1, address: {city: 'B'}},\n    }], ()=>db.transaction('rw', db.tables, function* (){\n        yield db.table1.add({id:1, address: {city: 'A'}}); // create\n        yield db.table1.update(1, {\"address.city\": \"B\"}); // update\n    }));\n\n    yield verifyErrorFlows(()=>db.transaction('rw', db.tables, function* () {\n        yield db.table3.bulkAdd([{id:1, idx:1},{id:2, idx:2}]);\n        yield db.table3.update(1, {idx:2}).catch(nop); // error event (constraint)\n        yield db.table3.update(1, 3).catch(nop); // Trigger direct exception?\n    }).catch(nop));\n});\n\nspawnedTest(\"updating using Collection.modify()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id:1, address: {city: 'A'}}\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1, address: {city: 'A'}},\n        mods: {\"address.city\": \"B\"},\n        updatedObj: {id:1, address: {city: 'B'}},\n    }], ()=>db.transaction('rw', db.tables, function* (){\n        yield db.table1.add({id:1, address: {city: 'A'}}); // create\n        yield db.table1.where('id').equals(1).modify({\"address.city\": \"B\"}); // update\n    }));\n\n    yield verifyErrorFlows(()=>db.transaction('rw', db.tables, function* () {\n        yield db.table3.bulkAdd([{id:1, idx:1},{id:2, idx:2}]);\n        yield db.table3.where('id').equals(1).modify({idx: 2}).catch(nop); // error event (constraint)\n        yield db.table3.where('id').equals(1).modify(()=>{throw \"apa\";}).catch(nop); // Trigger direct exception\n    }).catch(nop));\n});\n\n//\n// DELETING hook tests\n//\n// Ways to produce DELETEs:\n//  Table.delete(key)\n//  Table.bulkDetele(keys)\n//  Table.clear()\n//  Collection.modify()\n//  Collection.delete()\n\nspawnedTest(\"deleting using Table.delete(key)\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id:1}\n    },{\n        op: \"delete\",\n        key: 1,\n        obj: {id:1}\n    }], ()=>db.transaction('rw', db.tables, function* (){\n        yield db.table1.add({id:1}); // create\n        yield db.table1.delete(1); // delete\n    }));\n\n    // No error flows to verify. If anything is ever found, there's no way to make a deletion of it fail.\n});\n\nspawnedTest(\"deleting using Table.bulkDelete(key)\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id:1}\n    },{\n        op: \"delete\",\n        key: 1,\n        obj: {id:1}\n    }], ()=>db.transaction('rw', db.tables, function* (){\n        yield db.table1.add({id:1}); // create\n        yield db.table1.bulkDelete([1]); // delete\n    }));\n\n    // No error flows to verify. If anything is ever found, there's no way to make a deletion of it fail.\n});\n\nspawnedTest(\"deleting using Table.clear()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id:1}\n    },{\n        op: \"delete\",\n        key: 1,\n        obj: {id:1}\n    }], ()=>db.transaction('rw', db.tables, function* (){\n        yield db.table1.add({id:1}); // create\n        yield db.table1.clear(); // delete\n    }));\n});\n\nspawnedTest(\"deleting using Table.modify()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id:1}\n    },{\n        op: \"delete\",\n        key: 1,\n        obj: {id:1}\n    }], ()=>db.transaction('rw', db.tables, function* (){\n        yield db.table1.add({id:1}); // create\n        yield db.table1.where('id').between(0,2).modify(function (){\n            delete this.value;\n        }); // delete\n    }));\n});\n\nspawnedTest(\"deleting using Collection.delete()\", function*(){\n    yield expect ([{\n        op: \"create\",\n        key: 1,\n        value: {id:1}\n    },{\n        op: \"delete\",\n        key: 1,\n        obj: {id:1}\n    }], ()=>db.transaction('rw', db.tables, function* (){\n        yield db.table1.add({id:1}); // create\n        yield db.table1.where('id').between(0,2).delete(); // delete\n    }));\n});\n\npromisedTest(\"issue #1195 Update with array as value adds number objects\", async ()=>{\n    await expect([{\n        op: \"create\",\n        key: 1,\n        value: {id:1}\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1},\n        mods: {authors: [{foo: \"bar\"}]},\n        updatedObj: {id:1, authors: [{foo: \"bar\"}]},\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1, authors: [{foo: \"bar\"}]},\n        mods: {authors: []},\n        updatedObj: {id:1, authors: []},\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1, authors: []},\n        mods: {authors: [{name: \"foo\"}, {name: \"bar\"}]},\n        updatedObj: {id:1, authors: [{name: \"foo\"}, {name: \"bar\"}]},\n    }], ()=>db.transaction('rw', db.table1, async ()=>{\n        await db.table1.add({id:1});\n        await db.table1.put({id:1, authors: [{foo: \"bar\"}]});\n        await db.table1.put({id:1, authors: []});\n        await db.table1.put({id:1, authors: [{name: \"foo\"}, {name: \"bar\"}]});\n    }));\n});\n\npromisedTest(\"issue #1270 Modification object in updating hook not correct when changing array\", async ()=>{\n    // Test sub-array\n    await expect([{\n        op: \"create\",\n        key: 1,\n        value: {id:1, authors: [{name: \"foo\"}]}\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1, authors: [{name: \"foo\"}]},\n        mods: {authors: [{name: \"bar\"}]},\n        updatedObj: {id:1, authors: [{name: \"bar\"}]},\n    }], ()=>db.transaction('rw', db.table1, async ()=>{\n        await db.table1.add({id:1, authors: [{name: \"foo\"}]});\n        await db.table1.put({id:1, authors: [{name: \"bar\"}]});\n    }));\n\n    // Test sub-object\n    await expect([{\n        op: \"create\",\n        key: 1,\n        value: {id:1, author: {name: \"foo\"}}\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1, author: {name: \"foo\"}},\n        mods: {\"author.name\": \"bar\"},\n        updatedObj: {id:1, author: {name: \"bar\"}},\n    }], ()=>db.transaction('rw', db.table1, async ()=>{\n        await db.table1.add({id:1, author: {name: \"foo\"}});\n        await db.table1.put({id:1, author: {name: \"bar\"}});\n    }));\n\n    // Test Arraybuffer in sub-array\n    const buffer1 = new Uint8Array(8).fill(1)\n    const buffer2 = new Uint8Array(8).fill(2)\n    await expect([{\n        op: \"create\",\n        key: 1,\n        value: {id:1, author: {buf: buffer1}}\n    },{\n        op: \"update\",\n        key: 1,\n        obj: {id:1, author: {buf: buffer1}},\n        mods: {\"author.buf\": buffer2},\n        updatedObj: {id:1, author: {buf: buffer2}},\n    }], ()=>db.transaction('rw', db.table1, async ()=>{\n        await db.table1.add({id:1, author: {buf: buffer1}});\n        await db.table1.put({id:1, author: {buf: buffer2}});\n    }));\n});"
  },
  {
    "path": "test/tests-exception-handling.js",
    "content": "﻿import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {resetDatabase, spawnedTest, supports} from './dexie-unittest-utils';\n\nvar db = new Dexie(\"TestDBException\");\ndb.version(1).stores({ users: \"id,first,last,&username,&*email,*pets\" });\ndb.on(\"populate\", function (trans) {\n    db.users.add({ id: 1, first: \"David\", last: \"Fahlander\", username: \"dfahlander\", email: [\"david@awarica.com\", \"daw@thridi.com\"], pets: [\"dog\"] });\n    db.users.add({ id: 2, first: \"Karl\", last: \"Cedersköld\", username: \"kceder\", email: [\"karl@ceder.what\"], pets: [] });\n});\n\nmodule(\"exception-handling\", {\n    setup: function () {\n        stop();\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: function () {\n    }\n});\n\nspawnedTest(\"transaction should abort on collection error\", function*(){\n    yield db.transaction(\"rw\", db.users, function*() {\n        let id = yield db.users.add({id: 3, first: \"Foo\", last: \"Bar\", username: \"foobar\"});\n        equal (id, 3);\n        yield db.users.where('id').equals(null).toArray();\n        ok(false, \"Should not come here\");\n    }).catch(e => {\n        ok(true, \"Got error because WhereClause.equals(null) should throw DataError: \" + e);\n    });\n    equal (yield db.users.where('first').equals(\"Foo\").count(), 0, \"Should not have succeeded to add when transaction was aborted\");\n\n    yield db.transaction(\"rw\", db.users, function() {\n        db.users.add({id: 3, first: \"Foo\", last: \"Bar\", username: \"foobar\"});\n        db.users.where('id').equals(null).toArray(res=> {\n            ok(false, \"Not possible to query null\");\n        });\n    }).then(()=>{\n        ok(false, \"Transaction shouldnt commit\");\n    }).catch(e => {\n        ok(true, \"Got error because WhereClause.equals(null) should throw TypeError\");\n    });\n\n    equal (yield db.users.where('first').equals(\"Foo\").count(), 0, \"Should not have succeeded to add when transaction was aborted\");\n});\n\nasyncTest(\"eventError-transaction-catch\", function () {\n    db.transaction(\"rw\", db.users, function () {\n        db.users.add({ id: 100, username: \"dfahlander\" }).then(function () {\n            ok(false, \"Should not be able to add two users with same username\");\n        });\n    }).then(function () {\n        ok(false, \"Transaction should not complete since an error should have occurred\");\n    }).catch(function (e) {\n        ok(true, \"Got transaction error: \" + e);\n    }).finally(start);\n});\n    \nasyncTest(\"eventError-request-catch\", function () {\n    db.transaction(\"rw\", db.users, function () {\n        db.users.add({ id: 100, username: \"dfahlander\" }).then(function () {\n            ok(false, \"Should not be able to add two users with same username\");\n        }).catch(function (e) {\n            ok(true, \"Got request error: \" + e);\n        });\n        db.users.add({ id: 101, first: \"Trazan\", last: \"Apansson\", username: \"tapan\", email: [\"trazan@apansson.barnarne\"], pets: [\"monkey\"] }).then(function (id) {\n            ok(id > 2, \"Could continue transaction and add Trazan since last error event was catched\");\n        });\n    }).then(function () {\n        ok(true, \"Transaction should complete since the only error that occurred was catched\");\n    }).catch(function (e) {\n        ok(false, \"Should not get transaction error since we have catched the error. Got Transaction error: \" + e);\n    }).finally(start);\n});\n\n\nasyncTest(\"exceptionThrown-transaction-catch\", function () {\n    db.transaction(\"r\", db.users, function () {\n        throw new SyntaxError(\"Why not throw an exception for a change?\");\n    }).then(function () {\n        ok(false, \"Transaction should not complete since an error should have occurred\");\n    }).catch(TypeError, function (e) {\n        ok(false, \"Should not happen. The thrown error was not a TypeError\");\n    }).catch(SyntaxError, function (e) {\n        ok(true, \"Transaction got SyntaxError: \" + e);\n    }).catch(function (e) {\n        ok(false, \"Should not come here! The error should already have been catched above()\");\n    }).finally(start);\n});\n\nasyncTest(\"exceptionThrown-request-catch\", function () {\n    db.transaction(\"r\", db.users, function () {\n        db.users.where(\"username\").equals(\"apa\").toArray(function () {\n            db.users.where(\"username\").equals(\"kceder\").toArray().then(function () {\n                return \"a\";\n            }).then(function () {\n                NonExistingSymbol.EnotherIdioticError = \"Why not make an exception for a change?\";\n            });\n        });\n    }).then(function () {\n        ok(false, \"Transaction should not complete since an error should have occurred\");\n    }).catch(function (e) {\n        ok(true, \"Transaction got error: \" + e);\n    }).finally(start);\n});\n\nasyncTest(\"exceptionThrown-iteration-should-abort-when-using-hook\", function () {\n    function deletingHook () {\n        // Testing with \n    };\n    db.users.hook('deleting', deletingHook);\n    db.transaction('rw', db.users, function () {\n\n        function deleteKarls() {\n            db.users.toCollection().modify(function (user) {\n                delete this.value;\n                throw \"Throwing something\";\n            });\n        }\n            \n        db.users.delete(1);\n        deleteKarls();\n\n    }).then(function () {\n        ok(false, \"Transaction should not complete!\");\n    }).catch(function (err) {\n        ok(true, \"Transaction aborted\");\n    }).finally(()=>{\n        db.users.hook('deleting').unsubscribe(deletingHook);\n        start();\n    });\n});\n\nasyncTest(\"exceptionThrown-iteration-should-not-abort-when-using-hook\", function () {\n    db.users.hook('deleting', function () {\n        // Testing with \n    })\n    db.transaction('rw', db.users, function () {\n\n        function deleteKarls() {\n            db.users.toCollection().modify(function (user) {\n                delete this.value;\n                throw \"Throwing something\";\n            }).catch(function (err) {\n                // Catching error should prevent transaction from aborting.\n            });\n        }\n\n        db.users.delete(1);\n        deleteKarls();\n\n    }).then(function () {\n        ok(true, \"Transaction completed\");\n    }).catch(function (err) {\n        ok(false, \"Transaction should not abort!\");\n    }).finally(start);\n});\n\n/*asyncTest(\"promise-test\", function () {\n    var p = new Dexie.Promise(function (resolve, reject) {\n        setTimeout(function () {\n            reject(\"apa error\");\n        }, 0);\n    });\n    p.catch(function (err) {\n        return Dexie.Promise.reject(err);\n    });\n    p.then(function(){}).catch(function (err) {\n        return Dexie.Promise.reject(err);\n    });\n    p.onuncatched = function () {\n        debugger;\n    }\n    p.finally(start);\n});*/\n\n\nasyncTest(\"exception in upgrader\", function () {\n    // Create a database:\n    var db = new Dexie(\"TestUpgrader\");\n    db.version(1).stores({ cars: \"++id,name,brand\" });\n    db.open().then(function(){\n        // Once it opens, close it and create an upgraded version that will fail to upgrade.\n        db.close();\n        db = new Dexie(\"TestUpgrader\");\n        db.version(1).stores({ cars: \"++id,name,brand\" });\n        db.version(2).upgrade(function () { db.cars.add({ name: \"My car\", brand: \"Pegeut\" }); });\n        db.version(3).upgrade(function () {\n            throw new Error(\"Oops. Failing in upgrade function\");\n        });\n        return db.open();\n    }).catch(function (err) {\n        // Got error\n        ok(err.toString().indexOf(\"Oops. Failing in upgrade function\") != -1, \"Got error: \" + err);\n        // Create 3rd instance of db that will only read from the existing DB.\n        // What we want to check here is that the DB is there but is still\n        // only on version 1.\n        db = new Dexie(\"TestUpgrader\");\n        return db.open();\n    }).then(function (){\n        equal(db.verno, 1, \"Database is still on version 1 since it failed to upgrade to version 2.\");\n    }).finally(function () {\n        db.delete().then(start);\n    });\n});\n\nasyncTest(\"exception in on('populate')\", function () {\n    // Create a database:\n    var db = new Dexie(\"TestUpgrader\");\n    db.version(1).stores({ cars: \"++id,name,brand\" });\n    db.on('populate', function () {\n        throw new Error(\"Oops. Failing in upgrade function\");\n    });\n    db.open().catch(function (err) {\n        // Got error\n        ok(err.toString().indexOf(\"Oops. Failing in upgrade function\") != -1, \"Got error: \" + err.stack);\n        // Create 3rd instance of db that will only read from the existing DB.\n        // What we want to check here is that the DB is there but is still\n        // only on version 1.\n        db = new Dexie(\"TestUpgrader\");\n        return db.open();\n    }).then(function () {\n        ok(false, \"The database should not have been created\");\n    }).catch(err => {\n        ok(err instanceof Dexie.NoSuchDatabaseError, \"The database doesnt exist\");\n    }).finally(function () {\n        db.delete().then(start);\n    });\n});\n\nasyncTest(\"Error in on('populate') should abort database creation\", function () {\n    var popufail = new Dexie(\"PopufailDB\");\n    popufail.version(1).stores({ users: \"++id,first,last,&username,&*email,*pets\" });\n    popufail.on('populate', function () {\n        popufail.users.add({ first: NaN, last: undefined, username: function () { } }).catch(function (e) {\n            ok(true, \"Got error when catching add() operation: \" + e);\n            return Dexie.Promise.reject(e);\n        });\n    });\n    popufail.open().catch(function (err) {\n        ok(true, \"Got error (as expected):\" + err);\n    });\n    popufail.users.count(function (count) {\n        ok(false, \"Could query database even though an error happened in the populate event!\");\n    }).catch(function (err) {\n        ok(true, \"Got error when trying to query: \" + err);\n    }).finally(function () {\n        popufail.delete();\n        start();\n    });\n});\n\nasyncTest(\"Issue#73 Catching default error where specific error has already been declared in a previous catch clause(A)\", function () {\n    function CustomError() { }\n\n    var wasCatched = false;\n    new Dexie.Promise(function (resolve, reject) {\n        setTimeout(function () {\n            reject(new Error(\"apa\"));\n        }, 0);\n    }).then(function () {\n        ok(false, \"Should not come here\");\n    }).catch(CustomError, function (e) {\n        ok(false, \"Should not come here\");\n    }).catch(function (e) {\n        wasCatched = true;\n    }).finally(function () {\n        ok(wasCatched, \"The error was catched in the generic catcher\");\n        start();\n    });\n});\n\nasyncTest(\"Issue#73 Catching default error where specific error has already been declared in a previous catch clause(B)\", function () {\n    function CustomError() { }\n\n    var wasCatched = false;\n    Dexie.Promise.resolve(null).then(function () {\n        throw new Error(\"apa\");\n    }).then(function () {\n        ok(false, \"Should not come here\");\n    }).catch(CustomError, function (e) {\n        ok(false, \"Should not come here\");\n    }).catch(function (e) {\n        wasCatched = true;\n    }).finally(function () {\n        ok(wasCatched, \"The error was catched in the generic catcher\");\n        start();\n    });\n});\n\nasyncTest(\"Issue #67 - Exception can be thrown in WhereClause methods\", function() {\n    try {\n        Dexie.Promise.all([\n            // WhereClause.equals()\n            db.users.where('first').equals(false) // Using a non-valid key (boolean) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.equals() returned as a failed Promise and not an exception.\");\n            }),\n            // WhereClause.above()\n            db.users.where('first').above(undefined) // Using a non-valid key (undefined) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.above() returned as a failed Promise and not an exception.\");\n            }),\n            // WhereClause.aboveOrEqual()\n            db.users.where('first').aboveOrEqual(undefined) // Using a non-valid key (undefined) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.aboveOrEqual() returned as a failed Promise and not an exception.\");\n            }),\n            // WhereClause.below()\n            db.users.where('first').below(undefined) // Using a non-valid key (undefined) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.below() returned as a failed Promise and not an exception.\");\n            }),\n            // WhereClause.belowOrEqual()\n            db.users.where('first').belowOrEqual(undefined) // Using a non-valid key (undefined) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.belowOrEqual() returned as a failed Promise and not an exception.\");\n            }),\n            // WhereClause.anyOf()\n            db.users.where('first').anyOf([undefined, null, false]) // Using a non-valid key (undefined, false) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.anyOf() returned as a failed Promise and not an exception.\");\n            }),\n            // WhereClause.between()\n            db.users.where('first').between(false, true) // Using a non-valid key (boolean) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.between() returned as a failed Promise and not an exception.\");\n            }),\n            // WhereClause.equalsIgnoreCase()\n            db.users.where('first').equalsIgnoreCase(undefined) // Using a non-valid key (undefined) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.equalsIgnoreCase() returned as a failed Promise and not an exception.\");\n            }),\n            // WhereClause.startsWith()\n            db.users.where('first').startsWith(undefined) // Using a non-valid key (undefined) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.startsWith() returned as a failed Promise and not an exception.\");\n            }),\n            // WhereClause.startsWithIgnoreCase()\n            db.users.where('first').startsWithIgnoreCase(undefined) // Using a non-valid key (undefined) must fail but as a Promise rejection, not an exception.\n            .toArray()\n            .catch(function(err) {\n                ok(true, \"Invalid key passed to WhereClause.startsWithIgnoreCase() returned as a failed Promise and not an exception.\");\n            })\n        ]).catch(function() {\n            ok(false, \"No promise should finally reject because we catch them all explicitely.\")\n        }).finally(start);\n    } catch (ex) {\n        ok(false, \"Error was not encapsulated as a Promise failure: \" + (ex.stack || ex));\n        start();\n    }\n});\n\nasyncTest(\"Issue #67 - Regression test - Transaction still fails if error in key\", function () {\n    db.transaction('rw', db.users, function () {\n        db.users.where('first').above(\"\").delete().then(function (num) {\n            ok(true, num + \" users deleted\");\n            db.users.where('first').above(undefined).delete();\n        });\n    }).then(function() {\n        ok(false, \"Transaction should not commit when we an unhandled error has happened\");\n    }).catch(function(err) {\n        ok(true, \"Good, transaction failed as expected\");\n    }).finally(start);\n});\n\nasyncTest(\"Issue #67 - Regression test 2 - other error in key\", function () {\n    db.transaction('rw', db.users, function () {\n        db.users.where('first').above(\"\").delete().then(function (num) {\n            ok(true, num + \" users deleted\");\n            db.users.where('first').above(false).delete();\n        });\n    }).then(function() {\n        ok(false, \"Transaction should not commit when we an unhandled error has happened\");\n    }).catch(function(err) {\n        ok(true, \"Good, transaction failed as expected\");\n    }).finally(start);\n});\n"
  },
  {
    "path": "test/tests-extendability.js",
    "content": "﻿import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\n\nmodule(\"extendability\");\nasyncTest(\"recursive-pause\", function () {\n    var db = new Dexie(\"TestDB\");\n\n    db.version(1).stores({\n        activities: \"Oid,Task,Tick,Tock,Type,Flags\",\n        tasks: \"Oid,Name,Parent\"\n    });\n\n    var Activity = db.activities.defineClass({\n        Oid: String,\n        Task: String,\n        Tick: Number,\n        Tock: Number,\n        Type: Number,\n        Flags: Number\n    });\n\n    db.on('populate', function () {\n        db.tasks.add({ Oid: \"T1\", Name: \"The root task\" });\n        db.tasks.add({ Oid: \"T2\", Name: \"The child task\", Parent: \"T1\" });\n        db.activities.add({ Oid: \"A1\", Task: \"T2\", Tick: 0, Tock: 10, Type: 1 });\n        db.activities.add({ Oid: \"A2\", Task: \"T2\", Tick: 100, Tock: 110, Type: 1 });\n        db.activities.add({ Oid: \"A3\", Task: \"T2\", Tick: 200, Tock: 210, Type: 2 });\n    });\n\n    db.delete().then(()=>{\n        return db.open();\n    }).then(()=>{\n        \n        return db.transaction(\"rw\", db.activities, db.tasks, function () {\n            Dexie.Promise.newPSD(function () {\n                Dexie.currentTransaction._lock();\n                db.activities.where(\"Type\").equals(2).modify({ Flags: 2 }).finally(function () {\n                    Dexie.currentTransaction._unlock();\n                });\n            });\n            db.activities.where(\"Flags\").equals(2).count(function (count) {\n                equal(count, 1, \"Should have put one entry there now\");\n            });\n            db.activities.where(\"Flags\").equals(2).each(function (act) {\n                equal(act.Type, 2, \"The entry is correct\");\n            });\n        });\n\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n    }).finally(function () {\n        db.delete().then(start);\n    });\n});\n\ntest(\"protochain\", function () {\n    var Promise=Dexie.Promise;\n    var root,\n        branch1,\n        branch2;\n\n    Promise.newPSD(function () {\n        root = Promise.PSD;\n        root.constructor = function () { }\n        root.constructor.prototype = root;\n\n        Promise.newPSD(function () {\n            branch1 = Promise.PSD;\n            branch1.constructor = function () { }\n            branch1.constructor.prototype = branch1;\n        });\n        Promise.newPSD(function () {\n            branch2 = Promise.PSD;\n            branch2.constructor = function () { }\n            branch2.constructor.prototype = branch2;\n        });\n    });\n\n    ok(branch1 instanceof root.constructor, \"branch1 instanceof root.constructor\");\n    ok(branch2 instanceof root.constructor, \"branch2 instanceof root.constructor\");\n    ok(!(root instanceof branch1.constructor), \"!(root instanceof branch1.constructor)\");\n    ok(!(root instanceof branch2.constructor), \"!(root instanceof branch2.constructor)\");\n    ok(!(branch1 instanceof branch2.constructor), \"!(branch1 instanceof branch2.constructor)\");\n    ok(!(branch2 instanceof branch1.constructor), \"!(branch2 instanceof branch1.constructor)\");\n\n\n});\n\ntest(\"protochain2\", function () {\n    var derive = Dexie.derive;\n\n    function Root() { }\n    function Branch1() { }\n    function Branch2() { }\n\n    derive(Branch1).from(Root);\n    derive(Branch2).from(Root);\n\n    var root = new Root();\n    var branch1 = new Branch1();\n    var branch2 = new Branch2();\n\n    ok(branch1 instanceof root.constructor, \"branch1 instanceof root.constructor\");\n    ok(branch2 instanceof root.constructor, \"branch2 instanceof root.constructor\");\n    ok(!(root instanceof branch1.constructor), \"!(root instanceof branch1.constructor)\");\n    ok(!(root instanceof branch2.constructor), \"!(root instanceof branch2.constructor)\");\n    ok(!(branch1 instanceof branch2.constructor), \"!(branch1 instanceof branch2.constructor)\");\n    ok(!(branch2 instanceof branch1.constructor), \"!(branch2 instanceof branch1.constructor)\");\n\n});\n"
  },
  {
    "path": "test/tests-idb30.js",
    "content": "/**\n * Tests for IDB 3.0 getAll/getAllKeys direction optimization\n * @see https://github.com/dexie/Dexie.js/issues/2183\n */\nimport Dexie from 'dexie';\nimport { module, stop, start, asyncTest, ok, equal, deepEqual } from 'QUnit';\nimport { spawnedTest } from './dexie-unittest-utils';\n\nmodule(\"IndexedDB 3.0 features\", {\n    setup: function () {\n        stop();\n        Dexie.delete(\"TestDB-idb30\").then(start);\n    },\n    teardown: function () {\n        stop();\n        Dexie.delete(\"TestDB-idb30\").then(start);\n    }\n});\n\nspawnedTest(\"reverse toArray() should work correctly\", function* () {\n    const db = new Dexie(\"TestDB-idb30\");\n    db.version(1).stores({\n        items: '++id, name'\n    });\n    \n    yield db.open();\n    \n    // Add test data\n    yield db.items.bulkAdd([\n        { name: 'Alice' },\n        { name: 'Bob' },\n        { name: 'Charlie' },\n        { name: 'Diana' },\n        { name: 'Eve' }\n    ]);\n    \n    // Test forward toArray\n    const forward = yield db.items.toArray();\n    equal(forward.length, 5, \"Forward toArray returns 5 items\");\n    equal(forward[0].name, 'Alice', \"First item is Alice\");\n    equal(forward[4].name, 'Eve', \"Last item is Eve\");\n    \n    // Test reverse toArray - this should now use getAll() when IDB 3.0 's support for reverse direction is available\n    const reverse = yield db.items.reverse().toArray();\n    equal(reverse.length, 5, \"Reverse toArray returns 5 items\");\n    equal(reverse[0].name, 'Eve', \"First item in reverse is Eve\");\n    equal(reverse[4].name, 'Alice', \"Last item in reverse is Alice\");\n    \n    db.close();\n});\n\nspawnedTest(\"reverse primaryKeys() should work correctly\", function* () {\n    const db = new Dexie(\"TestDB-idb30\");\n    db.version(1).stores({\n        items: '++id, name'\n    });\n    \n    yield db.open();\n    \n    // Add test data\n    const ids = yield db.items.bulkAdd([\n        { name: 'Alice' },\n        { name: 'Bob' },\n        { name: 'Charlie' }\n    ], { allKeys: true });\n    \n    // Test forward primaryKeys (via toCollection())\n    const forwardKeys = yield db.items.toCollection().primaryKeys();\n    deepEqual(forwardKeys, ids, \"Forward primaryKeys returns correct order\");\n    \n    // Test reverse primaryKeys - uses cursor iteration\n    const reverseKeys = yield db.items.reverse().primaryKeys();\n    deepEqual(reverseKeys, [...ids].reverse(), \"Reverse primaryKeys returns reversed order\");\n    \n    db.close();\n});\n\nspawnedTest(\"reverse toArray() with limit should work correctly\", function* () {\n    const db = new Dexie(\"TestDB-idb30\");\n    db.version(1).stores({\n        items: '++id, value'\n    });\n    \n    yield db.open();\n    \n    // Add test data\n    for (let i = 1; i <= 10; i++) {\n        yield db.items.add({ value: i });\n    }\n    \n    // Test reverse with limit\n    const result = yield db.items.reverse().limit(3).toArray();\n    equal(result.length, 3, \"Limit works with reverse\");\n    equal(result[0].value, 10, \"First item is 10 (highest)\");\n    equal(result[1].value, 9, \"Second item is 9\");\n    equal(result[2].value, 8, \"Third item is 8\");\n    \n    db.close();\n});\n\nspawnedTest(\"reverse on index should work correctly\", function* () {\n    const db = new Dexie(\"TestDB-idb30\");\n    db.version(1).stores({\n        items: '++id, name'\n    });\n    \n    yield db.open();\n    \n    // Add test data\n    yield db.items.bulkAdd([\n        { name: 'Charlie' },\n        { name: 'Alice' },\n        { name: 'Bob' }\n    ]);\n    \n    // Test forward orderBy on index\n    const forward = yield db.items.orderBy('name').toArray();\n    equal(forward[0].name, 'Alice', \"Forward orderBy: first is Alice\");\n    equal(forward[2].name, 'Charlie', \"Forward orderBy: last is Charlie\");\n    \n    // Test reverse orderBy on index\n    const reverse = yield db.items.orderBy('name').reverse().toArray();\n    equal(reverse[0].name, 'Charlie', \"Reverse orderBy: first is Charlie\");\n    equal(reverse[2].name, 'Alice', \"Reverse orderBy: last is Alice\");\n    \n    db.close();\n});\n"
  },
  {
    "path": "test/tests-live-query.js",
    "content": "import Dexie, { liveQuery } from 'dexie';\nimport { equal, module, ok, start, stop } from 'QUnit';\nimport { from } from \"rxjs\";\nimport { map } from \"rxjs/operators\";\nimport { deepEqual, isDeepEqual } from './deepEqual';\nimport { isIE, promisedTest, resetDatabase } from './dexie-unittest-utils';\n\nconst db = new Dexie(\"TestLiveQuery\", {\n  cache: 'immutable' // Using immutable cache in tests because it is most likely to fail if not using properly.\n});\ndb.version(2).stores({\n    items: \"id, name\",\n    foo: \"++id\",\n    outbound: \"++,name\",\n    friends: \"++id, name, age\",\n    multiEntry: \"id, *tags\",\n    issue1946: \"++id, [name+age], [name+age+id]\",\n    issue2058: \"id, &[a+b]\"\n});\n\ndb.on('populate', ()=> {\n  db.items.bulkAdd([\n      {id: 1},\n      {id: 2},\n      {id: 3}\n  ]);\n  db.outbound.bulkAdd([\n    {num: 1, name: \"A\"},\n    {num: 2, name: \"B\"},\n    {num: 3, name: \"C\"}\n  ], [1, 2, 3]);\n});\n\nfunction objectify(map) {\n  const rv = {};\n  map.forEach((value, name) => {\n    rv[name] = value;\n  });\n  return rv;\n}\n\nclass Signal {\n  promise = new Promise(resolve => this.resolve = resolve);\n}\n\nfunction liveQueryUnitTester(lq, {graceTime}={graceTime: 0}) {\n  const lq = liveQuery(lq)\n  return {\n    waitTilDeepEqual(expected, description, timeout=500) {\n      return new Promise((resolve, reject) => {\n        let latestValue;\n        const subscription = lq.subscribe(value => {\n          latestValue = value;\n          if (isDeepEqual(value, expected)) {\n            deepEqual(value, expected, description);\n            clearTimeout(timer);\n            subscription.unsubscribe();\n            resolve();\n          }\n        });\n        let timer = setTimeout(() => {\n          subscription.unsubscribe();\n          try {\n            deepEqual(latestValue, expected, description);\n            resolve();\n          } catch (e) {\n            reject(e);\n          }\n        }, timeout);\n      });\n    }\n  }\n}\n\nmodule(\"live-query\", {\n  setup: function () {\n      stop();\n      resetDatabase(db).catch(function (e) {\n          ok(false, \"Error resetting database: \" + e.stack);\n      }).finally(start);\n  },\n  teardown: function () {\n  }\n});\n\n/*promisedTest(\"txcommitted event\", async ()=>{\n  let signal = new Signal();\n  let os = {};\n  function txCommitted(observabilitySet) {\n    Dexie.extendObservabilitySet(os, observabilitySet);\n    signal.resolve(observabilitySet);\n  }\n  await db.open();\n  Dexie.on('txcommitted', txCommitted);\n  await db.transaction('rw', db.items, db.foo, async ()=>{\n    await db.items.add({id: 4, name: \"aiwo1\"});\n    await db.items.add({id: 7, name: \"kjlj\"});\n    await db.foo.add({name: \"jkll\"});\n    await db.items.update(1, {name: \"A\"});\n  });\n  while (!os.TestLiveQuery || !os.TestLiveQuery.items || !hasKey(os.TestLiveQuery.items.keys, 4)) {\n    // When Dexie.Observable is active, we might see intermediate transactions taking place\n    // before our transaction.\n    signal = new Signal();\n    await signal.promise;\n    console.log(\"got new os:\", os);\n  }\n  ok(!!os.TestLiveQuery, \"Got changes in our table name TestLiveQuery\");\n  let itemsChanges = os.TestLiveQuery.items;\n  ok(itemsChanges, \"Got changes for items table\");\n  deepEqual(itemsChanges.keys, rangeSet([[4], [7], [1]]), \"Item changes on concattenated keys\");\n  deepEqual(itemsChanges.indexes, {\"\": rangeSet([[4],[7]]),name: rangeSet([[\"aiwo1\"],[\"kjlj\"],[\"A\"]])}, \"Index changes present\");\n\n  // Foo changes (auto-incremented id)\n  let fooChanges = os.TestLiveQuery.foo;\n  ok(fooChanges, \"Got changes for foo table\");\n\n  os = {};\n  let fooIds = await db.foo.toCollection().primaryKeys();\n  await db.transaction('rw', db.items, db.foo, async ()=>{\n    await db.items.update(4, {name: \"aiwo2\"});\n    await db.foo.where('id').between(0, 1000).delete();\n  });\n  while (!os.TestLiveQuery || !os.TestLiveQuery.items || !hasKey(os.TestLiveQuery.items.keys, 4)) {\n    // When Dexie.Observable is active, we might see intermediate transactions taking place\n    // before our transaction.\n    signal = new Signal();\n    await signal.promise;\n  }\n  itemsChanges = os.TestLiveQuery.items;\n  ok(hasKey(itemsChanges.keys, 4), \"Item 4 was updated\");\n  ok(hasKey(itemsChanges.indexes.name, \"aiwo1\"), \"Old value of name index were triggered\");\n  ok(hasKey(itemsChanges.indexes.name, \"aiwo2\"), \"New value of name index were triggered\");\n\n  fooChanges = os.TestLiveQuery.foo;\n  ok(!!fooChanges, \"Foo table changed\");\n  if (hasKey(fooChanges.keys, 0) && hasKey(fooChanges.keys, 1000)) {\n    // Without addons:\n    deepEqual(fooChanges.keys, rangeSet([[0, 1000]]), \"Got a range update of foo keys 0..1000\");\n  } else {\n    // With hooks / addons or browser workarounds:\n    deepEqual(fooChanges.keys, rangeSet(fooIds.map(id => [id])), \"Got individual delete updates of foo keys \", fooIds.join(','));\n  }\n\n  Dexie.on('txcommitted').unsubscribe(txCommitted);\n});*/\n\npromisedTest(\"subscribe to range\", async ()=> {\n  let tester = liveQueryUnitTester(()=>db.items.toArray());\n  await tester.waitTilDeepEqual([{id: 1}, {id: 2}, {id: 3}], \"First callback should give initally populated content\");\n  db.items.add({id:-1});\n  await tester.waitTilDeepEqual([{id:-1}, {id: 1}, {id: 2}, {id: 3}], \"2nd callback should give updated content\");\n  db.items.delete(2);\n  await tester.waitTilDeepEqual([{id:-1}, {id: 1}, {id: 3}], \"3rd callback should wake up when deletion was made\");\n});\n\npromisedTest(\"subscribe to keys\", async ()=>{\n  if (isIE) {\n    // The IE implementation becomes shaky here.\n    // Maybe becuase we launch several parallel queries to IDB.\n    ok(true, \"Skipping this test for IE - too shaky for the CI\");\n    return;\n  }\n  let signal1 = new Signal(), signal2 = new Signal();\n  let count1 = 0, count2 = 0;\n  //const debugTxCommitted = set => console.debug(\"txcommitted\", set);\n  //Dexie.on('txcommitted', debugTxCommitted);\n  let sub1 = liveQuery(()=>db.items.get(1)).subscribe(result => {\n    ++count1;\n    signal1.resolve(result);\n  });\n  let res1 = await signal1.promise;\n  equal(res1.id, 1, \"First result for ID 1 ok\");\n  equal(count1, 1, \"Callback called once\");\n  let sub2 = liveQuery(()=>db.items.get(2)).subscribe(result => {\n    ++count2;\n    signal2.resolve(result);\n  });\n  let res2 = await signal2.promise;\n  equal(res2.id, 2, \"2nd result for ID 2 ok\");\n  equal(count2, 1, \"2nd callback called once\");\n  equal(count1, 1, \"First callback wasn't called again\");\n  \n  // Now mutate using update - verify listeners don't wake up on other than the keys the subscribe\n  signal1 = new Signal();\n  signal2 = new Signal();\n  await db.items.update(1, {name: \"one\"});\n  ok(true, \"Could update item 1\");\n  res1 = await signal1.promise;\n  equal(count1, 2, \"First should have been called 2 times now\");\n  equal(count2, 1, \"2nd callback should still only have been called once\");\n  equal(res1.name, \"one\", \"We got the updated value from the expression\");\n  await db.items.update(2, {name: \"two\"});\n  res2 = await signal2.promise;\n  equal(count1, 2, \"First should have been called 2 times now\");\n  equal(count2, 2, \"2nd callback should have been called twice also\");\n  equal(res2.name, \"two\", \"We got the updated value from the 2nd expression\");\n\n  // Now mutate using delete\n  signal1 = new Signal();\n  signal2 = new Signal();\n  await db.items.delete(1);\n  res1 = await signal1.promise;\n  equal(count1, 3, \"First should have been called 3 times now\");\n  equal(count2, 2, \"2nd callback should still only have been called twice\");\n  ok(res1 === undefined, \"The updated result of db.items.get(1) should return undefined after the deletion\");\n\n  await db.items.delete(2);\n  res2 = await signal2.promise;\n  equal(count1, 3, \"First should still have been called 3 times\");\n  equal(count2, 3, \"2nd callback should have been called 3 times also now\");\n  ok(res2 === undefined, \"The updated result of db.items.get(2) should return undefined after the deletion\");\n  \n  // Verify that no more callbacks are called after unsubscribing\n  sub1.unsubscribe();\n  sub2.unsubscribe();\n  await db.items.update(1, {name: \"fljkds\"});\n  equal(count1, 3, \"No more calls after having unsubscribed\");\n  await db.items.update(1, {name: \"sfdfs\"});\n  equal(count1, 3, \"Just double-checking - no more calls after having unsubscribed\");\n});\n\npromisedTest(\"subscribe and error occur\", async ()=> {\n  let signal = new Signal();\n  let subscription = liveQuery(\n    ()=>db.items.get(NaN) // NaN is not a valid key\n  ).subscribe({\n    next: result => signal.resolve(\"success\"),\n    error: result => signal.resolve(\"error\"),\n    complete: ()=> signal.resolve(\"complete\")\n  });\n  ok(!subscription.closed, \"Subscription should not yet be closed\");\n  let result = await signal.promise;\n  equal(result, \"error\", \"The observable's error callback should have been called\");\n  //No. Should not close errored subscriptions. What if they did a fetch call in it that failed? SHould keep subscribing.\n  //ok(subscription.closed, \"Subscription should have been closed after error has occurred\");\n  subscription.unsubscribe();\n});\n\npromisedTest(\"optimistic updates that eventually fail must be reverted (Issue #1823)\", async ()=>{\n  const log = [];\n  let subscription = liveQuery(\n    ()=>db.items.toArray()\n  ).subscribe({\n    next: result => {\n      log.push(result);\n      console.log(\"optimistic result (from #1823 test)\", result);\n    },\n  });\n\n  await db.transaction('rw', db.items, async ()=>{\n    // Simple test a catched failing operation\n    await db.items.add(\n      {id: 1, iWillFail: true} // Contraint error (key 1 already exists)\n    ).catch(()=>{});\n    // Test another code path in adjustOptimisticFromFailures() where some operations succeed and some not.\n    await db.items.bulkAdd([\n      {id: 2, iWillFail: true}, // Constraint error (key 2 already exists)\n      {id: 99, iWillSucceed: true}\n    ]).catch(()=>{});\n  });\n  // Wait for a successful readonly transaction to complete after the write transaction.\n  // This will make sure that the liveQuery has been updated with the final result.\n  await db.transaction('r', db.items, ()=>db.items.toArray());\n  subscription.unsubscribe();\n  deepEqual(log.at(-1), [{id: 1},{id:2},{id:3},{id: 99, iWillSucceed: true}], \"Last log entry contains the correct result. There might be optimistic updates before though.\");\n});\n\n/* Use cases to cover:\n\n  Queries\n    get\n    getMany\n    query\n    queryKeys\n    itemsStartsWithAOffset3\n    openKeyCursor\n    count\n    queryOutbound\n    queryOutboundByPKey\n    openCursorOutbound\n\n  Mutations\n    add\n    addAuto\n    update\n    delete\n    deleteRange\n */\n\nlet abbaKey = 0;\nlet lastFriendId = 0;\nlet barbarFriendId = 0;\nconst bulkFriends = [];\nfor (let i=0; i<51; ++i) {\n  bulkFriends.push({name: `name${i}`, age: i});\n}\nconst bulkOutbounds = [];\nfor (let i=0; i<51; ++i) {\n  bulkOutbounds.push({name: \"z\"+i.toLocaleString('en-US', {\n    minimumIntegerDigits: 2,\n    useGrouping: false\n  })});\n}\nconst mutsAndExpects = () => [\n  // add\n  [\n    ()=>db.items.add({id: -1, name: \"A\"}),\n    {\n      itemsToArray: [{id: -1,name:\"A\"}, {id: 1}, {id: 2}, {id: 3}],\n      itemsGet1And2: [{id: 1}, {id: -1, name: \"A\"}],\n      itemsStartsWithA: [{id: -1, name: \"A\"}],\n      itemsStartsWithAPrimKeys: [-1],\n      itemsStartsWithAOffset3: [],\n      itemsStartsWithAKeys: [\"A\"],\n      itemsStartsWithACount: 1\n    }\n  ],\n  // addAuto\n  [\n    ()=>db.outbound.add({name: \"Abba\"}).then(id=>abbaKey = id),\n    {\n      outboundToArray: [{num:1,name:\"A\"},{num:2,name:\"B\"},{num:3,name:\"C\"},{name:\"Abba\"}],\n      outboundStartsWithA: [{name: \"A\", num: 1}, {name: \"Abba\"}]\n    }\n  ], [\n    ()=>db.outbound.bulkAdd([{name: \"Benny\"}, {name: \"C\"}], [-1, 0]),\n    {\n      outboundToArray: [{name:\"Benny\"},{name: \"C\"},{num:1,name:\"A\"},{num:2,name:\"B\"},{num:3,name:\"C\"},{name:\"Abba\"}],\n      outboundIdBtwnMinus1And2: [{name: \"Benny\"}, {name: \"C\"}, {name: \"A\", num: 1}, {name: \"B\", num: 2}],\n      outboundAnyOf_BCD_keys: [\"B\", \"C\", \"C\"]\n    }\n  ],\n  // update\n  [\n    ()=>db.outbound.update(abbaKey, {name: \"Zlatan\"}),\n    {\n      outboundToArray: [{name:\"Benny\"},{name: \"C\"},{num:1,name:\"A\"},{num:2,name:\"B\"},{num:3,name:\"C\"},{name:\"Zlatan\"}],\n      outboundStartsWithA: [{name: \"A\", num: 1}]\n    }\n  ],\n  [\n    // Testing that keys-only queries don't get bothered\n    ()=>db.items.update(-1, {foo: \"bar\"}),\n    {\n      itemsToArray: [{id: -1,name:\"A\", foo: \"bar\"}, {id: 1}, {id: 2}, {id: 3}],\n      itemsGet1And2: [{id: 1}, {id: -1, name: \"A\", foo: \"bar\"}],\n      itemsStartsWithA: [{id: -1, name: \"A\", foo: \"bar\"}],\n      //itemsStartsWithAPrimKeys: [-1], should not have to be updated!\n      //itemsStartsWithAKeys: [\"A\"] should not have to be updated!\n    }\n  ],\n  [\n    // Update an index property (name) should trigger\n    // listeners to that index:\n    ()=>db.items.update(-1, {foo: undefined, name: \"B\"}),\n    {\n      itemsToArray: [{id: -1, name: \"B\"}, {id: 1}, {id: 2}, {id: 3}],\n      itemsGet1And2: [{id: 1}, {id: -1, name: \"B\"}],\n      itemsStartsWithA: [],\n      itemsStartsWithAPrimKeys: [],\n      itemsStartsWithAOffset3: [],\n      itemsStartsWithAKeys: [],\n      itemsStartsWithACount: 0\n    }\n  ],\n  [\n    // Restoring and re-checking.\n    ()=>db.items.update(-1, {name: \"A\"}),\n    {\n      itemsToArray: [{id: -1, name: \"A\"}, {id: 1}, {id: 2}, {id: 3}],\n      itemsGet1And2: [{id: 1}, {id: -1, name: \"A\"}],\n      itemsStartsWithA: [{id: -1, name: \"A\"}],\n      itemsStartsWithAPrimKeys: [-1],\n      itemsStartsWithAOffset3: [],\n      itemsStartsWithAKeys: [\"A\"],\n      itemsStartsWithACount: 1\n    }\n  ],\n  // add again\n  [\n    ()=>db.items.bulkAdd([{id: 4, name: \"Abbot\"},{id: 5, name: \"Assot\"},{id: 6, name: \"Ambros\"}]).then(lastId => {}),\n    {\n      itemsToArray: [{id:-1,name:\"A\"},{id:1},{id:2},{id:3},{id:4,name:\"Abbot\"},{id:5,name:\"Assot\"},{id:6,name:\"Ambros\"}],\n      itemsStartsWithA: [{id: -1, name: \"A\"}, {id: 4, name: \"Abbot\"}, {id: 6, name: \"Ambros\"}, {id: 5, name: \"Assot\"}],\n      itemsStartsWithAPrimKeys: [-1, 4, 6, 5],\n      itemsStartsWithAOffset3: [{id: 5, name: \"Assot\"}], // offset 3\n      itemsStartsWithAKeys: [\"A\", \"Abbot\", \"Ambros\", \"Assot\"],\n      itemsStartsWithACount: 4\n    }\n  ],\n  // delete:\n  [\n    ()=>db.transaction('rw', db.items, db.outbound, ()=>{\n      db.items.delete(-1);\n    }),\n    {\n      itemsToArray: [{id:1},{id:2},{id:3},{id:4,name:\"Abbot\"},{id:5,name:\"Assot\"},{id:6,name:\"Ambros\"}],\n      itemsGet1And2: [{id: 1}, null],\n      itemsStartsWithA: [{id: 4, name: \"Abbot\"}, {id: 6, name: \"Ambros\"}, {id: 5, name: \"Assot\"}],\n      itemsStartsWithAPrimKeys: [4, 6, 5],\n      itemsStartsWithAKeys: [\"Abbot\", \"Ambros\", \"Assot\"],\n      itemsStartsWithACount: 3\n    },\n    // Allowed extras:\n    // If hooks is listened to we'll get an even more correct update of the itemsStartsWithAOffset3 query\n    // since oldVal will be available and offset-queries will be correcly triggered for deleted index keys before the offset.\n    {\n      itemsStartsWithAOffset3: [] \n    }\n  ],\n  // Special case for more fine grained keys observation of put (not knowing oldObjs\n  [\n    ()=>db.items.put({id: 5, name: \"Azlan\"}),\n    {\n      itemsToArray: [{id:1},{id:2},{id:3},{id:4,name:\"Abbot\"},{id:5,name:\"Azlan\"},{id:6,name:\"Ambros\"}],\n      itemsStartsWithA: [{id: 4, name: \"Abbot\"}, {id: 6, name: \"Ambros\"}, {id: 5, name: \"Azlan\"}],\n      itemsStartsWithAKeys: [\"Abbot\", \"Ambros\", \"Azlan\"],\n    }, {\n      // Things that optionally can be matched in result (if no hooks specified):\n      itemsStartsWithAPrimKeys: [4, 6, 5], \n      itemsStartsWithACount: 3,\n      itemsStartsWithAOffset3: []\n    }\n  ],\n  [\n    ()=>db.transaction('rw', db.items, db.outbound, ()=>{\n      db.items.bulkPut([{id: 5}]);\n    }),\n    {\n      itemsToArray: [{id:1},{id:2},{id:3},{id:4,name:\"Abbot\"},{id:5},{id:6,name:\"Ambros\"}],\n      itemsStartsWithA: [{id: 4, name: \"Abbot\"}, {id: 6, name: \"Ambros\"}],\n      itemsStartsWithAPrimKeys: [4, 6],\n      itemsStartsWithAKeys: [\"Abbot\", \"Ambros\"],\n      itemsStartsWithACount: 2\n    }, {\n      itemsStartsWithAOffset3: [] // This is\n    }\n  ],\n  [\n    ()=>db.transaction('rw', db.items, db.outbound, ()=>{\n      db.items.delete(5);\n      db.outbound.bulkDelete([abbaKey,-1,0]);\n    }),\n    {\n      itemsToArray: [{id:1},{id:2},{id:3},{id:4,name:\"Abbot\"},{id:6,name:\"Ambros\"}],\n      // (allOutbound was:\n      //  [{name:\"Benny\"},{name: \"C\"},{num:1,name:\"A\"},{num:2,name:\"B\"},{num:3,name:\"C\"},{name:\"Zlatan\"}])\n      // )\n      outboundToArray: [{num:1,name:\"A\"},{num:2,name:\"B\"},{num:3,name:\"C\"}],\n      //outboundStartsWithA: [{num:1,name:\"A\"}],\n      outboundIdBtwnMinus1And2: [{num:1,name:\"A\"},{num:2,name:\"B\"}],\n      outboundAnyOf_BCD_keys: [\"B\", \"C\"]\n    },[\n      \"itemsStartsWithACount\"\n    ]\n  ],\n  [\n    ()=>db.friends.add({name: \"Foo\", age: 20}).then(id => lastFriendId = id),\n    {\n      friendsOver18: [{get id(){return lastFriendId}, name: \"Foo\", age: 20}]\n    }\n  ],\n  [\n    ()=>db.friends.put({name: \"Barbar\", age: 21}).then(id => barbarFriendId = id),\n    {\n      friendsOver18: [\n        {get id(){return lastFriendId}, name: \"Foo\", age: 20},\n        {get id(){return barbarFriendId}, name: \"Barbar\", age: 21}\n      ]\n    }\n  ],\n  [\n    // bulkPut\n    ()=>db.friends.bulkPut(bulkFriends, {allKeys: true}).then(ids => {\n      // Record the actual ids here\n      for (let i=0; i<ids.length; ++i) {\n        bulkFriends[i].id = ids[i];\n      }\n    }),\n    {\n      friendsOver18: [\n        {get id(){return lastFriendId}, name: \"Foo\", age: 20},\n        {get id(){return barbarFriendId}, name: \"Barbar\", age: 21},\n        ...bulkFriends.map(f => ({name: f.name, age: f.age, get id() { return f.id; }})).filter(f => f.age > 18)\n      ].sort((a,b) => a.age - b.age)\n    }\n  ],\n  // bulkPut over 50 items on an outbound table:\n  [\n    ()=>db.outbound.bulkPut(bulkOutbounds),\n    {\n      outboundToArray: [{num:1,name:\"A\"},{num:2,name:\"B\"},{num:3,name:\"C\"}, ...bulkOutbounds],\n      outbound_above_z49: [...bulkOutbounds.filter(o => o.name > \"z49\")]\n    },[\"outboundStartsWithA\", \"outboundIdBtwnMinus1And2\", \"outboundAnyOf_BCD_keys\"]\n  ],\n  // deleteRange\n  [\n    ()=>db.friends.where('id').between(0, barbarFriendId, true, true).delete(),\n    {\n      friendsOver18: [...bulkFriends.filter(f => f.age > 18)]\n    }\n  ],\n  // bulkDelete\n  [\n    // Delete all but one:\n    ()=>db.friends.bulkDelete(bulkFriends.filter(f => f.age !== 20).map(f => f.id)),\n    {\n      friendsOver18: [...bulkFriends.filter(f => f.age === 20)]\n    }\n  ],\n  // multiEntry\n  [\n    () => db.multiEntry.add({id: 1, tags: [\"fooTag\", \"Apa\"]}),\n    {\n      multiEntry1: [1],\n      multiEntry2: [1]\n    }\n  ],\n  [\n    () => db.multiEntry.bulkPut([\n      {id: 1, tags: []},\n      {id: 2, tags: [\"Apa\", \"x\", \"y\"]},\n      {id: 3, tags: [\"barTag\", \"fooTag\"]}\n    ]),\n    {\n      multiEntry1: [2],\n      multiEntry2: [3],\n      multiEntry3: [{id: 2, tags: [\"Apa\", \"x\", \"y\"]}]\n    }\n  ],\n  // Issue https://github.com/dexie/Dexie.js/issues/1946\n  [\n    () => db.issue1946.add({name: \"A\", age: 20}),\n    {\n      compoundOrderBy: [{name: \"A\", age: 20, id: 1}],\n      compoundOrderByWithAutoIncKey: [{name: \"A\", age: 20, id: 1}]\n    }\n  ],\n  // Check that the order is valid in the 1946-case:\n  [\n    () => db.issue1946.bulkAdd([\n      {name: \"A\", age: 19},\n      {name: \"A\", age: 19, mark: \"x\"},\n      {name: \"C\", age: 18},\n      {name: \"A\", age: 20, id: -1} // Override auto-increment\n    ]),\n    {\n      compoundOrderBy: [\n        {name: \"A\", age: 19, id: 2},\n        {name: \"A\", age: 19, mark: \"x\", id: 3}, // Even if cmp([\"A\",19],[\"A\",19]) === 0, IDB orders implicitly by id.\n        {name: \"A\", age: 20, id: -1}, // Same here: order should implicitly be by PK last.\n        {name: \"A\", age: 20, id: 1},\n        {name: \"C\", age: 18, id: 4},\n      ],\n      compoundOrderByWithAutoIncKey: [\n        {name: \"A\", age: 19, id: 2},\n        {name: \"A\", age: 19, mark: \"x\", id: 3},\n        {name: \"A\", age: 20, id: -1},\n        {name: \"A\", age: 20, id: 1},\n        {name: \"C\", age: 18, id: 4},\n      ]\n    }\n  ],\n  // Issue 2011 / 2012\n  [\n    () => db.items.bulkPut([\n      {id: 6, name: \"One\"},\n      {id: 6, name: \"Two\"},\n      {id: 6, name: \"Three\"}\n    ]),\n    {\n      itemsToArray: [{id:1},{id:2},{id:3},{id:4,name:\"Abbot\"},{id:6,name:\"Three\"}],\n      itemsStartsWithA: [{id: 4, name: \"Abbot\"}],\n      itemsStartsWithAPrimKeys: [4],\n      itemsStartsWithAKeys: [\"Abbot\"],\n      itemsStartsWithACount: 1\n    },[\n      \"itemsStartsWithAOffset3\" // Should not be updated but need to be ignored because otherwise it fails in dexie-syncable's integration tests that expects it to update to another empty array\n    ]\n  ]\n]\n\npromisedTest(\"Full use case matrix\", async ()=>{\n  const queries = {\n    itemsToArray: () => db.items.toArray(),\n    itemsGet1And2: () => Promise.all(db.items.get(1), db.items.get(-1)),\n    itemsBulkGet123: () => db.items.bulkGet([1,2,3]),\n    itemsStartsWithA: () => db.items.where('name').startsWith(\"A\").toArray(),\n    itemsStartsWithAPrimKeys: () => db.items.where('name').startsWith(\"A\").primaryKeys(),\n    itemsStartsWithAOffset3: () => db.items.where('name').startsWith(\"A\").offset(3).toArray(),\n    itemsStartsWithAKeys: () => db.items.where('name').startsWith(\"A\").keys(),\n    itemsStartsWithACount: () => db.items.where('name').startsWith(\"A\").count(),\n    \n    outboundToArray: () => db.outbound.toArray(),\n    outboundStartsWithA: () => db.outbound.where('name').startsWith(\"A\").toArray(),\n    outboundIdBtwnMinus1And2: () => db.outbound.where(':id').between(-1, 2, true, true).toArray(),\n    outboundAnyOf_BCD_keys: () => db.outbound.where('name').anyOf(\"B\", \"C\", \"D\").keys(),\n    outbound_above_z49: () => db.outbound.where('name').above(\"z49\").toArray(),\n\n    friendsOver18: () => db.friends.where('age').above(18).toArray(),\n\n    multiEntry1: () => db.multiEntry.where('tags').startsWith('A').primaryKeys(),\n    multiEntry2: () => db.multiEntry.where({tags: \"fooTag\"}).primaryKeys(),\n    multiEntry3: () => db.multiEntry.where({tags: \"x\"}).toArray(),\n\n    // Issue https://github.com/dexie/Dexie.js/issues/1946\n    compoundOrderBy: () => db.issue1946.orderBy('[name+age]').toArray(),\n    compoundOrderByWithAutoIncKey: () => db.issue1946.orderBy('[name+age+id]').toArray(),\n  };\n  const expectedInitialResults = {\n    itemsToArray: [{id: 1}, {id: 2}, {id: 3}],\n    itemsGet1And2: [{id: 1}, undefined],\n    itemsBulkGet123: [{id: 1}, {id: 2}, {id: 3}],\n    itemsStartsWithA: [],\n    itemsStartsWithAPrimKeys: [],\n    itemsStartsWithAOffset3: [],\n    itemsStartsWithAKeys: [],\n    itemsStartsWithACount: 0,\n\n    outboundToArray: [\n      {num: 1, name: \"A\"},\n      {num: 2, name: \"B\"},\n      {num: 3, name: \"C\"}\n    ],\n    outboundStartsWithA: [{num: 1, name: \"A\"}],\n    outboundIdBtwnMinus1And2: [{num: 1, name: \"A\"}, {num: 2, name: \"B\"}],\n    outboundAnyOf_BCD_keys: [\"B\", \"C\"],\n    outbound_above_z49: [],\n\n    friendsOver18: [],\n\n    multiEntry1: [],\n    multiEntry2: [],\n    multiEntry3: [],\n\n    // Issue https://github.com/dexie/Dexie.js/issues/1946\n    compoundOrderBy: [],\n    compoundOrderByWithAutoIncKey: []\n  }\n  let flyingNow = 0;\n  //let signal = new Signal();\n  let eventTarget = new EventTarget();\n  const actualResults = objectify(new Map(Object.keys(queries).map(name => [name, undefined])));\n  const observables = new Map(Object.keys(queries).map(name => [\n    name,\n    liveQuery(async () => {\n      ++flyingNow;\n      try {\n        const res = await queries[name]();\n        console.log(`Setting actual result of ${name} to ${JSON.stringify(res)}`);\n        actualResults[name] = res;\n        return res;\n      } finally {\n        if (--flyingNow === 0) eventTarget.dispatchEvent(new CustomEvent('zeroflyers'));\n      }\n    })\n  ]));\n\n  function zeroFlyers(abortSignal) {\n    return new Promise((resolve, reject) => {\n      eventTarget.addEventListener('zeroflyers', resolve, {once: true});\n      abortSignal.addEventListener('abort', () => reject(new Error('flyers timeout')), { once: true});\n    });\n  }\n\n  function timeout(ms) {\n    const ac = new AbortController();\n    setTimeout(()=>ac.abort(), ms);\n    return ac.signal;\n  }\n  \n  async function allValuesOk(actual, expected, allowedExtra, prevActual, abortSignal) {\n    while (flyingNow > 0) await zeroFlyers(abortSignal);\n    while (!isDeepEqual(actual, expected, allowedExtra, prevActual)) {\n      await zeroFlyers(abortSignal);\n    }\n  }\n\n  const subscriptions = Object.keys(queries).map(name => {\n    let gotAnyData = false;\n    ++flyingNow;\n    const subscription = observables.get(name).subscribe({\n      next: res => {\n        if (!gotAnyData) {\n          gotAnyData = true;\n          if (--flyingNow === 0) eventTarget.dispatchEvent(new CustomEvent('zeroflyers'));\n        }\n      },\n      error: error => {\n        ok(false, ''+error)\n      }\n    });\n    return subscription;\n  });\n  try {\n    await zeroFlyers(timeout(200));\n    deepEqual(actualResults, expectedInitialResults, \"Initial results as expected\");\n    let prevActual = Dexie.deepClone(actualResults);\n    for (const [mut, expects, allowedExtra] of mutsAndExpects()) {\n      console.log(`RUNNING: ${mut.toString()}`);\n      console.log(`---------------------------------`);\n      actualResults = {};\n      await mut();\n      try {\n        await allValuesOk(actualResults, expects, allowedExtra, prevActual, timeout(200));\n      } catch {}\n      const expected = Dexie.deepClone(expects);\n      if (allowedExtra) Array.isArray(allowedExtra) ? allowedExtra.forEach(key => {\n        if (actualResults[key]) expected[key] = prevActual[key];\n      }) : Object.keys(allowedExtra).forEach(key => {\n        if (actualResults[key]) expected[key] = allowedExtra[key];\n      });\n      deepEqual(actualResults, expected, `${mut.toString()}`);\n      Object.assign(prevActual, actualResults);\n    }\n  } finally {\n    subscriptions.forEach(s => s.unsubscribe());\n  }\n});\n\npromisedTest(\"RxJS compability\", async ()=>{\n  let signal = new Signal();\n  const o = from(liveQuery(\n    ()=>db.items.toArray()\n  )).pipe(\n   map(items => items.map(item => item.id)) \n  );\n\n  const s = o.subscribe(results => signal.resolve(results));\n  const result = await signal.promise;\n  deepEqual(result, [1, 2, 3], \"We should have get a mapped result\");\n  signal = new Signal();\n  db.items.add({id: 4});\n  const res2 = await signal.promise;\n  deepEqual(res2, [1, 2, 3, 4], \"We should have get an updated mapped result\");\n  s.unsubscribe();\n});\n\npromisedTest(\"Isolation: Explicit rw transactions do not affect live queries before committed\", async ()=> {\n  let log = [];\n  let signal = new Signal();\n  let subscription = liveQuery(()=>db.items.toArray()).subscribe(result => {\n    log.push({type: \"emit\", result});\n    signal.resolve(result);\n  });\n  let result = await signal.promise;\n  deepEqual(result, [{id:1},{id:2},{id:3}], \"First callback should give initally populated content\");\n  deepEqual(log, [{type: \"emit\", result: [{id:1},{id:2},{id:3}]}], \"First callback should give initally populated content\");\n  signal = new Signal();\n  await db.transaction('rw', db.items, async ()=>{\n    await db.items.add({id: 4});\n    await db.items.update(4, {name: \"A\"});\n    await db.items.toArray(); // Make some additional work in the transaction\n    await db.items.count(); // Make some additional work in the transaction\n    equal(log.length, 1, \"No new emit should have been made yet\");\n  });\n  await signal.promise;\n  deepEqual(log, [\n    {type: \"emit\", result: [{id:1},{id:2},{id:3}]},\n    {type: \"emit\", result: [{id:1},{id:2},{id:3},{id:4, name: \"A\"}]}\n  ], \"The committed transaction should now have been made\");\n  //signal = new Signal();\n  await db.transaction('rw', db.items, async (tx)=>{\n    await db.items.add({id: 5});\n    equal(log.length, 2, \"No new emit should have been made\");\n    tx.abort(); // Aborting the transaction should make no new emit\n  }).catch(()=>{});\n  equal(log.length, 2, \"No new emit should have been made\");\n  subscription.unsubscribe();\n});\n\npromisedTest(\"Issue 1821: liveQuery containing primaryKeys should not emit when content on one element is changed\", async ()=>{\n  let log = [];\n  let signal = new Signal();\n  let subscription = liveQuery(()=>db.items.orderBy('id').primaryKeys()).subscribe(result => {\n    log.push({type: \"emit\", result});\n    signal.resolve(result);\n  });\n  let result = await signal.promise;\n  deepEqual(result, [1, 2, 3], \"First callback should give initally populated content\");\n  deepEqual(log, [{type: \"emit\", result: [1, 2, 3]}], \"First callback should give initally populated content\");\n  await db.items.update(1, {name: \"A\"}); // This should not emit anything since the primary key is not changed.\n  equal(log.length, 1, \"No new emit should have been made\");\n  await new Promise(resolve => setTimeout(resolve, 25));\n  signal = new Signal();\n  await db.items.add({id: 4}); // This should emit though.\n  await signal.promise;\n  equal(log.length, 2, \"Only one extra emit should have been made\");\n  deepEqual(log, [\n    {type: \"emit\", result: [1, 2, 3]},\n    {type: \"emit\", result: [1, 2, 3, 4]}\n  ], \"No new emit should have been made\");\n  subscription.unsubscribe();\n});\n\npromisedTest(\"Issue 2067: useLiveQuery does not update when multiple items are deleted\", async () => {\n  let items = await db.items.reverse().toArray();\n  deepEqual(items, [{id:3},{id:2},{id:1}], \"Initial items are correct\");\n  const tester = liveQueryUnitTester(()=>db.items.reverse().toArray());\n  db.items.where('id').above(0).delete();\n  await tester.waitTilDeepEqual([], \"Items are deleted\");\n  db.items.bulkAdd([{id: -10},{id: 0},{id: 10}]);\n  await tester.waitTilDeepEqual([{id: 10},{id: 0},{id: -10}], \"Reacts on bulkAdd\");\n  db.items.where('id').above(0).delete();\n  await tester.waitTilDeepEqual([{id: 0},{id: -10}], \"Should have deleted items where id > 0\");\n});\n\npromisedTest(\"Issue 2058 - related but with bulkAdd and constraint error on duplicate primary keys.\", async () => {\n  const tester = liveQueryUnitTester(\n    ()=>db.items.toArray(), { graceTime: 100 }\n  );\n  await tester.waitTilDeepEqual([{id:1},{id:2},{id:3}], \"Initial items are correct\");\n  await db.items.bulkAdd([\n    {id:3}, // This one won't be added (constraint violation)\n    {id:88} // This one will be added\n  ]).catch(error => {\n    equal(error.failuresByPos[0].name, \"ConstraintError\", \"Expected constraint error for the first operation\");\n  });\n  await tester.waitTilDeepEqual([{id:1},{id:2},{id:3},{id:88}], \"The livequery emitted correct result after bulk operation\");\n  // Now making sure we go through a different code path (where the number of items > 50 in cache-middleware.ts)\n  const itemsToAdd = new Array(51)\n  .fill({id: 1}, 0, 40) // Positions 0..40 is constraint violations agains existing data + themselves\n  .fill({id:99}, 40, 49) // Positions 40 is new value but 41...49 is constraint violation of pos 40.\n  .fill({id:100}, 49, 50) // Position 50 is ok.\n  .fill({id:101}, 50) // Position 51 is ok.\n\n  await db.items.bulkAdd(itemsToAdd).catch(error => {\n    deepEqual(Object.keys(error.failuresByPos).map(Number),[\n      0,1,2,3,4,5,6,7,8,9,\n      10,11,12,13,14,15,16,17,18,19,\n      20,21,22,23,24,25,26,27,28,29,\n      30,31,32,33,34,35,36,37,38,39,\n      41,42,43,44,45,46,47,48\n    ]\n    , \"We get errors on the expected positions\");\n  });\n  \n  console.log(\"Before await promise\", performance.now());\n  await tester.waitTilDeepEqual([\n    {id:1},\n    {id:2},\n    {id:3},\n    {id:88},\n    {id:99},\n    {id:100},\n    {id:101}\n  ], \"Correct state after trying to add these half baked entries\");\n  console.log(\"After await promise\", performance.now());\n});\n\npromisedTest(\"Issue 2058: Cache error after bulkPut failures\", async () => {\n  const tester = liveQueryUnitTester(\n    ()=>db.issue2058.toArray(),\n    {graceTime: 10}\n  );\n  await db.issue2058.bulkAdd([\n    {id:\"1.1\",a:1,b:1},\n    {id:\"1.2\",a:1,b:2},\n    {id:\"2.1\",a:2,b:1},\n  ]);\n  await tester.waitTilDeepEqual([\n    {id:\"1.1\",a:1,b:1},\n    {id:\"1.2\",a:1,b:2},\n    {id:\"2.1\",a:2,b:1}\n  ], \"Initial items are correct\");\n  await db.issue2058.bulkPut([\n    {id:\"2.2\",a:2,b:2},\n    {id:\"foo\", a: 1, b: 1}\n  ]).catch(error => {\n    equal(error.failuresByPos[1].name, \"ConstraintError\", \"Expected constraint error for the first operation\");\n  });\n  await tester.waitTilDeepEqual([\n    {id:\"1.1\",a:1,b:1},\n    {id:\"1.2\",a:1,b:2},\n    {id:\"2.1\",a:2,b:1},\n    {id:\"2.2\",a:2,b:2}\n  ], \"The livequery emitted correct result after bulk operation\");\n});\n\n"
  },
  {
    "path": "test/tests-max-connections.js",
    "content": "import Dexie, { DEFAULT_MAX_CONNECTIONS } from 'dexie';\nimport { module, asyncTest, ok, equal } from 'QUnit';\nimport { spawnedTest } from './dexie-unittest-utils';\n\nmodule(\"maxConnections\", {\n    setup: function () {\n        // Close all connections left open by previous tests to ensure they don't affect the results of these tests.\n        for (const connection of [...Dexie.connections]) {\n            connection.close();\n        }\n        equal(Dexie.connections.length, 0, \"No connections initially\");\n    },\n    teardown: function* () {\n        yield Dexie.delete(\"TestDB\");\n    }\n});\n\nspawnedTest(\"Should limit the number of connections\", function* () {\n    const max = 3;\n    const dbs = [];\n    try {\n        equal(dbs.length, 0, \"No connections initially\");\n        \n        for (let i = 0; i < max; ++i) {\n            const db = new Dexie(\"TestDB\", { maxConnections: max });\n            db.version(1).stores({foo: 'id'});\n            yield db.open();\n            dbs.push(db);\n        }\n\n        equal(Dexie.connections.length, max, \"Should have reached max connections\");\n\n        const extraDb = new Dexie(\"TestDB\", { maxConnections: max });\n        extraDb.version(1).stores({foo: 'id'});\n        \n        try {\n            yield extraDb.open();\n            equal(Dexie.connections.length, max, \"Should not have exceeded max connections\");\n        } catch (e) {\n            equal(e.name, \"MaxConnectionsReachedError\", \"Could throw MaxConnectionsReachedError: \" + e.message);\n        } finally {\n            yield extraDb.close();\n        }\n\n        // Close one and try again\n        yield dbs[0].close();\n        equal(Dexie.connections.length, max - 1, \"Should have one less connection\");\n        \n        yield dbs[0].open();\n        equal(Dexie.connections.length, max, \"Should have reached max connections again\");\n    } finally {\n        yield Promise.all(dbs.map(db => db.close()));\n    }\n});\n\nspawnedTest(\"Open connections should be tracked\", function* () {\n    if (typeof FinalizationRegistry === 'undefined') {\n        ok(true, \"FinalizationRegistry not supported in this environment\");\n        return;\n    }\n\n    const max = 20;\n    const dbs = [];\n    \n    try {\n        equal(Dexie.connections.length, 0, \"No tracked connections initially\");\n\n        for (let i = 0; i < 5; ++i) {\n            const db = new Dexie(\"TestDB\", { maxConnections: max });\n            db.version(1).stores({foo: 'id'});\n            yield db.open();\n            dbs.push(db);\n        }\n        \n        equal(Dexie.connections.length, 5, \"Connections should be tracked\");\n    } finally {\n        yield Promise.all(dbs.map(db => db.close()));\n    }\n    \n    equal(Dexie.connections.length, 0, \"Connections should not be tracked after explicit close\");\n});\n"
  },
  {
    "path": "test/tests-misc.js",
    "content": "import Dexie, { liveQuery } from 'dexie';\nimport {module, stop, start, asyncTest, equal, deepEqual, ok} from 'QUnit';\nimport {resetDatabase, spawnedTest, promisedTest, supports, isIE, isEdge} from './dexie-unittest-utils';\n\nconst async = Dexie.async;\n\nvar db = new Dexie(\"TestIssuesDB\");\ndb.version(1).stores({\n    users: \"id,first,last,&username,*&email,*pets\",\n    keyless: \",name\",\n    foo: \"id\",\n    bars: \"++id,text\",\n    metrics: \"id,[name+time]\",\n    // If required for your test, add more tables here\n});\n\nmodule(\"misc\", {\n    setup: () => {\n        stop();\n        resetDatabase(db).catch(e => {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: () => {\n    }\n});\n\n//\n// Misc Tests\n//\n\npromisedTest(\"issue#729\", async () => {\n    const onConsoleWarn = txt => {\n        ok(false, 'console warn happened: ' + txt);\n    }\n    const warnDescriptor = Object.getOwnPropertyDescriptor(console, 'warn');\n    console.warn = onConsoleWarn;\n    try {\n        await db.foo.bulkPut([{\n            id: 1,\n            foo: 'foo',\n          },{\n            id: 2,\n            foo: 'bar',\n          }\n        ]);\n    } catch (err) {\n        ok(false, \"Couldn't populate data: \" + err);\n    }\n    try {\n        const f = await db.foo.add({id: 1, foo: \"bar\"}); // id 1 already exists.\n    } catch (err) {\n        ok(true, \"Got the err:\" + err);\n    }  \n    try {\n        const f = await db.foo.get(false); // Invalid key used\n    } catch (err) {\n        ok(true, \"Got the err:\" + err);\n    }\n    try {\n        const f = await db.foo.where({id: 1})\n            .modify({id: 2, foo: \"bar\"}); // Changing primary key should fail + id 2 already exists.\n    } catch (err) {\n        ok(true, \"Got the err:\" + err);\n    }\n    if (warnDescriptor) {\n        Object.defineProperty(console, 'warn', warnDescriptor);\n    } else {\n        delete console.warn;\n    }\n});\n\nasyncTest(\"Adding object with falsy keys\", function () {\n    db.keyless.add({ name: \"foo\" }, 1).then(function (id) {\n        equal(id, 1, \"Normal case ok - Object with key 1 was successfully added.\")\n        return db.keyless.add({ name: \"bar\" }, 0);\n    }).then(function (id) {\n        equal(id, 0, \"Could add a numeric falsy value (0)\");\n        return db.keyless.add({ name: \"foobar\" }, \"\");\n    }).then(function (id) {\n        equal(id, \"\", \"Could add a string falsy value ('')\");\n        return db.keyless.put({ name: \"bar2\" }, 0);\n    }).then(function (id) {\n        equal(id, 0, \"Could put a numeric falsy value (0)\");\n        return db.keyless.put({ name: \"foobar2\" }, \"\");\n    }).then(function (id) {\n        equal(id, \"\", \"Could put a string falsy value ('')\");\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\npromisedTest(\"#770\", async () => {\n    const dbName = 'TestDB-' + Math.random();\n    const db = new Dexie(dbName, {addons: []});\n    const runnedVersions = [];\n    try {\n        db.version(1).stores({ test: 'id' });\n        await db.test.put({ id: 1 });\n        await db.open();\n        db.close();\n        db = new Dexie(dbName, {addons: []});\n        db.version(1).stores({ test: 'id' });\n        db.version(2).stores({ test: 'id' }).upgrade(async t => {\n            runnedVersions.push(2);\n            const rowsToCopy = await t.test.toArray();\n            await Dexie.waitFor((async ()=>{\n                const otherDB = new Dexie(dbName + '-another-unrelated-db', {addons: []});\n                otherDB.version(1).stores({foo: 'id'});\n                await otherDB.open();\n                await otherDB.foo.bulkAdd(rowsToCopy);\n                otherDB.close();\n            })());\n        });\n        db.version(3).stores({ test: 'id' }).upgrade(t => {\n            runnedVersions.push(3);\n        });\n\n        await db.open();\n        deepEqual(runnedVersions, [2, 3], \"Versions 3 did indeed proceed (as well as version 2)\");\n        const otherDB = new Dexie(dbName + '-another-unrelated-db', {addons: []});\n        otherDB.version(1).stores({foo: 'id'});\n        const otherDbRows = await otherDB.foo.toArray();\n        const origDbRows = await db.test.toArray();\n        deepEqual(otherDbRows, origDbRows, \"All rows was copied atomically\");\n        db.close();\n        otherDB.close();\n    } catch (err) {\n        ok(false, \"Error \" + err);\n    } finally {\n        await db.delete();\n        await new Dexie(dbName + '-another-unrelated-db', {addons: []}).delete();\n    }\n});\n\nasyncTest(\"#102 Passing an empty array to anyOf throws exception\", async(function* () {\n    try {\n        let count = yield db.users.where(\"username\").anyOf([]).count();\n        equal(count, 0, \"Zarro items matched the query anyOf([])\");\n    } catch (err) {\n        ok(false, \"Error when calling anyOf([]): \" + err);\n    } finally {\n        start();\n    }\n}));\n\nspawnedTest(\"#248 'modifications' object in 'updating' hook can be bizarre\", function*() {\n    var numCreating = 0,\n        numUpdating = 0;\n    function CustomDate (realDate) {\n        this._year = new Date(realDate).getFullYear();\n        this._month = new Date(realDate).getMonth();\n        this._day = new Date(realDate).getDate();\n        this._millisec = new Date(realDate).getTime();\n        //...\n    }\n    \n    function creatingHook (primKey, obj) {\n        ++numCreating;\n        var date = obj.date;\n        if (date && date instanceof CustomDate) {\n            obj.date = new Date(date._year, date._month, date._day);\n        }\n    }\n    function updatingHook (modifications, primKey, obj) {\n        ++numUpdating;\n        var date = modifications.date;\n        if (date && date instanceof CustomDate) {\n            return {date: new Date(date._year, date._month, date._day)};\n        }\n    }\n    function isDate(obj) {\n        // obj instanceof Date does NOT work with Safari when Date are retrieved from IDB.\n        return obj.getTime && obj.getDate && obj.getFullYear;\n    }\n    function readingHook (obj) {\n        if (obj.date && isDate(obj.date)) {\n            obj.date = new CustomDate(obj.date);\n        }\n        return obj;\n    }\n    \n    db.foo.hook('creating', creatingHook);\n    db.foo.hook('reading', readingHook);\n    db.foo.hook('updating', updatingHook);\n    var testDate = new CustomDate(new Date(2016, 5, 11));\n    equal(testDate._year, 2016, \"CustomDate has year 2016\");\n    equal(testDate._month, 5, \"CustomDate has month 5\");\n    equal(testDate._day, 11, \"CustomDate has day 11\");\n    var testDate2 = new CustomDate(new Date(2016, 5, 12));\n    try {\n        db.foo.add ({id: 1, date: testDate});\n        \n        var retrieved = yield db.foo.get(1);\n        \n        ok(retrieved.date instanceof CustomDate, \"Got a CustomDate object when retrieving object\");\n        equal (retrieved.date._day, 11, \"The CustomDate is on day 11\");\n        db.foo.put ({id: 1, date: testDate2});\n        \n        retrieved = yield db.foo.get(1);\n        \n        ok(retrieved.date.constructor === CustomDate, \"Got a CustomDate object when retrieving object\");\n        equal (retrieved.date._day, 12, \"The CustomDate is now on day 12\");\n        \n        // Check that hooks has been called expected number of times\n        equal(numCreating, 1, \"creating hook called once\");\n        equal(numUpdating, 1, \"updating hook called once\");\n    } finally {\n        db.foo.hook('creating').unsubscribe(creatingHook);\n        db.foo.hook('reading').unsubscribe(readingHook);\n        db.foo.hook('updating').unsubscribe(updatingHook);\n    }\n});\n\nasyncTest(\"Issue: Broken Promise rejection #264\", 1, function () {\n    db.open().then(()=>{\n        return db.users.where('id')\n            .equals('does-not-exist')\n            .first()\n    }).then(function(result){\n        return Promise.reject(undefined);\n    }).catch(function (err) {\n        equal(err, undefined, \"Should catch the rejection\");\n    }).then(res => {\n        start();\n    }).catch(err => {\n        start();\n    });\n});\n\nasyncTest (\"#323 @gitawego's post. Should not fail unexpectedly on readonly properties\", function(){\n    class Foo {\n        get synced() { return false;}\n    }\n\n    db.foo.mapToClass(Foo);\n    \n    db.transaction('rw', db.foo, function () {\n      db.foo.put({id:1});\n      db.foo.where('id').equals(1).modify({\n        synced: true\n      });\n    }).catch (e => {\n        ok(false, \"Could not update it: \" + (e.stack || e));\n    }).then (() => {\n        ok(true, \"Could update it\");\n        return db.foo.get(1);\n    }).then (foo => {\n        return db.foo.get(1);        \n    }).then (foo=>{\n        console.log(\"Wow, it could get it even though it's mapped to a class that forbids writing that property.\");\n    }).catch(e => {\n        ok(true, `Got error from get: ${e.stack || e}`);\n    }).then(() => {\n        return db.foo.toArray();\n    }).then(array => {\n        console.log(`Got array of length: ${array.length}`);\n    }).catch(e => {\n        ok(true, `Got error from toArray: ${e.stack || e}`);\n        return db.foo.each(item => console.log(item));\n    }).then(array => {\n        console.log(`Could do each`);\n    }).catch(e => {\n        ok(true, `Got error from each(): ${e.stack || e}`);\n        return db.foo.toCollection().sortBy('synced');\n    }).then(array => {\n        console.log(`Could do sortBy`);\n    }).catch(e => {\n        ok(true, `Got error from sortBy(): ${e.stack || e}`);\n    }).finally(start);\n    \n});\n\nspawnedTest (\"#360 DB unresponsive after multiple Table.update() or Collection.modify()\", function* () {\n    const NUM_UPDATES = 2000;\n    let result = yield db.transaction('rw', db.foo, function* () {\n        yield db.foo.put({id: 1, value: 0});\n        for (var i=0;i<NUM_UPDATES;++i) {\n            db.foo.where('id').equals(1).modify(item => ++item.value);\n        }\n        return yield db.foo.get(1);\n    });\n    equal(result.value, NUM_UPDATES, `Should have updated id 1 a ${NUM_UPDATES} times`);\n});\n\nspawnedTest (\"delByKeyPath not working correctly for arrays\", function* () {\n    const obj = {deepObject: {someArray: [\"a\", \"b\"]}};\n    const obj2 = {deepObject: {someArray: [\"a\", \"b\", \"c\"]}};\n    const jsonResult = JSON.stringify(obj);\n    console.log(\"jsonResult = \", jsonResult);\n    Dexie.delByKeyPath(obj2, \"deepObject.someArray.2\");\n    const jsonResult2 = JSON.stringify(obj2);\n    console.log(\"jsonResult2 = \", jsonResult2);\n    equal(jsonResult, jsonResult2, `Should be equal ${jsonResult} ${jsonResult2}`);\n});\n\nasyncTest (\"#1079 mapToClass\", function(){\n    class Foo {\n    }\n    db.foo.mapToClass(Foo);\n\n    db.transaction('rw', db.foo, function () {\n        db.foo.put({id:1});\n    }).catch(e => {\n        ok(true, `Unexpected error from put: ${e.stack || e}`);\n    }).then(() => {\n        return db.foo.get(1);\n    }).then (getResult => {\n        ok(getResult instanceof Foo, \"Result of get not mapped to class\");\n    }).catch(e => {\n        ok(true, `Unexpected error from get: ${e.stack || e}`);\n    }).then(() => {\n        return db.foo.bulkGet([1]);\n    }).then(bulkGetResult => {\n        ok(bulkGetResult.length === 1, `Unexpected array length ${bulkGetResult.length} from bulkGet`);\n        ok(bulkGetResult[0] instanceof Foo, \"Result of bulkGet not mapped to class\");\n    }).catch(e => {\n        ok(true, `Unexpected error from bulkGet: ${e.stack || e}`);\n    }).finally(start);\n\n});\n\nasyncTest(\"PR #1108\", async ()=>{\n    if (isIE || isEdge) {\n        ok(true, \"Disabling this test for IE and legacy Edge\");\n        start();\n        return;\n    }\n    const origConsoleWarn = console.warn;\n    const warnings = [];\n    console.warn = function(msg){\n        warnings.push(msg);\n        return origConsoleWarn.apply(this, arguments)\n    };\n    try {\n        const DBNAME = \"PR1108\";\n        await Dexie.delete(DBNAME);\n        let db = new Dexie(DBNAME);\n        db.version(1).stores({\n            foo: \"id\"\n        });\n        await db.open();\n        ok(!warnings.some(x => /SchemaDiff/.test(x)), `${DBNAME} could be opened without SchemaDiff warnings`);\n        db.close();\n\n        // Adding an index without updating version number:\n        db = new Dexie(DBNAME);\n        db.version(1).stores({\n            foo: \"id,name\"\n        });\n        warnings = [];\n        await db.open();\n        ok(warnings.some(x => /SchemaDiff/.test(x)), \"Should warn when a new index was declared without incrementing version number\");\n        db.close();\n        warnings = [];\n\n        // Adding a table without updating version number:\n        db = new Dexie(DBNAME);\n        db.version(1).stores({\n            foo: \"id\",\n            bar: \"\"\n        });\n        await db.open();\n        ok(warnings.some(x => /SchemaDiff/.test(x)), \"Should warn when a new table was declared without incrementing version number\");\n        db.close();\n        warnings = [];\n    } catch(error) {\n        ok(false, error);\n    } finally {\n        console.warn = origConsoleWarn;\n        start();\n    }\n});\n\nasyncTest(\"Issue #1112\", async ()=>{\n    function Bar(text) {\n        this.id = undefined;\n        this.text = text;\n    }\n\n    try {\n        // Verify the workaround for that IDB will tread explicit undefined as if key was provided,\n        // which is not very compatible with classs fields and typescript.\n        const id1 = await db.bars.add(new Bar(\"hello1\"));\n        ok(!isNaN(id1), \"got a real autoincremented id for my bar using add()\");\n        const id2 = await db.bars.put(new Bar(\"hello2\"));\n        ok(!isNaN(id2), \"got a real autoincremented id for my bar using put()\");\n        const id3 = await db.bars.bulkAdd([new Bar(\"hello3\")]);\n        ok(!isNaN(id3), \"got a real autoincremented id for my bar using bulkAdd()\");\n        const id4 = await db.bars.bulkPut([new Bar(\"hello4\")]);\n        ok(!isNaN(id4), \"got a real autoincremented id for my bar using bulkPut()\");\n\n        // Regression: possible to put back an item without anything getting destroyed:\n        const bar3 = await db.bars.get(id3);\n        equal(bar3.text, \"hello3\", \"Should get the object with text hello3\");\n        bar3.text = \"hello3 modified\";\n        await db.bars.put(bar3);\n        const bar3_2 = await db.bars.get(id3);\n        equal(bar3_2.text, \"hello3 modified\", \"Could successfully change a prop and put back.\");\n    } catch (error) {\n        ok(false, error);\n    } finally {\n        start();\n    }\n});\n\nasyncTest(\"Issue #1280 - Don't perform deep-clone workaround when adding non-POJO to auto-incrementing table\", async () => {\n    try {\n        await db.bars.add({ text: \"hello1\", fooProp: function(){} });\n        ok(false, \"Expected add() to fail since IDB would fail with DOMError if trying to store a function.\");\n    } catch (error) {\n        ok(true);\n    } finally {\n        start();\n    }\n});\n\npromisedTest(\"Issue #1333 - uniqueKeys on virtual index should produce unique results\", async () => {\n    if (!supports('compound'))\n        return ok(true, \"SKIPPED - COMPOUND UNSUPPORTED\");\n\n    await db.metrics.add({ id: \"id1\", name: \"a\", time: 1 });\n    await db.metrics.add({ id: \"id2\", name: \"b\", time: 2 });\n    await db.metrics.add({ id: \"id3\", name: \"a\", time: 3 });\n    const result = await db.metrics.orderBy(\"name\").uniqueKeys();\n    ok(result.length === 2, `Unexpected array length ${result.length} from uniqueKeys on virtual index, expected 2. Got ${result.join(',')}`);\n});\n\n/** Reproduce customer issue where ReadonlyError was thrown when using liveQuery.\n */\npromisedTest(\"Issue - ReadonlyError thrown in liveQuery despite user did not do write transactions\", async () => {\n    // Encapsulating the code in a string to avoid transpilation. We need native await here to trigger bug.\n    ok(!Promise.PSD, \"Must not be within async context when starting\");\n    ok(db.isOpen(), \"DB must be open when starting\");\n    await new Promise(resolve => setTimeout(resolve, 10));\n    const F = new Function('ok', 'equal', 'Dexie', 'db', 'liveQuery', `\n        ok(true, \"Got here\");\n        return (async ()=>{\n            equal(Dexie.Promise.PSD.id, 'global', \"PSD is the global PSD\");\n            const observable = liveQuery(async () => {\n                console.debug(\"liveQuery executing\");\n                const result = await db.metrics.toArray();\n                //await 3;\n                async function foo() {\n                    console.log(\"qm PSD.id = \" + Dexie.Promise.PSD?.id);\n                    await db.metrics.toArray();\n                    console.log(\"qm PSD.id = \" + Dexie.Promise.PSD?.id);\n                }\n                foo(); // Be naughty and spawn promises that we don't await.\n                // Verify that we handle this situation and escape from zone echoing before\n                // we return the result.\n                return result;\n            });\n            \n            equal(Dexie.Promise.PSD.id, 'global', \"PSD is the global PSD\");\n            ok(true, \"Now awaiting promise subscribing to liveQuery observable\");\n            console.log(\"before await in global\");\n            await new Promise(resolve => {\n                const o = observable.subscribe(val => {\n                    o.unsubscribe();\n                    console.log(\"PSD.id = \" + Dexie.Promise.PSD?.id);\n                    resolve(val);\n                });\n            });\n            console.log(\"after await in global\");\n            console.log(\"Got result from observable\");\n            equal(Dexie.Promise.PSD.id, \"global\", \"PSD is still the global PSD\");\n            await db.transaction('rw', db.metrics, () => {}); // Fails if we're in a liveQuery zone\n        })();\n    `);\n    return F(ok, equal, Dexie, db, liveQuery).catch(err => ok(false, 'final catch: '+err));\n});\n\n\npromisedTest(\"Issue #1890 - BigInt64Array getting corrupted after an update\", async () => {\n    if (typeof BigInt64Array === 'undefined') {\n        ok(true, \"BigInt64Array not supported in browser\");\n        return;\n    }\n    if (typeof Dexie.Observable?.version === 'string') {\n        ok(true, \"Skipping this test - Dexie.Observable bails out from BigInts\");\n        return;\n    }\n\n    await db.foo.put({\n        id: 1,\n        updated: Date.now(),\n        cols: [{\n          values: new BigInt64Array([1n, 2n])\n        }]\n    });\n    let val = await db.foo.get(1);\n    ok(val.cols[0].values instanceof BigInt64Array, \"cols[0].values is a BigInt64Array\");\n    await db.foo.update(1, {\n        updated: Date.now()\n    });\n    val = await db.foo.get(1);\n    ok(val.cols[0].values instanceof BigInt64Array, \"cols[0].values is still a BigInt64Array after update\");\n});\n"
  },
  {
    "path": "test/tests-open.js",
    "content": "﻿import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {promisedTest, spawnedTest, supports} from './dexie-unittest-utils';\n\nconst async = Dexie.async;\n\nmodule(\"open\", {\n    setup: function () {\n        stop();\n        Dexie.delete(\"TestDB\").then(function () {\n            start();\n        }).catch(function (e) {\n            ok(false, \"Could not delete database\");\n        });\n    },\n    teardown: function () {\n        stop(); Dexie.delete(\"TestDB\").then(start);\n    }\n});\n\nlet timeout = async(function* (promise, ms) {\n    yield Promise.race([promise, new Promise((resolve,reject)=>setTimeout(()=>reject(\"timeout\"), ms))]);\n});\n\nspawnedTest(\"multiple db should not block each other\", function*(){\n    if (!supports(\"versionchange\")) {\n        ok(true, \"SKIPPED - versionchange UNSUPPORTED\");\n        return;\n    }\n    let db1 = new Dexie(\"TestDB\"),\n       db2 = new Dexie(\"TestDB\");\n    db1.version(1).stores({\n        foo: 'bar'\n    });\n    db2.version(1).stores({\n        foo: 'bar'\n    });\n    yield db1.open();\n    ok(true, \"db1 should open\");\n    yield db2.open();\n    ok(true, \"db2 should open\");\n    try {\n        yield timeout(db1.delete(), 1500);\n        ok(true, \"Succeeded to delete db1 while db2 was open\");\n    } catch (e) {\n        db1.close();\n        db2.close();\n        ok(false, \"Could not delete db1 - \" + e);\n    }\n});\n\nspawnedTest(\"Using db on node should be rejected with MissingAPIError\", function*(){\n    let db = new Dexie('TestDB', {\n        indexedDB: undefined,\n        IDBKeyRange: undefined\n    });\n    db.version(1).stores({foo: 'bar'});\n    try {\n        yield db.foo.toArray();\n        ok(false, \"Should not get any result because API is missing.\");\n    } catch (e) {\n        ok(e instanceof Dexie.MissingAPIError, \"Should get MissingAPIError. Got: \" + e.name);\n    }\n    try {\n        yield db.open();\n    } catch (e) {\n        ok(e instanceof Dexie.MissingAPIError, \"Should get MissingAPIError. Got: \" + e.name);\n    }\n});\n\nasyncTest(\"open, add and query data without transaction\", 6, function () {\n    var db = new Dexie(\"TestDB\");\n    db.version(1).stores({ employees: \"++id,first,last\" });\n    ok(true, \"Simple version() and stores() passed\");\n    db.open().catch(function (e) {\n        ok(false, \"Could not open database: \" + (e.stack || e));\n        start();\n    });\n\n    db.employees.add({ first: \"David\", last: \"Fahlander\" }).then(function () {\n        ok(true, \"Could add employee\");\n        db.employees.where(\"first\").equals(\"David\").toArray(function (a) {\n            ok(true, \"Could retrieve employee based on where() clause\");\n            var first = a[0].first;\n            var last = a[0].last;\n            ok(first == \"David\" && last == \"Fahlander\", \"Could get the same object\");\n            equal(a.length, 1, \"Length of returned answer is 1\");\n            ok(a[0].id, \"Got an autoincremented id value from the object\");\n            db.close();\n            start();\n        });\n    });\n});\n\nasyncTest(\"open, add and query data using transaction\", function () {\n    var db = new Dexie(\"TestDB\");\n    db.version(1).stores({ employees: \"++id,first,last\" });\n    db.open().catch(function () {\n        ok(false, \"Could not open database\");\n        start();\n    });\n\n    db.transaction(\"rw\", db.employees, function () {\n\n        // Add employee\n        db.employees.add({ first: \"David\", last: \"Fahlander\" });\n\n        // Query employee\n        db.employees.where(\"first\").equals(\"David\").toArray(function (a) {\n            equal(a.length, 1, \"Could retrieve employee based on where() clause\");\n            var first = a[0].first;\n            var last = a[0].last;\n            ok(first == \"David\" && last == \"Fahlander\", \"Could get the same object\");\n            equal(a.length, 1, \"Length of returned answer is 1\");\n            ok(a[0].id, \"Got an autoincremented id value from the object\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(function() {\n        db.close();\n        start();\n    });\n});\n\nasyncTest(\"test-if-database-exists\", 3, function () {\n    var db = new Dexie(\"TestDB\");\n    var db2 = null;\n    return db.open().then(function () {\n        // Could open database without specifying any version. An existing database was opened.\n        ok(false, \"Expected database not to exist but it existed indeed\");\n        db.close();\n    }).catch(Dexie.NoSuchDatabaseError, function (err) {\n        // An error happened. Database did not exist.\n        ok(true, \"Database did not exist\");\n        db = new Dexie(\"TestDB\");\n        db.version(1).stores({dummy: \"\"});\n        return db.open();\n    }).then(function () {\n        // Database was created. Now open another instance to test if it exists\n        ok(true, \"Could create a dummy database\");\n        db2 = new Dexie(\"TestDB\");\n        return db2.open();\n    }).then(function () {\n        ok(true, \"Dummy Database did exist.\");\n        db2.close();\n    }).catch(function (err) {\n        ok(false, \"Error: \" + err.stack || err);\n    }).finally(function () {\n        db.delete().then(function () {\n            if (db2) return db2.delete();\n        }).finally(start);\n    });\n});\n\nasyncTest(\"open database without specifying version or schema\", Dexie.Observable ? 1 : 10, function () {\n    if (Dexie.Observable) {\n        ok(true, \"Dexie.Observable currently not compatible with this mode\");\n        return start();\n    }\n    var db = new Dexie(\"TestDB\");\n    var db2 = null;\n    db.open().then(function () {\n        ok(false, \"Should not be able to open a non-existing database when not specifying any version schema\");\n    }).catch(function (err) {\n        ok(true, \"Got error when trying to open non-existing DB: \" + err);\n        // Create a non-empty database that we later on will open in other instance (see next then()-clause)...\n        db = new Dexie(\"TestDB\");\n        db.version(1).stores({ friends: \"++id,name\", pets: \"++,name,kind\" });\n        return db.open();\n    }).then(function () {\n        ok(true, \"Could create TestDB with specified version schema.\");\n        db2 = new Dexie(\"TestDB\"); // Opening another instans without specifying schema\n        return db2.open().then(function () {\n            equal(db2.tables.length, 2, \"We got two tables in database\");\n            ok(db2.tables.every(function (table) { return table.name == \"friends\" || table.name == \"pets\" }), \"db2 contains the tables friends and pets\");\n            equal(db2.table(\"friends\").schema.primKey.name, \"id\", \"Primary key of friends is 'id'\");\n            ok(true, \"Primary key of friends is auto-incremented: \" + db2.table(\"friends\").schema.primKey.auto); // Just logging. Not important for functionality. I know this fails on IE11.\n            equal(db2.table(\"friends\").schema.indexes[0].name, \"name\", \"First index of friends table is the 'name' index\");\n            ok(!db2.table(\"pets\").schema.primKey.name, \"Primary key of pets has no name (not inline)\");\n            ok(true, \"Primary key of pets is auto-incremented: \" + db2.table(\"pets\").schema.primKey.auto); // Just logging. Not important for functionality. I know this fails on IE11.\n            equal(db2.table(\"pets\").schema.indexes.length, 2, \"Pets table has two indexes\");\n        });\n    }).catch(function (err) {\n        ok(false, \"Error: \" + err);\n    }).finally(function () {\n        db.close();\n        if (db2) db2.close();\n        start();\n    });\n});\n\nasyncTest(\"Dexie.getDatabaseNames\", 13, function () {\n    var defaultDatabases = [];\n    var db1, db2;\n    Dexie.getDatabaseNames(function (names) {\n        defaultDatabases = [].slice.call(names, 0);\n        ok(true, \"Current databases: \" + (defaultDatabases.length ? defaultDatabases.join(',') : \"(none)\"));\n        db1 = new Dexie(\"TestDB1\");\n        db1.version(1).stores({});\n        return db1.open();\n    }).then(function () {\n        // One DB created\n        ok(true, \"TestDB1 successfully created\");\n        return Dexie.getDatabaseNames();\n    }).then(function (names) {\n        equal(names.length, defaultDatabases.length + 1, \"Another DB has been created\");\n        ok(names.indexOf(\"TestDB1\") !== -1, \"Database names now contains TestDB1\");\n        db2 = new Dexie(\"TestDB2\");\n        db2.version(1).stores({});\n        return db2.open();\n    }).then(function () {\n        ok(true, \"TestDB2 successfully created\");\n        return Dexie.getDatabaseNames();\n    }).then(function (names) {\n        equal(names.length, defaultDatabases.length + 2, \"Yet another DB has been created\");\n        ok(names.indexOf(\"TestDB2\") !== -1, \"Database names now contains TestDB2\");\n        return db1.delete();\n    }).then(function () {\n        return Dexie.getDatabaseNames();\n    }).then(function(names){\n        equal(names.length, defaultDatabases.length + 1, \"A database has been deleted\");\n        ok(!names.indexOf(\"TestDB1\") !== -1, \"TestDB1 not in database list anymore\");\n        return db2.delete();\n    }).then(function () {\n        return Dexie.getDatabaseNames();\n    }).then(function (names) {\n        equal(names.length, defaultDatabases.length, \"All of our databases have been deleted\");\n        ok(!names.indexOf(\"TestDB2\") !== -1, \"TestDB2 not in database list anymore\");\n    }).then(function (names) {\n        return Dexie.exists(\"nonexistingDB\");\n    }).then(function (exists) {\n        ok(!exists, \"'nonexistingDB' should not exist indeed\");\n        return Dexie.getDatabaseNames();\n    }).then(function (names) {\n        ok(!names.indexOf(\"nonexistingDB\") !== -1, \"nonexistingDB must not have been recorded when calling Dexie.exists()\");\n    }).catch(function (err) {\n        ok(false, err.stack || err);\n    }).finally(function () {\n        (db1 ? db1.delete() : Dexie.Promise.resolve()).finally(function () {\n            (db2 ? db2.delete() : Dexie.Promise.resolve()).finally(start);\n        });\n    });\n});\n\nasyncTest(\"Issue #76 Dexie inside Web Worker\", function () {\n    //\n    // Imports to include from the web worker:\n    //\n    var imports = window.workerImports || [\"../dist/dexie.js\"];\n\n    //\n    // Code to execute in the web worker:\n    //\n    var CodeToExecuteInWebWorker = `function CodeToExecuteInWebWorker(ok, done) {\n        ok(true, \"Could enter the web worker\");\n        if (!self.Promise) self.Promise = Dexie.Promise;\n\n        Dexie.delete(\"codeFromWorker\").then(function() {\n            var db = new Dexie(\"codeFromWorker\");\n            ok(true, \"Could create a Dexie instance from within a web worker\");\n\n            db.version(1).stores({ table1: \"++\" });\n            ok(true, \"Could define schema\");\n\n            db.open();\n            ok(true, \"Could open the database\");\n            \n            return db.transaction('rw', db.table1, function() {\n                ok(true, \"Could create a transaction\");\n                db.table1.add({ name: \"My first object\" }).then(function(id) {\n                    ok(true, \"Could add object that got id \" + id);\n                    // Verify we workaround Safari issues with getAll() in workers\n                    // ... as discussed in PR #579.\n                    return db.table1.toArray();\n                }).then(function(){\n                    ok(true, \"Could all toArray() on a table (verified workaround for Safari 10.1 issue with getAll())\");\n                }).catch(function(err) {\n                    ok(false, \"Got error: \" + err);\n                });\n            });\n        }).then(function () {\n            ok(true, \"Transaction committed\");\n        }).catch(function(err) {\n            ok(false, \"Transaction failed: \" + err.stack);\n        }).finally(done);\n    }`;\n\n    //\n    // Frameworking...\n    //\n    if (!window.Worker) {\n        ok(false, \"WebWorkers not supported\");\n        start();\n        return;\n    }\n\n    var worker = new Worker(window.workerSource || \"worker.js\");\n    worker.postMessage({\n        imports: imports,\n        code: CodeToExecuteInWebWorker.toString()\n    });\n\n    worker.onmessage = function(e) {\n        switch (e.data[0]) {\n        case \"ok\":\n            ok(e.data[1], e.data[2]);\n            break;\n        case \"done\":\n            worker.terminate();\n            start();\n            break;\n        }\n    }\n\n    worker.onerror = function(e) {\n        worker.terminate();\n        ok(false, \"Worker errored: \" + e.message);\n        start();\n    };\n});\n\nasyncTest(\"Issue#100 - not all indexes are created\", function () {\n    var db = new Dexie(\"TestDB\");\n    db.version(20)\n      .stores({\n          t: 'id,displayName,*displayNameParts,isDeleted,countryRef,[countryRef+isDeleted],autoCreated,needsReview,[autoCreated+isDeleted],[needsReview+isDeleted],[autoCreated+needsReview+isDeleted],[autoCreated+countryRef+needsReview+isDeleted],[autoCreated+countryRef+needsReview+isDeleted],[autoCreated+robotsNoIndex+isDeleted],[autoCreated+needsReview+robotsNoIndex+isDeleted],[autoCreated+countryRef+robotsNoIndex+isDeleted],[autoCreated+countryRef+needsReview+robotsNoIndex+isDeleted]',\n      });\n    db.open().then(function() {\n        return Dexie.Promise.all(\n            db.t.orderBy(\"id\").first(),\n            db.t.orderBy(\"displayName\").first(),\n            db.t.orderBy(\"displayNameParts\").first(),\n            db.t.orderBy(\"isDeleted\").first(),\n            db.t.orderBy(\"countryRef\").first(),\n            db.t.orderBy(\"[countryRef+isDeleted]\").first(),\n            db.t.orderBy(\"autoCreated\").first(),\n            db.t.orderBy(\"needsReview\").first(),\n            db.t.orderBy(\"[autoCreated+isDeleted]\").first(),\n            db.t.orderBy(\"[needsReview+isDeleted]\").first(),\n            db.t.orderBy(\"[autoCreated+needsReview+isDeleted]\").first(),\n            db.t.orderBy(\"[autoCreated+countryRef+needsReview+isDeleted]\").first(),\n            db.t.orderBy(\"[autoCreated+robotsNoIndex+isDeleted]\").first(),\n            db.t.orderBy(\"[autoCreated+needsReview+robotsNoIndex+isDeleted]\").first(),\n            db.t.orderBy(\"[autoCreated+countryRef+robotsNoIndex+isDeleted]\").first(),\n            db.t.orderBy(\"[autoCreated+countryRef+needsReview+robotsNoIndex+isDeleted]\").first()\n        );\n    }).then(function(res) {\n        ok(false, \"Should not succeed with creating the same index twice\");\n    }).catch(function(err) {\n        ok(true, \"Catched error trying to create duplicate indexes: \" + err);\n        return db.t.toArray();\n    }).then(function(a) {\n        ok(false, \"Database should have failed here\");\n    }).catch(function(err) {\n        ok(true, \"Got exception when trying to work agains DB: \" + err);\n    }).then(function () {\n        // Close the database and open dynamically to check that\n        // it should not exist when failed to open.\n        db.close();\n        db = new Dexie(\"TestDB\");\n        return db.open();\n    }).then(function() {\n        ok(false, \"Should not succeed to open the database. It should not have been created.\");\n        equal(db.tables.length, 0, \"At least expect no tables to have been created on the database\");\n    }).catch(function(err) {\n        ok(true, \"Should not succeed to dynamically open db because it should not exist\");\n    }).finally(start);\n\n});\n\nasyncTest(\"Dexie.exists\", function () {\n    var db = null;\n    Dexie.exists(\"TestDB\").then(function(result) {\n        equal(result, false, \"Should not exist yet\");\n        db = new Dexie(\"TestDB\");\n        db.version(1).stores({\n            some: \"schema\"\n        });\n        return db.open();\n    }).then(function() {\n        return Dexie.exists(\"TestDB\");\n    }).then(function(result) {\n        equal(result, true, \"Should exist now and has another open connection.\");\n        db.close();\n        return Dexie.exists(\"TestDB\");\n    }).then(function(result) {\n        equal(result, true, \"Should still exist\");\n        return Dexie.delete(\"TestDB\");\n    }).then(function () {\n        return Dexie.exists(\"TestDB\");\n    }).then(function(result) {\n        equal(result, false, \"Should have been deleted now\");\n    }).catch(function(e) {\n        ok(false, \"Error: \" + e);\n    }).finally(start);\n});\n\nasyncTest(\"No auto-open\", ()=> {\n    let db = new Dexie(\"TestDB\", {autoOpen: false});\n    db.version(1).stores({foo: \"id\"});\n    db.foo.toArray(res => {\n        ok(false, \"Should not get result. Should have failed.\");\n    }).catch(e => {\n        ok(e instanceof Dexie.DatabaseClosedError, \"Should catch DatabaseClosedError\");\n    }).then(() => {\n        db.open();\n        return db.foo.toArray();\n    }).then(res => {\n        equal(res.length, 0, \"Got an answer now when opened.\");\n        db.close();\n        let openPromise = db.open().then(()=>{\n            //console.log(\"Why are we here? \" + Dexie.Promise.reject().stack);\n            ok(false, \"Should not succeed to open because we closed it during the open sequence.\")\n        }).catch(e=> {\n            ok(e instanceof Dexie.DatabaseClosedError, \"Got DatabaseClosedError from the db.open() call.\");\n        });\n        let queryPromise = db.foo.toArray().then(()=>{\n            ok(false, \"Should not succeed to query because we closed it during the open sequence.\")\n        }).catch(e=> {\n            ok(e instanceof Dexie.DatabaseClosedError, \"Got DatabaseClosedError when querying: \" + e);\n        });\n        db.close();\n        return Promise.all([openPromise, queryPromise]);\n    }).catch(e => {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"db.close\", ()=> {\n    let db = new Dexie(\"TestDB\");\n    db.version(1).stores({foo: \"id\"});\n    db.foo.toArray(res => {\n        equal(res.length, 0, \"Database auto-opened and I got a result from my query\");\n    }).then(() => {\n        db.close();\n        return db.foo.toArray();\n    }).catch(e => {\n        ok(e instanceof Dexie.DatabaseClosedError, \"Should catch DatabaseClosedError\");\n        return db.open();\n    }).then(()=>{\n        console.log(\"The call to db.open() completed\");\n        return db.foo.toArray();\n    }).then(res => {\n        equal(res.length, 0, \"Database re-opened and I got a result from my query\");\n    }).catch(e => {\n        ok(false, e);\n    }).finally(()=>{\n        db.delete().catch(e=>console.error(e)).finally(start);\n    });\n});\n\nspawnedTest(\"db.open several times\", 2, function*(){\n    let db = new Dexie(\"TestDB\");\n    db.version(1).stores({foo: \"id\"});\n    db.on('populate', ()=>{throw \"Failed in populate\";});\n    db.open().then(()=>{\n        ok(false, \"Should not succeed to open\");\n    }).catch(err =>{\n        ok(true, \"Got error: \" + (err.stack || err));\n    });\n    yield db.open().then(()=>{\n        ok(false, \"Should not succeed to open\");\n    }).catch(err =>{\n        ok(true, \"Got error: \" + (err.stack || err));\n    });\n});\n\nasyncTest(\"#306 db.on('ready') subscriber should be called also if db is already open\", ()=>{\n    let db = new Dexie(\"TestDB\");\n    db.version(1).stores({foo: \"id\"});\n    db.on('ready', ()=>{\n        ok(true, \"Early db.on('ready') subscriber called.\");\n    });\n    var lateSubscriberCalled = false;\n    db.open().then(()=>{\n        ok(true, \"db successfully opened\");\n        db.on('ready', ()=>{\n           lateSubscriberCalled = true;\n        });\n    }).then(() => {\n        ok(lateSubscriberCalled, \"Late db.on('ready') subscriber should also be called.\");\n    }).catch (err => {\n        ok(false, err.stack || err);\n    }).finally(start);\n});\n\npromisedTest(\"#392 db.on('ready') don't fire if subscribed while waiting other promise-returning subscriber\", async ()=>{\n    //const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));\n    let db = new Dexie('TestDB');\n    db.version(1).stores({foobar: 'id'});\n    let first = false, second = false, third = false;\n\n    // first is registered before open\n    db.on('ready', async ()=> {\n        first = true;\n        // second is registered while first is executing\n        db.on('ready', ()=>{\n            second = true;\n        });\n    });\n\n    await db.open();\n    db.on('ready', ()=>third = true);\n    await Dexie.Promise.resolve();\n\n    ok(first, \"First subscriber should have been called\");\n    ok(second, \"Second subscriber should have been called\");\n    ok(third, \"Third subscriber should have been called\");\n    \n});\n\npromisedTest(\"Should be possible to open a vip DB\", async ()=>{\n    await Dexie.delete('TestDB');\n    let db = new Dexie('TestDB');\n    db.version(1).stores({foobar: 'id'});\n    await db.vip.open();\n    ok(true, \"Could open viped db\");\n    await db.vip.foobar.toArray();\n    ok(true, \"Could query viped db\");\n    await db.foobar.toArray();\n    ok(true, \"Could query non-viped db after opening it through vip mode\");\n    db.vip.close();\n    // Try testing it dynamically\n    db = new Dexie('TestDB');\n    await db.vip.open();\n    ok(true, \"Could open viped db dynamically\");\n    await db.vip.table('foobar').toArray();\n    ok(true, \"Could query dynamically opened viped db\");\n});\n\npromisedTest(\"#1842 - Should set the unique flag for primKey to true\", async () => {\n    // First test if flag is correctly set when creating new store\n    // version.ts, _parseStoresSpec()\n    await Dexie.delete(\"PrimKey1842\");\n    let db = new Dexie(\"PrimKey1842\");\n    db.version(1).stores({ foo: \"++id, name\"});\n    await db.open();\n    ok(true, \"Could open new db\");\n    const primKey = db.foo.schema.primKey.unique;\n    ok(primKey, \"primKey should be unique\");\n\n    // Closing and reopening db, to test if flag is correctly set when opening existing store\n    // schema-helpers.ts, buildGlobalSchema()\n    await db.close();\n    db = new Dexie(\"PrimKey1842\");\n    await db.open();\n    ok(true, \"Could open db after close\");\n    const fooTable = await db.table(\"foo\");\n    const primKeyAfterCloseOpen = fooTable.schema.primKey.unique;\n    ok(primKeyAfterCloseOpen, \"primKey should be unique after close and then open\");\n\n    // Cleanup\n    db.close();\n    await Dexie.delete(\"PrimKey1842\");\n    const databases = await Dexie.getDatabaseNames();\n    ok(!databases.includes(\"PrimKey1842\"), \"'PrimKey1842' should NOT be in the list of database names\");\n})\n"
  },
  {
    "path": "test/tests-performance.js",
    "content": "﻿import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {spawnedTest} from './dexie-unittest-utils';\n\nmodule(\"performance\", {\n    setup: function () {\n    },\n    teardown: function () {\n        stop(); Dexie.delete(\"PerformanceDB\").onblocked(function() {\n            //alert(\"Please close other browsers and tabs! Another browser or tab is blocking the database from being deleted. \");\n        }).catch(function (e) {\n            ok(false, e);\n        }).finally(function () {\n            start();\n        });\n    }\n});\n\nvar tick = 0,lastPerf=false;\nfunction log(txt, noPerf) {\n    let logstr = (tick && lastPerf ? \"took \" + (Date.now()-tick) + \"ms.\\n\" :\"\") + txt + (noPerf?\"\\n\":\"\");\n    ok(true, logstr);\n    tick = Date.now();\n    lastPerf = !noPerf;\n}\n\nspawnedTest(\"Collection.delete()\", function* () {\n    const db = new Dexie(\"dedatabase\");\n    const Promise = Dexie.Promise;\n    db.version(1).stores({\n        storage: \"id\",\n    });\n\n    const MAX = 10000;\n    var data = [];\n    for(let i = 0; i<MAX; i++) {\n        data.push({id: i});\n    }\n\n    try {\n        log(\"Deleting db\");\n        yield db.delete();\n        log(`Inserting data (${MAX} items):`);\n        yield db.storage.bulkAdd(data);\n        log(`done. Deleting items using db.storage.where(\"id\").between(100, ${MAX - 100}).delete()`);\n        yield db.storage.where(\"id\").between(100, MAX - 100).delete();\n        log(\"done\");\n        equal (yield db.storage.count(), 200, \"Should be just 200 items left now after deletion\");\n    } catch (e) {\n        ok(false, \"Uh oh ERROR: \" + e);\n    } finally {\n        yield db.delete();\n    }\n});\n\nasyncTest(\"performance: add/equalsIgnoreCase/each\", function () {\n    var db = new Dexie(\"PerformanceDB\");\n    db.version(1).stores({ emails: \"++id,from,to,subject,message,shortStr\" });\n    db.on(\"blocked\", function() {\n        alert(\"Please close other browsers and tabs! Another browser or tab is blocking the database from being upgraded or deleted.\");\n    });\n\n    var tick;\n    function randomString(count) {\n        return function () {\n            var ms = [];\n            for (var i = 0; i < count; ++i) {\n                ms.push(String.fromCharCode(32 + Math.floor(Math.random() * 96)));\n            }\n            return ms.join('');\n        }\n    }\n    db.delete().then(function () {\n        return db.open();\n    }).then(function(){\n\n        var i;\n        var bulkArray = new Array(10000);\n        for (i = 1; i <= 10000; ++i) {\n            bulkArray[i - 1] = {\n                from: \"from\" + i + \"@test.com\",\n                to: \"to\" + i + \"@test.com\",\n                subject: \"subject\" + i,\n                message: \"message\" + i,\n                shortStr: randomString(2)()\n            };\n        }\n\n        tick = Date.now();\n\n        // Create 10,000 emails\n        ok(true, \"Creating 10,000 emails\");\n        return db.emails.bulkAdd(bulkArray);\n    }).then(function () {\n        ok(true, \"Time taken: \" + (Date.now() - tick));\n\n        // Speed of equals()\n        ok(true, \"Speed of equals()\");\n        tick = Date.now();\n        return db.emails.where(\"shortStr\").equals(\"yk\").toArray();\n\n    }).then(function (a) {\n        var tock = Date.now();\n        ok(true, \"Time taken: \" + (tock - tick));\n        ok(true, \"Num emails found: \" + a.length);\n        ok(true, \"Time taken per found item: \" + (tock - tick) / a.length);\n\n        // Speed of equalsIgnoreCase()\n        ok(true, \"Speed of equalsIgnoreCase()\");\n        tick = Date.now();\n        return db.emails.where(\"shortStr\").equalsIgnoreCase(\"yk\").toArray();\n\n    }).then (function (a) {\n        var tock = Date.now();\n        ok(true, \"Time taken: \" + (tock - tick));\n        ok(true, \"Num emails found: \" + a.length);\n        ok(true, \"Time taken per found item: \" + (tock - tick) / a.length);\n                        \n\n        // Speed of manual filter case insensitive search\n        ok(true, \"Speed of manual filter case insensitive search\");\n        tick = Date.now();\n        var foundEmails = [];\n        return db.emails.each(function (email) {\n            if (email.shortStr.toLowerCase() === \"yk\")\n                foundEmails.push(email);\n        }).then(function () { return foundEmails; });\n\n    }).then (function(foundEmails) {\n        var tock = Date.now();\n        ok(true, \"Time taken: \" + (tock - tick));\n        ok(true, \"Num emails found: \" + foundEmails.length);\n        ok(true, \"Time taken per found item: \" + (tock - tick) / foundEmails.length);\n        // Measure the time it takes for db.emails.toArra():\n        ok(true, \"Speed of db.emails.toArray()\");\n        tick = Date.now();\n        return db.emails.toArray();\n    }).then(function(result) {\n        var tock = Date.now();\n        ok(true, \"Time taken: \" + (tock - tick));\n        ok(true, \"Num emails found: \" + result.length);\n        // Measure the time it takes for db.emails.where('message').startsWith('message').toArray()\n        ok(true, \"Speed of db.emails.where('message').startsWith('message').toArray();\");\n        tick = Date.now();\n        return db.emails.where('message').startsWith('message').toArray();\n    }).then(function (result) {\n        var tock = Date.now();\n        ok(true, \"Time taken: \" + (tock - tick));\n        ok(true, \"Num emails found: \" + result.length);\n    }).catch(Error, function (e) {\n        ok(false, e.name + \":\" + e.message + \" \" + e.stack);\n    }).catch(function (e) {\n        ok(false, e.toString());\n\n    }).finally(function() {\n        db.close();\n        start();\n    });\n});\n"
  },
  {
    "path": "test/tests-promise.js",
    "content": "﻿import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {spawnedTest, supports} from './dexie-unittest-utils';\n\nmodule(\"promise\");\n\n//Dexie.debug = \"dexie\";\n\nfunction createDirectlyResolvedPromise() {\n    return new Dexie.Promise(function(resolve) {\n        resolve();\n    });\n}\n\nasyncTest(\"Promise basics\", ()=>{\n   new Dexie.Promise(resolve => resolve(\"value\"))\n   .then(value => {\n      equal(value, \"value\", \"Promise should be resolved with 'value'\");\n   }).then(()=>{\n      start(); \n   });\n});\n\nasyncTest(\"return Promise.resolve() from Promise.then(...)\", ()=>{\n    new Dexie.Promise(resolve => resolve(\"value\"))\n    .then (value => {\n        return Dexie.Promise.resolve(value);\n    }).then (value => {\n        equal (value, \"value\", \"returning Dexie.Promise.resolve() from then handler should work\");\n        start();\n    })\n});\n\nasyncTest(\"return unresolved Promise from Promise.then(...)\", ()=>{\n    new Dexie.Promise(resolve => resolve(\"value\"))\n    .then (value => {\n        return new Dexie.Promise(resolve => setTimeout(resolve, 0, \"value\"));\n    }).then (value => {\n        equal (value, \"value\", \"When unresolved promise is resolved, this promise should resolve with its value\");\n        start();\n    })\n});\n\nasyncTest(\"Compatibility with other promises\", ()=>{\n    Dexie.Promise.resolve().then(()=>{\n       return window.Promise.resolve(3); \n    }).then(x => {\n        equal(x, 3, \"returning a window.Promise should be ok\");\n        start();\n    })\n});\n\nasyncTest(\"When to promise resolve\", ()=>{\n    var Promise = Dexie.Promise;\n    var res = [];\n    Promise.follow(()=>{\n        new Promise (resolve => resolve()).then(()=>res.push(\"B1\"));\n        res.push(\"A1\");\n        new Promise (resolve => resolve()).then(()=>res.push(\"B2\"));\n        res.push(\"A2\");\n    }).then(()=>{\n        equal(JSON.stringify(res), JSON.stringify([\n            \"A1\",\n            \"A2\",\n            \"B1\",\n            \"B2\"\n        ]), \"Resolves come in expected order.\");\n    }).catch(e => {\n        ok(false, e.stack || e);\n    }).then(start);\n});\n\nasyncTest(\"Promise.follow()\", ()=>{\n    var Promise = Dexie.Promise;\n    Promise.follow(() => {\n        Promise.resolve(\"test\")\n            .then(x => x + \":\")\n            .then(x => Promise.reject(\"rejection\"))\n            .then(()=>ok(false, \"Should not come here\"))\n            .catch(e => equal(e, \"rejection\", \"Should catch rejection\"));\n    }).then(()=>ok(true, \"Scope ended\"))\n      .catch(e => ok(false, \"Error: \" + e.stack))\n      .then(start);\n});\n\nasyncTest(\"Promise.follow() 2\", ()=>{\n    var Promise = Dexie.Promise;\n    Promise.follow(() => {\n        Promise.resolve(\"test\")\n            .then(x => x + \":\")\n            .then(x => Promise.reject(\"rejection\"))\n            .then(()=>ok(false, \"Should not come here\"))\n    }).then(()=>ok(false, \"Scope should not resolve\"))\n      .catch(e => ok(true, \"Got error: \" + e.stack))\n      .then(start);\n});\n\nasyncTest(\"Promise.follow() 3 (empty)\", ()=>{\n    Dexie.Promise.follow(()=>{})\n        .then(()=>ok(true, \"Promise resolved when nothing was done\"))\n        .then(start); \n});\n\nasyncTest (\"Promise.follow chained\", ()=>{\n    var Promise = Dexie.Promise;\n    //Promise._rootExec(()=>{        \n    //Promise.scheduler = (fn, args) => setTimeout(fn, 0, args[0], args[1], args[2]);\n        \n    Promise.follow(()=>{\n        new Promise(resolve => resolve()).then(()=>Promise.follow(()=>{\n            Promise.PSD.inner = true;\n            \n            // Chains and rejection\n            new Promise(resolve => resolve())\n                .then(x => 3)\n                .then(null, e => \"catched\")\n                .then(x => {}) \n                .then(()=>{throw new TypeError(\"oops\");})\n            }).then(()=>ok(false, \"Promise.follow() should not resolve since an unhandled rejection should have been detected\"))\n        ).then(()=>ok(false, \"Promise.follow() should not resolve since an unhandled rejection should have been detected\"))\n        .catch (TypeError, err => {\n            ok(true, \"Got TypeError: \" + err.stack);\n        });\n    }).then (()=> ok(true, \"Outer Promise.follow() should resolve because inner was catched\"))\n    .catch (err => {\n        ok(false, \"Should have catched TypeError: \" + err.stack);\n    }).then(()=>{\n        start();\n    });\n    //});\n});\n\nasyncTest(\"Issue#27(A) - Then handlers are called synchronously for already resolved promises\", function () {\n    // Test with plain Dexie.Promise()\n    var expectedLog = ['1', '3', '2', 'a', 'c', 'b'];\n    var log = [];\n\n    var promise = createDirectlyResolvedPromise();\n    log.push('1');\n    promise.then(function() {\n        log.push('2');\n        log.push('a');\n        promise.then(function() {\n            log.push('b');\n            check();\n        });\n        log.push('c');\n        check();\n    });\n    log.push('3');\n    check();\n\n    function check() {\n        if (log.length == expectedLog.length) {\n            for (var i = 0; i < log.length; ++i) {\n                equal(log[i], expectedLog[i], \"Position \" + i + \" is \" + log[i] + \" and was expected to be \" + expectedLog[i]);\n            }\n            start();\n        }\n    }\n});\n\nasyncTest(\"Issue#27(B) - Then handlers are called synchronously for already resolved promises\", function () {\n    // Test with a Promise returned from the Dexie library\n    var expectedLog = ['1', '3', '2', 'a', 'c', 'b'];\n    var log = [];\n\n    var db = new Dexie(\"Promise-test\");\n    db.version(1).stores({ friends: '++id' });\n    db.on('populate', function () {\n        db.friends.add({ name: \"one\" });\n        db.friends.add({ name: \"two\" });\n        db.friends.add({ name: \"three\" });\n    });\n    db.delete().then(function () {\n        return db.open();\n    }).then(function () {\n        var promise = db.friends.toCollection().each(function() {});\n        log.push('1');\n        promise.then(function () {\n            log.push('2');\n            log.push('a');\n            promise.then(function() {\n                log.push('b');\n                check();\n            }).catch(function(e) {\n                ok(false, \"error: \" + e);\n                start();\n            });\n            log.push('c');\n            check();\n        }).catch(function(e) {\n            ok(false, \"error: \" + e);\n            start();\n        });\n        log.push('3');\n        check();\n\n        function check() {\n            if (log.length == expectedLog.length) {\n                for (var i = 0; i < log.length; ++i) {\n                    equal(log[i], expectedLog[i], \"Position \" + i + \" is \" + log[i] + \" and was expected to be \" + expectedLog[i]);\n                }\n                db.delete().then(start);\n            }\n        }\n    });\n});\n\nasyncTest(\"Issue #97 A transaction may be lost after calling Dexie.Promise.resolve().then(...)\", function() {\n    Dexie.Promise.newPSD(function () {\n\n        Dexie.Promise.PSD.hello = \"promise land\";\n\n        Dexie.Promise.resolve().then(function () {\n            ok(!!Dexie.Promise.PSD, \"We should have a Dexie.Promise.PSD\");\n            equal(Dexie.Promise.PSD.hello, \"promise land\");\n        }).catch(function(e) {\n            ok(false, \"Error: \" + e);\n        }).finally(start);\n\n    });\n});\n"
  },
  {
    "path": "test/tests-rangeset.js",
    "content": "import {test, module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {RangeSet, mergeRanges, rangesOverlap} from \"../src/helpers/rangeset\";\n\nmodule(\"rangeset\");\n\ntest(\"it should do match correctly\", () => {\n  const set = new RangeSet()\n    .add({from: 1, to: 1})\n    .add({from: 2, to: 10})\n    .add({from: 20, to: 30})\n    .add({from: 40, to: 40});\n\n  ok(!rangesOverlap(set, new RangeSet(1.1, 1.9)), \"set does not overlap with 1.1-1.9\");\n  ok(rangesOverlap(set, new RangeSet(1, 1)), \"set overlaps with 1-1\");\n  ok(rangesOverlap(set, new RangeSet(0, 1)), \"set overlaps with 0-1\");\n  ok(!rangesOverlap(set, new RangeSet(0, 0.9)), \"set doesnt overlap with 0-0.9\");\n  ok(rangesOverlap(set, new RangeSet(39, 41)), \"set overlaps with 39-40\");\n});\n\ntest(\"it should balance itself\", () => {\n  const set = new RangeSet();\n  for (let i = 0; i < 10000; ++i) {\n    set.add({from: i, to: i + 0.5});\n  }\n  ok(!isNaN(set.d) && set.d <= 14 && set.d >= 1, \"Depth should be below or equal to 14\");\n  set.add({from: 0, to: 10000});\n  equal(set.d, 1, \"After adding a super range, the depth should be 1\");\n  equal([...set].length, 1, \"After adding the super range, the entire set should just contain the super set\");\n});\n\ntest(\"it should be iterable\", () => {\n  const set = new RangeSet();\n  for (let i = 0; i < 10; ++i) {\n    set.addKey(i);\n  }\n  equal([...set].length, 10, \"Should iterate 10 items\");\n  ok([...set].every((node, i) => node.from === i && node.to === i), \"Each node should have correct ranges\");\n});\n\ntest(\"it should be mergable\", () => {\n  const set = RangeSet().addKeys([0, 2, 4, 7, 8]);\n  const set2 = RangeSet().addKeys([1, 3, 5, 7, 9, 11]);\n  set.add(set2);\n  equal([...set].map(({ from }) => from).join(','), [\n    0,\n    1,\n    2,\n    3,\n    4,\n    5,\n    7,\n    8,\n    9,\n    11,\n  ].join(','), \"successful merge of two sets\");\n\n  set.add(new RangeSet(1, 6));\n  equal(JSON.stringify([...set].map(({ from, to }) => [from, to])), JSON.stringify([\n    [0, 0],\n    [1, 6],\n    [7, 7],\n    [8, 8],\n    [9, 9],\n    [11, 11],\n  ]), \"after adding a super range to some of the containing ranges, the rangeset should have replaced the subranges with their subset\");\n\n  set.add({from: 0, to: 20});\n  equal(\n    JSON.stringify([...set].map(({ from, to }) => [from, to])),\n    JSON.stringify([[0, 20]]),\n    \"after adding a superset, the entire set should just equal the super set\");\n});\n\nfunction isSequencial(set) {\n  let lastFrom = -Infinity;\n  for (const node of [...set]) {\n    if (node.from <= lastFrom) {\n      return false;\n    }\n    lastFrom = node.from;\n  }\n  return true;\n}\n\ntest(\"stress\", () => {\n  const set = new RangeSet();\n  //console.log(\"depth\", set.d);\n  for (let i=1; i<=600; ++i) {\n    set.addKey(i);\n  }\n  ok(isSequencial(set), \"set is sequencial\");\n  //console.log(\"the set 1\", [...set]);\n  //console.log(\"depth\", set.d);\n  equal([...set].length, 600, \"Set should contain individual ranges\");\n  ok(isSequencial(set), \"set is sequencial\");\n  //debugger;\n  set.add({from: 280, to: 321});\n  //console.log(\"the set 2\", JSON.parse(JSON.stringify([...set])));\n  ok(isSequencial(set), \"set is sequencial\");\n  //console.log(\"depth\", set.d);\n  equal([...set].length, 559, \"Set should have less ranges\");\n  //console.log(\"depth\", set.d);\n});\n\nfunction printTree(nodes, pad=-1) {\n  /*function branchLength(node, lr, num=0) {\n    return node[lr] ? branchLength(node[lr], lr, num + 1) : num;\n  }\n  const treeWidth = 1 + branchLength(set, \"l\") + branchLength(set, \"r\");*/\n  if (pad === -1) pad = 4 * Math.pow(2, nodes[0].d);\n  const toPad = pad - ((nodes.length - 1) * 4);\n  console.log(nodes.map(node => (node\n    ? `[${node.from}-${node.to}]`\n    : node === \"\"\n    ? \"\"\n    : \" (null) \"\n  ).padStart(8, \" \") ).join('').padStart(toPad, \" \"));\n  const children = nodes.map (node => node ? [node.l, node.r] : [\"\",\"\"]).flat();\n  if (children.some(child => child)) {\n    printTree(children, pad);\n  }\n}\n\nconst issue1268_triggering_input = [\n  { from: 63, to: 71 },  // 0. Tree: [63-71]\n  { from: 99, to: 102 }, // 1. Tree: [63-71]\n                         //      (null)  [99-102]\n  { from: 90, to: 92 },  // 2. Tree: [99-102]\n                         //       [63-71]  (null)\n                         //    [90-92] [90-92]\n                         // WOW: Here both left and right leafs are the same node!\n  { from: 92, to: 95 },  // 3\n  { from: 4, to: 10 },   // 4\n  { from: 51, to: 51 },  // 5\n  { from: 45, to: 46 },  // 6\n  { from: 14, to: 20 },  // 7\n  { from: 13, to: 20 },  // 8\n  { from: 9, to: 12 },   // 9\n  { from: 23, to: 25 },  // 10\n  { from: 31, to: 35 },  // 11!! After adding this range, circularity is shaped!\n  { from: 80, to: 88 },\n  { from: 87, to: 91 },\n  { from: 36, to: 37 },\n  { from: 77, to: 79 }\n];\n\ntest(\"issue1268\", () => {\n  const set = new RangeSet();\n  issue1268_triggering_input.forEach((range, idx) => {\n    try {\n      //if (idx === 11) debugger;\n      //console.log(`Adding [${range.from}-${range.to}]`);\n      set.add(range);\n      if (!verifySet(set)) {\n        ok(false, \"set not ok at idx \" + idx + \" depth: \" + set.d);\n      } else {\n        //console.log(\"Printing tree on idx \" + idx);\n        //if (idx === 2) debugger;\n        //printTree([set]);\n      }\n    } catch (e) {\n      console.log(\"crashed on idx\", idx);\n      ok(false, \"died on idx \" + idx + \" with \" + e);\n    }\n  });\n  ok(true, \"Done\");\n});\n\nfunction verifySet(set) {\n  let i = issue1268_triggering_input.length + 2;\n  for (const node of set) {\n    if (--i === 0) return false;\n  }\n  return true;\n}\n"
  },
  {
    "path": "test/tests-table.js",
    "content": "﻿import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {resetDatabase, supports, spawnedTest, promisedTest, isSafari, isSafariPrivateMode} from './dexie-unittest-utils';\n\nvar db = new Dexie(\"TestDBTable\");\ndb.version(1).stores({\n    users: \"++id,first,last,&username,*&email,*pets\",\n    folks: \"++,first,last\",\n    items: \"id\",\n    outbound: \"\", // For testing outbound primary keys\n    schema: \"\" // Test issue #1039\n});\n\nvar User = db.users.defineClass({\n    id:         Number,\n    first:      String,\n    last:       String,\n    username:   String,\n    email:      [String],\n    pets:       [String],\n});\nvar idOfFirstUser = 0,\n    idOfLastUser = 0;\n\ndb.on(\"populate\", function (trans) {\n    db.users.add({first: \"David\", last: \"Fahlander\", username: \"dfahlander\", email: [\"david@awarica.com\", \"daw@thridi.com\"], pets: [\"dog\"]}).then(function(id) {\n        idOfFirstUser = id;\n    });\n    db.users.add({first: \"Karl\", last: \"Faadersköld\", username: \"kceder\", email: [\"karl@ceder.what\", \"dadda@ceder.what\"], pets: []}).then(function(id) {\n        idOfLastUser = id;\n    });\n});\n\nmodule(\"table\", {\n    setup: function () {\n        stop();\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: function () {\n    }\n});\n\npromisedTest(\"Table.upsert()\", async () => {\n    // Test 1: Upsert on existing object (should behave like update)\n    const existingId = await db.users.add({\n        first: \"Alice\", \n        last: \"Smith\", \n        username: \"asmith\", \n        email: [\"alice@example.com\"]\n    });\n    \n    // Upsert should update the existing object\n    const wasUpdate = await db.users.upsert(existingId, {\n        last: \"Johnson\",\n        \"email.0\": \"alice.johnson@example.com\"\n    });\n    \n    equal(wasUpdate, true, \"upsert() should return true when updating existing object\");\n    \n    const updatedUser = await db.users.get(existingId);\n    equal(updatedUser.first, \"Alice\", \"First name should remain unchanged\");\n    equal(updatedUser.last, \"Johnson\", \"Last name should be updated\");\n    equal(updatedUser.username, \"asmith\", \"Username should remain unchanged\");\n    equal(updatedUser.email[0], \"alice.johnson@example.com\", \"Email should be updated using dotted path\");\n    \n    // Test 2: Upsert on non-existing object (should create new object)\n    const nonExistingId = existingId + 1000; // Use a key that definitely doesn't exist\n    \n    // Upsert should create a new object\n    const wasCreate = await db.users.upsert(nonExistingId, {\n        first: \"Bob\",\n        last: \"Wilson\",\n        \"email.0\": \"bob@example.com\"\n    });\n    \n    equal(wasCreate, false, \"upsert() should return false when creating new object\");\n    \n    const newUser = await db.users.get(nonExistingId);\n    ok(newUser !== null, \"New user should exist\");\n    equal(newUser.id, nonExistingId, \"Primary key should be set correctly\");\n    equal(newUser.first, \"Bob\", \"First name should be set\");\n    equal(newUser.last, \"Wilson\", \"Last name should be set\");\n    equal(newUser.email[0], \"bob@example.com\", \"Email should be set using dotted path\");\n    ok(newUser.username === undefined, \"Username should be undefined (not specified in modifications)\");\n    \n    // Test 3: Upsert on table with outbound primary key (outbound table)\n    await db.outbound.put({name: \"Original Item\"}, 100);\n    \n    // Update existing item\n    const itemWasUpdate = await db.outbound.upsert(100, {\n        name: \"Updated Item\",\n        description: \"Added description\"\n    });\n    \n    equal(itemWasUpdate, true, \"Should return true for existing item update\");\n    \n    const updatedItem = await db.outbound.get(100);\n    equal(updatedItem.name, \"Updated Item\", \"Item name should be updated\");\n    equal(updatedItem.description, \"Added description\", \"Description should be added\");\n    \n    // Create new item\n    const itemWasCreate = await db.outbound.upsert(101, {\n        name: \"New Item\",\n        category: \"Test\"\n    });\n    \n    equal(itemWasCreate, false, \"Should return false for new item creation\");\n    \n    const newItem = await db.outbound.get(101);\n    equal(newItem.name, \"New Item\", \"Item name should be set\");\n    equal(newItem.category, \"Test\", \"Category should be set\");\n    ok(newItem.id === undefined, \"Primary key should not be set in object for outbound keys\");\n    \n    // Test 4: Upsert with nested object modifications\n    const nestedId = await db.folks.add({\n        first: \"Carol\",\n        last: \"Davis\",\n        address: {\n            city: \"Stockholm\",\n            street: \"Kungsgatan\"\n        }\n    });\n    \n    await db.folks.upsert(nestedId, {\n        \"address.postalCode\": \"11122\",\n        \"address.country\": \"Sweden\"\n    });\n    \n    const nestedUser = await db.folks.get(nestedId);\n    equal(nestedUser.address.city, \"Stockholm\", \"Existing nested property should remain\");\n    equal(nestedUser.address.street, \"Kungsgatan\", \"Existing nested property should remain\");\n    equal(nestedUser.address.postalCode, \"11122\", \"New nested property should be added\");\n    equal(nestedUser.address.country, \"Sweden\", \"New nested property should be added\");\n    \n    // Test 5: Upsert creating object with nested structure from scratch\n    const newNestedId = await db.folks.count() + 1000; // Ensure non-existing ID\n    \n    await db.folks.upsert(newNestedId, {\n        first: \"David\",\n        \"address.city\": \"Göteborg\",\n        \"address.street\": \"Avenyn\"\n    });\n    \n    const newNestedUser = await db.folks.get(newNestedId);\n    equal(newNestedUser.first, \"David\", \"First name should be set\");\n    equal(newNestedUser.address.city, \"Göteborg\", \"Nested city should be set\");\n    equal(newNestedUser.address.street, \"Avenyn\", \"Nested street should be set\");\n    ok(newNestedUser.last === undefined, \"Unspecified properties should be undefined\");\n});\n\npromisedTest(\"Issue #841 - put() ignores date changes\", async ()=> {\n    const updateAssertions = (mods) => {\n        equal(mods.first, first2, `first value should be ${first2} but is ${mods.first}`)\n        \n        equal(!!mods.date, true, \"date should be included in modifications\");\n\n        if (mods.date) {\n            equal(mods.date.getTime(), date2.getTime(), `date should be ${date2} but is ${mods.date}`)\n        }\n    };\n    db.folks.hook(\"updating\", updateAssertions);\n\n    const date1 = new Date(\"2019-05-03\");\n    const date2 = new Date(\"2020-01-01\");\n\n    const first1 = \"Foo1\";\n    const first2 = \"Foo2\";\n\n    ok(date1.getTime() !== date2.getTime(), \"Just verifying input data so that date1 !== date2\");\n    ok(first1 != first2, \"first1 different first2\");\n\n    const id = await db.folks.add({first: first1, last: \"Bar\", date: date1});\n    let obj = await db.folks.get(id);\n    equal(obj.date.getTime(), date1.getTime(), \"Date should first equal date1\");\n    equal(obj.first, first1, `first should be '${first1}'`);\n\n    obj.first = first2;\n    obj.date = date2;\n\n    await db.folks.put(obj, id);\n\n    obj = await db.folks.get(id);\n    equal(obj.first, first2, `first should have been successfully updated to '${first2}'`);\n    equal(obj.date.getTime(), date2.getTime(), \"Date should have been successfully updated to be date2\");\n\n    db.folks.hook(\"updating\").unsubscribe(updateAssertions);\n});\n\npromisedTest(\"Issue #966 - put() with dotted field in update hook\", async () => {\n    const updateAssertions = (mods) => {\n        equal(mods[\"nested.field\"], \"value\", \"mods.nested.field should contain 'value'\");\n        equal(mods.nested, undefined, \"mods.nested field should be empty\");\n        return {...mods};\n    };\n    db.folks.hook(\"updating\", updateAssertions);\n\n    const id = await db.folks.add({first: \"first\", last: \"last\"});\n    await db.folks.put({first: \"first\", last: \"last\", \"nested.field\": \"value\"}, id);\n\n    let obj = await db.folks.get(id);\n    equal(obj[\"nested.field\"], \"value\", \"obj.nested.field should have been successfully updated to 'value'\");\n    equal(obj.nested, undefined, \"obj.nested field should have remained undefined\");\n\n    db.folks.hook(\"updating\").unsubscribe(updateAssertions);\n});\n\npromisedTest(\"update array property\", async () => {\n    const id = await db.items.put({id: 1, foo: [{bar: 123}]});\n    await db.items.update(1, {foo: [{bar: 222}]});\n    const obj = await db.items.get(1);\n    equal(JSON.stringify(obj.foo), JSON.stringify([{bar: 222}]), \"foo har been updated to the new array\");\n});\n\npromisedTest(\"Verify #1130 doesn't break contract of hook('updating')\", async ()=>{\n    const updateHook = (mods) => {\n        return {\"address.postalCode\": 111};\n    };\n    try {\n      const id = await db.folks.add({\n        first: \"Foo\",\n        last: \"Bar\",\n        address: {\n            city: \"Stockholm\",\n            street: \"Folkungagatan\"\n        }\n      });\n      db.folks.hook(\"updating\", updateHook);\n      await db.folks.update(id, {\n          \"address.streetNo\": 23\n      });\n      let foo = await db.folks.get(id);\n      equal(foo.address.city, \"Stockholm\", \"Correct city Stockholm\");\n      equal(foo.address.street, \"Folkungagatan\", \"Correct street Folkungagatan\");\n      equal(foo.address.streetNo, 23, \"Correct streetNo: 23\");\n      equal(foo.address.postalCode, 111, \"Hooks should have added postal code\");\n    } finally {\n      db.folks.hook(\"updating\").unsubscribe(updateHook);\n    }\n});\n\nasyncTest(\"get\", 4, function () {\n    db.table(\"users\").get(idOfFirstUser).then(function(obj) {\n        equal(obj.first, \"David\", \"Got the first object\");\n        return db.users.get(idOfLastUser);\n    }).then(function(obj) {\n        equal(obj.first, \"Karl\", \"Got the second object\");\n        return db.users.get(\"nonexisting key\");\n    }).then(function(obj) {\n        ok(true, \"Got then() even when getting non-existing object\");\n        equal(obj, undefined, \"Result is 'undefined' when not existing\");\n    }).catch(function(err) {\n        ok(false, \"Error: \" + err);\n    }).finally(start);\n});\n\nasyncTest(\"where\", function () {\n    db.transaction(\"r\", db.users, function () {\n        db.users.where(\"username\").equals(\"kceder\").first(function (user) {\n            equal(user.first, \"Karl\", \"where().equals()\");\n        }),\n        db.users.where(\"id\").above(idOfFirstUser).toArray(function (a) {\n            ok(a.length == 1, \"where().above()\");\n        }),\n        db.users.where(\"id\").aboveOrEqual(idOfFirstUser).toArray(function (a) {\n            ok(a.length == 2, \"where().aboveOrEqual()\");\n        }),\n        db.users.where(\"id\").below(idOfLastUser).count(function (count) {\n            ok(count == 1, \"where().below().count()\");\n        }),\n        db.users.where(\"id\").below(idOfFirstUser).count(function (count) {\n            ok(count == 0, \"where().below().count() should be zero\");\n        }),\n        db.users.where(\"id\").belowOrEqual(idOfFirstUser).count(function (count) {\n            ok(count == 1, \"where().belowOrEqual()\");\n        }),\n        db.users.where(\"id\").between(idOfFirstUser, idOfFirstUser).count(function (count) {\n            ok(count == 0, \"where().between(1, 1)\");\n        }),\n        db.users.where(\"id\").between(0, Infinity).count(function (count) {\n            ok(count == 2, \"where().between(0, Infinity)\");\n        }),\n        db.users.where(\"id\").between(idOfFirstUser, idOfFirstUser, true, true).count(function (count) {\n            ok(count == 1, \"where().between(1, 1, true, true)\");\n        }),\n        db.users.where(\"id\").between(1, -1, true, true).count(function (count) {\n            ok(count == 0, \"where().between(1, -1, true, true)\");\n        }),\n        db.users.where(\"id\").between(idOfFirstUser, idOfLastUser).count(function (count) {\n            ok(count == 1, \"where().between(1, 2)\");\n        }),\n        db.users.where(\"id\").between(idOfFirstUser, idOfLastUser, true, true).count(function (count) {\n            ok(count == 2, \"where().between(1, 2, true, true)\");\n        }),\n        db.users.where(\"id\").between(idOfFirstUser, idOfLastUser, false, false).count(function (count) {\n            ok(count == 0, \"where().between(1, 2, false, false)\");\n        });\n        db.users.where(\"last\").startsWith(\"Fah\").toArray(function (a) {\n            equal(a.length, 1, \"where().startsWith(existing) only matches Fahlander, not Faadersköld\");\n            equal(a[0].first, \"David\");\n        });\n        db.users.where(\"last\").startsWith(\"Faa\").toArray(function (a) {\n            equal(a.length, 1, \"where().startsWith(existing) only matches Faadersköld, not Fahlander\");\n            equal(a[0].first, \"Karl\");\n        });\n        db.users.where(\"last\").startsWith(\"Fa\").toArray(function (a) {\n            equal(a.length, 2, \"length = 2 on: where().startsWith(2 existing)\");\n            equal(a[0].first, \"Karl\", \"Karl found first on last 'Faadersköld'\");\n            equal(a[1].first, \"David\", \"David found second on last 'Fahlander'\");\n        });\n        db.users.where(\"last\").anyOf(\"Fahlander\", \"Faadersköld\").toArray(function (a) {\n            equal(a.length, 2, \"in() returned expected number of items\");\n            equal(a[0].last, \"Faadersköld\", \"Faadersköld is first\");\n        });\n        db.users.where(\"last\").anyOf(\"Fahlander\", \"Faadersköld\").reverse().toArray(function (a) {\n            equal(a.length, 2, \"in().reverse() returned expected number of items\");\n            equal(a[0].last, \"Fahlander\", \"Fahlander is first\");\n        });\n        db.users.where(\"last\").anyOf(\"Faadersköld\").toArray(function (a) {\n            equal(a.length, 1, \"in() returned expected number of items\");\n        });\n\n        if (supports(\"multiEntry\")) {\n            db.users.where(\"email\").equals(\"david@awarica.com\").toArray(function (a) { // Fails in IE with 0 due to that IE is not implementing to index string arrays.\n                equal(a.length, 1, \"Finding items from array members. Expect to fail on IE10/IE11.\");\n            });\n            db.users.where(\"email\").startsWith(\"da\").distinct().toArray(function (a) { // Fails on IE with 0\n                equal(a.length, 2, \"Found both because both have emails starting with 'da'. Expect to fail on IE10/IE11.\");\n            });\n        } else {\n            ok(true, \"SKIPPED - MULTIENTRY UNSUPPORTED\");\n            ok(true, \"SKIPPED - MULTIENTRY UNSUPPORTED\");\n        }\n    }).catch(function (e) {\n        ok(false, \"Transaction failed: \" + e);\n    }).finally(function () {\n        start();\n    });\n});\n\nasyncTest(\"count\", function () {\n    db.users.count(function (count) {\n        equal(count, 2, \"Table.count()\");\n    }).catch(function (e) {\n        ok(false, e.message);\n    }).finally(function(){\n        start();\n    });;\n});\nasyncTest(\"count with limit\", function () {\n    db.users.limit(1).count(function (count) {\n        equal(count, 1, \"Table.limit().count()\");\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\nasyncTest(\"limit(),orderBy(),modify(), abort(), reverse()\", function () {\n    db.transaction(\"rw\", db.users, function () {\n        // Modify first found user with a helloMessage\n        db.users.orderBy(\"first\").reverse().limit(1).modify(function (user) {\n            user.helloMessage = \"Hello \" + user.first;\n        });\n\n        // Check that the modification went fine:\n        db.users.orderBy(\"first\").reverse().toArray(function (a) {\n            equal(a[0].first, \"Karl\", \"First item is Karl\");\n            equal(a[0].helloMessage, \"Hello Karl\", \"Karl got helloMessage 'Hello Karl'\");\n            equal(a[1].first, \"David\", \"Second item is David\");\n            ok(!a[1].helloMessage, \"David was not modified due to limit()\");\n        });\n    }).catch(function (e) {\n        ok(false, \"Error: \" + e);\n    }).finally(function () {\n        start();\n    });\n});\n\nasyncTest(\"filter\", function () {\n    db.users.filter(function (user) { return user.email.indexOf(\"david@awarica.com\") != -1 }).toArray(function (davids) {\n        equal(1, davids.length, \"Got one David\");\n        equal(\"David\", davids[0].first, \"The name of the David is David\");\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n    }).finally(start);\n});\n\nasyncTest(\"each\", function () {\n    var users = [];\n    db.users.each(function (user) {\n        users.push(user);\n    }).then(function () {\n        equal(users.length, 2, \"Got 2 users\");\n        equal(users[0].first, \"David\", \"Got David\");\n        equal(users[1].first, \"Karl\", \"Got Karl\");\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"put\", function () {\n    db.transaction(\"rw\", db.users, function () {\n        var newUser = { first: \"Åke\", last: \"Persbrant\", username: \"aper\", email: [\"aper@persbrant.net\"] };\n        db.users.put(newUser).then(function (id) {\n            ok(id > idOfLastUser, \"Got id \" + id + \" because we didnt supply an id\");\n            equal(newUser.id, id, \"The id property of the new user was set\");\n        });\n        db.users.where(\"username\").equals(\"aper\").first(function (user) {\n            equal(user.last, \"Persbrant\", \"The correct item was actually added\");\n            user.last = \"ChangedLastName\";\n            var currentId = user.id;\n            db.users.put(user).then(function (id) {\n                equal(id, currentId, \"Still got same id because we update same object\");\n            });\n            db.users.where(\"last\").equals(\"ChangedLastName\").first(function (user) {\n                equal(user.last, \"ChangedLastName\", \"LastName was successfully changed\");\n            });\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nasyncTest(\"put-no-transaction\", function () {\n    var newUser = { first: \"Åke\", last: \"Persbrant\", username: \"aper\", email: [\"aper@persbrant.net\"] };\n    db.users.put(newUser).then(function(id) {\n        ok(id > idOfLastUser, \"Got id \" + id + \" because we didnt supply an id\");\n        equal(newUser.id, id, \"The id property of the new user was set\");\n        return db.users.where(\"username\").equals(\"aper\").first(function(user) {\n            equal(user.last, \"Persbrant\", \"The correct item was actually added\");\n            user.last = \"ChangedLastName\";\n            var userId = user.id;\n            return db.users.put(user).then(function(id) {\n                equal(id, userId, \"Still got same id because we update same object\");\n                return db.users.where(\"last\").equals(\"ChangedLastName\").first(function(user) {\n                    equal(user.last, \"ChangedLastName\", \"LastName was successfully changed\");\n                });\n            });\n        });\n    }).catch(function(e) {\n        ok(false, e);\n    }).finally(start);\n});\n\npromisedTest(\"bulkUpdate\", async ()=>{\n    await db.items.bulkAdd([\n        {id: 1, foo: {bar: 1}},\n        {id: 2, foo: {bar: 2}},\n        {id: 3, foo: {bar: 3}}\n    ]);\n    await db.items.bulkUpdate([\n        {key: 1, changes: {\"foo.bar\": 101}},\n        {key: 2, changes: {\"foo.bar\": 102, \"foo.baz\": \"x\"}},\n        {key: 4, changes: {\"foo.bar\": 104}},\n    ]);\n    const allItems = await db.items.toArray();\n    const expected = [\n        {id: 1, foo: {bar: 101}},\n        {id: 2, foo: {bar: 102, baz: \"x\"}},\n        {id: 3, foo: {bar: 3}}\n    ];\n    deepEqual(allItems, expected, \"2 items updated as expected. nonexisting item not updated.\");\n});\n\npromisedTest(\"bulkUpdate without actual changes (check it doesn't bail out)\", async ()=>{\n  const dbCoreMutateCalls = [];\n  db.use({\n    stack: 'dbcore',\n    name: 'temp-logger',\n    create: (downDb) => ({\n      ...downDb,\n      table: (tableName) => {\n        const downTable = downDb.table(tableName);\n        return {\n          ...downTable,\n          mutate(req) {\n            if (tableName === 'items') {\n                dbCoreMutateCalls.push(req);\n            }\n            return downTable.mutate(req);\n          }\n        };\n      }\n    })\n  });\n  db.close();\n  await db.open(); // Apply the middleware\n\n  try {\n    // Clear the log (in case another middleware or addon did something in db ready in integration tests)\n    dbCoreMutateCalls.splice(0, dbCoreMutateCalls.length);\n    equal(dbCoreMutateCalls.length, 0, 'No mutate calls yet');\n    await db.items.bulkUpdate([\n      { key: 'nonexist1', changes: { 'foo.bar': 101 } },\n      { key: 'nonexist2', changes: { 'foo.bar': 102 } },\n      { key: 'nonexist3', changes: { 'foo.bar': 103 } }\n    ]);\n    equal(dbCoreMutateCalls.length, 1, 'One mutate call has taken place');\n    deepEqualPartial(\n      dbCoreMutateCalls,\n      [\n        {\n          type: 'put',\n          values: [],\n          criteria: undefined,\n          changeSpec: undefined,\n          updates: {\n            keys: ['nonexist1', 'nonexist2', 'nonexist3'],\n            changeSpecs: [\n              { 'foo.bar': 101 },\n              { 'foo.bar': 102 },\n              { 'foo.bar': 103 }\n            ],\n          },\n        },\n      ],\n      'The mutate call was a put call and contained the intended updates for consistent sync addons to consume'\n    );\n\n    const allItems = await db.items.toArray();\n    const expected = [];\n    deepEqual(allItems, expected, 'Nonexisting item not updated.');\n  } finally {\n    // Cleanup temporary middleware:\n    db.unuse({ stack: 'dbcore', name: 'temp-logger' });\n    db.close();\n    await db.open();\n  }\n});\n\n\npromisedTest(\"bulkUpdate with failure\", async ()=>{\n    if (isSafari) {\n        // Avoid bug https://bugs.webkit.org/show_bug.cgi?id=247053\n        ok(true, \"Avoiding Safari issue https://bugs.webkit.org/show_bug.cgi?id=247053\");\n        return;\n    }\n    try {\n        const users = await db.users.toArray();\n        deepEqualPartial(users, [\n            {first: \"David\", username: \"dfahlander\"},\n            {first: \"Karl\", username: \"kceder\" }\n        ], \"We have the expected users to begin with\");\n        await db.users.bulkUpdate([\n            {key: \"nonexisting\", changes: {username: \"xyz\"}}, // Shall be ignored\n            {key: idOfFirstUser, changes: {username: \"kceder\"}}, // Shall fail (unique index)\n            {key: idOfFirstUser + 1, changes: {first: \"Baz\"}} // Shall succeed\n        ]);\n        throw new Error(\"Should not have succeeded\");\n    } catch (error) {\n        equal(error.failures.length, 1, \"Should be 1 failure\");\n        const failurePositions = Object.keys(error.failuresByPos);\n        equal(failurePositions.length, 1, \"failuresByPos should have one key only (array with holes)\");\n        const failurePosition = failurePositions[0];\n        equal(failurePosition, 1, \"The failure should have occurred at position 1\");\n        const failure = error.failuresByPos[failurePosition];\n        ok(failure != null, \"There was a failure\");\n    }\n    const allItems = await db.users.toArray();\n    const expected = [\n        {first: \"David\", username: \"dfahlander\" },\n        {first: \"Baz\", username: \"kceder\" }\n    ];\n    deepEqualPartial(allItems, expected, \"The end result in a non-transactional bulkUpdate() should be that non-failing entries succeeded to update\");\n});\n\npromisedTest(\"bulkUpdate with failure (transactional)\", async ()=>{\n    try {\n        await db.transaction('rw', db.users, async () => {\n            await db.users.bulkUpdate([\n                {key: \"nonexisting\", changes: {username: \"xyz\"}}, // Shall be ignored\n                {key: idOfFirstUser, changes: {username: \"kceder\"}}, // Shall fail (unique index)\n                {key: idOfFirstUser + 1, changes: {\"foo.bar\": 102}} // Shall succeed\n            ]);\n        });\n        throw new Error(\"Should not have succeeded\");\n    } catch (error) {\n        equal(error.failures.length, 1, \"Should be 1 failure\");\n        const failurePositions = Object.keys(error.failuresByPos);\n        equal(failurePositions.length, 1, \"failuresByPos should have one key only (array with holes)\");\n        const failurePosition = failurePositions[0];\n        equal(failurePosition, 1, \"The failure should have occurred at position 1\");\n        const failure = error.failuresByPos[failurePosition];\n        ok(failure != null && failure instanceof Error, \"There was a failure and it was an error\");\n    }\n    const allItems = await db.users.toArray();\n    const expected = [\n        {first: \"David\", username: \"dfahlander\" },\n        {first: \"Karl\", username: \"kceder\" }\n    ];\n    deepEqualPartial(allItems, expected, \"The end result in a transactional bulkUpdate() should be that no entries succeeeded to update if not catching error within transaction\");\n});\n\npromisedTest(\"bulkUpdate with failure (transactional with catch)\", async ()=>{\n    if (isSafari) {\n        // Avoid bug https://bugs.webkit.org/show_bug.cgi?id=247053\n        ok(true, \"Avoiding Safari issue https://bugs.webkit.org/show_bug.cgi?id=247053\");\n        return;\n    }\n\n    await db.transaction('rw', db.users, async () => {\n        try {\n            await db.users.bulkUpdate([\n                {key: \"nonexisting\", changes: {username: \"xyz\"}}, // Shall be ignored\n                {key: idOfFirstUser, changes: {username: \"kceder\"}}, // Shall fail (unique index)\n                {key: idOfFirstUser + 1, changes: {\"foo.bar\": 102}} // Shall succeed\n            ]);\n        } catch {}\n    });\n    const allItems = await db.users.toArray();\n    const expected = [\n        {first: \"David\", last: \"Fahlander\", username: \"dfahlander\" },\n        {first: \"Karl\", last: \"Faadersköld\", username: \"kceder\", foo: {bar: 102} }\n    ];\n    deepEqualPartial(allItems, expected, \"The end result in a transactional bulkUpdate() (with catch inside transaction) should be that non-failing entries succeeded to update\");\n});\n\nasyncTest(\"add\", function () {\n    db.transaction(\"rw\", db.users, function () {\n        var newUser = { first: \"Åke\", last: \"Persbrant\", username: \"aper\", email: [\"aper@persbrant.net\"] };\n\n        db.users.add(newUser).then(function (id) {\n            ok(id > idOfLastUser, \"Got id \" + id + \" because we didnt supply an id\");\n            equal(newUser.id, id, \"The id property of the new user was set\");\n        });\n\n        db.users.where(\"username\").equals(\"aper\").first(function (user) {\n            equal(user.last, \"Persbrant\", \"The correct item was actually added\");\n        });\n\n    }).catch(function (e) {\n        ok(false, \"Error: \" + e);\n    }).finally(start);\n});\n\nspawnedTest(\"bulkAdd\", function*(){\n    var highestKey = yield db.users.add({username: \"fsdkljfd\", email: [\"fjkljslk\"]});\n    ok(true, \"Highest key was: \" + highestKey);\n    // Delete test item.\n    yield db.users.delete(highestKey);\n    ok(true, \"Deleted test item\");\n    var result = yield db.users.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\", username: \"aper1\", email: [\"aper1@persbrant.net\"] },\n        { first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] }\n    ]);\n    equal (result, highestKey + 2, \"Result of bulkAdd() operation was equal to highestKey + 2\");\n});\n\nspawnedTest(\"bulkAdd-all-results\", function* () {\n    var dbBulkAddAll = new Dexie(\"TestDBTableBulkAddAllResults\");\n    dbBulkAddAll.version(1).stores({\n        dudes: \"++,first,last\"\n    });\n    var highestKey = yield dbBulkAddAll.dudes.add({ username: \"fsdkljfd\", email: \"fjkljslk\", find: \"bulkAddAll\" });\n\n    // should be able to get all keys with options object as second argument\n    var allKeys = yield dbBulkAddAll.dudes.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\", find: \"bulkAddAll\" },\n        { first: \"Åke2\", last: \"Persbrant2\", find: \"bulkAddAll\" }\n    ], { allKeys: true });\n    var expectedKeys = [highestKey + 1, highestKey + 2];\n    deepEqual(allKeys, expectedKeys,\n        \"Result of bulkAdd(objects, { allKeys: true }) operation was equal to [highestKey + 1, highestKey + 2]\");\n\n    // should be able to get all keys with options object as third argument\n    highestKey = yield dbBulkAddAll.dudes.add({ username: \"fsdkljfd\", email: \"fjkljslk\", find: \"bulkAddAll\" });\n    var allKeys2 = yield dbBulkAddAll.dudes.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\", find: \"bulkAddAll\" },\n        { first: \"Åke2\", last: \"Persbrant2\", find: \"bulkAddAll\" }\n    ], undefined, { allKeys: true });\n    var expectedKeys2 = [highestKey + 1, highestKey + 2];\n    deepEqual(allKeys2, expectedKeys2,\n        \"Result of bulkAdd(objects, undefined, { allKeys: true }) operation was equal to [highestKey + 1, highestKey + 2]\");\n\n    // should be able to get all keys with options object as third argument with some keys\n    var allKeys3 = yield dbBulkAddAll.dudes.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\" },\n        { first: \"Åke2\", last: \"Persbrant2\" }\n    ], ['sd5fs2df', 'dasfsd3fs7df'], { allKeys: true });\n    deepEqual(allKeys3, ['sd5fs2df', 'dasfsd3fs7df'],\n        \"Result of bulkAdd(objects, ['sd5fs2df', 'dasfsd3fs7df'], { allKeys: true }) operation was equal to ['sd5fs2df', 'dasfsd3fs7df']\");\n\n    // should return last key with 1 argument and options: { allKeys: false }\n    highestKey = yield dbBulkAddAll.dudes.add({ username: \"fsdkljfd\", email: \"fjkljslk\", find: \"bulkAddAll\" });\n    var lastKey = yield dbBulkAddAll.dudes.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\" },\n        { first: \"Åke2\", last: \"Persbrant2\" }\n    ], { allKeys: false });\n    equal(lastKey, highestKey + 2,\n        \"Result of bulkAdd(objects, { allKeys: false }) operation was equal to highestKey + 2\");\n\n    // should return last key with 2 arguments and options: { allKeys: false }\n    var lastKey = yield dbBulkAddAll.dudes.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\" },\n        { first: \"Åke2\", last: \"Persbrant2\" }\n    ], ['cv4btr45fbrt', 'b33vn3fytn'], { allKeys: false });\n    equal(lastKey, 'b33vn3fytn',\n        \"Result of bulkAdd(objects, ['cv4btr45fbrt', 'b33vn3fytn'], { allKeys: false }) operation was equal to 'b33vn3fytn'\");\n\n    // should return last key with 2 arguments and no options object\n    var lastKey = yield dbBulkAddAll.dudes.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\" },\n        { first: \"Åke2\", last: \"Persbrant2\" }\n    ], ['dfgd2vdfh4d', 'ty1jxdbd9']);\n    equal(lastKey, 'ty1jxdbd9',\n        \"Result of bulkAdd(objects, ['dfgd2vdfh4d', 'ty1jxdbd9']) operation was equal to 'ty1jxdbd9'\");\n\n    yield dbBulkAddAll.delete();\n});\n\nspawnedTest(\"bulkAdd-catching errors\", function*() {\n    yield db.transaction(\"rw\", db.users, function() {\n        var newUsers = [\n            { first: \"Åke1\", last: \"Persbrant1\", username: \"aper1\", email: [\"aper1@persbrant.net\"] },\n            { first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] },\n            { first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] }, // Should fail\n            { first: \"Åke3\", last: \"Persbrant3\", username: \"aper3\", email: [\"aper3@persbrant.net\"] }\n        ];\n        db.users.bulkAdd(newUsers).then(()=> {\n            ok(false, \"Should not resolve when one operation failed\");\n        }).catch(Dexie.BulkError, e=>{\n            ok(true, \"Got BulkError: \" + e.message);\n            equal(e.failures.length, 1, \"One error due to a duplicate username: \" + e.failures[0]);\n        });\n\n        // Now, since we catched the error, the transaction should continue living.\n        db.users.where(\"username\").startsWith(\"aper\").count(function(count) {\n            equal(count, 3, \"Got three matches now when users are bulk-added\");\n        });\n    });\n\n    equal(yield db.users.where(\"username\").startsWith('aper').count(), 3, \"Previous transaction committed\");\n\n    var newUsersX = [\n        {first: \"Xke1\", last: \"Persbrant1\", username: \"xper1\", email: [\"xper1@persbrant.net\"]},\n        {first: \"Xke2\", last: \"Persbrant2\", username: \"xper2\", email: [\"xper2@persbrant.net\"]},\n        {first: \"Xke2\", last: \"Persbrant2\", username: \"xper2\", email: [\"xper2@persbrant.net\"]}, // Should fail\n        {first: \"Xke3\", last: \"Persbrant3\", username: \"xper3\", email: [\"xper3@persbrant.net\"]}\n    ];\n    try {\n        yield db.transaction(\"rw\", db.users, () => {\n            db.users.bulkAdd(newUsersX).then(()=> {\n                ok(false, \"Should not resolve\");\n            });\n        });\n        ok(false, \"Should not come here\");\n    } catch (e) {\n        ok(true, \"Got: \" + e);\n    }\n\n    equal(yield db.users.where('username').startsWith('xper').count(), 0, \"0 users! Good, means that previous transaction did not commit\");\n\n    yield db.users.bulkAdd(newUsersX).catch(e => {\n        ok(true, \"Got error. Catching it should make the successors work.\")\n    });\n    if (!isSafariPrivateMode) {\n        equal(yield db.users.where('username').startsWith('xper').count(), 3, \"3 users! Good - means that previous operation catched and therefore committed\");\n    }\n\n    var newUsersY = [\n        {first: \"Yke1\", last: \"Persbrant1\", username: \"yper1\", email: [\"yper1@persbrant.net\"]},\n        {first: \"Yke2\", last: \"Persbrant2\", username: \"yper2\", email: [\"yper2@persbrant.net\"]},\n        {first: \"Yke2\", last: \"Persbrant2\", username: \"yper2\", email: [\"yper2@persbrant.net\"]}, // Should fail\n        {first: \"Yke3\", last: \"Persbrant3\", username: \"yper3\", email: [\"yper3@persbrant.net\"]}\n    ];\n\n    // Now check that catching the operation via try..catch should also make it succeed.\n    try {\n        yield db.users.bulkAdd(newUsersY);\n    } catch (e) {\n        ok(true, \"Got: \" + e);\n    }\n    if (!isSafariPrivateMode) {\n        equal(yield db.users.where('username').startsWith('yper').count(), 3, \"3 users! Good - means that previous operation catched (via try..yield..catch this time, and therefore committed\");\n    }\n\n    // Now check that catching and rethrowing should indeed make it fail\n    var newUsersZ = [\n        {first: \"Zke1\", last: \"Persbrant1\", username: \"zper1\", email: [\"zper1@persbrant.net\"]},\n        {first: \"Zke2\", last: \"Persbrant2\", username: \"zper2\", email: [\"zper2@persbrant.net\"]},\n        {first: \"Zke2\", last: \"Persbrant2\", username: \"zper2\", email: [\"zper2@persbrant.net\"]}, // Should fail\n        {first: \"Zke3\", last: \"Persbrant3\", username: \"zper3\", email: [\"zper3@persbrant.net\"]}\n    ];\n\n    yield db.transaction('rw', db.users, function*() {\n        try {\n            yield db.users.bulkAdd(newUsersZ);\n        } catch (e) {\n            throw e;\n        }\n    }).catch(Dexie.BulkError, e => {\n        ok(true, \"Got rethrown BulkError: \" + e.stack);\n    });\n\n    equal(yield db.users.where('username').startsWith('zper').count(), 0, \"0 users! Good - means that previous operation rethrown (via try..yield..catch--throw this time, and therefore not committed\");\n});\n\nspawnedTest(\"bulkAdd-non-inbound-autoincrement\", function*(){\n    yield db.folks.bulkAdd([\n        { first: \"Foo\", last: \"Bar\"},\n        { first: \"Foo\", last: \"Bar2\"},\n        { first: \"Foo\", last: \"Bar3\"},\n        { first: \"Foo\", last: \"Bar4\"}\n    ]);\n    equal (yield db.folks.where('first').equals('Foo').count(), 4, \"Should be 4 Foos\");\n    equal (yield db.folks.where('last').equals('Bar').count(), 1, \"Shoudl be 1 Bar\");\n});\n\nspawnedTest(\"bulkAdd-catch sub transaction\", function*(){\n    yield db.transaction('rw', db.users, ()=>{\n        var newUsers = [\n            { first: \"Åke1\", last: \"Persbrant1\", username: \"aper1\", email: [\"aper1@persbrant.net\"] },\n            { first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] },\n            { first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] }, // Should fail\n            { first: \"Åke3\", last: \"Persbrant3\", username: \"aper3\", email: [\"aper3@persbrant.net\"] }\n        ];\n        db.transaction('rw', db.users, ()=>{\n            db.users.bulkAdd(newUsers);\n        }).then(()=>{\n            ok(false, \"Should not succeed with all these operations\");\n        }).catch(e => {\n            equal(e.failures.length, 1, \"Should get one failure\");\n        });\n    }).catch(e => {\n        ok(true, \"Outer transaction aborted due to inner transaction abort. This is ok: \" + e);\n    });\n\n    equal(yield db.users.where('username').startsWith('aper').count(), 0, \"0 users! Good, means that inner transaction did not commit\");\n});\n\nspawnedTest(\"Issue #1280 - add() with auto-incrementing ID and CryptoKey\", function* () {\n    if (!self?.crypto?.subtle) {\n        ok(true, \"This browser doesnt have WebCrypto\");\n        return;\n    }\n    var generatedKey = yield self.crypto.subtle.generateKey(\n        {\n            name: \"RSA-OAEP\",\n            modulusLength: 1024,\n            publicExponent: new Uint8Array([1, 0, 1]),\n            hash: \"SHA-256\",\n        },\n        true,\n        [\"encrypt\", \"decrypt\"],\n    );\n\n    yield Dexie.delete(\"MyDatabaseToStoreCryptoKeys\");\n    var db = new Dexie(\"MyDatabaseToStoreCryptoKeys\");\n    db.version(1).stores({\n        keys: \"++id\",\n    });\n    var objToAdd = { key: generatedKey.privateKey };\n    ok(generatedKey.privateKey instanceof CryptoKey, \"The CryptoKey object was generated correctly\");\n\n    var id = yield db.keys.add(objToAdd);\n    ok(id != null, \"The id we got was not nullish\");\n\n    var storedObj = yield db.keys.get(id);\n    ok(storedObj.key instanceof CryptoKey, \"The CryptoKey object exists in storage\");\n\n    // Verify that update works\n    yield db.keys.update(id, {someOtherProp: 'x'});\n    storedObj = yield db.keys.get(id);\n    ok(storedObj.key instanceof CryptoKey, \"The CryptoKey object is still a CryptoKey\");\n});\n\nspawnedTest(\"bulkPut\", function*(){\n    var highestKey = yield db.users.add({username: \"fsdkljfd\", email: [\"fjkljslk\"]});\n    ok(true, \"Highest key was: \" + highestKey);\n    // Delete test item.\n    yield db.users.delete(highestKey);\n    ok(true, \"Deleted test item\");\n    let existingFirstUserToReplace = yield db.users.get(idOfFirstUser);\n    equal (existingFirstUserToReplace.username, \"dfahlander\", \"Existing user should be dfahlander\");\n    var result = yield db.users.bulkPut([\n        { first: \"Åke1\", last: \"Persbrant1\", username: \"aper1\", email: [\"aper1@persbrant.net\"] },\n        { id: idOfFirstUser, first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] },\n        { first: \"Åke3\", last: \"Persbrant3\", username: \"aper3\", email: [\"aper3@persbrant.net\"] }\n    ]);\n    equal (result, highestKey + 2, \"Result of bulkPut() operation was equal to highestKey + 2\");\n    let ourAddedUsers = yield db.users.where('username').startsWith(\"aper\").toArray();\n    equal(ourAddedUsers.length, 3, \"Should have put 3 users there (two additions and one replaced\");\n    let replacedDfahlander = yield db.users.get(idOfFirstUser);\n    equal(replacedDfahlander.username, \"aper2\", \"dfahlander Should now be aper2 instead\");\n});\n\nspawnedTest(\"bulkPut-all-results\", function* () {\n    var dbBulkPutAll = new Dexie(\"TestDBTableBulkPutAllResults\");\n    dbBulkPutAll.version(1).stores({\n        users: \"++id,first,last,find\",\n        mates: \"++id,first,last,find\",\n        dudes: \"++,first,last,find\",\n    });\n    var highestKey = yield dbBulkPutAll.users.add({ first: \"fsdkljfd\", last: \"fjkljslk\", find: \"bulkPutAll\" });\n\n    // should be able to get all keys with options object as second argument (users)\n    var allKeys = yield dbBulkPutAll.users.bulkPut([\n        { first: \"Åke1\", last: \"Persbrant1\", find: \"bulkPutAll\" },\n        { id: highestKey, first: \"Åke2\", last: \"Persbrant2\", find: \"bulkPutAll\" },\n        { first: \"Åke3\", last: \"Persbrant3\", find: \"bulkPutAll\" }\n    ], { allKeys: true });\n    deepEqual(allKeys, [highestKey + 1, highestKey, highestKey + 2],\n        \"Result of bulkAdd(objects, { allKeys: true }) operation was equal to [highestKey + 1, highestKey, highestKey + 2]\");\n    let ourAddedUsers = yield dbBulkPutAll.users.where('find').startsWith(\"bulkPutAll\").toArray();\n    equal(ourAddedUsers.length, 3, \"Should have put 3 users there (two additions and one replaced\");\n    let replacedRecord = yield dbBulkPutAll.users.get(highestKey);\n    equal(replacedRecord.last, \"Persbrant2\", \"fjkljslk should now be Persbrant2 instead\");\n\n    // should be able to get all keys with options object as third argument (mates)\n    highestKey = yield dbBulkPutAll.mates.add({ first: \"fsdkljfd\", last: \"fjkljslk\", find: \"bulkPutAll\" });\n    var allKeys2 = yield dbBulkPutAll.mates.bulkPut([\n        { first: \"Åke1\", last: \"Persbrant1\", find: \"bulkPutAll\" },\n        { id: highestKey, first: \"Åke2\", last: \"Persbrant2\", find: \"bulkPutAll\" },\n        { first: \"Åke3\", last: \"Persbrant3\", find: \"bulkPutAll\" }\n    ], undefined, { allKeys: true });\n    deepEqual(allKeys2, [highestKey + 1, highestKey, highestKey + 2],\n        \"Result of bulkPut(objects, undefined, { allKeys: true }) operation was equal to [highestKey + 1, highestKey, highestKey + 2]\");\n    let ourAddedUsers2 = yield dbBulkPutAll.mates.where('find').startsWith(\"bulkPutAll\").toArray();\n    equal(ourAddedUsers2.length, 3, \"Should have put 3 users there (two additions and one replaced\");\n    let replacedRecord2 = yield dbBulkPutAll.mates.get(highestKey);\n    equal(replacedRecord2.last, \"Persbrant2\", \"fjkljslk should now be Persbrant2 instead\");\n\n    // should be able to get all keys with options object as third argument with keys array (dudes)\n    highestKey = yield dbBulkPutAll.dudes.add({ first: \"fsdkljfd\", last: \"fjkljslk\", find: \"bulkPutAll\" });\n    var allKeys3 = yield dbBulkPutAll.dudes.bulkPut([\n        { first: \"Åke1\", last: \"Persbrant1\", find: \"bulkPutAll\" },\n        { id: highestKey, first: \"Åke2\", last: \"Persbrant2\", find: \"bulkPutAll\" },\n        { first: \"Åke3\", last: \"Persbrant3\", find: \"bulkPutAll\" }\n    ], ['sd5fs2df', highestKey, 'dasfsd3fs7df'], { allKeys: true });\n    deepEqual(allKeys3, ['sd5fs2df', highestKey, 'dasfsd3fs7df'],\n        \"Result of bulkPut(objects, ['sd5fs2df', highestKey, 'dasfsd3fs7df'], { allKeys: true }) operation was equal to ['sd5fs2df', highestKey, 'dasfsd3fs7df']\");\n    let ourAddedUsers3 = yield dbBulkPutAll.dudes.where('find').startsWith(\"bulkPutAll\").toArray();\n    equal(ourAddedUsers3.length, 3, \"Should have put 3 users there (two additions and one replaced\");\n    let replacedRecord3 = yield dbBulkPutAll.dudes.get(highestKey);\n    equal(replacedRecord3.last, \"Persbrant2\", \"fjkljslk should now be Persbrant2 instead\");\n\n    // should return last key with 1 argument and options: { allKeys: false }\n    highestKey = yield dbBulkPutAll.dudes.add({ username: \"fsdkljfd\", email: \"fjkljslk\", find: \"bulkAddAll\" });\n    var lastKey = yield dbBulkPutAll.dudes.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\" },\n        { first: \"Åke2\", last: \"Persbrant2\" }\n    ], { allKeys: false });\n    equal(lastKey, highestKey + 2,\n        \"Result of bulkAdd(objects, { allKeys: false }) operation was equal to highestKey + 2\");\n\n    // should return last key with 2 arguments and options: { allKeys: false }\n    var lastKey = yield dbBulkPutAll.dudes.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\" },\n        { first: \"Åke2\", last: \"Persbrant2\" }\n    ], ['cv4btr45fbrt', 'b33vn3fytn'], { allKeys: false });\n    equal(lastKey, 'b33vn3fytn',\n        \"Result of bulkAdd(objects, ['cv4btr45fbrt', 'b33vn3fytn'], { allKeys: false }) operation was equal to 'b33vn3fytn'\");\n\n    // should return last key with 2 arguments and no options object\n    var lastKey = yield dbBulkPutAll.dudes.bulkAdd([\n        { first: \"Åke1\", last: \"Persbrant1\" },\n        { first: \"Åke2\", last: \"Persbrant2\" }\n    ], ['dfgd2vdfh4d', 'ty1jxdbd9']);\n    equal(lastKey, 'ty1jxdbd9',\n        \"Result of bulkAdd(objects, ['dfgd2vdfh4d', 'ty1jxdbd9']) operation was equal to 'ty1jxdbd9'\");\n\n    yield dbBulkPutAll.delete();\n});\n\nspawnedTest(\"bulkPut with overlapping objects\", function*(){\n    yield db.users.bulkPut([{\n        id: \"sdjls83\",\n        first: \"Daveious\"\n    },{\n        id: \"sdjls83\",\n        last: \"Olvono\"\n    }]);\n    let theOne = yield db.users.get(\"sdjls83\");\n    equal (theOne.last, \"Olvono\", \"Last item is the one inserted\");\n    ok (theOne.first === undefined, \"Object doesnt have a first property\");\n});\n\nspawnedTest(\"bulkPut-catching errors\", function*() {\n    yield db.transaction(\"rw\", db.users, function() {\n        var newUsers = [\n            { first: \"Åke1\", last: \"Persbrant1\", username: \"aper1\", email: [\"aper1@persbrant.net\"] },\n            { id: idOfLastUser, first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] }, // update success\n            { id: idOfFirstUser, first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] }, // update should fail\n            { first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] }, // Add should fail\n            { first: \"Åke3\", last: \"Persbrant3\", username: \"aper3\", email: [\"aper3@persbrant.net\"] }\n        ];\n        db.users.bulkPut(newUsers).then(()=> {\n            ok(false, \"Should not resolve when one operation failed\");\n        }).catch(Dexie.BulkError, e=>{\n            ok(true, \"Got BulkError: \" + e.message);\n            equal(e.failures.length, 2, \"Two errors due to a duplicate username: \" + e.failures[0]);\n        });\n\n        // Now, since we catched the error, the transaction should continue living.\n        db.users.where(\"username\").startsWith(\"aper\").count(function(count) {\n            equal(count, 3, \"Got three matches now when users are bulk-putted\");\n        });\n    });\n\n    equal(yield db.users.where(\"username\").startsWith('aper').count(), 3, \"Previous transaction committed\");\n\n    var newUsersX = [\n        {first: \"Xke1\", last: \"Persbrant1\", username: \"xper1\", email: [\"xper1@persbrant.net\"]},\n        {id: idOfLastUser, first: \"Xke2\", last: \"Persbrant2\", username: \"xper2\", email: [\"xper2@persbrant.net\"]},\n        {first: \"Xke2\", last: \"Persbrant2\", username: \"xper2\", email: [\"xper2@persbrant.net\"]}, // Should fail (add)\n        {id: idOfFirstUser, first: \"Xke2\", last: \"Persbrant2\", username: \"xper2\", email: [\"xper2@persbrant.net\"]}, // Should fail (update)\n        {first: \"Xke3\", last: \"Persbrant3\", username: \"xper3\", email: [\"xper3@persbrant.net\"]}\n    ];\n    try {\n        yield db.transaction(\"rw\", db.users, () => {\n            db.users.bulkPut(newUsersX).then(()=> {\n                ok(false, \"Should not resolve\");\n            });\n        });\n        ok(false, \"Should not come here\");\n    } catch (e) {\n        ok(true, \"Got: \" + e);\n    }\n\n    equal(yield db.users.where('username').startsWith('xper').count(), 0, \"0 users! Good, means that previous transaction did not commit\");\n\n    yield db.users.bulkPut(newUsersX).catch(e => {\n        ok(true, \"Got error. Catching it should make the successors work.\")\n    });\n\n    equal(yield db.users.where('username').startsWith('xper').count(), 3,\n        \"Should count to 3 users because previous operation was catched and therefore should have been committed\");\n\n    var newUsersY = [\n        {first: \"Yke1\", last: \"Persbrant1\", username: \"yper1\", email: [\"yper1@persbrant.net\"]},\n        {first: \"Yke2\", last: \"Persbrant2\", username: \"yper2\", email: [\"yper2@persbrant.net\"]},\n        {id: idOfFirstUser, first: \"Yke2\", last: \"Persbrant2\", username: \"yper2\", email: [\"yper2@persbrant.net\"]}, // Should fail\n        {first: \"Yke2\", last: \"Persbrant2\", username: \"yper2\", email: [\"yper2@persbrant.net\"]}, // Should fail\n        {first: \"Yke3\", last: \"Persbrant3\", username: \"yper3\", email: [\"yper3@persbrant.net\"]}\n    ];\n\n    // Now check that catching the operation via try..catch should also make it succeed.\n    try {\n        yield db.users.bulkPut(newUsersY);\n    } catch (e) {\n        ok(true, \"Got: \" + e);\n    }\n    equal(yield db.users.where('username').startsWith('yper').count(), 3,\n        \"Should count to 3 users because previous previous operation catched (via try..yield..catch this time, and therefore should have been committed\");\n\n    // Now check that catching and rethrowing should indeed make it fail\n    var newUsersZ = [\n        {first: \"Zke1\", last: \"Persbrant1\", username: \"zper1\", email: [\"zper1@persbrant.net\"]},\n        {first: \"Zke2\", last: \"Persbrant2\", username: \"zper2\", email: [\"zper2@persbrant.net\"]},\n        {first: \"Zke2\", last: \"Persbrant2\", username: \"zper2\", email: [\"zper2@persbrant.net\"]}, // Should fail\n        {id: idOfLastUser, first: \"Zke2\", last: \"Persbrant2\", username: \"zper2\", email: [\"zper2@persbrant.net\"]}, // Should fail\n        {first: \"Zke3\", last: \"Persbrant3\", username: \"zper3\", email: [\"zper3@persbrant.net\"]}\n    ];\n\n    yield db.transaction('rw', db.users, function*() {\n        try {\n            yield db.users.bulkPut(newUsersZ);\n        } catch (e) {\n            throw e;\n        }\n    }).catch(Dexie.BulkError, e => {\n        ok(true, \"Got rethrown BulkError: \" + e.stack);\n    });\n\n    equal(yield db.users.where('username').startsWith('zper').count(), 0, \"0 users! Good - means that previous operation rethrown (via try..yield..catch--throw this time, and therefore not committed\");\n});\n\nspawnedTest(\"bulkPut-non-inbound-autoincrement\", function*(){\n    yield db.folks.bulkPut([\n        { first: \"Foo\", last: \"Bar\"},\n        { first: \"Foo\", last: \"Bar2\"},\n        { first: \"Foo\", last: \"Bar3\"},\n        { first: \"Foo\", last: \"Bar4\"}\n    ]);\n    equal (yield db.folks.where('first').equals('Foo').count(), 4, \"Should be 4 Foos\");\n    equal (yield db.folks.where('last').equals('Bar').count(), 1, \"Should be 1 Bar\");\n});\n\nspawnedTest(\"bulkPut - mixed inbound autoIncrement\", function* () {\n    let lastId = yield db.users.bulkPut([\n        { first: \"Foo\", last: \"Bar\"},\n        { first: \"Foo\", last: \"Bar2\"},\n        { first: \"Foo\", last: \"Bar3\"},\n        { first: \"Foo\", last: \"Bar4\"}\n    ]);\n    equal (yield db.users.where('first').equals('Foo').count(), 4, \"Should be 4 Foos\");\n    equal (yield db.users.where('last').equals('Bar').count(), 1, \"Should be 1 Bar\");\n    let newLastId = yield db.users.bulkPut([\n        { id: lastId - 3, first: \"Foo2\", last: \"BarA\"}, // Will update \"Foo Bar\" to \"Foo2 BarA\"\n        { first: \"Foo2\", last: \"BarB\"}, // Will create\n        { id: lastId - 1, first: \"Foo2\", last: \"BarC\"}, // Will update \"Foo Bar3\" to \"Foo2 BarC\"\n        { first: \"Foo2\", last: \"BarD\"}  // Will create\n    ]);\n    equal (newLastId, lastId + 2, \"Should have incremented last ID twice now\");\n    equal (yield db.users.where('first').equals('Foo').count(), 2, \"Should be 2 Foos now\");\n    equal (yield db.users.where('first').equals('Foo2').count(), 4, \"Should be 4 Foo2s now\");\n    let foo2s = yield db.users.where('first').equals('Foo2').toArray();\n    equal (foo2s[0].last, \"BarA\", \"BarA should be first (updated previous ID)\");\n    equal (foo2s[1].last, \"BarC\", \"BarC should be second (updated previous ID\");\n    equal (foo2s[2].last, \"BarB\", \"BarB should be third (got new key)\");\n    equal (foo2s[3].last, \"BarD\", \"BarD should be forth (got new key)\");\n});\n\nspawnedTest(\"bulkPut-catch sub transaction\", function*(){\n    yield db.transaction('rw', db.users, ()=>{\n        var newUsers = [\n            { first: \"Åke1\", last: \"Persbrant1\", username: \"aper1\", email: [\"aper1@persbrant.net\"] },\n            { first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] },\n            { first: \"Åke2\", last: \"Persbrant2\", username: \"aper2\", email: [\"aper2@persbrant.net\"] }, // Should fail\n            { first: \"Åke3\", last: \"Persbrant3\", username: \"aper3\", email: [\"aper3@persbrant.net\"] }\n        ];\n        db.transaction('rw', db.users, ()=>{\n            db.users.bulkPut(newUsers);\n        }).then(()=>{\n            ok(false, \"Should not succeed with all these operations\");\n        }).catch(e => {\n            equal(e.failures.length, 1, \"Should get one failure\");\n        });\n    }).catch(e => {\n        ok(true, \"Outer transaction aborted due to inner transaction abort. This is ok: \" + e);\n    });\n\n    equal(yield db.users.where('username').startsWith('aper').count(), 0, \"0 users! Good, means that inner transaction did not commit\");\n});\n\nspawnedTest(\"bulkDelete\", function*(){\n    let userKeys = yield db.users.orderBy('id').keys();\n    ok(userKeys.length > 0, \"User keys found: \" + userKeys.join(','));\n    yield db.users.bulkDelete(userKeys);\n    let userCount = yield db.users.count();\n    equal (userCount, 0, \"Should be no users there now\");\n});\n\nspawnedTest(\"bulkDelete - nonexisting keys\", function*(){\n    let userKeys = [\"nonexisting1\", \"nonexisting2\", yield db.users.orderBy(':id').lastKey()];\n    yield db.users.bulkDelete(userKeys);\n    let userCount = yield db.users.count();\n    equal (userCount, 1, \"Should be one user there now. (the other should have been deleted)\");\n});\n\nspawnedTest(\"bulkDelete-faulty-key\", function*(){\n    let userKeys = [{faulty: \"ohyes\"}];\n    yield db.users.bulkDelete(userKeys).then (()=>{\n        ok (false, \"Should not succeed\");\n    }).catch('DataError', e => {\n        ok (true, \"Should get error: \" + e);\n    });\n});\n\nasyncTest(\"delete\", function () {\n    // Without transaction\n    db.users.get(idOfFirstUser, function (user) {\n        notEqual(user, null, \"User with id 1 exists\");\n    }).then(function () {\n        db.users.delete(1).then(function () {\n            db.users.get(1, function (user) {\n                equal(user, null, \"User not found anymore\");\n                start();\n            });\n        });\n    }).catch(function (e) {\n        ok(false, e);\n        start();\n    });\n});\nasyncTest(\"delete(using transaction)\", function() {\n    // With transaction\n    db.transaction(\"rw\", db.users, function () {\n        db.users.get(idOfFirstUser, function (user) {\n            notEqual(user, null, \"User with id 1 exists\");\n        });\n        db.users.delete(idOfFirstUser);\n        db.users.get(idOfFirstUser, function (user) {\n            equal(user, null, \"User not found anymore\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\nasyncTest(\"delete nonexisting item\", 3, function () {\n\tvar numUsers;\n\tdb.users.count().then(function(count) {\n\t\tnumUsers = count;\n\t\tok(true, \"Number of users before delete: \" + count);\n\t}).then(function() {\n\t\treturn db.users.delete(\"nonexisting key\");\n\t}).then(function(){\n\t\tok(true, \"Success even though nothing was deleted\");\n\t}).then(function(){\n\t\treturn db.users.count();\n\t}).then(function(count){\n\t\tequal(numUsers, count, \"Just verifying number of items in user table is still same\");\n\t}).catch(function (err) {\n\t\tok(false, \"Got error: \" + err);\n\t}).finally (start);\n});\nasyncTest(\"clear\", function () {\n    db.transaction(\"rw\", \"users\", function () {\n        db.users.count(function (count) {\n            equal(count, 2, \"There are 2 items in database before clearing it\");\n        });\n        db.users.clear();\n        db.users.count(function (count) {\n            equal(count, 0, \"There are 0 items in database after it has been cleared\");\n        });\n    }).catch(function (e) {\n        ok(false, e);\n    }).finally(start);\n});\n\nspawnedTest(\"failReadonly\", function*(){\n    yield db.transaction('r', 'users', function*() {\n        yield db.users.bulkAdd([{first: \"Foo\", last: \"Bar\"}]);\n    }).then(()=>{\n        ok(false, \"Should not happen\");\n    }).catch ('ReadOnlyError', e => {\n        ok(true, \"Got ReadOnlyError: \" + e.stack);\n    });\n});\n\nspawnedTest(\"failNotIncludedStore\", function*(){\n    yield db.transaction('rw', 'folks', function*() {\n        yield db.users.bulkAdd([{first: \"Foo\", last: \"Bar\"}]);\n    }).then(()=>{\n        ok(false, \"Should not happen\");\n    }).catch ('NotFoundError', e => {\n        ok(true, \"Got NotFoundError: \" + e.stack);\n    });\n});\n\nasyncTest(\"failNotIncludedStoreTrans\", () => {\n    db.transaction('rw', 'foodassaddas', ()=>{\n    }).then(()=>{\n        ok(false, \"Should not happen\");\n    }).catch ('NotFoundError', e => {\n        ok(true, \"Got NotFoundError: \" + e.stack);\n    }).catch (e => {\n        ok(false, \"Oops: \" + e.stack);\n    }).then(start);\n});\n\n// Must use this rather than QUnit's deepEqual() because that one fails on Safari when run via karma-browserstack-launcher\nexport function deepEqual(actual, expected, description) {\n    equal(JSON.stringify(actual, null, 2), JSON.stringify(expected, null, 2), description);\n}\n\nfunction stripObj(obj, props) {\n    const rv = {};\n    for (const key of props.slice().sort()) {\n        rv[key] = obj[key];\n    }\n    return rv;\n}\n\nfunction sortObj(obj) {\n    return stripObj(obj, Object.keys(obj));\n}\n\nexport function deepEqualPartial(actual, expected, description) {\n    if (Array.isArray(actual)) {\n        return deepEqual(\n            actual.map((a, idx) => stripObj(a, Object.keys(expected[idx]))),\n            expected.map(sortObj),\n            description);\n    }\n    return deepEqual(stripObj(actual, Object.keys(expected)), sortObj(expected), description);\n}\n  \npromisedTest(\"bulkGet()\", async () => {\n    const bulkData = [];\n    for (let i=0; i<400; ++i) {\n        bulkData.push({id: i, first: \"Foo\"+i, last: \"Bar\" + i});\n    }\n    ok(`Putting ${bulkData.length} users into the table`);\n    await db.users.bulkPut(bulkData);\n    ok(`Done putting users. Now getting them using bulkGet()`);\n    const keys = bulkData.map(({id}) => id);\n    const retrieved = await db.users.bulkGet(keys);\n    deepEqual(retrieved, bulkData, \"Put and retrieved should be the same\");\n\n    ok(\"Now validating that is should be possible to request nonexisting keys but yet get all results in the order of the given keys\");\n    const [u1, u2, u3, u4] = await db.users.bulkGet([\"x\", \"y\", 100, \"z\"]);\n    ok(u1 === undefined, \"First result should be undefined, as there where no object with that key\");\n    ok(u2 === undefined, \"Second objects -''-\");\n    ok(u3 && u3.first === 'Foo100', \"Third should be Foo100\");\n    ok(u4 === undefined, \"Forth should be undefined\");\n});\n\npromisedTest(\"bulkError by pos\", async () => {\n  try {\n    const ids = await db.users.bulkAdd([\n      { first: \"foo1\", last: \"bar1\", username: \"foobar\" },\n      { first: \"foo2\", last: \"bar2\", username: \"foobar\" }, // should fail because username is unique idx\n      { first: \"foo3\", last: \"bar3\", username: \"foobar3\" },\n    ]);\n    ok(false, \"Should not succeed\");\n  } catch (bulkError) {\n    ok(bulkError instanceof Dexie.BulkError, \"Got BulkError\");\n    equal(bulkError.failures.length, 1, \"Got one failure\");\n    ok(!!bulkError.failures[0], \"failures[0] is one Error\");\n    ok(bulkError.failures[1] === undefined, \"failures[1] is undefined\");\n    equal(Object.keys(bulkError.failuresByPos).length, 1, \"Got one key in failuresByPos\");\n    equal(Object.keys(bulkError.failuresByPos)[0], 1, \"Failure in position 1\");\n    ok(bulkError.failuresByPos[0] === undefined, \"failuresByPos[0] is undefined\");\n    ok(!!bulkError.failuresByPos[1], \"failuresByPos[1] is one Error\");\n  }\n});\n"
  },
  {
    "path": "test/tests-transaction.js",
    "content": "import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {resetDatabase, spawnedTest, promisedTest} from './dexie-unittest-utils';\n\n\"use strict\";\n\nvar db = new Dexie(\"TestDBTrans\");\ndb.version(1).stores({\n    users: \"username\",\n    pets: \"++id,kind\",\n    petsPerUser: \"++,user,pet\"\n});\n\nmodule(\"transaction\", {\n    setup: function () {\n        stop();\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: function () {\n    }\n});\n\nvar NativePromise = window.Promise;\n\nasyncTest(\"Transaction should work when returning native Promise in transaction scope\", function() {\n    if (!NativePromise) {\n        ok(true, \"Current Browser doesn't have a native Promise\");\n        return start();\n    }\n    db.transaction('rw', db.users, trans => {\n        ok(Dexie.currentTransaction === trans, \"First argument to transaction callback should be the transaction instance itself\");\n        return NativePromise.resolve().then(()=> {\n            ok(Dexie.currentTransaction === trans, \"Dexie.currentTransaction should persted through the native promise!\");\n        }).then(()=>{\n            return db.users.add({ username: \"barfoo\" }); // Will only work on Chrome, Opera and Edge as of Oktober 6, 2016.\n        }).then(()=>{\n            ok(Dexie.currentTransaction === trans, \"Dexie.currentTransaction should persted through the native promise!\");\n            return db.users.count();\n        })\n    }).then (count => {\n        ok(true, `User count: ${count}. REJOICE! YOUR BROWSER'S INDEXEDDB PLAYS BALL WITH PROMISES!`);\n    }).catch ('TransactionInactiveError', e => {\n        ok(true, \"Your browser has native incompatibility between native Promise and IndexedDB. This is why we still avoid returning native promises.\");\n    }).catch (e => {\n        ok(false, `Failed: ${e.stack || e}`);\n    }).finally(start);\n});\n\nasyncTest(\"empty transaction block\", function () {\n    db.transaction('rw', db.users, db.pets, function () {\n        ok(true, \"Entering transaction block but dont start any transaction\");\n        // Leave it empty. \n    }).catch(function (err) {\n        ok(false, err);\n    }).finally(function () {\n        setTimeout(start, 10);\n    });\n});\n\nasyncTest(\"db.transaction()\", function () {\n    db.transaction('rw', db.users, function () {\n        db.users.add({ username: \"arne\" });\n        return db.users.get(\"arne\", function (user) {\n            equal(user.username, \"arne\", \"Got user arne the line after adding it - we must be in a transaction\");\n            ok(Dexie.currentTransaction != null, \"Current Transaction must be set\");\n        });\n    }).then(function () {\n        ok(Dexie.currentTransaction == null, \"Current Transaction must be null even when transaction scope returned a Promise that was bound to the transaction\");\n    }).finally(start);\n});\n\nasyncTest(\"Table not in transaction\", function () {\n    db.pets.add({kind: \"dog\"}).then(function() {\n        return db.transaction('rw', db.users, function () {\n            db.users.add({ username: \"arne\" });\n            return db.pets.get(1, function (pet) {\n                ok(false, \"Should not be able to get a pet because pets is not in transaction\");\n            });\n        }).then(function () {\n            ok(false, \"Transaction should not commit because I made an error\");\n        }).catch(function (err) {\n            ok(true, \"Got error since we tried using a table not in transaction: \" + err.stack);\n        });\n    }).finally(start);\n});\n\nasyncTest(\"Table not in transaction 2\", function () {\n    return db.transaction('rw', db.users, function () {\n    db.pets.add({kind: \"dog\"});\n    }).then(function () {\n    ok(false, \"Transaction should not commit because I made an error\");\n    }).catch(function (err) {\n    ok(true, \"Got error since we tried using a table not in transaction: \" + err.stack);\n    }).finally(start);\n});\n\nasyncTest(\"Write into readonly transaction\", function () {\n    return db.transaction('r', db.users, function () {\n        db.users.add({ username: \"arne\" }).then(function(){\n            ok(false, \"Should not be able to get a here because we tried to write to users when in a readonly transaction\");\n        });\n    }).then(function () {\n        ok(false, \"Transaction should not commit because I made an error\");\n    }).catch(function (err) {\n        ok(true, \"Got error since we tried to write to users when in a readonly transaction: \" + err.stack);\n    }).finally(start);\n});\n\nasyncTest(\"Inactive transaction\", function () {\n    return db.transaction('rw', db.users, function () {\n        return new Dexie.Promise(function (resolve, reject) {\n\n            // Notify log when transaction completes too early\n            Dexie.currentTransaction.complete(function () {\n                ok(true, \"Transaction committing too early...\");\n                // Resolve the promise after transaction commit.\n                // Flow will continue in the same Transaction scope but with an\n                // inactive transaction\n                resolve();\n            });\n\n        }).then(function () {\n            // Now when transaction has already committed, try to add a user with the current transaction:\n            return db.users.add({ username: \"arne\" });\n        }).then(function () {\n            ok(false, \"Should not be able to get a here transaction has become inactive\");\n        });\n    }).then(function () {\n        ok(false, \"Should not be able to get a here transaction has become inactive\");\n    }).catch(function (err) {\n        ok(true, \"Got error because the transaction has already committed: \" + err.stack);\n    }).finally(start);\n});\n\nasyncTest(\"Inactive transaction 2\", function () {\n    return db.transaction('rw', db.users, function () {\n        // First make an operation so that transaction is internally created (this is the thing differing from the previous test case\n        return db.users.add({ username: \"arne\" }).then(function () {\n\n            // Create a custom promise that will use setTimeout() so that IDB transaction will commit\n            return new Dexie.Promise(function (resolve, reject) {\n                // Notify log when transaction completes too early\n                Dexie.currentTransaction.complete(function() {\n                    ok(true, \"Transaction committing too early...\");\n                    resolve();\n                });\n            });\n        }).then(function () {\n            // Now when transaction has already committed, try to add a user with the current transaction:\n            return db.users.add({ username: \"arne\" });\n        }).then(function () {\n            ok(false, \"Should not be able to get a here transaction has become inactive\");\n        });\n    }).then(function () {\n        ok(false, \"Should not be able to get a here transaction has become inactive\");\n    }).catch(function (err) {\n        ok(true, \"Got error because the transaction has already committed: \" + err.stack);\n    }).finally(start);\n});\n\nasyncTest(\"sub-transactions\", function () {\n    var parentTrans;\n\n    function addUser(user, pets) {\n        return db.transaction('rw', db.users, db.pets, db.petsPerUser, function () {\n            ok(parentTrans._reculock > 0, \"Parent transaction is locked\");\n            db.users.add(user);\n            pets.forEach(function (pet) {\n                db.pets.add(pet).then(function (petId) {\n                    return db.petsPerUser.add({ user: user.username, pet: petId });\n                });\n            });\n        }).then(function () {\n            return db.transaction('rw', db.users, function () {\n                db.users.add({ username: user.username + \"2\" });\n                return \"hello...\";\n            });\n        });\n    }\n        \n    db.transaction('rw', db.users, db.pets, db.petsPerUser, function () {\n        var trans = Dexie.currentTransaction;\n        parentTrans = Dexie.currentTransaction;\n        ok(trans._reculock === 0, \"Main transaction not locked yet\");\n        addUser({ username: \"user1\" }, [{ kind: \"dog\" }, { kind: \"cat\" }]).then(function () {\n            db.users.get(\"someoneelse\", function (someone) {\n                equal(someone.username, \"someoneelse\", \"Someonelse was recently added\");\n            });\n        });\n        ok(trans._reculock > 0, \"Main transaction is now locked\");\n        db.users.get(\"someoneelse\", function (someone) {\n            ok(!someone, \"Someoneelse not yet added\");\n        });\n        db.users.add({ username: \"someoneelse\" });\n        return addUser({ username: \"user2\" }, [{ kind: \"giraff\" }]).then(function (val) {\n            ok(trans._reculock == 0, \"Main transaction not locked anymore\");\n            return val;\n        });\n    }).then(function (retval) {\n        equal(retval, \"hello...\", \"Return value went all the way down to transaction resolvance\");\n        ok(Dexie.currentTransaction == null, \"Dexie.currentTransaction is null\");\n        db.users.count(function (count) { // Transaction-less operation!\n            equal(count, 5, \"There are five users in db\");\n        });\n        db.pets.count(function (count) {// Transaction-less operation!\n            equal(count, 3, \"There are three pets in db\");\n        });\n        db.petsPerUser.count(function (count) {// Transaction-less operation!\n            equal(count, 3, \"There are three pets-to-user relations\");\n        });\n    }).then(function () {\n        ok(Dexie.currentTransaction == null, \"Dexie.currentTransaction is null\");\n        // Start an outer transaction\n        return db.transaction('rw', db.users, function () {\n            // Do an add operation\n            db.users.add({ username: \"sune\" });//.then(function () {\n            // Start an inner transaction\n            db.transaction('rw', db.users, function () {\n                // Do an add-operation that will result in ConstraintError:\n                db.users.add({ username: \"sune\" });\n            }).then(function () {\n                ok(false, \"Transaction shouldn't have committed\");\n            }).catch(\"ConstraintError\", function (err) {\n                ok(true, \"Got ContraintError when trying to add multiple users with same username\");\n            }).catch(function (err) {\n                ok(false, \"Got unknown error: \" + err);\n            });\n            //});\n        }).catch(\"ConstraintError\", function (err) {\n            // Yes, it should fail beause of limited rollback support on nested transactions:\n            // https://github.com/dfahlander/Dexie.js/wiki/Dexie.transaction()#limitations-with-nested-transactions\n            ok(true, \"Got constraint error on outer transaction as well\");\n        });\n    }).catch(function (err) {\n        ok(false, \"Error: \" + err);\n    }).finally(start);\n});\n\nasyncTest(\"Three-level sub transactions\", function () {\n    db.transaction('rw', db.users, db.pets, db.petsPerUser, function () {\n        db.users.add({ username: \"ojsan\" });\n        db.transaction('rw', db.users, db.pets, function () {\n            db.users.add({ username: \"ojsan2\" });\n            db.users.toCollection().delete();\n            db.transaction('r', db.users, function () {\n                db.users.toArray(function (usersArray) {\n                    equal(usersArray.length, 0, \"All users should be deleted\");\n                    Dexie.currentTransaction.abort();\n                });\n            });\n        });\n    }).then(function () {\n        ok(false, \"Shouldnt work\");\n    }).catch(function (err) {\n        ok(true, \"Got error: \" + err);\n    }).finally(start);\n});\n\n\nasyncTest(\"Table not in main transactions\", function () {\n    Dexie.Promise.resolve().then(()=>{\n        return db.transaction('rw', db.users, function () {\n            db.users.add({username: \"bertil\"});\n            db.transaction('rw', db.users, db.pets, function () {\n                db.pets.add({kind: \"cat\"});\n            });\n        });\n    }).then(function () {\n        ok(false, \"Shouldnt work\");\n    }).catch(function (err) {\n        ok(true, \"Got error: \" + err);\n    }).finally(start);\n});\n\nasyncTest(\"Transaction is not in read-mode\", function () {\n    db.transaction('r', db.users, db.pets, function () {\n        db.users.toArray();\n        db.transaction('rw', db.users, db.pets, function () {\n            db.pets.add({ kind: \"cat\" });\n        });\n    }).then(function () {\n        ok(false, \"Shouldnt work\");\n    }).catch(function (err) {\n        ok(true, \"Got error: \" + err);\n    }).finally(start);\n});\n    \n//\n// Testing the \"!\" mode\n//\n\nasyncTest(\"'!' mode: Table not in main transactions\", function () {\n    var counter = 0;\n    db.transaction('rw', db.users, function () {\n        db.users.add({ username: \"bertil\" });\n        db.transaction('rw!', db.users, db.pets, function () {\n            db.pets.add({ kind: \"cat\" });\n        }).then(function () {\n            ok(true, \"Inner transaction complete\");\n        }).catch(function (err) {\n            ok(false, \"Got error in inner transaction: \" + err);\n        }).finally(function () {\n            if (++counter == 2) start();\n        });\n        Dexie.currentTransaction.abort(); // Aborting outer transaction should not abort inner.\n\n    }).then(function () {\n        ok(false, \"Outer transaction should not complete\");\n    }).catch(function (err) {\n        ok(true, \"Got Abort Error: \" + err);\n    }).finally(function () {\n        if (++counter == 2) start();\n    });\n});\n\nasyncTest(\"'!' mode: Transaction is not in read-mode\", function () {\n    var counter = 0;\n    db.transaction('r', db.users, db.pets, function () {\n        db.users.toArray();\n        db.transaction('rw!', db.users, db.pets, function () {\n            db.pets.add({ kind: \"cat\" });\n        }).then(function () {\n            ok(true, \"Inner transaction complete\");\n        }).catch(function (err) {\n            ok(false, \"Got error: \" + err);\n        }).finally(function () {\n            if (++counter == 2) start();\n        });\n    }).then(function () {\n        ok(true, \"Outer transaction complete\");\n    }).catch(function (err) {\n        ok(false, \"Got error: \" + err);\n    }).finally(function () {\n        if (++counter == 2) start();\n    });\n});\n\nasyncTest(\"'!' mode: Transaction bound to different db instance\", function () {\n    var counter = 0;\n    var db2 = new Dexie(\"TestDB2\");\n    db2.version(1).stores({\n        users: \"username\",\n        pets: \"++id,kind\",\n        petsPerUser: \"++,user,pet\"\n    });\n    \n    db2.delete()\n    .then(()=>db2.open())\n    .then(()=>db.transaction('rw', \"users\", \"pets\", function () {\n        db2.transaction('rw!', \"users\", \"pets\", function () {\n            ok(true, \"Possible to enter a transaction in db2\");\n        }).catch(function (err) {\n            ok(false, \"Got error: \" + err);\n        }).finally(function () {\n            if (++counter == 2) db2.delete().then(start);\n            console.log(\"finally() in db2.transaction(). counter == \" + counter);\n        });\n    })).finally(function () {\n        if (++counter == 2) db2.delete().then(start);\n        console.log(\"finally() in db.transaction(). counter == \" + counter);\n    });\n});\n\n//\n// Testing the \"?\" mode\n//\n\nasyncTest(\"'?' mode: Table not in main transactions\", function () {\n    var counter = 0;\n    db.transaction('rw', db.users, function () {\n        db.users.add({ username: \"bertil\" });\n        db.transaction('rw?', db.users, db.pets, function () {\n            db.pets.add({ kind: \"cat\" });\n        }).then(function () {\n            ok(true, \"Inner transaction complete\");\n        }).catch(function (err) {\n            ok(false, \"Got error in inner transaction: \" + err);\n        }).finally(function () {\n            if (++counter == 2) start();\n        });\n        Dexie.currentTransaction.abort(); // Aborting outer transaction should not abort inner.\n\n    }).then(function () {\n        ok(false, \"Outer transaction should not complete\");\n    }).catch(function (err) {\n        ok(true, \"Got Abort Error: \" + err);\n    }).finally(function () {\n        if (++counter == 2) start();\n    });\n});\n\nasyncTest(\"'?' mode: Transaction is not in read-mode\", function () {\n    var counter = 0;\n    db.transaction('r', db.users, db.pets, function () {\n        db.users.toArray();\n        db.transaction('rw?', db.users, db.pets, function () {\n            db.pets.add({ kind: \"cat\" });\n        }).then(function () {\n            ok(true, \"Inner transaction complete\");\n        }).catch(function (err) {\n            ok(false, \"Got error: \" + err);\n        }).finally(function () {\n            if (++counter == 2) start();\n        });\n    }).then(function () {\n        ok(true, \"Outer transaction complete\");\n    }).catch(function (err) {\n        ok(false, \"Got error: \" + err);\n    }).finally(function () {\n        if (++counter == 2) start();\n    });\n});\n\nasyncTest(\"'?' mode: Transaction bound to different db instance\", function () {\n    var counter = 0;\n    var db2 = new Dexie(\"TestDB2\");\n    db2.version(1).stores({\n        users: \"username\",\n        pets: \"++id,kind\",\n        petsPerUser: \"++,user,pet\"\n    });\n    db2.open();\n    db.transaction('rw', \"users\", \"pets\", function () {\n        db2.transaction('rw?', \"users\", \"pets\", function () {\n            ok(true, \"Possible to enter a transaction in db2\");\n        }).catch(function (err) {\n            ok(false, \"Got error: \" + err);\n        }).finally(function () {\n            if (++counter == 2) db2.delete().then(start);\n        });\n    }).finally(function () {\n        if (++counter == 2) db2.delete().then(start);\n    });\n});\n\nasyncTest(\"'?' mode: Three-level sub transactions\", function () {\n    db.transaction('rw', db.users, db.pets, db.petsPerUser, function () {\n        db.users.add({ username: \"ojsan\" });\n        db.transaction('rw?', db.users, db.pets, function () {\n            db.users.add({ username: \"ojsan2\" });\n            db.users.toCollection().delete();\n            db.transaction('r?', db.users, function () {\n                db.users.toArray(function (usersArray) {\n                    equal(usersArray.length, 0, \"All users should be deleted\");\n                    Dexie.currentTransaction.abort();\n                });\n            });\n        });\n    }).then(function () {\n        ok(false, \"Shouldnt work\");\n    }).catch(function (err) {\n        ok(true, \"Got error: \" + err);\n    }).finally(start);\n});\n\nasyncTest(\"Transactions in multiple databases\", function () {\n\tvar logDb = new Dexie(\"logger\");\n\tlogDb.version(1).stores({\n\t\tlog: \"++,time,type,message\"\n\t});\n\tvar lastLogAddPromise;\n\tlogDb.open().then(()=>{\n\t    return db.transaction('rw', db.pets, function () {\n            // Test that a non-transactional add in the other DB can coexist with\n            // the current transaction on db:\n            logDb.log.add({time: new Date(), type: \"info\", message: \"Now adding a dog\"});\n            db.pets.add({kind: \"dog\"}).then(function(petId){\n                // Test that a transactional add in the other DB can coexist with\n                // the current transaction on db:\n                lastLogAddPromise = logDb.transaction('rw!', logDb.log, function (){\n                    logDb.log.add({time: new Date(), type: \"info\", message: \"Added dog got key \" + petId});\n                });\n            });\n        });\n\t}).then(function() {\n\t\treturn lastLogAddPromise; // Need to wait for the transaction of the other database to complete as well.\n\t}).then(function(){\n\t\treturn logDb.log.toArray();\n\t}).then(function (logItems) {\n\t\tequal(logItems.length, 2, \"Log has two items\");\n\t\tequal(logItems[0].message, \"Now adding a dog\", \"First message in log is: \" + logItems[0].message);\n\t\tok(logItems[1].message.indexOf(\"Added dog got key \") === 0, \"Second message in log is: \" + logItems[1].message);\n\t}).catch(function (err) {\n\t\tok(false, err);\n\t}).finally(function(){\n\t\treturn logDb.delete();\n\t}).finally(start);\n});\n\nasyncTest(\"Issue #71 If returning a Promise from from a sub transaction, parent transaction will abort\", function () {\n    db.transaction('rw', db.users, db.pets, function () {\n        ok(true, \"Entered parent transaction\");\n        ok(true, \"Now adding Gunnar in parent transaction\");\n        db.users.add({ username: \"Gunnar\" }).then(function() {\n            ok(true, \"First add on parent transaction finished. Now adding another object in parent transaction.\");\n            db.pets.add({ kind: \"cat\", name: \"Garfield\" }).then(function() {\n                ok(true, \"Successfully added second object in parent transaction.\");\n            }).catch(function(err) {\n                ok(false, \"Failed to add second object in parent transaction: \" + err.stack || err);\n            });\n        });\n\n        db.transaction('rw', db.users, function() {\n            ok(true, \"Entered sub transaction\");\n            return db.users.add({ username: \"JustAnnoyingMyParentTransaction\" }).then(function() {\n                ok(true, \"Add on sub transaction succeeded\");\n            }).catch(function(err) {\n                ok(false, \"Failed to add object in sub transaction: \" + err.stack || err);\n            });\n        });\n    }).finally(start);\n});\n\nasyncTest(\"Issue #91 Promise.resolve() from within parent transaction\", function () {\n\tdb.transaction('rw', db.users, db.pets, function () {\n\t    ok(true, \"Entered parent transaction\");\n\t    var trans = Dexie.currentTransaction;\n\n\t    return db.transaction('rw', db.users, function() {\n\t        ok(true, \"Entered sub transaction\");\n\t        ok(Dexie.currentTransaction !== trans, \"We are not in parent transaction\");\n\t        ok(Dexie.currentTransaction.parent === trans, \"...but in a sub transaction\");\n\t        return Dexie.Promise.resolve(3);\n\t    }).then(function (result) {\n\t        equal(result, 3, \"Got 3\");\n\t        ok(Dexie.currentTransaction === trans, \"Now we are in parent transaction\");\n\t        db.users.add({ username: \"Gunnar\" });\n\t        return db.users.where(\"username\").equals(\"Gunnar\").first();\n\t    }).then(function(result) {\n\t        ok(!!result, \"Got result\");\n\t        equal(result.username, \"Gunnar\", \"Got the Gunnar we expected\");\n\t        return Dexie.Promise.resolve(result);\n\t    }).catch(function(e) {\n\t        ok(false, \"Error: \" + e.stack);\n\t    });\n\t}).then(function(result) {\n\t    ok(!!result, \"Got result\");\n\t    equal(result.username, \"Gunnar\", \"Got the Gunnar we expected\");\n\t}).catch(function(e) {\n\t    ok(false, \"Error at root scope: \" + e.stack);\n\t}).finally(start);\n});\n\nasyncTest(\"Issue #95 Nested transactions fails if parent transaction don't execute any operation\", function () {\n    function smallChild() {\n        return db.transaction('rw', db.users, db.pets, function () {\n            console.log(\"Entering small child\");\n            return db.users.add({ // Here: Test succeeded if removing the 'return' statement here!\n                username: 123,\n                value: 'val'\n            }).then(function (res) {\n                ok(true, \"smallChild() could add user with primary key \" + res);\n                return res;\n            }).catch(function (err) {\n                ok(false, 'SCCA' + err);\n            });\n        }).then(function(res) {\n            ok(true, \"smallChild's 3rd level nested transaction commited with result \" + res);\n        }).catch (function (err) {\n            ok(false, 'SCTR' + err);\n        });\n    }\n\n    function middleChild() {\n        return db.transaction('rw', db.users, db.pets, function () {\n            console.log(\"Entering middle child\");\n            return db.pets.add({\n                id: 321,\n                value: 'anotherval'\n            }).catch (function (err) {\n                ok(false, 'MCCA' + err);\n            });\n        }).catch (function (err) {\n            ok(false, 'MCTR' + err);\n        });\n    }\n\n    function bigParent() {\n        // Nesting transaction without starting the real indexedDB transaction cause an error?\n        return db.transaction('rw', db.users, db.pets, function () { // Here: Test succeeded if skipping the outermost transaction scope.\n            console.log(\"Entering root transaction\");\n            return db.transaction('rw', db.users, db.pets, function () {\n                console.log(\"Entering first sub transaction\");\n                return smallChild().then(function () {\n                    return middleChild();\n                }).catch (function (err) {\n                    ok(false, 'BPCA ' + err);\n                });\n            }).catch (function (err) {\n                ok(false, 'BPTRI ' + err);\n            });\n        }).catch (function (err) {\n            ok(false, 'BPTRX ' + err);\n        });\n    }\n\n    bigParent().then(function(res) {\n        ok(true, \"done\");\n    }).catch(function(e) {\n        ok(false, \"Final error: \" + e);\n    }).finally(start);\n});\n\nasyncTest(\"Issue #91 / #95 with Dexie.Promise.resolve() mixed in here and there...\", function () {\n\tok(!Dexie.currentTransaction, \"There is no ongoing transaction\");\n    db.transaction('rw', db.pets, function () {\n        var rootLevelTransaction = Dexie.currentTransaction;\n        ok(true, \"Entered root transaction scope\");\n        return db.transaction('rw', db.pets, function() {\n            ok(true, \"Entered sub scope\");\n            var level2Transaction = Dexie.currentTransaction;\n            ok(level2Transaction.parent === rootLevelTransaction, \"Level2 transaction's parent is the root level transaction\");\n            return db.transaction('rw', db.pets, function() {\n                ok(true, \"Entered sub of sub scope\");\n                var innermostTransaction = Dexie.currentTransaction;\n                ok(!!innermostTransaction, \"There is an ongoing transaction (direct in 3rd level scope)\");\n                ok(innermostTransaction.parent === level2Transaction, \"Parent is level2 transaction\");\n                return Dexie.Promise.resolve().then(function() {\n                    ok(true, \"Sub of sub scope: Promise.resolve().then() called\");\n                    ok(!!Dexie.currentTransaction, \"There is an ongoing transaction\");\n                    ok(Dexie.currentTransaction === innermostTransaction, \"Still in innermost transaction\");\n                    return db.pets.add({\n                        id: 123,\n                        value: 'val'\n                    }).then(function(resultId) {\n                        ok(true, \"Sub of sub scope: add() resolved\");\n                        ok(Dexie.currentTransaction === innermostTransaction, \"Still in innermost transaction\");\n                        return Dexie.Promise.resolve(resultId).then(function(res) {\n                            return Dexie.Promise.resolve(res);\n                        });\n                    }).then(function(resultId) {\n                        ok(true, \"Sub if sub scope: Promise.resolve() after add() resolve\");\n                        ok(Dexie.currentTransaction === innermostTransaction, \"Still in innermost transaction\");\n                        return Dexie.Promise.resolve(resultId);\n                    });\n                }).then(function() {\n                    ok(true, \"sub of sub scope chaining further in promise chains...\");\n                    ok(Dexie.currentTransaction === innermostTransaction, \"Still in innermost transaction\");\n                    return Dexie.Promise.resolve(db.pets.get(123));\n                }).then(function(pet) {\n                    ok(true, \"sub of sub scope chaining further in promise chains 2...\");\n                    ok(Dexie.currentTransaction === innermostTransaction, \"Still in innermost transaction\");\n                    return Dexie.Promise.resolve(pet.id);\n                });\n            }).then(function(resultId) {\n                ok(true, \"Innermost transaction completed\");\n                ok(Dexie.currentTransaction == level2Transaction, \"We should now be executing within level 2 sub transaction\");\n                return Dexie.Promise.resolve(resultId);\n            }).then(function(resultId) {\n                ok(Dexie.currentTransaction == level2Transaction, \"We should still be executing within level 2 sub transaction\");\n                return Dexie.Promise.resolve(resultId);\n            }).then(function(resultId) {\n                equal(resultId, 123, \"Result was 123 as expected\");\n            }).then(function() {\n                return db.transaction('rw', db.pets, function() {\n                    var innermostTransaction2 = Dexie.currentTransaction;\n                    ok(innermostTransaction2.parent == level2Transaction, \"Another 3rd level transaction has parent set to our level2 transaction\");\n                    return db.pets.add({\n                        id: 321,\n                        value: 'val'\n                    }).then(function(resultId2) {\n                        return Dexie.Promise.resolve(resultId2);\n                    }).then(function(resultId2) {\n                        ok(Dexie.currentTransaction === innermostTransaction2, \"We're still in the innermostTransaction (second one)\");\n                        return Dexie.Promise.resolve(resultId2).then(function(x) {\n                            ok(Dexie.currentTransaction === innermostTransaction2, \"We're still in the innermostTransaction (second one)\");\n                            return x;\n                        });\n                    });\n                }).then(function(resultId2) {\n                    equal(resultId2, 321, \"Result2 was 321 as expected\");\n                    ok(Dexie.currentTransaction === level2Transaction, \"We should still be executing within level 2 sub transaction\");\n                    return \"finalResult\";\n                });\n            });\n        }).then(function(x) {\n\n            ok(Dexie.currentTransaction === rootLevelTransaction, \"Now we're at the root level transaction and can do some more stuff here\");\n\n            return db.pets.clear().then(function() {\n                return x;\n            }).then(function(y) {\n                ok(true, \"Could clear the pets table for example.\");\n                return y;\n            }).catch(function(e) {\n                ok(false, \"oops, this was not what I expected!: \" + e);\n            });\n        });\n\n    }).then(function(finalResult) {\n        equal(finalResult, \"finalResult\", \"Got the final result\");\n        ok(!Dexie.currentTransaction, \"No ongoing transaction now\");\n        ok(true, \"done\");\n    }).catch(function(error) {\n        ok(false, error.stack);\n    }).finally(start);\n    ok(!Dexie.currentTransaction, \"After main transaction scope: Still no ongoing transaction at this scope\");\n});\n\nasyncTest(\"Issue #137 db.table() does not respect current transaction\", function() {\n    db.transaction('rw', db.users, function() {\n        db.users.add({ username: \"erictheviking\", color: \"blue\" }).then(function() {\n            db.table('users').get('erictheviking', function (eric) {\n                ok(eric, \"Got back an object\");\n                equal(eric.color, \"blue\", \"eric.color is still blue. If red, the getter must have been run from another transaction.\");\n            });\n            db.users.put({ username: \"erictheviking\", color: \"red\" });\n        });\n    }).catch(function(e) {\n        ok(false, \"Error: \" + e);\n    }).finally(start);\n});\n\nasyncTest(\"Dexie.currentTransaction in CRUD hooks\", 83 /* If fails on num assertions, it's ok to change to expected if all looks fine */,\nfunction () {\n\n    function CurrentTransChecker(scope, trans) {\n        return function() {\n            ok(Dexie.currentTransaction === trans, \"Dexie.currentTransaction correct in \" + scope);\n        }\n    }\n\n    function onCreating(primKey, obj, transaction) {\n        ok(!!Dexie.currentTransaction, \"Dexie.currentTransaction should exist in creating\");\n        ok(Dexie.currentTransaction === transaction,\n            \"Dexie.currentTransaction correct in creating\");\n        this.onerror = CurrentTransChecker(\"creating.onerror\", transaction);\n        this.onsuccess = CurrentTransChecker(\"creating.onsuccess\", transaction);\n    }\n\n    function onReading(obj) {\n        ok(!!Dexie.currentTransaction, \"Dexie.currentTransaction should exist in reading\");\n        return obj;\n    }\n\n    function onUpdating(modifications, primKey, obj, transaction) {\n        ok(Dexie.currentTransaction === transaction,\n            \"Dexie.currentTransaction correct in updating\");\n        this.onerror = CurrentTransChecker(\"updating.onerror\", transaction);\n        this.onsuccess = CurrentTransChecker(\"updating.onsuccess\", transaction);\n    }\n\n    function onDeleting(primKey, obj, transaction) {\n        ok(Dexie.currentTransaction === transaction,\n            \"Dexie.currentTransaction correct in deleting\");\n        this.onsuccess = CurrentTransChecker(\"deleting.onsuccess\", transaction);\n    }\n\n    db.users.hook.creating.subscribe(onCreating);\n    db.users.hook.reading.subscribe(onReading);\n    db.users.hook.updating.subscribe(onUpdating);\n    db.users.hook.deleting.subscribe(onDeleting);\n\n    async function doTheTests() {\n        await db.users.add({ username: \"monkey1\" });\n        await db.users.add({ username: \"monkey1\" }).catch(function(ex) {\n            ok(true, \"Should fail adding a second monkey1\");\n        }); // Trigger creating.onerror\n        // Test bulkAdd as well:\n        ok(true, \"Testing bulkAdd\");\n        await db.users.bulkAdd([{ username: \"monkey1\" }, { username: \"monkey2\" }])\n            .then(()=>ok(false, \"Should get error on one of the adds\"))\n            .catch(Dexie.BulkError, e=>{\n                ok(true, \"Got BulkError\");\n                ok(e.failures.length === 1, \"One error out of two: \" + e);\n        });\n        await db.users.where(\"username\").equals(\"monkey1\").modify({\n            name: \"Monkey 1\"\n        });\n        await db.users.where(\"username\").equals(\"monkey1\").modify(user => {\n            user.username = \"monkey2\";// trigger updating.onerror\n        }).catch(function(ex) {\n            ok(true, \"Should fail modifying primary key to an already existing primary key\");\n        });\n        ok(true, \"Will now be modifying primary key of monkey1...\");\n        await db.users.where(\"username\").equals(\"monkey1\").modify(user => {\n            user.username = \"monkey88\";// trigger updating.onerror\n        }).then(res => {\n            ok(true, \"Should succeed modifying primary key to non-existing primary key, resulting in deletion and creation: \" + res);\n        }).catch(function(ex) {\n            ok(false, \"Should succeed modifying primary key to non-existing primary key, resulting in deletion and creation: \" + ex);\n        });\n        ok(true, \"Will now modify monkey88 back to monkey1 again...\");\n        await db.users.where({username: \"monkey88\"}).modify({username: \"monkey1\"});\n        await db.users.toArray();\n        await db.users.delete(\"monkey2\");\n        await db.users.delete(\"monkey1\");\n    };\n\n    doTheTests().then(function () {\n        ok(true, \"Now in an explicit transaction block...\");\n        return db.transaction('rw', db.users, async () => {\n            await doTheTests();\n        });\n    }).catch(function(ex) {\n        ok(false, ex);\n    }).then(() => {\n        db.users.hook.creating.unsubscribe(onCreating);\n        db.users.hook.reading.unsubscribe(onReading);\n        db.users.hook.updating.unsubscribe(onUpdating);\n        db.users.hook.deleting.unsubscribe(onDeleting);\n        start();\n    });\n});\n\nfunction sleep (ms) {\n    return new Promise(resolve => setTimeout(resolve, ms));\n}\n\npromisedTest(\"waitFor()\", async ()=>{\n    await db.transaction('rw', db.users, async trans =>{\n        // Wait for a promise:\n        await trans.waitFor(sleep(100));\n        // Do an operation on transaction\n        await trans.users.put({username: \"testingtesting\"});\n        await trans.waitFor(sleep(100));\n        let result = await trans.users.get(\"testingtesting\");\n        ok(result && result.username === \"testingtesting\", \"Should be able to continue transaction after waiting for non-indexedDB promise\");\n        ok(true, `Waiting spin count:${trans._spinCount}`);\n\n        // With timeout\n        await Dexie.waitFor(sleep(2000), 10) // Timeout of 10 ms.\n            .then (()=>ok(false, \"Should have timed out!\"))\n            .catch('TimeoutError', ex => ok(true, \"Timed out as expected\"));\n        \n        // Wait for function\n        await Dexie.waitFor(async ()=>{ \n            ok(Dexie.currentTransaction === null,\n                \"We should not be in the transaction zone here because transaction can be in a temporary inactive state here\");\n            await sleep(10);\n            ok (true, \"Slept 10 ms\")\n            // Let's test if we can access the transaction from here.\n            // The transaction should be alive indeed but not in an active state.\n            await trans.users.count().then(()=>{\n                // This happens on IE11\n                ok(true, \"Could access transaction within the wait callback. Nice for you, but you were just lucky!\");\n            }).catch(ex => {\n                // This happens on Firefox and Chrome\n                ok(true, \"Could NOT access transaction within the wait callback. As expected. Error: \" + ex);\n            });\n            ok(Dexie.currentTransaction === null,\n                \"We should not be in the transaction zone here because transaction can be in inactive state here\");\n        });\n        \n        result = await trans.users.get(\"testingtesting\");\n        ok(result && result.username === \"testingtesting\", \"Should still be able to operate on the transaction\");\n        ok(true, `Waiting spin count:${trans._spinCount}`);\n        ok(Dexie.currentTransaction === trans, \"Zone info should still be correct\");\n\n        // Subtransaction\n        await db.transaction('r', db.users, function* (subTrans) {\n            ok(subTrans !== trans, \"Should be in a sub transaction\");\n            ok(Dexie.currentTransaction === subTrans, \"Should be in a sub transaction\");\n            let count = yield trans.users.count();\n            ok(true, \"Should be able to operate on sub transaction. User count = \" + count);\n            yield subTrans.waitFor(sleep(10));\n            ok(true, \"Should be able to call waitFor() on sub transaction\");\n            count = yield trans.users.count();\n            ok(true, \"Should be able to operate on sub transaction. User count = \" + count);\n        });\n\n        // Calling waitFor multiple times in parallell\n        await Promise.all([\n            trans.waitFor(sleep(10)),\n            trans.waitFor(sleep(10)),\n            trans.waitFor(sleep(10))]);\n        ok (true, \"Could wait for several tasks in parallell\");\n        \n        result = await trans.users.get(\"testingtesting\");\n        ok(result && result.username === \"testingtesting\", \"Should still be able to operate on the transaction\");\n        //await sleep(100);\n        //ok(true, `Waiting spin count:${trans._spinCount}`);\n    }).then(()=>ok(true, \"Transaction committed\"));\n});\n\npromisedTest(\"Dexie.waitFor() outside transaction\", async ()=> {\n    // Test that waitFor can be called when not in a transaction as well.\n    // The meaning of this is that sometimes a function does db operations without\n    // a transaction, but should be able to call also within the caller's transaction.\n    // A function should therefore be able to call Dexie.waitFor() no matter if is executing\n    // within a transaction or not.\n    let result = await Dexie.waitFor(sleep(10).then(()=>true));\n    ok(result, \"Could call waitFor outside a transaction as well\");\n    let codeExecuted = false;\n    await Dexie.waitFor(async ()=>{\n        await sleep(10);\n        codeExecuted = true;\n    });\n    ok(codeExecuted, \"Could call waitFor(function) outside a transation as well\");\n});\n\npromisedTest(\"Dexie.waitFor() TransactionInactiveError\", async() => {\n    await db.transaction('r', db.users, async ()=>{\n        await sleep(100); // Force transaction to become inactive\n        try {\n            await Dexie.waitFor(sleep(10));\n            ok(false, 'After sleeping, transaction just cannot be alive.');\n        } catch (err) {\n            ok(err.name == 'TransactionInactiveError' || err.name == 'InvalidStateError',\n            `Got TransactionInactiveError or InvalidStateError as expected`);\n        }\n    }).then (()=>{\n        ok(false, 'The transaction should not possibly succeed even though catching, because it was too late.');\n    }).catch ('PrematureCommitError', err => {\n        ok(true, 'Got PrematureCommitError as expected');\n    });\n});\n\npromisedTest(\"Promise.follow() should omit promises spawned under Dexie.ignoreTransaction()\", async ()=>{\n    let resolve, reject;\n    const p = new Promise((res, rej) => { resolve = res; reject = rej; });\n    const log = [];\n\n    await db.transaction('r', db.users, function () {\n        // Since we do not return a promise here,\n        // Promise.follow() will be used for awaitint all tasks.\n        // However, tasks spawned under Dexie.ignoreTransacion() should not be included in promises to wait for.\n        Dexie.ignoreTransaction(()=>{\n            return new Dexie.Promise(resolve => setTimeout(resolve, 50)).then(()=>{\n                return db.pets.put({kind: \"dog\"});\n            }).then(()=>{\n                return db.pets.count();\n            }).then(numPets => {\n                ok(true, `num pets: ${numPets}`);\n                log.push(\"inner-task-done\");\n            }).then(resolve, reject);\n        });\n        // The following promise should be awaited for though (because new Promise is spawned from withing a zone or sub-zone to current transaction.)\n        new Dexie.Promise(resolve => setTimeout(resolve, 25)).then(()=>{\n            //return db.users.get(1);\n        }).then(()=>{\n            ok(true, \"followed promise done\");\n            log.push(\"spawned-promise-done\");\n        }).catch(e => {\n            ok(false, e);\n        });\n    });\n\n    log.push(\"outer-task-done\");\n    ok(true, \"transaction done\");\n\n    await p;\n\n    equal(log.join(','), \"spawned-promise-done,outer-task-done,inner-task-done\", \"outer-task-done should have happened before inner-task-done\");\n\n});\n\npromisedTest(\"db.transaction() should not wait for non-awaited new top-level transactions to commit\", async ()=>{\n    let resolve, reject;\n    const p = new Promise((res, rej) => { resolve = res; reject = rej; });\n    const log = [];\n\n    await db.transaction('r', db.users, () => {\n        // Since we do not return a promise here,\n        // Promise.follow() will be used for awaitint all tasks.\n        // However, if we spawn a new top-level transaction. It should be omitted and not waited for:\n        db.transaction('rw!', db.pets, () => {\n            return db.pets.put({kind: \"dog\"}).then(()=>{\n                return db.pets.count();\n            }).then(numPets => {\n                ok(true, `num pets: ${numPets}`);\n            }).then(()=>{\n                return Dexie.waitFor(sleep(50)); // In IE, it sometimes happens that outer transaction is slow to commit (even though it doesnt to anything)\n            }).then(()=>{\n                log.push(\"inner-transaction-done\");\n            }).then(resolve, reject);\n        });\n    });\n\n    log.push(\"outer-transaction-done\");\n    ok(true, \"transaction done\");\n\n    await p;\n\n    equal(log.join(','), \"outer-transaction-done,inner-transaction-done\", \"outer-transaction-done should have happened before inner-transaction-done\");\n});\n\nasyncTest(\"abort will rollback previous writes\", function() {\n    db.transaction('rw', db.users, function() {\n        db.users.add({ username: \"james\", color: \"red\" });\n        Dexie.currentTransaction.abort();\n    }).catch(function() {\n        ok(true, \"transaction done\");\n    }).then(function() {\n        return db.users.get('james')\n    }).then(function(user) {\n        ok(user == null, \"should not written if transaction aborted\");\n    })\n    .finally(start);\n});\n"
  },
  {
    "path": "test/tests-upgrading.js",
    "content": "﻿import Dexie from 'dexie';\nimport {module, test, equal, ok, deepEqual} from 'QUnit';\nimport {resetDatabase, supports, promisedTest, isIE, isEdge} from './dexie-unittest-utils';\n\nmodule(\"upgrading\");\n\nvar Promise = Dexie.Promise;\n\n// tests:\n// * separate tests with a commented line of --- up to column 80.\n// * put test result checking as a then of the relevant db.open call.\n// * db.close at the top of a new section.\n// another top-level then should indicate another part of the sequence\n// of upgrade actions.\n// put db.delete() in its own clause.\ntest(\"upgrade\", (assert) => {\n    let done = assert.async();\n    // To test:\n    // V Start with empty schema\n    // V Add indexes\n    // V Remove indexes\n    // V Specify the changed object stores only\n    // V Run an upgrader function\n    // V Run a series of upgrader functions (done when creating DB from scratch with ALL version specs and at least two of them have upgrader functions)\n    // V Add object store\n    // V Remove object store\n    // V Reverse order of specifying versions\n    // V Delete DB and open it with ALL version specs specified (check it will run in sequence)\n    // V Delete DB and open it with all version specs again but in reverse order\n    var DBNAME = \"Upgrade-test\";\n    var db = null;\n    // Instead of expecting an empty database to have 0 tables, we read\n    // how many an empty database has.\n    // Reason: Addons may add meta tables.\n    var baseNumberOfTables = 0;\n    var baseTables = [];\n\n    // Ensure Dexie verno and backing IDB version are as expected.\n    function checkVersion(version) {\n        equal(db.verno, version, `DB should be version ${version}`);\n        equal(db.backendDB().version, version * 10,\n              `idb should be version ${version * 10}`);\n    }\n\n    // Ensure object store names are as expected.\n    function checkObjectStores(expected) {\n        // Add baseTables.\n        expected = expected.concat(baseTables).sort();\n        // Already sorted.\n        var idbNames = [].slice.call(db.backendDB().objectStoreNames);\n        var dexieNames = db.tables.map(t => t.name).sort();\n        deepEqual(dexieNames,\n                  expected,\n                  \"Dexie.tables must match expected.\");\n        if (supports(\"deleteObjectStoreAfterRead\")) {\n            // Special treatment for IE/Edge where Dexie avoids deleting the actual store to avoid a bug.\n            // This special treatment in the unit tests may not need to be here if we can work around Dexie issue #1.\n            deepEqual(idbNames,\n                    expected,\n                    \"IDB object stores must match expected.\");\n        }\n    }\n\n    function checkTransactionObjectStores(t, expected) {\n        // Add baseTables.\n        expected = expected.concat(baseTables).sort();\n        deepEqual(t.storeNames.slice().sort(),\n                  expected,\n                  \"Transaction stores must match expected.\");\n    }\n\n    Promise.resolve(() => {\n        return Dexie.delete(DBNAME);\n    }).then(() => {\n        // --------------------------------------------------------------------\n        // Test: Empty schema\n        db = new Dexie(DBNAME);\n        db.version(1).stores({});\n        return db.open().then(function () {\n            ok(true, \"Could create empty database without any schema\");\n            // Set so add-on tables don't invalidate checks.\n            baseNumberOfTables = db.tables.length;\n            baseTables = db.tables.map(t => t.name);\n        });\n    }).then(() => {\n        // --------------------------------------------------------------------\n        // Test: Adding version.\n        db = new Dexie(DBNAME);\n        db.version(1).stores({});\n        db.version(2).stores({ store1: \"++id\" });\n        return db.open().then(function () {\n            ok(true, \"Could upgrade to version 2\");\n            checkVersion(2);\n            //equal(db.verno, 2, \"DB should be version 2\");\n            equal(db.table(\"store1\").schema.primKey.name, \"id\",\n                  \"Primary key is 'id'\");\n        });\n    }).then(() => {\n        db.close();\n        // --------------------------------------------------------------------\n        // Test: Adding an index to a store\n        db = new Dexie(DBNAME);\n        db.version(1).stores({});\n        db.version(2).stores({ store1: \"++id\" });\n        // Adding the name index\n        db.version(3).stores({ store1: \"++id,name\" });\n        return db.open().then(() => {\n            ok(true, \"Could upgrade to version 3 (adding an index to a store)\");\n            checkVersion(3);\n        });\n    }).then(() => {\n        // Testing that the added index is working indeed:\n        return db.transaction('rw', \"store1\", function () {\n            db.store1.add({ name: \"apa\" });\n            db.store1.where(\"name\").equals(\"apa\").count(function (count) {\n                equal(count, 1,\n                    \"Apa was found by its new index (The newly added index really works!)\");\n            });\n        });\n    }).then(() => {\n        db.close();\n        // --------------------------------------------------------------------\n        // Testing:\n        //  1. Place latest version first (order should not matter)\n        //  2. Removing the 'name' index.\n        db = new Dexie(DBNAME);\n        db.version(4).stores({ store1: \"++id\" });\n        db.version(3).stores({ store1: \"++id,name\" });\n        db.version(2).stores({ store1: \"++id\" });\n        db.version(1).stores({});\n        return db.open().then(() => {\n            ok(true, \"Could upgrade to version 4 (removing an index)\");\n            checkVersion(4);\n            equal(db.tables[0].schema.indexes.length, 0, \"No indexes in schema now when 'name' index was removed\");\n        });\n    }).then(() => {\n        db.close();\n        // --------------------------------------------------------------------\n        // Test: Running an upgrader function.\n        db = new Dexie(DBNAME);\n        var upgraders = 0;\n        // (Need not to specify earlier versions than 4 because 'I have no users out there running on version below 4'.)\n        db.version(4).stores({ store1: \"++id\" });\n        db.version(5).stores({ store1: \"++id,&email\" }).upgrade(function (trans) {\n            upgraders++;\n            var counter = 0;\n            db.store1.toCollection().modify(function (obj) {\n                // Since we have a new primary key we must make sure it's unique on all objects\n                obj.email = \"user\" + (++counter) +\"@abc.com\";\n            });\n        });\n        return db.open().then(() => {\n            ok(true, \"Could upgrade to version 5 where an upgrader function was applied\");\n            checkVersion(5);\n            equal(upgraders, 1, \"1 upgrade function should have run.\");\n        });\n    }).then(() => {\n        return db.table(\"store1\").toArray().then(array => {\n            equal(array.length, 1,\n                \"We still have the object created in version 3 there\");\n            equal(array[0].email, \"user1@abc.com\", \"The object got its upgrade function running\");\n            equal(array[0].id, 1, \"The object still has the same primary key\");\n            equal(array[0].name, \"apa\", \"The object still has the name 'apa' that was given to it when it was created\");\n        });\n    }).then(() => {\n        db.close();\n        // --------------------------------------------------------------------\n        // Test: Changing a property of an index\n        db = new Dexie(DBNAME);\n        db.version(5).stores({ store1: \"++id,&email\" });\n        // Changing email index from unique to multi-valued\n        db.version(6).stores({ store1: \"++id,*email\" }).upgrade(t => {\n            t.table(\"store1\").toCollection().modify(obj => {\n                // Turning single-valued unique email into an array of\n                // emails.\n                obj.email = [obj.email];\n            });\n        }); \n        return db.open().then(() => {\n            ok(true, \"Could upgrade to version 6\");\n            checkVersion(6);\n            checkObjectStores([\"store1\"]);\n        });\n    }).then(() => {\n        return db.table('store1').get(1, function (apaUser) {\n            ok(Array.isArray(apaUser.email), \"email is now an array\");\n            equal(apaUser.email[0], \"user1@abc.com\", \"First email is user1@abc.com\");\n        });\n    }).then(() => {\n        // Test that it is now ok to add two different users with the same email, since we have removed the uniqueness requirement of the index\n        return db.table('store1').add({ name: \"apa2\", email: [\"user1@abc.com\"] });\n    }).then(() => {\n        return db.table('store1').toArray().then(array => {\n            equal(array.length, 2, \"There are now two users in db\");\n            equal(array[0].email[0], array[1].email[0], \"The two users share the same email value\");\n        });\n    }).then((array) => {\n        db.close();\n        // --------------------------------------------------------------------\n        // Test: Only changed object stores need to be specified.\n        db = new Dexie(DBNAME);\n        // No need to specify an upgrade function when we know it's not\n        // gonna run (we are already on ver 5)\n        db.version(6).stores({ store1: \"++id,*email\" });\n        db.version(7).stores({ store2: \"uuid\" });\n        return db.open().then(() => {\n            ok(true, \"Could upgrade to version 7\");\n            checkVersion(7);\n            checkObjectStores([\"store1\", \"store2\"]);\n        });\n    }).then(() => {\n        db.close();\n        // --------------------------------------------------------------------\n        // Test: Object store removal.\n        db = new Dexie(DBNAME);\n        // Need to keep version 6 or add its missing stores to version 7,\n        // 7. Choosing to keep version 6.\n        db.version(6).stores({ store1: \"++id,*email\" });\n        db.version(7).stores({ store2: \"uuid\" });\n        // Deleting a version.\n        db.version(8).stores({store1: null });\n        return db.open().then(() => {\n            ok(true, \"Could upgrade to version 8 - deleting an object store\");\n            checkVersion(8);\n            checkObjectStores([\"store2\"]);\n        });\n    }).then(() => {\n        // --------------------------------------------------------------------\n        // Test: Use a removed object store while running an upgrade function.\n        /*db = new Dexie(DBNAME);\n        db.version(7).stores({ store2: \"uuid\" });\n        db.version(8).stores({ store1: null });\n        db.version(9).stores({ store1: \"++id,email\" });\n        db.version(10).stores({ store1: null }).upgrade(t => {\n            checkTransactionObjectStores(t, [\"store1\"]);\n            // TODO: actually use the object store.\n            ok(true, \"Upgrade transaction contains deleted store.\");\n        });\n        return db.open().then(() => {\n            ok(true, \"Could upgrade to version 10 - deleting an object store with upgrade function\");\n            checkVersion(10);\n            checkObjectStores([\"store2\"]);\n        });*/\n    }).then(() => {\n        // Reset.\n        return db.delete();\n    }).then(() => {\n        // --------------------------------------------------------------------\n        // Test:\n        // 1. Upgrade transactions should have the correct object\n        //    stores available. (future version)\n        db = new Dexie(DBNAME);\n        \n        db.version(1).stores({\n            store1: \"++id,name\"\n        });\n        return db.open().then(() => {\n            // Populate db.\n            return db.store1.put({ name: \"A B\" });\n        });\n    }).then(() => {\n        db.close();\n        // Add upgrade functions.\n        // Track number of upgrade functions run.\n        var upgraders = 0;\n        db.version(2).stores({\n            store2: \"++id,firstname,lastname\"\n        }).upgrade(t => {\n            /*checkTransactionObjectStores(t,\n                [\"store1\", \"store2\"]);*/\n            ok(true, \"Upgrade transaction has stores deleted later.\");\n            upgraders++;\n            // TODO: copy value to store2.\n        });\n        db.version(3).stores({\n            store1: null,\n            store3: \"++id\"\n        }).upgrade(t => {\n            /*checkTransactionObjectStores(t,\n                [\"store1\", \"store2\", \"store3\"]);*/\n            upgraders++;\n            // TODO: Add some value to store3.\n        });\n        return db.open().then(() => {\n            checkVersion(3);\n            equal(upgraders, 2, \"2 upgrade functions should have run.\");\n            checkObjectStores([\"store2\", \"store3\"]);\n            // TODO: Check that the data is as-expected.\n        });\n    }).then(() => {\n        return db.delete();\n    }).then(() => {\n        // --------------------------------------------------------------------\n        // Test: Dexie identifies the correct table name and schema given a\n        // sequence of versions to go through.\n        db = new Dexie(DBNAME);\n        db.version(1).stores({});\n        db.version(2).stores({ store1: \"++id\" });\n        // Adding the name index\n        db.version(3).stores({ store1: \"++id,name\" });\n        db.version(4).stores({ store1: \"++id\" });\n        db.version(5).stores({ store1: \"++id,&email\" }).upgrade(t => {\n            var counter = 0;\n            t.table(\"store1\").toCollection().modify(obj => {\n                // Since we have a new primary key we must make sure\n                // it's unique on all objects\n                obj.email = \"user\" + (++counter) + \"@abc.com\";\n            });\n        });\n        // Changing email index from unique to multi-valued\n        db.version(6).stores({ store1: \"++id,*email\" }).upgrade(t => {\n            t.table(\"store1\").toCollection().modify(obj => {\n                // Turning single-valued unique email into an array of\n                // emails.\n                obj.email = [obj.email];\n            });\n        });\n        db.version(7).stores({ store2: \"uuid\" });\n        db.version(8).stores({ store1: null });\n        return db.open().then(() => {\n            ok(true, \"Could create new database\");\n            checkVersion(8);\n            checkObjectStores([\"store2\"]);\n            equal(db.table(\"store2\").schema.primKey.name, \"uuid\", \"The prim key is uuid\");\n        });\n    }).then(() => {\n        return db.delete();\n    }).then(() => {\n        // --------------------------------------------------------------------\n        // Test: Order of version declaration should not matter.\n        db = new Dexie(DBNAME);\n        db.version(8).stores({ store1: null });\n        db.version(7).stores({ store2: \"uuid\" });\n        db.version(6).stores({ store1: \"++id,*email\" }).upgrade(function () { // Changing email index from unique to multi-valued\n            db.store1.toCollection().modify(function (obj) {\n                obj.email = [obj.email]; // Turning single-valued unique email into an array of emails.\n            });\n        });\n        db.version(5).stores({ store1: \"++id,&email\" }).upgrade(function () {\n            var counter = 0;\n            db.store1.toCollection().modify(function (obj) {\n                // Since we have a new primary key we must make sure it's unique on all objects\n                obj.email = \"user\" + (++counter) + \"@abc.com\";\n            });\n        });\n        db.version(4).stores({ store1: \"++id\" });\n        db.version(3).stores({ store1: \"++id,name\" }); // Adding the name index\n        db.version(2).stores({ store1: \"++id\" });\n        db.version(1).stores({});\n        return db.open().then(() => {\n            ok(true, \"Could create new database\");\n            checkVersion(8);\n            checkObjectStores([\"store2\"]);\n            equal(db.table(\"store2\").schema.primKey.name, \"uuid\", \"The prim key is uuid\");\n        });\n    }).catch((err) => {\n        ok(false, \"Error: \" + err);\n    }).finally(() => {\n        if (db) db.close();\n        Dexie.delete(DBNAME).then(done);\n    });\n});\n\ntest(\"Issue #30 - Problem with existing db\", (assert) => {\n    let done = assert.async();\n    if (!supports(\"compound+multiEntry\")) {\n        ok(true, \"SKIPPED - COMPOUND + MULTIENTRY UNSUPPORTED\");\n        return done();\n    }\n    ///<var type=\"Dexie\" />\n    var db; // Will be used as a migrated version of the db.\n\n    // Start by deleting the db if it exists:\n    Dexie.delete(\"raw-db\").then(function () {\n\n        // Create a bare-bone indexedDB database with custom indexes of various kinds.\n        return new Dexie.Promise(function (resolve, reject) {\n            var indexedDB = Dexie.dependencies.indexedDB;\n            var rawdb, req;\n\n            function error(e) {\n                if (rawdb) rawdb.close();\n                reject(e.target.error);\n            }\n\n            req = indexedDB.open(\"raw-db\", 2);\n            req.onupgradeneeded = function (ev) {\n                try {\n                    console.log(\"onupgradeneeded called\");\n                    rawdb = req.result;\n                    // Stores\n                    var people = rawdb.createObjectStore(\"people\", {keyPath: \"_id\", autoIncrement: false});\n                    var messages = rawdb.createObjectStore(\"messages\", {autoIncrement: true});\n                    var umbrellas = rawdb.createObjectStore(\"umbrellas\", {keyPath: [\"date\", \"time\"]});\n                    // Indexes:\n                    messages.createIndex(\"text_index\", \"text\", {unique: false, multiEntry: false});\n                    messages.createIndex(\"words_index\", \"words\", {unique: false, multiEntry: true});\n                    messages.createIndex(\"id_index\", \"id\", {unique: true, multiEntry: false});\n                    umbrellas.createIndex(\"size_color_index\", [\"size\", \"color\"], {\n                        unique: false,\n                        multiEntry: false\n                    });\n                    // Data:\n                    people.add({_id: \"9AF56447-66CE-470A-A70F-674A32EF2D51\", name: \"Kalle\"});\n                    messages.add({text: \"Here is a text\", words: [\"here\", \"is\", \"a\", \"text\"], id: 1});\n                    umbrellas.add({\n                        date: \"2014-11-20\",\n                        time: \"22:18\",\n                        size: 98,\n                        color: \"pink\",\n                        name: \"My Fine Umbrella!\"\n                    });\n                } catch (ex) {\n                    if (rawdb) rawdb.close();\n                    reject(ex);\n                }\n            }\n            req.onsuccess = function () {\n                console.log(\"onsuccess called\");\n                rawdb = req.result;\n\n                rawdb.close();\n\n                resolve();\n            };\n            req.onerror = error;\n        });\n    }).then(function () {\n        // Try open the database using Dexie:\n        db = new Dexie(\"raw-db\", {addons: []}); // Explicitely don't use addons here. Syncable would fail to open an existing db.\n        db.version(0.2).stores({\n            people: \"_id\",\n            messages: \"++,text,*words,&id\",\n            umbrellas: \"[date+time],[size+color]\"\n        });\n        return db.open();\n    }).then(function () {\n        // Verify \"people\" data\n        return db.people.toArray(function (people) {\n            equal(people.length, 1, \"One person in people\");\n            equal(people[0].name, \"Kalle\", \"The persons' name is Kalle\");\n        });\n    }).then(function () {\n        // Verify \"messages\" data\n        return db.messages.toArray(function (messages) {\n            equal(messages.length, 1, \"One message in messages\");\n            equal(messages[0].text, \"Here is a text\", \"The message has the correct text\");\n            equal(messages[0].words.length, 4, \"The message has 4 words\");\n        });\n    }).then(function () {\n        // Verify \"umbrellas\" data\n        return db.umbrellas.toArray(function (umbrellas) {\n            equal(umbrellas.length, 1, \"One umbrella in umbrellas\");\n            equal(umbrellas[0].name, \"My Fine Umbrella!\", \"The umbrella has the correct name\");\n            equal(umbrellas[0].date, \"2014-11-20\", \"The umbrella has the correct date\");\n            equal(umbrellas[0].time, \"22:18\", \"The umbrella has the correct time\");\n            equal(umbrellas[0].size, 98, \"The umbrella has the currect size\");\n            equal(umbrellas[0].color, \"pink\", \"The umbrella has the correct color\");\n        });\n    }).then(function () {\n        // Test messages indexes\n        return db.messages.orderBy(\"text\").first(function (message) {\n            ok(!!message, \"Could find a message when iterating the 'text' index\");\n        });\n    }).then(function () {\n        // Test words index\n        return db.messages.where(\"words\").equals(\"is\").first(function (message) {\n            ok(!!message, \"Could find a message when querying the 'words' index\");\n        });\n    }).then(function () {\n        // Test id index\n        return db.messages.where(\"id\").equals(1).count(function (count) {\n            equal(count, 1, \"Could count id's\");\n        });\n    }).then(function () {\n        // Test umbrella compound primary key\n        return db.umbrellas.get([\"2014-11-20\", \"22:18\"], function (umbrella) {\n            ok(!!umbrella, \"Umbrella was found by compound primary key\");\n            equal(umbrella.color, \"pink\", \"Umbrella has the correct color\");\n        });\n    }).then(function () {\n        // Test umbrella compound index\n        return db.umbrellas.where(\"[size+color]\").above([98, \"pina\"]).count(function (count) {\n            equal(count, 1, \"Could count umbrellas based on a query on compound index\");\n        });\n    }).then(function () {\n        // Now, let's upgrade the migrated database\n        db.close();\n        db = new Dexie(\"raw-db\");\n        // First, as required with Dexie so far, specify the existing stores:\n        db.version(0.2).stores({\n            people: \"_id\",\n            messages: \"++,text,words,id,[size+color]\",\n            umbrellas: \"[date+time],[size+color]\"\n        });\n        // Then, add the 'name' index to people:\n        db.version(3).stores({\n            people: \"_id,name\"\n        });\n        return db.open();\n    }).then(function () {\n        // Now test the new name index:\n        return db.people.where(\"name\").equalsIgnoreCase(\"kalle\").first();\n    }).then(function (kalle) {\n        ok(!!kalle, \"Could find at least one object by its name index\");\n        equal(kalle.name, \"Kalle\", \"The found object was Kalle indeed\");\n    }).catch(function (err) {\n        ok(false, \"Error: \" + err);\n    }).finally(function () {\n        if (db) db.close();\n        Dexie.delete(\"raw-db\").then(done);\n    });\n});\n\npromisedTest(\"Issue #713 - how to change table name\", async ()=> {\n    await Dexie.delete(\"issue713\");\n    const db = new Dexie('issue713');\n    try {\n        db.version(1).stores({\n            friends: '++id, name, age'\n        });\n        await db.friends.bulkAdd([\n            {name: \"Foo\", age: 25},\n            {name: \"Bar\", age: 75}\n        ]);\n        db.close();\n        const db2 = new Dexie('issue713');\n        db2.version(1).stores({\n            friends: '++id, name, age'\n        });\n        db2.version(2).stores({\n            friends2: 'id, name, age'\n        }).upgrade(tx=>{\n            return tx.friends.toArray().then(objs => {\n                return tx.friends2.bulkAdd(objs);\n            });\n        });\n        db2.version(3).stores({\n            friends: null\n        });\n        const result = await db2.friends2.toArray();\n        equal(result.length, 2, \"Should get 2 friends\");\n        equal(result[0].name, \"Foo\", \"First friend is 'Foo'\");\n        equal(result[1].name, \"Bar\", \"First friend is 'Bar'\");\n    } finally {\n        await db.delete();   \n    }\n});\n\npromisedTest(\"Issue #713 - how to change table name (short)\", async ()=> {\n    await Dexie.delete(\"issue713Short\");\n    const db = new Dexie('issue713Short');\n    try {\n        db.version(1).stores({\n            friends: '++id, name, age'\n        });\n        await db.friends.bulkAdd([\n            {name: \"Foo\", age: 25},\n            {name: \"Bar\", age: 75}\n        ]);\n        db.close();\n        const db2 = new Dexie('issue713Short');\n        db2.version(1).stores({\n            friends: '++id, name, age'\n        });\n        db2.version(2).stores({\n            friends2: 'id, name, age',\n            friends: null // delete after upgrader\n        }).upgrade(tx=>{\n            return tx.friends.toArray().then(objs => {\n                return tx.friends2.bulkAdd(objs);\n            });\n        });\n        const result = await db2.friends2.toArray();\n        equal(result.length, 2, \"Should get 2 friends\");\n        equal(result[0].name, \"Foo\", \"First friend is 'Foo'\");\n        equal(result[1].name, \"Bar\", \"First friend is 'Bar'\");\n    } finally {\n        await db.delete();\n    }\n});\n\npromisedTest(\"Changing primary key\", async ()=> {\n    if (isIE || isEdge) {\n        ok(true, \"Skipping this test for IE and Edge - it has a bug that prevents it from renaming a table\");\n        return;\n    }\n\n    await Dexie.delete(\"changePrimKey\");\n\n    // First, create the initial version of the DB, populate some data, and then close it.\n    let db = new Dexie(\"changePrimKey\");\n    db.version(1).stores({\n        foos: '++id'\n    });\n    await db.foos.bulkAdd([{name: \"Hola\"}, {name: \"Hello\"}]);\n    db.close();\n\n    // To change primary key, let's start by copying the table\n    // and then deleting and recreating the original table\n    // to copy it back again\n    db = new Dexie(\"changePrimKey\");\n    db.version(1).stores({\n        foos: '++id'\n    });\n\n    // Add version 2 that copies the data to foos2\n    db.version(2).stores({\n        foos2: 'objId'\n    }).upgrade(async tx => {\n        const foos = await tx.foos.toArray();\n        await tx.foos2.bulkAdd(foos.map(foo => ({\n            objId: \"obj:\"+foo.id,\n            hello: foo.name\n        })));\n    });\n\n    // Add version 3 that deletes old \"foos\"\n    db.version(3).stores({\n        foos: null\n    });\n\n    // Add version 4 that recreates \"foos\" with wanted primary key\n    // and do the copying again\n    db.version(4).stores({\n        foos: 'objId, hello'\n    }).upgrade(async tx => {\n        const foos = await tx.foos2.toArray();\n        await tx.foos.bulkAdd(foos);\n    });\n\n    // Finally delete the temp table\n    db.version(5).stores({\n        foos2: null\n    });\n\n    // Now, verify we have what we expect\n    const foos = await db.foos.toArray();\n    equal(foos.length, 2, \"Should have 2 rows\");\n    equal(foos[0].objId, \"obj:1\", \"A primary key with an object ID 1 is there\");\n    equal(foos[1].objId, \"obj:2\", \"A primary key with an object ID 2 is there\");\n    // Verify we can use the new index as well\n    const foo2 = await db.foos.get({hello: \"Hello\"});\n    ok(foo2 != null, \"Should get a match\");\n    equal(foo2.objId, \"obj:2\", \"The expected ID was returned\");\n});\n\npromisedTest(\"Changing primary key (short)\", async ()=> {\n    if (isIE || isEdge) {\n        ok(true, \"Skipping this test for IE and Edge - it has a bug that prevents it from renaming a table\");\n        return;\n    }\n\n    await Dexie.delete(\"changePrimKeyShort\");\n\n    // First, create the initial version of the DB, populate some data, and then close it.\n    let db = new Dexie(\"changePrimKeyShort\");\n    db.version(1).stores({\n        foos: '++id'\n    });\n    await db.foos.bulkAdd([{name: \"Hola\"}, {name: \"Hello\"}]);\n    db.close();\n\n    // To change primary key, let's start by copying the table\n    // and then deleting and recreating the original table\n    // to copy it back again\n    db = new Dexie(\"changePrimKeyShort\");\n    db.version(1).stores({\n        foos: '++id'\n    });\n\n    // Add version 2 that copies the data to foos2\n    db.version(2).stores({\n        foos: null, // delete after upgrader\n        foos2: 'objId'\n    }).upgrade(async tx => {\n        const foos = await tx.foos.toArray();\n        await tx.foos2.bulkAdd(foos.map(foo => ({\n            objId: \"obj:\"+foo.id,\n            hello: foo.name\n        })));\n    });\n\n    // Add version 3 that recreates \"foos\" with wanted primary key\n    // and do the copying again\n    db.version(3).stores({\n        foos: 'objId, hello',\n        foos2: null // delete after upgrader\n    }).upgrade(async tx => {\n        const foos = await tx.foos2.toArray();\n        await tx.foos.bulkAdd(foos);\n    });\n\n    // Now, verify we have what we expect\n    const foos = await db.foos.toArray();\n    equal(foos.length, 2, \"Should have 2 rows\");\n    equal(foos[0].objId, \"obj:1\", \"A primary key with an object ID 1 is there\");\n    equal(foos[1].objId, \"obj:2\", \"A primary key with an object ID 2 is there\");\n    // Verify we can use the new index as well\n    const foo2 = await db.foos.get({hello: \"Hello\"});\n    ok(foo2 != null, \"Should get a match\");\n    equal(foo2.objId, \"obj:2\", \"The expected ID was returned\");\n});\n\n\npromisedTest(\n  \"Issue 919: Store not found when versions declared in decending order\",\n  async () => {\n    await Dexie.delete(\"issue919\");\n    let db = new Dexie(\"issue919\");\n    db.version(1).stores({\n      friends: \"++id,name,age\"\n    });\n    await db.open();\n    // succeeds\n    ok(true, `Could open v1: ${await db.friends.toArray()}`);\n    db.close();\n\n    db = new Dexie(\"issue919\");\n    // add a new store, `friends` store remains as before\n    db.version(2).stores({\n      enemies: \"++id,name\"\n    });\n    db.version(1).stores({\n      friends: \"++id,name,age\"\n    });\n\n    await db.open();\n    // fails with: NotFoundError: `The operation failed because the requested database object could not be found. For example, an object store did not exist but was being opened.`\n    ok(true, `Could open version 2: ${await db.friends.toArray()}`);\n    await db.delete();\n  }\n);\n\n\npromisedTest(\n    \"PR #959: Dexie should no more require users to keep old versions if they don't attach an upgrader to it\",\n    async ()=>{\n        const DBNAME = \"pr959\";\n\n        await Dexie.delete(DBNAME);\n        let db = new Dexie(DBNAME);\n        db.version(1).stores({\n            friends: \"id\"\n        });\n        await db.open();\n        ok(true, \"Could open v1\");\n        await db.friends.add({id: 1, name: \"Foo 959\"});\n        db.close();\n        db = new Dexie(DBNAME);\n        db.version(2).stores({\n            friends: \"id, name\"\n        });\n        await db.open();\n        ok(true, \"Could open v2 without having v1 specified. Name should now be indexed.\");\n        const foo = await db.friends.where(\"name\").startsWith(\"Foo\").first();\n        ok(!!foo, \"Could find friend using newly added index\");\n        equal(foo.id, 1, \"Got the right foo here\");\n        db.close();\n    }\n);\n\npromisedTest(\"Issue #959 - Should migrate successfully with an explicit unique modifier of the primary key\",\n    async () => {\n        await Dexie.delete(\"issue959\");\n        let db = new Dexie(\"issue959\");\n\n        db.version(1).stores({\n            friends: \"&name, age\"\n        });\n        await db.friends.bulkAdd([\n            { name: \"Foo\", age: 25, weight: 70 },\n            { name: \"Bar\", age: 75, weight: 100 }\n        ]);\n        db.close();\n    \n        db = new Dexie(\"issue959\");\n        db.version(1).stores({\n            friends: \"&name, age\"\n        });\n        db.version(2).stores({\n            friends: \"&name, age, weight\"\n        });\n\n        // Now, verify we have what we expect\n        const result = await db.friends.orderBy(\"age\").toArray();\n        equal(result.length, 2, \"Should get 2 friends\");\n        equal(result[0].name, \"Foo\", \"First friend is 'Foo'\");\n        equal(result[1].name, \"Bar\", \"First friend is 'Bar'\");\n        // Verify we can use the new index as well\n        const result2 = await db.friends.get({ weight: 100 });\n        ok(result2 != null, \"Should get a match\");\n        equal(result2.name, \"Bar\", \"The expected friends was returned\");\n    }\n);\n\n\npromisedTest(\n  \"Issue 1145 - Regression: SchemaError during version upgrade\",\n  async () => {\n    const DBNAME = \"issue1145\";\n    await Dexie.delete(DBNAME);\n    const db = new Dexie(DBNAME);\n    db.version(1).stores({ Y: \"id\" });\n    await db.open();\n    await db.close();\n    db.version(2).upgrade((trans) => {\n      ok(true, \"Starting version 2 upgrade.\");\n      return trans.Y.count();\n    });\n    db.version(3).stores({\n      Y: \"id,X\",\n    });\n    db.version(4).upgrade((trans) => {\n      ok(true, \"Starting version 4 upgrade.\");\n      return trans.Y.where(\"X\").equals(\"value\").toArray();\n    });\n\n    try {\n      await db.open();\n      ok(true, \"Open successful\");\n    } catch (e) {\n      ok(false, \"Open Failed:: \" + e);\n    } finally {\n      await db.delete();\n    }\n  }\n);\n\n\npromisedTest(\n    \"Issue 1418 - Not deleting all object stores\",\n    async () => {\n      if (!supports(\"deleteObjectStoreAfterRead\")) {\n        ok(true, \"Skipping this test - buggy browser.\");\n        return;\n      }\n      if (Dexie.addons.length > 0) {\n        ok(true, \"Skipping this test - default addons are acitve and can add more object stores\");\n        return;\n      }\n      const DBNAME = \"issue1418\";\n      await Dexie.delete(DBNAME);\n      let db = new Dexie(DBNAME);\n      db.version(1).stores({\n        a: '++',\n        b: '++',\n        c: '++',\n        d: '++',\n        e: '++'          \n      });\n      await db.open();\n      equal(db.idbdb.objectStoreNames.length, 5, \"There are 5 object stores\");\n      db.close();\n\n      db = new Dexie(DBNAME);\n      db.version(2).stores({\n        a: null,\n        b: null,\n        c: null,\n        d: null,\n        e: '++'\n      });\n      await db.open();\n      equal(db.idbdb.objectStoreNames.length, 1, \"There is only one object store now\");\n      db.close();\n      await Dexie.delete(DBNAME);\n    }\n  );\n    \n  promisedTest(\n    \"Dexie 4: Should not throw VersionError on downgrade\",\n    async ()=>{\n        const DBNAME = \"downgradedDB\";\n\n        await Dexie.delete(DBNAME);\n        let db = new Dexie(DBNAME);\n        db.version(2).stores({\n            friends: \"id, name\"\n        });\n        await db.friends.get(undefined).catch(e => {});\n        await db.open();\n        ok(true, \"Could open v2\");\n        await db.friends.add({id: 1, name: \"Foo 959\"});\n        db.close();\n        db = new Dexie(DBNAME);\n        db.version(1).stores({\n            friends: \"id, age\"\n        });\n        await db.open();\n        ok(true, \"Could open v1 even though installed version is at verion 2.\");\n        const friends = await db.friends.toArray();\n        equal(friends.length, 1, \"Could use the database for querying\");\n        await db.delete();\n    }\n);\n\npromisedTest(\n    \"Dexie 4: It should add indexes and tables also when not incrementing version number\",\n    async ()=>{\n        const DBNAME = \"forgettingVerNoIncrease\";\n\n        await Dexie.delete(DBNAME);\n        let db = new Dexie(DBNAME);\n        db.version(1).stores({\n            friends: \"id\"\n        });\n        await db.open();\n        ok(true, \"Could open v1 with {friends: 'id'}\");\n        await db.friends.add({id: 1, name: \"Foo 123\"});\n        db.close();\n        db = new Dexie(DBNAME);\n        db.version(1).stores({\n            friends: \"id, name, age\",\n            pets: 'id, friendId, kind'\n        });\n        await db.open();\n        ok(true, \"Could open v1 even though we have added some indexes and a table.\");\n        await db.friends.add({id: 2, name: \"Bar 123\", age: 25});\n        await db.pets.add({id: 1, friendId: 2, kind: \"dog\"});\n        ok(true, \"Could add pets to the new table\");\n        const pets = await db.pets.toArray();\n        const friends = await db.friends.toArray();\n        equal(friends.length, 2, \"Got the two friends\");\n        equal(pets.length, 1, \"Got the one pet\");\n        db.close();\n    }\n);\n\n\npromisedTest(\n    \"Dexie 4: It should work having two versions of the DB opened at the same time as long as they have a compatible schema\",\n    async ()=>{\n        if (typeof Dexie.Observable?.version === 'string') {\n            ok(true, \"Skipping this test - Dexie.Observable bails out when opening two versions of the same database\");\n            return;\n        }\n        const DBNAME = \"competingDBs\";\n\n        await Dexie.delete(DBNAME);\n        let db1 = new Dexie(DBNAME);\n        db1.version(1).stores({\n            friends: \"id\"\n        });\n        \n        let db2 = new Dexie(DBNAME);\n        db2.version(2).stores({\n            friends: \"id, name, age\",\n            pets: 'id, friendId, kind'\n        })\n        await Promise.all(db1.open(), db2.open());\n        await db1.friends.add({id: 1, name: \"Foo 123\"});\n        let foo = await db2.friends.where('name').startsWith('Foo').first();\n        ok(true, \"We could use the 'name' index only declared on db2\");\n        foo.age = 23;\n        await db2.friends.put(foo);\n        foo = await db1.friends.get(1);\n        equal(foo.age, 23, \"We could get the data using db1\");\n\n        db1.close();\n        db2.close();\n        await db1.open();\n        await db2.open();\n\n        db1.close();\n        db2.close();\n        db1.version(1).stores({\n            friends: \"id, name, age, [name+age]\",\n            cars: 'id, name'\n        });\n        await db2.open();\n        await db1.open();\n        foo = await db1.friends.where('[name+age]').equals([\"Foo 123\", 23]).first(); // Should be able to use the new index\n        equal(foo.age, 23, \"We could get the data using db1 and the added index 'name+age' still in v1\");\n        foo = await db2.friends.get({age: 23}); // Be able to use the age index that db2 declares.\n        equal(foo.age, 23, \"We could get the data using db2 and the 'name' index\");\n        const db = await new Dexie(DBNAME).open();\n        ok(db.verno < 3, \"The database should be at version 2 (or exactly: \" + db.verno + \")\");\n\n        await Dexie.delete(DBNAME);\n    }\n);\n\npromisedTest(\"Dexie 4: An attached upgrader on version 2 and 3 shall run even if version 1 was reused for schema manipulation more than 20 times\", async ()=>{\n    if (typeof Dexie.Observable?.version === 'string') {\n        ok(true, \"Skipping this test - Dexie.Observable bails out when database reopen in background\");\n        return;\n    }\n    const DBNAME = \"attachedUpgrader\";\n    const NUM_SCHEMA_CHANGES = 31; // 10 works but 11 fails unless we work around it in Dexie with a meta table.\n\n    await Dexie.delete(DBNAME);\n    let db = new Dexie(DBNAME);\n    for (let i=1; i<=NUM_SCHEMA_CHANGES; ++i) {\n        db.version(1) // Yes, reuse version 1. We're testing that reusing version for schema changes is ok.\n            .stores({\n            friends: \"id\",\n            [\"table\"+i]: \"id\"\n        });\n        await db.open();\n        db.close();\n    }\n    ok(true, `Could change schema a ${NUM_SCHEMA_CHANGES} times while still being on version 1, without error`);\n    await db.open();\n    equal(db.verno, 1, \"The database should be at version 1\");\n    await db.table(\"table1\").add({id: 1, name: \"Foo 123\"});\n    ok(true, `Could add things to table1`);\n    await db.table(\"table\" + NUM_SCHEMA_CHANGES).add({id: 1, name: \"Foo 123\"});\n    ok(true, `Could add things to table${NUM_SCHEMA_CHANGES}`);\n    db.close();\n    db = new Dexie(DBNAME);\n    db.version(2).stores({\n        version2Table: \"id\",\n    }).upgrade(async tx => {\n        await tx.version2Table.add({id: 1, foo: \"bar\"});\n    });\n    await db.open();\n    ok(true, \"Could open v2\");\n    const objFromUpgrader = await db.version2Table.get(1);\n    ok(!!objFromUpgrader, \"The upgrader of version 2 have run\");\n    db.close();\n\n    db.version(3).stores({\n        version3Table: \"id\",\n    }).upgrade(async tx => {\n        await tx.version3Table.add({id: 1, foo: \"bar\"});\n    });\n    await db.open().catch(err => {\n        ok(false, \"Failed to upgrade to version 3: \" + err); // Would fail here if version 2 was rerun a second time (ConstraintError)\n        throw err;\n    });\n    ok(true, \"Could open v3\");\n    const objFromUpgrader3 = await db.version3Table.get(1);\n    ok (!!objFromUpgrader3, \"The upgrader of version 3 have run\");\n    await Dexie.delete(DBNAME);\n});\n"
  },
  {
    "path": "test/tests-whereclause.js",
    "content": "﻿import Dexie from 'dexie';\nimport {module, stop, start, test, asyncTest, equal, ok} from 'QUnit';\nimport {resetDatabase, supports, spawnedTest, promisedTest} from './dexie-unittest-utils';\n\nconst async = Dexie.async;\n\nvar db = new Dexie(\"TestDBWhereClause\");\ndb.version(2).stores({\n    folders: \"++id,&path\",\n    files: \"++id,filename,extension,[filename+extension],folderId\",\n    people: \"[name+number],name,number\",\n    friends: \"++id,name,age\",\n    chart: '[patno+row+col], patno',\n    chaps: \"++id,[name+number+age]\",\n    multiMulti: \"id,*tags,*categories\"\n});\n\nvar Folder = db.folders.defineClass({\n    id: Number,\n    path: String,\n    description: String\n});\n\nvar File = db.files.defineClass({\n    id: Number,\n    filename: String,\n    extension: String,\n    folderId: Number\n});\n\nFile.prototype.getFullPath = function () {\n    var file = this;\n    return db.folders.get(this.folderId, function (folder) {\n        return folder.path + \"/\" + file.filename + (file.extension || \"\");\n    });\n}\n\nFolder.prototype.getFiles = function () {\n    return db.files.where('folderId').equals(this.id).toArray();\n}\n\nvar Chart = db.chart.defineClass({\n    patno: Number,\n    row: Number,\n    col: Number,\n    sym: Number\n});\nChart.prototype.save = function() {\n    return db.chart.put(this);\n}\n\nvar firstFolderId = 0,\n    lastFolderId = 0,\n    firstFileId = 0,\n    lastFileId = 0;\n\ndb.on(\"populate\", () => {\n    var folders = db.table(\"folders\");\n    var files = db.table(\"files\");\n    folders.add({path: \"/\", description: \"Root folder\"}).then(function(id) {\n        firstFolderId = id;\n    });\n    folders.add({path: \"/usr\"}); // 2\n    folders.add({path: \"/usr/local\"}); // 3\n    folders.add({path: \"/usr/local/bin\" }).then(function (id) { // 4\n        files.add({ filename: \"Hello\", folderId: id }).then(function(fileId) {\n            firstFileId = fileId;\n        });\n        files.add({filename: \"hello\", extension: \".exe\", folderId: id});\n    });\n    folders.add({path: \"/usr/local/src\"}).then(function (id) { // 5\n        files.add({filename: \"world\", extension: \".js\", folderId: id});\n        files.add({filename: \"README\", extension: \".TXT\", folderId: id});\n    });\n    folders.add({ path: \"/usr/local/var\" }); // 6\n    folders.add({ path: \"/USR/local/VAR\" }); // 7\n    folders.add({ path: \"/var\"}); // 8\n    folders.add({ path: \"/var/bin\" }).then(function(id) { // 9\n        lastFolderId = id;\n        return files.add({ filename: \"hello-there\", extension: \".exe\", folderId: id });\n    }).then(function(id) {\n        lastFileId = id;\n    });\n});\n\nmodule(\"WhereClause\", {\n    setup: function () {\n        stop();\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: function () {\n    }\n});\n\nspawnedTest('Issue#31 Compound Index with anyOf', function*(){\n    if (!supports('compound'))\n        return ok(true, \"SKIPPED - COMPOUND UNSUPPORTED\");\n    \n    yield db.people.bulkAdd([{\n       name: 0,\n       number: 0,\n       tag: \"A\"\n    },{\n       name: -1,\n       number: 0,\n       tag: \"B\"\n    },{\n       name: -2,\n       number: 0,\n       tag: \"C\"\n    }, {\n       name: -3,\n       number: 0,\n       tag: \"D\"\n    }]);\n\n    var items = yield db.people\n    .where('[name+number]')\n    .anyOf([ [ -2, 0 ], [ -3, 0 ] ] ) // https://github.com/dfahlander/Dexie.js/issues/31\n    .toArray();\n\n    equal (items.length, 2, \"It should contain 2 items.\");\n    equal (items[0].tag, \"D\", \"First we should get D\");\n    equal (items[1].tag, \"C\", \"then we should get C\");\n});\n\nasyncTest(\"startsWithAnyOf()\", function () {\n\n    function runTheTests (mippler) {\n        /// <param name=\"mippler\" value=\"function(x){return x;}\"></param>\n\n        //\n        // Basic Flow:\n        //\n        return mippler(db.folders\n            .where('path').startsWithAnyOf('/usr/local', '/var'))\n            .toArray(function (result) {\n                equal(result.length, 6, \"Query should match 6 folders\");\n                ok(result.some(function(x) { return x.path == '/usr/local'; }), '/usr/local');\n                ok(result.some(function(x) { return x.path == '/usr/local/bin'; }), '/usr/local/bin');\n                ok(result.some(function (x) { return x.path == '/usr/local/src'; }), '/usr/local/src');\n                ok(result.some(function (x) { return x.path == '/usr/local/var'; }), '/usr/local/var');\n                ok(result.some(function (x) { return x.path == '/var'; }), '/var');\n                ok(result.some(function (x) { return x.path == '/var/bin'; }), '/var/bin');\n\n                //\n                // Require a slash at beginning (and use an array of strings as argument instead)\n                //\n                return mippler(db.folders\n                    .where('path').startsWithAnyOf(['/usr/local/', '/var/']))\n                    .toArray();\n\n            }).then(function(result) {\n                equal(result.length, 4, \"Query should match 4 folders\");\n                ok(result.some(function(x) { return x.path == '/usr/local/bin'; }), '/usr/local/bin');\n                ok(result.some(function(x) { return x.path == '/usr/local/src'; }), '/usr/local/src');\n                ok(result.some(function(x) { return x.path == '/usr/local/var'; }), '/usr/local/var');\n                ok(result.some(function (x) { return x.path == '/var/bin'; }), '/var/bin');\n\n                //\n                // Some specialities\n                //\n                return Dexie.Promise.all(\n                    mippler(db.folders.where('path').startsWithAnyOf([])).count(), // Empty\n                    mippler(db.folders.where('path').startsWithAnyOf('/var', '/var', '/var')).count(), // Duplicates\n                    mippler(db.folders.where('path').startsWithAnyOf('')).count(), // Empty string should match all\n                    mippler(db.folders).count(),\n                    mippler(db.folders.where('path').startsWithAnyOf('nonexisting')).count() // Non-existing match\n                );\n            }).then(function(results) {\n                equal(results[0], 0, \"startsWithAnyOf([]).count() == 0\");\n                equal(results[1], 2, \"startsWithAnyOf('/var', '/var', '/var') == 2\");\n                equal(results[2], results[3], \"startsWithAnyOf('').count() == db.folders.count()\");\n                equal(results[4], 0, \"startsWithAnyOf('nonexisting').count() == 0\");\n\n                //\n                // Error handling\n                //\n\n                return mippler(db.folders.where('path').startsWithAnyOf([null, '/'])).toArray(function(res) {\n                    ok(false, \"Should not succeed to have null in parameter\");\n                }).catch(function(e) {\n                    ok(true, \"As expected: failed to have null in arguments: \" + e);\n                });\n            });\n    }\n\n    // Run tests without transaction and without reverse()\n    runTheTests(function (x) { return x; }).then(function () {\n        ok(true, \"FINISHED NORMAL TEST!\");\n        // Run tests with reverse()\n        return runTheTests(function(x) { return x.reverse(); });\n    }).then(function() {\n        ok(true, \"FINISHED REVERSE TEST!\");\n        // Run tests within a transaction\n        return db.transaction('r', db.folders, db.files, function() {\n            return runTheTests(function(x) { return x; });\n        });\n    }).then(function () {\n        ok(true, \"FINISHED TRANSACTION TEST!\");\n    }).catch(function (e) {\n        ok(false, \"Error: \" + e);\n    }).finally(start);\n});\n\nasyncTest(\"anyOf()\", function () {\n    db.transaction(\"r\", db.files, db.folders, function () {\n\n        db.files.where(\"filename\").anyOf(\"hello\", \"hello-there\", \"README\", \"gösta\").toArray(function (a) {\n            equal(a.length, 3, \"Should find 3 files\");\n            equal(a[0].filename, \"README\", \"First match is README because capital R comes before lower 'h' in lexical sort\");\n            equal(a[1].filename, \"hello\", \"Second match is hello\");\n            equal(a[2].filename, \"hello-there\", \"Third match is hello-there\");\n\n            a[0].getFullPath().then(function (fullPath) {\n                equal(fullPath, \"/usr/local/src/README.TXT\", \"Full path of README.TXT is: \" + fullPath);\n            });\n            a[1].getFullPath().then(function (fullPath) {\n                equal(fullPath, \"/usr/local/bin/hello.exe\", \"Full path of hello.exe is: \" + fullPath);\n            });\n            a[2].getFullPath().then(function (fullPath) {\n                equal(\"/var/bin/hello-there.exe\", fullPath, \"Full path of hello-there.exe is: \" + fullPath);\n            });\n        });\n\n    }).catch(function (e) {\n        ok(false, \"Error: \" + e.stack || e);\n    }).finally(start);\n});\n\nasyncTest(\"anyOf(integerArray)\", function () {\n    // Testing bug #11 Integer Indexes in anyOf handled incorrectly\n    db.files.put({ id: 9000, filename: \"new file 1\", folderId: firstFolderId });\n    db.files.put({ id: 10000, filename: \"new file 2\", folderId: firstFolderId });\n    db.files.where('id').anyOf([9000, 11000]).toArray(function (a) {\n        equal(a.length, 1, \"Should be only one found entry\");\n        equal(a[0].id, 9000, \"Item no 9000 should be found\");\n    }).finally(start);\n});\n\nasyncTest(\"anyOf(emptyArray)\", function () {\n    db.files.where('id').anyOf([]).toArray(function (a) {\n        equal(a.length, 0, \"Should be empty\");\n    }).catch(function (e) {\n        ok(false, \"Error: \" + e.stack || e);\n    }).finally(start);\n});\n\nasyncTest(\"equalsIgnoreCase()\", function () {\n\n    db.files.where(\"filename\").equalsIgnoreCase(\"hello\").toArray(function (a) {\n        equal(a.length, 2, \"Got two files\");\n        equal(a[0].filename, \"Hello\", \"First file is Hello\");\n        equal(a[1].filename, \"hello\", \"Second file is hello\");\n        start();\n    });\n\n});\n\nasyncTest(\"equalsIgnoreCase() 2\", function () {\n    var folder = new Folder();\n    folder.path = \"/etc\";\n    folder.description = \"Slasktratten\";\n    db.folders.add(folder).then(function (folderId) {\n        var filenames = [\"\", \"\\t \", \"AA\", \"AAron\", \"APAN JAPAN\", \"APAN japaö\", \"APGALEN\", \"APaLAT\", \"APaÖNSKAN\", \"APalster\",\n\t\t\t\t\"Aaron\", \"Apan JapaN\", \"Apan Japaa\", \"Apan Japan\", \"Gösta\",\n\t\t\t\t\"apan JA\", \"apan JAPA\", \"apan JAPAA\", \"apan JAPANer\",\n\t\t\t\t\"apan JAPAÖ\", \"apan japan\", \"apan japanER\", \"östen\"];\n\n        var fileArray = filenames.map(function (filename) {\n            var file = new File();\n            file.filename = filename;\n            file.folderId = folderId;\n            return file;\n        });\n\n        db.transaction(\"rw\", db.files, function () {\n            fileArray.forEach(function (file) {\n                db.files.add(file);\n            });\n\n            db.files.where(\"filename\").equalsIgnoreCase(\"apan japan\").toArray(function (a) {\n                equal(a.length, 4, \"There should be 4 files with that name\");\n                equal(a[0].filename, \"APAN JAPAN\", \"APAN JAPAN\");\n                equal(a[1].filename, \"Apan JapaN\", \"Apan JapaN\");\n                equal(a[2].filename, \"Apan Japan\", \"Apan Japan\");\n                equal(a[3].filename, \"apan japan\", \"apan japan\");\n            });\n        }).catch(function (e) {\n            ok(false, \"Error: \" + e.stack || e);\n        }).finally(start);\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n        start();\n    });\n});\n\nasyncTest(\"equalsIgnoreCase() 2 descending\", function () {\n    var folder = new Folder();\n    folder.path = \"/etc\";\n    folder.description = \"Slasktratten\";\n    db.folders.add(folder).then(function (folderId) {\n        var filenames = [\"\", \"\\t \", \"AA\", \"AAron\", \"APAN JAPAN\", \"APAN japaö\", \"APGALEN\", \"APaLAT\", \"APaÖNSKAN\", \"APalster\",\n\t\t\t\t\"Aaron\", \"Apan JapaN\", \"Apan Japaa\", \"Apan Japan\", \"Gösta\",\n\t\t\t\t\"apan JA\", \"apan JAPA\", \"apan JAPAA\", \"apan JAPANer\",\n\t\t\t\t\"apan JAPAÖ\", \"apan japan\", \"apan japanER\", \"östen\"];\n\n        var fileArray = filenames.map(function (filename) {\n            var file = new File();\n            file.filename = filename;\n            file.folderId = folderId;\n            return file;\n        });\n\n        db.transaction(\"rw\", db.files, function () {\n\n            fileArray.forEach(function (file) {\n                db.files.add(file);\n            });\n\n            db.files\n                .where(\"filename\").equalsIgnoreCase(\"apan japan\")\n                .and(function (f) { return f.folderId === folderId }) // Just for fun - only look in the newly created /etc folder.\n                .reverse()\n                .toArray(function (a) {\n                    equal(a.length, 4, \"There should be 4 files with that name in \" + folder.path);\n                    equal(a[0].filename, \"apan japan\", \"apan japan\");\n                    equal(a[1].filename, \"Apan Japan\", \"Apan Japan\");\n                    equal(a[2].filename, \"Apan JapaN\", \"Apan JapaN\");\n                    equal(a[3].filename, \"APAN JAPAN\", \"APAN JAPAN\");\n                });\n        }).catch(function (e) {\n            ok(false, \"Error: \" + e.stack || e);\n            start();\n        }).finally(start);\n    });\n});\n\nasyncTest(\"equalsIgnoreCase() 3 (first key shorter than needle)\", function () {\n    if (typeof idbModules !== 'undefined' && Dexie.dependencies.indexedDB === idbModules.shimIndexedDB) {\n        // Using indexedDBShim.\n        ok(false, \"This test would hang with IndexedDBShim as of 2015-05-07\");\n        start();\n        return;\n    }\n    db.transaction(\"rw\", db.files, function () {\n        db.files.clear();\n        db.files.add({ filename: \"Hello-there-\", folderId: 1 });\n        db.files.add({ filename: \"hello-there-\", folderId: 1 });\n        db.files.add({ filename: \"hello-there-everyone\", folderId: 1 });\n        db.files.add({ filename: \"hello-there-everyone-of-you!\", folderId: 1 });\n        // Ascending\n        db.files.where(\"filename\").equalsIgnoreCase(\"hello-there-everyone\").toArray(function (a) {\n            equal(a.length, 1, \"Should find one file\");\n            equal(a[0].filename, \"hello-there-everyone\", \"First file is \" + a[0].filename);\n        });\n        // Descending\n        db.files.where(\"filename\").equalsIgnoreCase(\"hello-there-everyone\").reverse().toArray(function (a) {\n            equal(a.length, 1, \"Should find one file\");\n            equal(a[0].filename, \"hello-there-everyone\", \"First file is \" + a[0].filename);\n        });\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n    }).finally(start);\n});\n\nasyncTest(\"startsWithIgnoreCase()\", function () {\n    db.transaction(\"r\", db.folders, function () {\n\n        db.folders.count(function (count) {\n            ok(true, \"Number of folders in database: \" + count);\n            db.folders.where(\"path\").startsWithIgnoreCase(\"/\").toArray(function (a) {\n                equal(a.length, count, \"Got all folder objects because all of them starts with '/'\");\n            });\n        });\n\n        db.folders.where(\"path\").startsWithIgnoreCase(\"/usr\").toArray(function (a) {\n            equal(a.length, 6, \"6 folders found: \" + a.map(function (folder) { return '\"' + folder.path + '\"' }).join(', '));\n        });\n\n        db.folders.where(\"path\").startsWithIgnoreCase(\"/usr\").reverse().toArray(function (a) {\n            equal(a.length, 6, \"6 folders found in reverse mode: \" + a.map(function(folder){ return '\"' + folder.path + '\"' }).join(', '));\n        });\n\n    }).then(function(){\n        ok(true, \"Transaction complete\");\n    }).catch(function(e) {\n        ok(false, e.stack || e);\n    }).finally(function () {\n        start();\n    });\n});\n\nasyncTest(\"queryingNonExistingObj\", function () {\n    db.files.where(\"filename\").equals(\"fdsojifdsjoisdf\").toArray(function (a) {\n        equal(a.length, 0, \"File fdsojifdsjoisdf was not found\");\n    }).catch(function (e) {\n        ok(false, e.stack || e);\n    }).finally(start);\n});\n\nif (!supports(\"compound\")) {\n    test(\"compound-index\", ()=>ok(true, \"SKIPPED - COMPOUND UNSUPPORTED\"));\n    test(\"compound-primkey (Issue #37)\", ()=>ok(true, \"SKIPPED - COMPOUND UNSUPPORTED\"));\n    test(\"Issue #31 - Compound Index with anyOf\", ()=>ok(true, \"SKIPPED - COMPOUND UNSUPPORTED\"));\n    test(\"Erratic behavior of between #190\", ()=>ok(true, \"SKIPPED - COMPOUND UNSUPPORTED\"));\n} else {\n    asyncTest(\"compound-index\", 2, function () {\n        db.transaction(\"r\", db.files, function () {\n            db.files.where(\"[filename+extension]\").equals([\"README\", \".TXT\"]).toArray(function (a) {\n                equal(a.length, 1, \"Found one file by compound index search\");\n                equal(a[0].filename, \"README\", \"The found file was README.TXT\");\n            });\n        }).catch(function (e) {\n            ok(false, e + \". Expected to fail on IE10/IE11 - no support compound indexs.\");\n        }).finally(start);\n    });\n\n    asyncTest(\"compound-primkey (Issue #37)\", function () {\n        db.transaction('rw', db.people, function () {\n            db.people.add({name: \"Santaclaus\", number: 123});\n            db.people.add({name: \"Santaclaus\", number: 124});\n            db.people.add({name: \"Santaclaus2\", number: 1});\n            return db.people.get([\"Santaclaus\", 123]);\n        }).then(function (santa) {\n            ok(!!santa, \"Got santa\");\n            equal(santa.name, \"Santaclaus\", \"Santa's name is correct\");\n            equal(santa.number, 123, \"Santa's number is correct\");\n\n            return db.people.where(\"[name+number]\").between([\"Santaclaus\", 1], [\"Santaclaus\", 200]).toArray();\n        }).then(function (santas) {\n            equal(santas.length, 2, \"Got two santas\");\n        }).catch(function (e) {\n            ok(false, \"Failed (will fail in IE without polyfill):\" + e);\n        }).finally(start);\n    });\n\n    asyncTest(\"Issue #31 - Compound Index with anyOf\", function () {\n        db.files\n            .where(\"[filename+extension]\")\n            .anyOf([[\"hello\", \".exe\"], [\"README\", \".TXT\"]])\n            .toArray(function (a) {\n                equal(a.length, 2, \"Should find two files\");\n                equal(a[0].filename, \"README\", \"First comes the uppercase README.TXT\");\n                equal(a[1].filename, \"hello\", \"Second comes the lowercase hello.exe\");\n\n            }).catch(function (e) {\n            ok(false, \"Failed (will fail in IE without polyfill):\" + e);\n        }).finally(start);\n    });\n\n    asyncTest(\"Erratic behavior of between #190\", ()=>{\n        db.transaction(\"rw\", db.chart, function() {\n            var chart = [];\n            for (var r=1; r<=2; r++) {\n                for (var c=1; c<=150; c++) {\n                    chart.push({patno: 1,\n                        row: r,\n                        col: c,\n                        sym: 1});\n                }\n            }\n            db.chart.bulkAdd(chart);\n        }).then(function () {\n            var grid = [],\n                x1 = 91,\n                x2 = 130;\n            return db.chart.where(\"[patno+row+col]\").between([1, 1, x1], [1, 1, x2], true, true).each(cell => {\n                grid.push(cell.sym);\n            }).then(function() {\n                equal(grid.length, 40, \"Should find 40 cells\");\n                //console.log(\"range \" + x1 + \"-\" + x2 + \" found \" + grid.length);\n            });\n        }).catch(e => {\n            ok(false, \"Error: \" + e + \" (Will fail in IE and Edge due to lack of compound primary keys)\");\n        }).finally(start);\n    });\n}\n\nasyncTest(\"above, aboveOrEqual, below, belowOrEqual, between\", 32, function () {\n    db.folders.where('id').above(firstFolderId + 4).toArray(function (a) {\n        equal(a.length, 4, \"Four folders have id above 5\");\n        equal(a[0].path, \"/usr/local/var\");\n        equal(a[1].path, \"/USR/local/VAR\");\n        equal(a[2].path, \"/var\");\n        equal(a[3].path, \"/var/bin\");\n    }).then(function () {\n        return db.folders.where('id').aboveOrEqual(firstFolderId + 4).toArray(function (a) {\n            equal(a.length, 5, \"Five folders have id above or equal 5\");\n            equal(a[0].path, \"/usr/local/src\");\n            equal(a[1].path, \"/usr/local/var\");\n            equal(a[2].path, \"/USR/local/VAR\");\n            equal(a[3].path, \"/var\");\n            equal(a[4].path, \"/var/bin\");\n        });\n    }).then(function () {\n        return db.folders.where('id').below(firstFolderId + 4).toArray(function (a) {\n            equal(a.length, 4, \"Four folders have id below 5\");\n            equal(a[0].path, \"/\");\n            equal(a[1].path, \"/usr\");\n            equal(a[2].path, \"/usr/local\");\n            equal(a[3].path, \"/usr/local/bin\");\n        });\n    }).then(function () {\n        return db.folders.where('id').belowOrEqual(firstFolderId + 4).toArray(function (a) {\n            equal(a.length, 5, \"Five folders have id below or equal to 5\");\n            equal(a[0].path, \"/\");\n            equal(a[1].path, \"/usr\");\n            equal(a[2].path, \"/usr/local\");\n            equal(a[3].path, \"/usr/local/bin\");\n            equal(a[4].path, \"/usr/local/src\");\n        });\n    }).then(function () {\n        return db.folders.where('id').between(firstFolderId, firstFolderId + 1).toArray(function (a) {\n            equal(a.length, 1, \"One folder between 1 and 2\");\n            equal(a[0].id, firstFolderId, \"Found item is number 1\");\n        });\n    }).then(function () {\n        return db.folders.where('id').between(firstFolderId, firstFolderId + 1, true, false).toArray(function (a) {\n            equal(a.length, 1, \"One folder between 1 and 2 (including lower but not upper)\");\n            equal(a[0].id, firstFolderId, \"Found item is number 1\");\n        });\n    }).then(function () {\n        return db.folders.where('id').between(firstFolderId, firstFolderId + 1, false, true).toArray(function (a) {\n            equal(a.length, 1, \"One folder between 1 and 2 (including upper but not lower)\");\n            equal(a[0].id, firstFolderId + 1, \"Found item is number 2\");\n        });\n    }).then(function () {\n        return db.folders.where('id').between(firstFolderId, firstFolderId + 1, false, false).toArray(function (a) {\n            equal(a.length, 0, \"Zarro folders between 1 and 2 (neither including lower nor upper)\");\n        });\n    }).then(function () {\n        return db.folders.where('id').between(firstFolderId, firstFolderId + 1, true, true).toArray(function (a) {\n            equal(a.length, 2, \"Two folder between 1 and 2 (including both lower and upper)\");\n            equal(a[0].id, firstFolderId, \"Number 1 among found items\");\n            equal(a[1].id, firstFolderId + 1, \"Number 2 among found items\");\n        });\n    }).catch(function (err) {\n        ok(false, err.stack || err);\n    }).finally(start);\n});\n\nasyncTest(\"notEqual\", function () {\n    db.folders.where('path').notEqual(\"/usr/local\").sortBy(\"path\", function (result) {\n        result = result.map(function(x) { return x.path; });\n        equal(JSON.stringify(result,null,4), JSON.stringify([\n            \"/\",\n            \"/USR/local/VAR\",\n            \"/usr\",\n            //\"/usr/local\"\n            \"/usr/local/bin\",\n            \"/usr/local/src\",\n            \"/usr/local/var\",\n            \"/var\",\n            \"/var/bin\"\n        ],null,4), \"/usr/local should be removed\");\n    }).catch(function (err) {\n        ok(false, err.stack || err);\n    }).finally(start);\n});\n\nasyncTest(\"noneOf\", function () {\n    db.folders.where('path').noneOf(\"/usr/local\", \"/\", \"/var/bin\", \"not existing key\").sortBy(\"path\", function (result) {\n        result = result.map(function (x) { return x.path; });\n        equal(JSON.stringify(result, null, 4), JSON.stringify([\n            //\"/\",\n            \"/USR/local/VAR\",\n            \"/usr\",\n            //\"/usr/local\"\n            \"/usr/local/bin\",\n            \"/usr/local/src\",\n            \"/usr/local/var\",\n            \"/var\",\n            //\"/var/bin\"\n        ], null, 4), \"Only items not specified in query should come into result\");\n    }).catch(function (err) {\n        ok(false, err.stack || err);\n    }).finally(start);\n});\n\nasyncTest(\"noneOf keys\", function () {\n    db.folders.where('path').noneOf(\"/usr/local\", \"/\", \"/var/bin\", \"not existing key\").keys(function (result) {\n        result = result.sort(function(a, b) { return a < b ? -1 : a === b ? 0 : 1; });\n        equal(JSON.stringify(result, null, 4), JSON.stringify([\n            //\"/\",\n            \"/USR/local/VAR\",\n            \"/usr\",\n            //\"/usr/local\"\n            \"/usr/local/bin\",\n            \"/usr/local/src\",\n            \"/usr/local/var\",\n            \"/var\",\n            //\"/var/bin\"\n        ], null, 4), \"Only keys not specified in query should come into result\");\n    }).catch(function (err) {\n        ok(false, err.stack || err);\n    }).finally(start);\n});\n\nasyncTest(\"inAnyOfRanges\", function () {\n    db.transaction('rw', db.friends, function () {\n        db.friends.bulkAdd([\n            { name: \"Simon\", age: 3 },\n            { name: \"Tyra\", age: 0 },\n            { name: \"David\", age: 42 },\n            { name: \"Ylva\", age: 40 },\n            { name: \"Ann-Sofie\", age: 72 }]\n        ).then(function () {\n            //equal(errors.length, 0, \"bulkAdd() succeeded\");\n            return db.friends.where('age').inAnyRange([[0, 3], [65, Infinity]]).toArray();\n        }).then (function (result) {\n            equal(result.length, 2, \"Should give us two persons\");\n            equal(result[0].name, \"Tyra\", \"First is Tyra\");\n            equal(result[1].name, \"Ann-Sofie\", \"Second is Ann-Sofie\");\n            return db.friends.where(\"age\").inAnyRange([[0, 3], [65, Infinity]], { includeUppers: true }).toArray();\n        }).then(function (result) {\n            equal(result.length, 3, \"Should give us three persons\");\n            equal(result[0].name, \"Tyra\", \"First is Tyra\");\n            equal(result[1].name, \"Simon\", \"Second is Simon\");\n            equal(result[2].name, \"Ann-Sofie\", \"Third is Ann-Sofie\");\n            return db.friends.where(\"age\").inAnyRange([[0, 3], [65, Infinity]], { includeLowers: false }).toArray();\n        }).then(function (result) {\n            equal(result.length, 1, \"Should give us one person\");\n            equal(result[0].name, \"Ann-Sofie\", \"Ann-Sofie is the only match\");\n            return db.friends.where(\"age\").inAnyRange([[40, 40], [40, 40], [40, 41], [41, 41], [42, 42]], { includeUppers: true }).toArray();\n        }).then(function (result) {\n            equal(result.length, 2, \"Should give us two persons\");\n            equal(result[0].name, \"Ylva\", \"First is Ylva\");\n            equal(result[1].name, \"David\", \"Second is David\");\n        });\n    }).catch(function (err) {\n        ok(false, err.stack || err);\n    }).finally(start);\n});\n\nasyncTest(\"anyOfIgnoreCase\", function () {\n    db.transaction('r', db.folders, db.files, function () {\n        db.folders.where('path').anyOfIgnoreCase(\"/usr/local/var\", \"/\").toArray(function (result) {\n            equal(result.length, 3);\n            equal(result[0].path, \"/\");\n            equal(result[1].path, \"/USR/local/VAR\");\n            equal(result[2].path, \"/usr/local/var\");\n            return db.folders.where('path').anyOfIgnoreCase(\"/usr/local/var\", \"/\").reverse().toArray();\n        }).then(function (result) {\n            equal(result.length, 3);\n            equal(result[0].path, \"/usr/local/var\");\n            equal(result[1].path, \"/USR/local/VAR\");\n            equal(result[2].path, \"/\");\n            return db.files.where('filename').anyOfIgnoreCase([\"hello\", \"world\", \"readme\"]).toArray();\n        }).then(function (result) {\n            equal(result.length, 4);\n            equal(result[0].filename, \"Hello\");\n            equal(result[1].filename, \"README\");\n            equal(result[2].filename, \"hello\");\n            equal(result[3].filename, \"world\");\n        });\n    }).catch(function (err) {\n        ok(false, err.stack || err);\n    }).finally(start);\n});\nasyncTest(\"anyOfIgnoreCase(2)\", function () {\n    db.files.where('filename').anyOfIgnoreCase([\"hello\", \"world\", \"readme\"]).toArray(function (result) {\n        equal(result.length, 4);\n        equal(result[0].filename, \"Hello\");\n        equal(result[1].filename, \"README\");\n        equal(result[2].filename, \"hello\");\n        equal(result[3].filename, \"world\");\n    }).catch(function (err) {\n        ok(false, err.stack || err);\n    }).finally(start);\n});\n\nasyncTest(\"startsWithAnyOfIgnoreCase()\", function () {\n\n    function runTheTests(mippler) {\n        /// <param name=\"mippler\" value=\"function(x){return x;}\"></param>\n\n        //\n        // Basic Flow:\n        //\n        return mippler(db.folders\n            .where('path').startsWithAnyOfIgnoreCase('/usr/local', '/var'))\n            .toArray(function (result) {\n                equal(result.length, 7, \"Query should match 7 folders\");\n                ok(result.some(function (x) { return x.path == '/USR/local/VAR'; }), '/USR/local/VAR');\n                ok(result.some(function (x) { return x.path == '/usr/local'; }), '/usr/local');\n                ok(result.some(function (x) { return x.path == '/usr/local/bin'; }), '/usr/local/bin');\n                ok(result.some(function (x) { return x.path == '/usr/local/src'; }), '/usr/local/src');\n                ok(result.some(function (x) { return x.path == '/usr/local/var'; }), '/usr/local/var');\n                ok(result.some(function (x) { return x.path == '/var'; }), '/var');\n                ok(result.some(function (x) { return x.path == '/var/bin'; }), '/var/bin');\n\n                //\n                // Require a slash at beginning (and use an array of strings as argument instead)\n                //\n                return mippler(db.folders\n                    .where('path').startsWithAnyOfIgnoreCase(['/usr/local/', '/var/']))\n                    .toArray();\n\n            }).then(function (result) {\n                equal(result.length, 5, \"Query should match 5 folders\");\n                ok(result.some(function (x) { return x.path == '/USR/local/VAR'; }), '/USR/local/VAR');\n                ok(result.some(function (x) { return x.path == '/usr/local/bin'; }), '/usr/local/bin');\n                ok(result.some(function (x) { return x.path == '/usr/local/src'; }), '/usr/local/src');\n                ok(result.some(function (x) { return x.path == '/usr/local/var'; }), '/usr/local/var');\n                ok(result.some(function (x) { return x.path == '/var/bin'; }), '/var/bin');\n\n                //\n                // Some specialities\n                //\n                return Dexie.Promise.all(\n                    mippler(db.folders.where('path').startsWithAnyOfIgnoreCase([])).count(), // Empty\n                    mippler(db.folders.where('path').startsWithAnyOfIgnoreCase('/var', '/var', '/var')).count(), // Duplicates\n                    mippler(db.folders.where('path').startsWithAnyOfIgnoreCase('')).count(), // Empty string should match all\n                    mippler(db.folders).count(),\n                    mippler(db.folders.where('path').startsWithAnyOfIgnoreCase('nonexisting')).count() // Non-existing match\n                );\n            }).then(function (results) {\n                equal(results[0], 0, \"startsWithAnyOfIgnoreCase([]).count() == 0\");\n                equal(results[1], 2, \"startsWithAnyOfIgnoreCase('/var', '/var', '/var').count() == 2\");\n                equal(results[2], results[3], \"startsWithAnyOfIgnoreCase('').count() == db.folders.count()\");\n                equal(results[4], 0, \"startsWithAnyOfIgnoreCase('nonexisting').count() == 0\");\n\n                //\n                // Error handling\n                //\n\n                return mippler(db.folders.where('path').startsWithAnyOfIgnoreCase([null, '/'])).toArray(function (res) {\n                    ok(false, \"Should not succeed to have null in parameter\");\n                }).catch(function (e) {\n                    ok(true, \"As expected: failed to have null in arguments: \" + e);\n                });\n            });\n    }\n\n    // Run tests without transaction and without reverse()\n    runTheTests(function (x) { return x; }).then(function () {\n        ok(true, \"FINISHED NORMAL TEST!\");\n        // Run tests with reverse()\n        return runTheTests(function (x) { return x.reverse(); });\n    }).then(function () {\n        ok(true, \"FINISHED REVERSE TEST!\");\n        // Run tests within a transaction\n        return db.transaction('r', db.folders, db.files, function () {\n            return runTheTests(function (x) { return x; });\n        });\n    }).then(function () {\n        ok(true, \"FINISHED TRANSACTION TEST!\");\n    }).catch(function (e) {\n        ok(false, \"Error: \" + e);\n    }).finally(start);\n});\n\npromisedTest(\"where({key: value})\", async () => {\n    let readme = await db.files.where({filename: \"README\"}).first();\n    ok (readme, 'Should get a result for db.files.get({filename: \"README\"});');\n    equal (readme.extension, \".TXT\", \"Should get README.TXT\");\n    readme = await db.files.get({filename: \"README\", extension: \".TXT\"});\n    ok (readme, 'Should get a result for db.files.get({filename: \"README\", extension: \".TXT\"});');\n    let noResult = await db.files.get({filename: \"apa\", extension: \"otto\"});\n    ok (!noResult, \"Should not get a result when querying non-existing stuff\");\n\n    // Friends have single indexes on \"name\" and \"age\"\n    await db.friends.add({name: \"Ulla Bella\", number: 888, age: 88});\n    // People have compound index for [name, number]\n    await db.chaps.add({name: \"Ulla Bella\", number: 888, age: 88});\n    // Folders haven't indexed any of \"name\", \"number\" or \"age\"\n    await db.folders.add({name: \"Ulla Bella\", number: 888, age: 88});\n\n    let ullaBella1 = await db.friends.get({name: \"Ulla Bella\", number: 888});\n    ok(!!ullaBella1, \"Should be able to query multiple columns even when only one of them is indexed\");\n    let ullaBella2 = await db.chaps.get({name: \"Ulla Bella\", number: 888});\n    ok(!!ullaBella2, \"Should be able to query multiple columns. This time utilizing compound index.\");\n    let ullaBella3 = await db.chaps.get({number: 888, name: \"Ulla Bella\"});\n    ok(!!ullaBella3, \"Should be able to utilize compound index no matter the order of criterias.\");\n    await db.folders.get({name: \"Ulla Bella\", number: 888}).then(ulla => {\n        ok(false, \"Should not get Ulla Bella when no index was found\");\n    }).catch('SchemaError', e => {\n        ok(true, \"Got SchemaError because we're not utilizing any index at all: \" + e);\n    });\n});\n\npromisedTest(\"where({notIndexed: value}) should not return emtpy result\", async () => {\n    await db.chaps.add({name: \"Chaplin\", number: 1, age: 134, notIndexed: 'foo'});\n    try {\n        let chaplin = await db.chaps.where({name: \"Chaplin\", notIndexed: \"foo\"}).first();\n        if (!chaplin) {\n            ok(false, \"Got empty result when data exists matching the query. If we're not using an index we should not get an empty result but an error.\");\n        } else {\n            ok(chaplin,\n                \"Ok, so we got a result even though 'notIndexed' is not indexed at all. \"+\n                \"This is not expected but ok if the result would be valid\"\n            );\n            equal(chaplin.name, \"Chaplin\", \"Got the correct data Chaplin!\");\n        }\n    } catch (e) {\n        ok(true, `Got error: \"${e}\" because 'notIndexed' is not indexed at all. This is also ok.`);\n    }\n});\n\n\n\npromisedTest(\"orderBy(['idx1','idx2'])\", async () => {\n    if (!supports(\"compound\")) {\n        ok(true, \"Browser does not support compound indexes. Ignoring test.\");\n        return;\n    }\n    db.files.add({filename: \"hello\", extension: \".bat\"});\n    let files = await db.files.orderBy([\"filename\", \"extension\"]).toArray();\n    equal (files.length, 5, \"Should be 5 files in total that has both filename and extension\");\n    equal (files.map(f=>f.filename+f.extension).join(','), \"README.TXT,hello.bat,hello.exe,hello-there.exe,world.js\",\n        'Files should be ordered according to the orderBy query');\n});\n\npromisedTest(\"Issue #696 - Query on multiple multi-entry indexes doesn't seem to work\", async () => {\n    if (!supports(\"multientry\")) {\n        ok(true, \"No support for multiEntry in browser. Skippping.\");\n        return;\n    }\n    await db.multiMulti.bulkAdd([\n        {id: 1, name: \"Foo\", age: 42, categories: ['cat_a','cat_b'], tags: ['tag_a','tag_b']},\n        {id: 2, name: \"Bar\", age: 32, categories: ['cat_b','cat_c'], tags: ['tag_b','tag_c']}\n    ]);\n\n    const cat_a = await db.multiMulti.where({categories: 'cat_a'}).distinct().toArray();\n    equal(cat_a.length, 1, \"Should get one item\");\n    const cat_a_tag_a = await db.multiMulti.where({'categories':'cat_a','tags':'tag_a'})\n        .distinct()\n        .toArray();\n    equal(cat_a_tag_a.length, 1, \"Should get one item here as well\");\n});\n\npromisedTest(\"Virtual Index\", async () => {\n    if (!supports('compound'))\n        return ok(true, \"SKIPPED - COMPOUND UNSUPPORTED\");\n\n    await db.chaps.bulkAdd([{\n        name: \"David\",\n        number: 2,\n        age: 29\n    },{\n        name: \"David\",\n        number: 3,\n        age: 39\n    },{\n        name: \"David\",\n        number: 1,\n        age: 49\n    },{\n        name: \"Mambo\",\n        number: 5,\n        age: 55\n    }]);\n\n    // Verify that Dexie can use the [name+number] index to query name only:\n    const davids = await db.chaps.where({name: \"David\"}).toArray();\n    equal(davids.length, 3, \"There should be 3 Davids in the result\");\n    const mambosNumber5 = await db.chaps.where({name: \"Mambo\", number: 5}).toArray();\n    equal(mambosNumber5.length, 1, \"There should be 1 Mambo no 5 in the result\");\n    // Verify that equalsIgnoreCase also works:\n    const daves = await db.chaps.where('name').equalsIgnoreCase('david').toArray();\n    equal(JSON.stringify(daves.map(({name, number}) => ({name, number})), null, 2),\n        JSON.stringify([\n            {name: \"David\", number: 1},\n            {name: \"David\", number: 2},\n            {name: \"David\", number: 3}\n        ], null, 2), \"equalsIgnoreCase() should work with virtual indexes\");\n    //equal(daves.length, 3, \"There should be 3 davids in the result when using equalsIgnoreCase()\");\n});\n\npromisedTest(\"WhereClause.equals(invalid key)\", async () => {\n    await db.files.where(\"filename\").equals(null).first().then(()=>{\n        ok(false, \"db.files.where('filename').equals(null) must fail but it didnt!\");\n    }).catch(error => {\n        ok(true, `db.files.where('filename').equals(null) failed as expected (with ${error})`);\n    });\n    await db.files.where(\"filename\").equals(undefined).first().then(()=>{\n        ok(false, \"db.files.where('filename').equals(undefined) must fail but it didnt!\");\n    }).catch(error => {\n        ok(true, `db.files.where('filename').equals(undefined) failed as expected (with ${error})`);\n    });\n    await db.files.where(\"filename\").equals(function(){}).first().then(()=>{\n        ok(false, \"db.files.where('filename').equals(function(){}) must fail but it didnt!\");\n    }).catch(error => {\n        ok(true, `db.files.where('filename').equals(function(){}) failed as expected (with ${error})`);\n    });\n});\n"
  },
  {
    "path": "test/tests-yield.js",
    "content": "﻿import Dexie from 'dexie';\nimport {module, stop, start, asyncTest, equal, ok} from 'QUnit';\nimport {isSafariPrivateMode, resetDatabase} from './dexie-unittest-utils';\n\nconst db = new Dexie(\"TestYieldDb\");\nconst async = Dexie.async;\nconst spawn = Dexie.spawn;\n\ndb.version(1).stores({\n    friends: '++id,name,*groups',\n    pets: '++id,name'\n});\n\nmodule(\"yield\", {\n    setup: function () {\n        stop();\n        resetDatabase(db).catch(function (e) {\n            ok(false, \"Error resetting database: \" + e.stack);\n        }).finally(start);\n    },\n    teardown: function () {\n    }\n});\n\nasyncTest (\"db.transaction() with yield\", async(function* () {\n    var finallyWasReached = false;\n    try {\n        yield db.transaction('rw', 'friends', 'pets', function* () {\n            // Add a cat and store it's final ID\n            var catId = yield db.pets.add({ name: \"Tito\", kind: \"cat\" });\n            // Add a dog in the same way.\n            var dogId = yield db.pets.add({ name: \"Josephina\", kind: \"dog\" });\n            // Add a friend who owns the pets\n            db.friends.add({ name: \"Gurra G\", pets: [catId, dogId] });\n\n            var gurra = yield db.friends.where('name').equals(\"Gurra G\").first();\n            ok(!!gurra, \"Gurra could be found with yield\");\n\n            // Now retrieve the pet objects that Gurra is referring to:\n            var gurrasPets = yield db.pets.where('id').anyOf(gurra.pets).toArray();\n            equal(gurrasPets.length, 2, \"Gurras all two pets could be retrieved via yield\");\n            equal(gurrasPets[0].kind, \"cat\", \"Gurras first pet is a cat\");\n            equal(gurrasPets[1].kind, \"dog\", \"Gurras second pet is a dog\");\n        });\n\n    } catch(e) {\n        ok(false, \"Caught error: \" + e);\n    } finally {\n        finallyWasReached = true;\n    }\n    ok(finallyWasReached, \"finally was reached\");\n    start();\n}));\n\nasyncTest (\"Catching indexedDB error event\", 2, async(function* ()\n{\n    try {\n        yield db.pets.add({id: 1, name: \"Tidi\", kind: \"Honeybadger\"});\n        ok(true, \"Should come so far\");\n        yield db.pets.add({id: 1, name: \"Todoo\", kind: \"Snake\"}); // Should generate an IDB error event!\n        ok(false, \"Should not come here\");\n    } catch (e) {\n        equal(e.name, \"ConstraintError\", \"Caught indexedDB DOMError event ConstraintError\");\n    }\n    start();\n}));\n\nasyncTest (\"Catching error prevents transaction from aborting\", 5, async(function* () {\n    try {\n        yield db.transaction('rw', 'pets', function*(){\n            try {\n                yield db.pets.add({id: 1, name: \"Tidi\", kind: \"Honeybadger\"});\n                ok(true, \"Should come so far\");\n                yield db.pets.add({id: 1, name: \"Todoo\", kind: \"Snake\"}); // Should generate an IDB error event!\n                ok(false, \"Should not come here\");\n            } catch (e) {\n                equal(e.name, \"ConstraintError\", \"Caught indexedDB DOMError event ConstraintError\");\n            }\n        });\n        ok (true, \"Should come here - transaction committed because we caught the error\");\n\n        ok ((yield db.pets.get(1)), \"A pet with ID 1 exists in DB\");\n        equal ((yield db.pets.get(1)).name, \"Tidi\", \"It was Tidi in the first position\");\n    } finally {\n        start();\n    }\n}));\n\nasyncTest(\"Transaction not committing when not catching error event\", 4, async(function* ()\n{\n    try {\n        yield db.transaction('rw', 'pets', function* ()\n        {\n            yield db.pets.add({id: 1, name: \"Tidi\", kind: \"Honeybadger\"});\n            ok(true, \"Should come so far\");\n            yield db.pets.add({id: 1, name: \"Todoo\", kind: \"Snake\"}); // Should generate an IDB error event!\n            ok(false, \"Should not come here\");\n        });\n        ok(false, \"Should not come here\");\n\n    } catch (e) {\n\n        ok(true, \"Transaction should fail\");\n        equal (e.name, \"ConstraintError\", \"Error caught was a ConstraintError!\");\n        if (isSafariPrivateMode) {\n            ok(true, \"Safari private mode: Ignoring test due to IndexedDB bug with Safari private mode\");\n        } else {\n            equal ((yield db.pets.count()), 0, \"Pets table should still be empty because transaction failed\");\n        }\n\n    } finally {\n        start();\n    }\n}));\n\nasyncTest(\"Should allow yielding a non-promise\", async(function* () {\n    try {\n        var x = yield 3;\n        equal(x, 3, \"Could yield a non-promise\");\n    } catch (e) {\n        ok(false, \"Yielding a non-Thenable wasn't be allowed\");\n    } finally {\n        start();\n    }\n}));\n\nasyncTest(\"Should allow yielding an array with a mix of values and thenables\", async(function* () {\n    try {\n        var results = yield [1, 2, Dexie.Promise.resolve(3)];\n        equal(results.length, 3, \"Yielded array is of size 3\");\n        equal(results[0], 1, \"First value is 1\");\n        equal(results[1], 2, \"Second value is 2\");\n        equal(results[2], 3, \"Third value is 3\");\n    } catch (e) {\n        ok(false, \"Got exception when trying to do yield an array of mixed values/promises\");\n    } finally {\n        start();\n    }\n}));\n\nasyncTest(\"Should allow yielding an array of non-promises only\", async(function* () {\n    try {\n        var results = yield [1,2,3];\n        equal(results.length, 3, \"Yielded array is of size 3\");\n        equal(results[0], 1, \"First value is 1\");\n        equal(results[1], 2, \"Second value is 2\");\n        equal(results[2], 3, \"Third value is 3\");\n    } catch (e) {\n        ok(false, e);\n    } finally {\n        start();\n    }\n}));\n\nasyncTest(\"Should allow yielding an empty array\", async(function* () {\n    try {\n        var results = yield [];\n        equal(results.length, 0, \"Yielded array is of size 0\");\n    } catch (e) {\n        ok(false, e);\n    } finally {\n        start();\n    }\n}));\n\n\nasyncTest(\"Should allow yielding an array of different kind of any kind of promise\", function () {\n    spawn (function*()\n    {\n        var results = yield [Promise.resolve(1), Dexie.Promise.resolve(2), Promise.resolve(3)];\n        equal(results.length, 3, \"Yielded array is of size 3\");\n        equal(results[0], 1, \"First value is 1\");\n        equal(results[1], 2, \"Second value is 2\");\n        equal(results[2], 3, \"Third value is 3\");\n        return 4;\n    }).then (function(x) {\n        equal(x, 4, \"Finally got the value 4\");\n    }).catch (function(e) {\n        ok(false, \"Something is rotten in the state of Denmark: \" + e);\n    }).then(start);\n});\n\nasyncTest(\"Throw after yield 1\", function () {\n    spawn (function*()\n    {\n        try {\n            yield Promise.resolve(3);\n            ok(true, \"yielded a value\");\n            throw \"error\";\n        } catch (e) {\n            ok(e === \"error\", \"Catched exception: \" + e);\n        }\n        return 4;\n    }).then (function(x) {\n        equal(x, 4, \"Finally got the value 4\");\n    }).catch (function(e) {\n        ok(false, \"Something is rotten in the state of Denmark: \" + e);\n    }).then(start);\n});\n\nasyncTest(\"Throw after yield 2\", function () {\n    Promise.resolve(spawn (function*()\n    {\n        try {\n            yield 3;\n            ok(true, \"yielded a value\");\n            throw \"error\";\n        } catch (e) {\n            ok(e === \"error\", \"Catched exception: \" + e);\n        }\n        return 4;\n    })).then (function(x) {\n        equal(x, 4, \"Finally got the value 4\");\n    }).catch (function(e) {\n        ok(false, \"Something is rotten in the state of Denmark: \" + e);\n    }).then(start);\n});\n        \nasyncTest(\"Throw before yield\", function () {\n    Promise.resolve(spawn (function*()\n    {\n        try {\n            throw \"error\";\n        } catch (e) {\n            ok(e === \"error\", \"Catched exception: \" + e);\n        }\n        return 4;\n    })).then (function(x) {\n        equal(x, 4, \"Finally got the value 4\");\n    }).catch (function(e) {\n        ok(false, \"Something is rotten in the state of Denmark: \" + e);\n    }).then(start);\n});\n\nasyncTest(\"Catch rejected promise\", function () {\n    spawn (function*() {\n        try {\n            yield new Promise(function(resolve, reject) { reject(\"fault fault!\"); });\n            ok(false, \"Shouldn't come here\");\n        } catch (e) {\n            ok(e === \"fault fault!\", \"Catched exception: \" + e);\n        }\n        return 4;\n    }).then (function(x) {\n        equal(x, 4, \"Finally got the value 4\");\n    }).catch (function(e) {\n        ok(false, \"Something is rotten in the state of Denmark: \" + e);\n    }).then(start);\n});\n\nasyncTest(\"Catch rejected promise in an array\", function () {\n    spawn (function*() {\n        try {\n            yield [1, 2, new Promise(function(resolve, reject) { reject(\"fault fault!\"); }), 4];\n            ok(false, \"Shouldn't come here\");\n        } catch (e) {\n            ok(e === \"fault fault!\", \"Catched exception: \" + e);\n        }\n        return 4;\n    }).then (function(x) {\n        equal(x, 4, \"Finally got the value 4\");\n    }).catch (function(e) {\n        ok(false, \"Something is rotten in the state of Denmark: \" + e);\n    }).then(start);\n});\n\nasyncTest(\"Should allow returning a promise\", function () {\n    spawn (function*()\n    {\n        return Promise.resolve(3);\n    }).then (function(result) {\n        equal(result, 3, \"Returning a directly should also be allowed\");\n    }).catch (function(e) {\n        ok(false, e);\n    }).then(start);\n});\n\nasyncTest(\"Should be able to do 'return yield Promise.resolve(x);'\", function () {\n    spawn (function*()\n    {\n        return yield Promise.resolve(3);\n    }).then (function() {\n        ok(true, \"Should be able to do 'return yield Promise.resolve(x);'\");\n    }).catch (function(e) {\n        ok(false, \"Error occurred: \" + e);\n    }).then(start);\n});\n\nasyncTest(\"Arrow functions and let\", async(function*() {\n    let x = yield [1, 2, Promise.resolve(3)];\n    let y = x.map(a => a - 1);\n    equal(y[0], 0);\n    equal(y[1], 1);\n    equal(y[2], 2);\n    start();\n}));\n\nasyncTest(\"Calling sub async function\", async(function*(){\n    var addFriend = async(function* addFriend(friend) {\n        let friendId = yield db.friends.add(friend);\n        return yield db.friends.get (friendId);\n    });\n\n    var deleteFriends = async(function* deleteFriends() {\n        return yield db.friends.where('name').anyOf(\"Foo\", \"Bar\").delete();\n    });\n\n    try {\n        let foo = yield addFriend({name: \"Foo\"});\n        let bar = yield addFriend({name: \"Bar\"});\n        ok(foo.name == \"Foo\", \"Foo got its name\");\n        ok(bar.name == \"Bar\", \"Bar got its name\");\n        let numDeleted = yield deleteFriends();\n        ok (true, numDeleted + \" friends successfully deleted\")\n    } catch (e) {\n        ok(false, e);\n    } finally {\n        start();\n    }\n}));\n"
  },
  {
    "path": "test/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"target\": \"es5\",\n    \"module\": \"es2015\",\n    \"importHelpers\": true,\n    \"strictNullChecks\": false,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": false,\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"es2021\", \"dom\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"outDir\": \"../tools/tmp/\",\n    \"sourceMap\": true,\n    \"rootDir\": \"..\",\n    \"downlevelIteration\": true\n  },\n  \"files\": [\n    \"tests-all.js\", \"./deepEqual.js\"\n  ]\n}\n"
  },
  {
    "path": "test/typings-test/test-extend-dexie.ts",
    "content": "\nimport {Dexie, Table, DexieConstructor, IndexableType} from '../../dist/dexie';\n\n//\n// Extend Dexie interface\n//\ndeclare module '../../dist/dexie' {\n    interface Table<T, TKey> {\n        extendedTableMethod() : any;\n    }\n    interface DbEvents {\n        (eventName: 'changes', subscriber: ()=>any): void;\n        (eventName: 'customEvent2', subscriber: ()=>any): void;\n        changes: DexieEvent;\n        customEvent2: DexieEvent;\n    }\n    interface DexieConstructor {\n        extendedStaticMethod (param1: string) : string;    \n    }\n    \n    interface Dexie {\n        extendedDBMethod() : any;\n    }\n}\n\nDexie.addons.push(db => {\n    db.Table.prototype.extendedTableMethod = ()=>{};\n    db.extendedDBMethod = ()=>{};\n    db.on.addEventType({\n        changes: 'asap'\n    });\n    db.on.addEventType('customEvent2', (a,b)=>()=>{a(); b();}, ()=>{});\n});\n\nDexie.extendedStaticMethod = param1 => param1;\n\n\n//var x: Dexie.Table<{name: string, age: number}, number>;\nvar db: Dexie = null as any as Dexie;\n\nvar x: Dexie.Promise = null as any as Dexie.Promise;\nvar x2 = Dexie.Promise.all([1]);\nx = x2;\n\n\nvar y: Dexie.Table<{hello: any}, number>;\nvar y2: Table<{hello: any}, number>;\nvar y3 = db.table<{hello: any}, number>(\"hello\");\ny2 = y3;\ny = y2;\n\n"
  },
  {
    "path": "test/typings-test/test-misc.ts",
    "content": "import Dexie from '../../dist/dexie';\n\nclass MyDb extends Dexie {\n  friends: Dexie.Table<{name, id}, number>;\n  constructor() {\n    super(\"MyDb\");\n    this.version(1).stores({\n      friends: '++id,age'\n    });\n    \n  }\n}\n/*\ninterface Addon<XTable=undefined,XCollection=undefined> {\n  (db: DB):void;\n  XTable?: XTable;\n  XCollection?: XCollection;\n}\n\ntype TblSchema<T=any,K=any> =  {value: T, key: K};\ntype Schema<TTblScheam extends TblSchema, TSchema extends {[tableName: string]: TTblScheam}> = {};\ninterface Friend {name: string, id: number};\nvar x: Schema<{friends: Friend, key: number}}>;\n\ntype Addons<A1 extends Addon=undefined,A2 extends Addon=undefined> = {0?: A1, 1?:A2};\ntype DBX<TSchema extends Schema, TAddons extends Addons> = {schema: TSchema, addons}\n\nclass DB<Ext1 =undefined,Ext2=undefined> {\n  constructor(options: {addons: {0: Ext1, 1:Ext2}}) {\n\n  }\n\n  get friends() {\n    return this.table<Friend, number>('friends');\n  }\n}\n*/"
  },
  {
    "path": "test/typings-test/test-typings.ts",
    "content": "/**\n * This file is only meant to compile, not run!\n * It tests Dexie.d.ts.\n */\n\nimport Dexie, { EntityTable, IndexableType, Table, add, replacePrefix } from '../../dist/dexie'; // Imports the source Dexie.d.ts file\nimport './test-extend-dexie';\nimport './test-updatespec';\nimport * as Y from 'yjs';\n\n// constructor overloads:\n{\n    let db = new Dexie('dbname');\n    db = new Dexie('dbname', {addons: []});\n    db = new Dexie('dbname', {autoOpen: false});\n    db = new Dexie('dbname', {indexedDB: indexedDB, IDBKeyRange: IDBKeyRange});\n    // Testing db.open(), but also catching its returned promise:\n    db.open().catch('OpenFailedError', ex => {\n        // ex will be Error interface\n        ex.name;\n    }).catch(ex => {\n        // ex will be ProbablyError interface, which helps out nicely with code completion.\n        console.error(ex.stack);\n    }).catch(Dexie.BulkError, ex => {\n        // When catching a specific error type, ex will implicitly have that type!\n        console.error(ex.failures.join(','));\n    });\n\n    // name\n    (()=>{let x:string = db.name;});\n    // tables\n    (()=>{let x:Dexie.Table<any,any>[] = db.tables;});\n    // verno\n    (()=>{let x:number = db.verno;});\n\n    //\n    // Use extended API from './test-extend-dexie.ts'\n    //\n    db.table('someTable').extendedTableMethod();\n    db.on.customEvent2.subscribe(x => {});\n    db.on('customEvent2', ()=>{});\n    Dexie.extendedStaticMethod('foo').toLowerCase();\n\n    // Promise compatibility\n    {\n        const func = (promise: Promise<any>) => {};\n        func(db.open());\n        class SomeClass {\n            method<T> (promise: Promise<T>) { return promise; }\n        }\n        new SomeClass().method(db.open()).then(db => db.backendDB().createObjectStore(\"something\"));\n    } \n\n    // version\n    db.version(1).stores({\n        table: 'anything here',\n    }).upgrade(trans => {\n        return trans.table('table').add({something: 1});\n    });\n\n}\n\n{\n    //\n    // Inherit Dexie\n    //\n    interface Friend {\n        id?: number;\n        name: string;\n        isGoodFriend: boolean;\n        address: {\n            city: string;\n        }\n    }\n\n    class Entity2 {\n        oid!: string;\n        prop1!: Date;\n    }\n\n    class BaseEntity {\n        oid!: string;\n        prop2!: Date;\n        foo(): void {\n            console.log('foo');\n        }\n    }\n\n    class Entity3 extends BaseEntity {\n        prop1!: Date;\n        foo2(): void {\n            console.log('foo');\n        }\n    }\n    interface CompoundKeyEntity {\n        firstName: string;\n        lastName: string;\n    }\n\n    class MyDatabase extends Dexie {\n        friends!: Dexie.Table<Friend, number>;\n        table2!: Dexie.Table<Entity2, string>;\n        table3!: Dexie.Table<Entity3, 'oid'>;\n        table4!: Dexie.Table<Entity3, string>;\n        table5!: Table;\n        compoundTable!: Dexie.Table<CompoundKeyEntity, [string, string]>;\n\n        constructor () {\n            super ('MyDatabase');\n            this.version(1).stores({\n                table1: '++id',\n                table2: 'oid',\n                table3: '++oid',\n                compoundTable: '[firstName+lastName]'\n            });\n        }\n    }\n\n    const fooAny: string = 'null'\n    const foo: IndexableType = fooAny\n\n    let db = new MyDatabase();\n\n    // Extended table method\n    db.friends.extendedTableMethod();\n    // Extended DB method\n    db.extendedDBMethod();\n    // Extended event\n    db.on('customEvent2', ()=>{});\n    // Transaction\n    db.transaction('rw', db.friends, ()=>{});\n\n    // Table.get\n    db.friends.get(1).then(friend => friend && friend.address.city);\n    db.friends.get(2, friend => friend ? friend.address.city : \"otherString\")\n        .then(friend => friend.charCodeAt(2))\n        .finally(()=>{});\n    db.friends.get({name: 'Kalle'})\n        .then(friend => friend && friend.name.toLowerCase() == \"kalle\")\n        .finally(()=>{});\n    db.friends.get({name: 'Kalle'}, friend => friend && friend.name.toLowerCase() == \"kalle\")\n        .catch(Dexie.AbortError, e => e.inner)\n        .finally(()=>{});\n\n    // Table.where\n    db.friends.where('name').equalsIgnoreCase('kalle').count(count => count.toExponential());\n    // Table.where with compound key\n    db.compoundTable.where('[firstName+lastName]').anyOf([['Kalle', 'Smith'], ['Fred', 'Smith']]);\n    db.compoundTable.where('[firstName+lastName]').anyOf(['Kalle', 'Smith'], ['Fred', 'Smith']);\n    db.compoundTable.where('[firstName+lastName]').noneOf([['Kalle', 'Smith'], ['Fred', 'Smith']]);\n    db.compoundTable.where('[firstName+lastName]').equals(['Kalle', 'Smith'])\n    db.compoundTable.where('[firstName+lastName]').notEqual(['Kalle', 'Smith'])\n    // Table.filter\n    db.friends.filter(friend => /kalle/.test(friend.name)).count();\n    // Table.count\n    db.friends.count();\n    db.friends.count(count => count.toExponential());\n    db.friends.count(count => count.toExponential()).then(exp => exp.toLowerCase());\n    db.friends.count(count => {count.toExponential(); return 7;}).then(exp => exp.toExponential());\n    // Table.offset\n    db.friends.offset(1).toArray();\n    // Table.limit\n    db.friends.limit(10).toArray();\n    // Table.each\n    db.friends.each(friend => friend.address);\n    // Table.toArray\n    db.friends.toArray().then(friends => friends[0].address);\n    db.friends.toArray(friends => friends[0].address).finally(()=>{});\n    // Table.toCollection\n    db.friends.toCollection().eachPrimaryKey(key => key.toExponential());\n    // Table.orderBy\n    db.friends.orderBy('name').eachPrimaryKey(key => key.toFixed());\n    // Table.bulkGet\n    db.friends.bulkGet([1, 2, 3]).then(friends => friends.map(\n        // friend => friend.address // should cause TS2532: Object is possibly 'undefined'\n        friend => friend === undefined ? \"missing\" : friend.address\n    ));\n\n    // Hooks\n    db.friends.hook('creating', function(key, friend) {\n        key.toFixed();\n        friend.isGoodFriend;\n        friend.address.city;\n        this.onsuccess = function(primKey) {\n            primKey.toFixed();\n        };\n        this.onerror = function(err) {\n            console.log('creating error', err);\n        };\n    });\n    db.friends.hook('reading', friend => {friend.isGoodFriend = true; return friend; });\n    db.friends.hook('updating', function(mods, key, friend) {\n        mods.valueOf();\n        key.toExponential();\n        friend.isGoodFriend;\n        friend.address.city;\n        this.onsuccess = function(updatedObj) {\n            updatedObj.isGoodFriend;\n            updatedObj.address.city;\n        };\n        this.onerror = function(err) {\n            console.log('updating error', err);\n        };\n    });\n    db.friends.hook('deleting', function(key, friend) {\n        key.toExponential();\n        friend.isGoodFriend;\n        friend.address.city;\n        this.onsuccess = function() {\n            console.log('deleting success');\n        };\n        this.onerror = function(err) {\n            console.log('deleting error', err);\n        };\n    });\n\n    // Issue #404\n    class NotFoundError extends Error {\n        constructor() {\n            super (\"Not found\");\n        }\n        get name() {\n            return \"NotFoundError\";\n        }\n    }\n\n    db.friends.get({keyPath1: 'value1', keyPath2: 'value2'}, friend => {\n        if (!friend) throw new NotFoundError();\n        return friend;\n    }).then (friend => {\n        console.log(friend.address.city);\n    }).catch (NotFoundError, err => {\n        console.log(\"Could not find the friend\");\n    }).catch (err => {\n        console.log(`Error: ${err}`);\n    });\n\n\n    const takeFriend = (friend: Friend) => {\n        //friend.address = {city: \"x\"}; // would compile.\n        return friend.address.city;\n    }\n\n    const retrieveFriend = async () => {\n        const friend = await db.friends.get(1);\n        if (!friend) throw \"\";\n        //friend.address.city = \"x\"; // wouldn't compile.\n        return takeFriend(friend); // Allowed in TS despite that friend is Readonly<Friend>. This is maybe good. But could be a headache for users if TS changes this.\n    };\n\n}\n\n// Issue 756\n// Also that Dexie.currentTransaction is given as first argument.\n{\n    let db = new Dexie('dbname');\n    db.transaction('rw', 'foo', 'baa', trans=>{\n        trans.abort();\n    });\n}\n\n\n{\n    // Replace modification:\n\n    interface Friend {\n        id?: number;\n        name: string;\n        isGoodFriend: boolean;\n        address: {\n            city: string;\n        },\n        matrix: number[][]; // Trigger issue #2026\n    }\n\n    let db = new Dexie('dbname') as Dexie & {friends: Table<Friend, number>};\n    \n    db.friends.where({name: 'Kalle'}).modify({name: replacePrefix('K', 'C')});\n\n    // Issue #2026\n    db.friends.update(1, {\"address.city\": \"New York\"});\n    db.friends.update(2, {matrix: [[1,2]]});\n}\n\n\n{\n    // Typings for tables in both Dexie and Transaction\n    interface Friend {\n        id: number;\n        name: string;\n        age: number;\n    }\n\n    const db = new Dexie('dbname') as Dexie & {\n        friends: EntityTable<Friend, 'id'>;\n        items: Table<{id: number, name: string}, number>;\n    }\n\n    db.friends.add({name: \"Foo\", age: 22});\n    db.transaction('rw', db.friends, tx => {\n        tx.friends.add({name: \"Bar\", age: 33});\n    })\n}\n\n{\n  // Typings for Y.Doc and other exotic types\n  interface TodoItem {\n    id: string;\n    title: string;\n    done: number;\n    text: Y.Doc;\n    address?: {\n      city: string;\n      blob: Blob;\n      shoeSize?: number;\n      tags?: string[];\n      items?: {name: string}[];\n    };\n  }\n\n  const db = new Dexie('dbname') as Dexie & {\n    todos: EntityTable<TodoItem, 'id'>;\n  };\n\n  db.version(1).stores({\n    todos: 'id, title, done, text:Y.Doc',\n  });\n\n  db.todos.add({ title: 'Foo', done: 0, address: {} as TodoItem[\"address\"] }); // Verify that Y.Doc prop is not allowed here\n\n  db.todos.update('some-id', (item) => {});\n  db.todos.update('some-id', {\n    \"address.blob\": new Blob(),\n    address: { city: 'New York', blob: new Blob() },\n    \"address.shoeSize\": undefined,\n    done: add(1),\n    \"address.tags.0\": 'important',\n    \"address.items.278.name\": 'foo',\n    \"address.items\": add([{name: 'bar'}]),\n  }); \n\n  db.todos.get('some-id').then((item) => {\n    if (!item) return;\n    // Verify that Y.Doc prop is retrieved here:\n    item.text.get('some-prop');\n\n    // Verify we can change things and put it back:\n    item.done = 1;\n    db.todos.put(item); // Yes, we could put it back despite having more props than in the InsertType.\n  });\n}\n\n// Strongly typed stores spec\n{\n    const db = new Dexie('dbname') as Dexie & {\n      friends: Table<\n        {\n          id: number;\n          name: string;\n          age: number;\n          picture: Blob;\n          tags: string[];\n          doc: Y.Doc\n        },\n        number\n      >;\n      items: Table<{ id: number; name: string }, number>;\n    };\n\n    db.version(1).stores({\n      friends: '++id, name, age'\n    });\n    /*db.version(2).stores({\n      friends: ['++id', 'name', 'age', '*tags', 'doc:Y.Doc', '&[age+id]'],\n      items: ['id', 'name'],\n    });*/\n}\n\n// Strongly typed stores spec2\n{\n    class MyDexie extends Dexie {\n        friends!: Table<{\n            id: number;\n            name: string;\n            age: number;\n            picture: Blob;\n            tags: string[];\n            doc: Y.Doc\n          }, number>;\n          \n        constructor() {\n            super(\"myDexie\");\n            this.version(1).stores({\n                //friends: ['++id', 'doc:Y.Doc'] // Typescript 5.5.4 don't understand.\n                friends: '++id, name, age'\n            })\n        }\n    }\n    const db = new MyDexie();\n    /*db.version(1).stores({\n        friends: ['', '&[age+id+name]'],\n    })*/\n}\n"
  },
  {
    "path": "test/typings-test/test-updatespec.ts",
    "content": "import { UpdateSpec } from '../../dist/dexie';\n\n// Issue 1714:\ninterface WithArray {\n  fielda: number[];\n}\n\nconst err: UpdateSpec<WithArray> = {\n  fielda: [1], // Gives \"Type 'number[]' is not assignable to type 'void'.ts(2322)\" in 4.0.1-alpha.10\n};\n\n// Nested Objects\ninterface Address {\n  city: string;\n}\ninterface WithNestedObject {\n  address: Address;\n}\nconst spec1: UpdateSpec<WithNestedObject> = {\n  'address.city': 'Stockholm',\n};\n\nconst spec2: UpdateSpec<WithNestedObject> = {\n  address: { city: 'Stockholm' },\n};\n\n// Nested Arrays\ninterface WithNestedArray {\n  addresses: Address[];\n}\n\nconst specB1: UpdateSpec<WithNestedArray> = {\n  'addresses.0': { city: 'Stockholm' },\n};\n\nconst specB2: UpdateSpec<WithNestedArray> = {\n  'addresses.0.city': 'Stockholm',\n};\n\n\nconst specB3: UpdateSpec<WithNestedArray> = {\n  addresses: [{ city: 'Stockholm' }],\n};\n"
  },
  {
    "path": "test/typings-test/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"es6\",\n        \"target\": \"ES2015\",\n        \"noImplicitAny\": true,\n        \"strict\": true,\n        \"strictNullChecks\": true,\n        \"noImplicitThis\": true,\n        \"outDir\": \"../../tools/tmp/test-typings\",\n        \"moduleResolution\": \"node\",\n        \"lib\": [\"dom\", \"es2021\"]\n    },\n    \"files\": [\n        \"test-typings.ts\"\n    ]\n}\n"
  },
  {
    "path": "test/worker.js",
    "content": "﻿\nonmessage = function (e) {\n    var imports = e.data.imports || [];\n    var code = e.data.code;\n    if (imports.length > 0)\n        importScripts.apply(self, imports);\n\n    var pCodeBegin = code.indexOf('{'),\n        pCodeEnd = code.lastIndexOf('}');\n    if (pCodeBegin === -1 || pCodeEnd === -1) {\n        postMessage([\"ok\", false, \"Worker.js error: Provided code must be (a Function).toString()\"]);\n        postMessage([\"done\"]);\n        return;\n    }\n    try {\n        code = code.substring(pCodeBegin + 1, pCodeEnd);\n        new Function(\"ok\", \"done\", code)(function ok(b, msg) {\n            postMessage([\"ok\", b, msg]);\n        }, function() {\n            postMessage([\"done\"]);\n        });\n    } catch (ex) {\n        postMessage([\"ok\", false, \"Worker error: \" + ex.toString() + (ex.stack ? \"\\n\" + ex.stack : \"\")]);\n        postMessage([\"done\"]);\n        return;\n    }\n}\n"
  },
  {
    "path": "tools/.eslintrc.json",
    "content": "{\n  \"parserOptions\": {\n    \"ecmaVersion\": 6,\n    \"sourceType\": \"module\",\n    \"ecmaFeatures\": {\n    }\n  },\n  \"env\": {\n    \"node\": true\n  },\n  \"rules\": {\n    \"no-undef\": [\"error\"]\n  },\n  \"globals\": {\n    \"Promise\": true\n  }\n}\n"
  },
  {
    "path": "tools/build-configs/banner.txt",
    "content": "/*\n * Dexie.js - a minimalistic wrapper for IndexedDB\n * ===============================================\n *\n * By David Fahlander, david.fahlander@gmail.com\n *\n * Version {version}, {date}\n *\n * https://dexie.org\n *\n * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/\n */\n "
  },
  {
    "path": "tools/build-configs/rollup.config.js",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport cleanup from 'rollup-plugin-cleanup';\n\nimport {readFileSync} from 'fs';\nimport path from 'path';\n\nconst version = require(path.resolve(__dirname, '../../package.json')).version;\n\nconst ERRORS_TO_IGNORE = [\n  \"CIRCULAR_DEPENDENCY\" // Circular imports are OK. See https://github.com/rollup/rollup/issues/2271\n];\n\nexport default {\n  input: path.resolve(__dirname, '../../tools/tmp/src/index.js'),\n  output: [{\n    file: path.resolve(__dirname, '../../dist/dexie.mjs'),\n    format: 'es',\n    sourcemap: true,\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n  }],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true, ignoreGlobal: false}),\n    cleanup()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "tools/build-configs/rollup.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport cleanup from 'rollup-plugin-cleanup';\n\nimport {readFileSync} from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst packageJson = JSON.parse(readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));\nconst version = packageJson.version;\n\nconst ERRORS_TO_IGNORE = [\n  \"CIRCULAR_DEPENDENCY\" // Circular imports are OK. See https://github.com/rollup/rollup/issues/2271\n];\n\nexport default {\n  input: path.resolve(__dirname, '../../tools/tmp/src/index.js'),\n  output: [{\n    file: path.resolve(__dirname, '../../dist/dexie.mjs'),\n    format: 'es',\n    sourcemap: true,\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n  }],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true, ignoreGlobal: false}),\n    cleanup()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "tools/build-configs/rollup.modern.config.js",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport cleanup from 'rollup-plugin-cleanup';\n\nimport {readFileSync} from 'fs';\nimport path from 'path';\n\nconst version = require(path.resolve(__dirname, '../../package.json')).version;\n\nconst ERRORS_TO_IGNORE = [\n  \"CIRCULAR_DEPENDENCY\" // Circular imports are OK. See https://github.com/rollup/rollup/issues/2271\n];\n\nexport default {\n  input: path.resolve(__dirname, '../../tools/tmp/modern/src/index.js'),\n  output: [{\n    file: path.resolve(__dirname, '../../dist/modern/dexie.mjs'),\n    format: 'es',\n    sourcemap: true,\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n  }],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true, ignoreGlobal: false}),\n    cleanup()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "tools/build-configs/rollup.modern.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport cleanup from 'rollup-plugin-cleanup';\n\nimport {readFileSync} from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst packageJson = JSON.parse(readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));\nconst version = packageJson.version;\n\nconst ERRORS_TO_IGNORE = [\n  \"CIRCULAR_DEPENDENCY\" // Circular imports are OK. See https://github.com/rollup/rollup/issues/2271\n];\n\nexport default {\n  input: path.resolve(__dirname, '../../tools/tmp/modern/src/index.js'),\n  output: [{\n    file: path.resolve(__dirname, '../../dist/modern/dexie.mjs'),\n    format: 'es',\n    sourcemap: true,\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n  }],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true, ignoreGlobal: false}),\n    cleanup()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "tools/build-configs/rollup.tests.config.js",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport path from 'path';\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\",\n  \"CIRCULAR_DEPENDENCY\" // Circular imports are OK. See https://github.com/rollup/rollup/issues/2271\n];\n\nexport default {\n  input: path.resolve(__dirname, '../tmp/test/tests-all.js'),\n  output: {\n    file: path.resolve(__dirname, '../../test/bundle.js'),\n    format: 'umd',\n    sourcemap: true,\n    name: 'dexieTests',\n    globals: {dexie: \"Dexie\", QUnit: \"QUnit\"},\n  },\n  external: ['dexie', 'QUnit'],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true}),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "tools/build-configs/rollup.tests.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport commonjs from '@rollup/plugin-commonjs';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst ERRORS_TO_IGNORE = [\n  \"THIS_IS_UNDEFINED\",\n  \"CIRCULAR_DEPENDENCY\" // Circular imports are OK. See https://github.com/rollup/rollup/issues/2271\n];\n\nexport default {\n  input: path.resolve(__dirname, '../tmp/test/tests-all.js'),\n  output: {\n    file: path.resolve(__dirname, '../../test/bundle.js'),\n    format: 'umd',\n    sourcemap: true,\n    name: 'dexieTests',\n    globals: {dexie: \"Dexie\", QUnit: \"QUnit\"},\n  },\n  external: ['dexie', 'QUnit'],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true}),\n    commonjs()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "tools/build-configs/rollup.umd.config.js",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport cleanup from 'rollup-plugin-cleanup';\n\nimport {readFileSync} from 'fs';\nimport path from 'path';\n\nconst version = require(path.resolve(__dirname, '../../package.json')).version;\n\nconst ERRORS_TO_IGNORE = [\n  \"CIRCULAR_DEPENDENCY\" // Circular imports are OK. See https://github.com/rollup/rollup/issues/2271\n];\n\nexport default {\n  input: path.resolve(__dirname, '../../tools/tmp/src/index-umd.js'),\n  output: [{\n    file: path.resolve(__dirname, '../../dist/dexie.js'),\n    format: 'umd',\n    name: 'Dexie',\n    globals: {}, // For tests, use \"QUnit\". For addons, use \"Dexie\"\n    sourcemap: true,\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n  }],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true, ignoreGlobal: false}),\n    cleanup()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "tools/build-configs/rollup.umd.config.mjs",
    "content": "import sourcemaps from 'rollup-plugin-sourcemaps';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport cleanup from 'rollup-plugin-cleanup';\n\nimport {readFileSync} from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst packageJson = JSON.parse(readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));\nconst version = packageJson.version;\n\nconst ERRORS_TO_IGNORE = [\n  \"CIRCULAR_DEPENDENCY\" // Circular imports are OK. See https://github.com/rollup/rollup/issues/2271\n];\n\nexport default {\n  input: path.resolve(__dirname, '../../tools/tmp/src/index-umd.js'),\n  output: [{\n    file: path.resolve(__dirname, '../../dist/dexie.js'),\n    format: 'umd',\n    name: 'Dexie',\n    globals: {}, // For tests, use \"QUnit\". For addons, use \"Dexie\"\n    sourcemap: true,\n    banner: readFileSync(path.resolve(__dirname, 'banner.txt')),\n  }],\n  plugins: [\n    sourcemaps(),\n    nodeResolve({browser: true, ignoreGlobal: false}),\n    cleanup()\n  ],\n  onwarn ({loc, frame, code, message}) {\n    if (ERRORS_TO_IGNORE.includes(code)) return;\n    if ( loc ) {\n      console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );\n      if ( frame ) console.warn( frame );\n    } else {\n      console.warn(`${code} ${message}`);\n    }    \n  }\n};\n"
  },
  {
    "path": "tools/fix-dts-duplicates.js",
    "content": "#!/usr/bin/env node\n// Post-process the generated dexie.d.ts to remove duplicate module declarations\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst filePath = process.argv[2];\nif (!filePath) {\n  console.error('Usage: node fix-dts-duplicates.js <path-to-dts-file>');\n  process.exit(1);\n}\n\nlet content = fs.readFileSync(filePath, 'utf-8');\nconst lines = content.split('\\n');\n\n// Find all \"export declare module Dexie {\" blocks\nlet moduleStarts = [];\nlet moduleEnds = [];\n\nfor (let i = 0; i < lines.length; i++) {\n  if (lines[i].trim().startsWith('export declare module Dexie {')) {\n    moduleStarts.push(i);\n  }\n}\n\n// Find the end of each module (closing brace)\nfor (let start of moduleStarts) {\n  let braceCount = 0;\n  for (let i = start; i < lines.length; i++) {\n    const line = lines[i].trim();\n    if (line.includes('{')) braceCount += (line.match(/\\{/g) || []).length;\n    if (line.includes('}')) braceCount -= (line.match(/\\}/g) || []).length;\n    if (braceCount === 0 && i > start) {\n      moduleEnds.push(i);\n      break;\n    }\n  }\n}\n\n// If we have duplicates, remove all but the first\nif (moduleStarts.length > 1) {\n  console.log(`Found ${moduleStarts.length} duplicate module declarations. Removing duplicates...`);\n  \n  // Remove duplicates in reverse order to preserve line numbers\n  for (let i = moduleStarts.length - 1; i >= 1; i--) {\n    const start = moduleStarts[i];\n    // Find the start of the comment block before this duplicate\n    let commentStart = start;\n    while (commentStart > 0 && (lines[commentStart - 1].trim().startsWith('//') || lines[commentStart - 1].trim() === '')) {\n      commentStart--;\n    }\n    \n    const end = moduleEnds[i];\n    lines.splice(commentStart, end - commentStart + 1);\n  }\n  \n  content = lines.join('\\n');\n  fs.writeFileSync(filePath, content, 'utf-8');\n  console.log('Fixed duplicate module declarations');\n} else {\n  console.log('No duplicate module declarations found');\n}\n"
  },
  {
    "path": "tools/prepend.js",
    "content": "const fs = require('fs');\nconst [file, prepentionFile] = process.argv.slice(2);\n\nconst prepention = fs.readFileSync(prepentionFile);\nconst fileContent = fs.readFileSync(file);\n\nconst fd = fs.openSync(file, 'w');\ntry {\n    fs.writeSync(fd, prepention);\n    fs.writeSync(fd, fileContent);\n} finally {\n    fs.closeSync(fd);\n}\n"
  },
  {
    "path": "tools/release.sh",
    "content": "#!/bin/bash -e\n\nexport NODE_ENV=release\nprevious_commit=$(git rev-parse HEAD)\n\nif ! [ -e tools/release.sh ]; then\n  echo >&2 \"Please run tools/release.sh from the repo root\"\n  exit 1\nfi\n\nupdate_version() {\n  echo \"$(node -p \"p=require('./${1}');p.version='${2}';JSON.stringify(p,null,2)\")\" > $1\n  echo \"Updated ${1} version to ${2}\"\n}\n\nvalidate_semver() {\n  if ! [[ $1 =~ ^[0-9]\\.[0-9]+\\.[0-9](-.+)? ]]; then\n    echo >&2 \"Version $1 is not valid! It must be a valid semver string like 1.0.2 or 2.3.0-beta.1\"\n    exit 1\n  fi\n}\n\ncurrent_version=$(node -p \"require('./package').version\")\n\n# Adapt to master branch name (master or master-1, master-2 etc)\nmaster_branch=\"$(git symbolic-ref HEAD)\"\nmaster_branch=${master_branch##refs/heads/}\necho \"Master branch is: '$master_branch'\"\nif ! [[ $master_branch =~ ^master ]]; then\n    echo >&2 \"Error: Must be on a branch prefixed 'master'\";\n    exit 1;\nfi\nmaster_suffix=\"${master_branch##master}\"\n\n\n# Next version?\nprintf \"Next version (current is $current_version)? \"\nread next_version\nvalidate_semver $next_version\n\nif echo \"$next_version\" | grep -q \"-\"; then\n\tNPMTAG=\"next$master_suffix\"\nelif [ \"$1\" = \"canary\" ]; then\n  NPMTAG=\"canary$master_suffix\"\nelse\n\tNPMTAG=\"latest$master_suffix\"\nfi\n\necho \"Will use: pnpm publish --tag $NPMTAG\"\n\nnext_ref=\"v$next_version\"\n\n# Auto-publish addons?\nADDONS_DIR=\"addons/\"\n# Use an array to make sure that Observable is built before Syncable\naddons=(\"Dexie.Observable\" \"Dexie.Syncable\" \"dexie-export-import\")\n\nautoPublishAddons=false\n# build addons\nfor addon in \"${addons[@]}\"\ndo\n    dir=\"${ADDONS_DIR}${addon}\"\n    cd ${dir}\n    addonNpmName=$(node -p \"require('./package').name\")\n    addonPublishedVersion=$(pnpm view $addonNpmName version)\n    addonLocalVersion=$(node -p \"require('./package').version\")\n    if ! [ \"${addonPublishedVersion}\" = \"${addonLocalVersion}\" ]; then\n      printf \"$addonNpmName version ($addonLocalVersion) differs from its published version ($addonPublishedVersion)\\n\"\n      autoPublishAddons=true\n    fi\n    cd -\ndone\n\nif [ \"${autoPublishAddons}\" = \"true\" ]; then\n  printf \"Do you want to publish these addons if all tests pass (Y/n)? \";\n  read autoPublishAddons\nfi\n\nif [ \"${autoPublishAddons}\" = \"Y\" ]; then\n  echo \"Will publish updated addons to npm if tests pass.\"\nelse\n  echo \"Will not publish any addons.\"\nfi\n\nupdate_version 'package.json' $next_version\n#update_version 'package-lock.json' $next_version\npnpm install\n\n# Commit package.json change\ngit commit package.json --allow-empty -m \"Releasing v$next_version\" 2>/dev/null\n# Save this SHA to cherry pick later\nmaster_release_commit=$(git rev-parse HEAD)\n\n#\n# Merge last release output here before rebuilding\n#\ngit merge --no-edit -s ours origin/releases$master_suffix\n\n#\n# Rebuild\n#\n\n# clean\nrm -rf tools/tmp\nrm -rf dist/*\nrm -rf addons/*/tools/tmp\nrm -rf addons/*/dist/*\n\n# build\npnpm run build\n   \n# test\nprintf \"Testing (will retry up to 4 times)\\n\"\nn=0\nuntil [ $n -ge 4 ]\ndo\n  pnpm run test && break\n  n=$[$n+1]\n  printf \"Tests failed.\\n\"\n  if [ $n -lt 4 ]; then\n    printf \"Retrying (this will be retry no ${n})...\"\n  fi\ndone\nif  [ $n -ge 4 ]; then\n  printf \"Tests failed 3 times. Quitting!\"\n  git reset --hard $previous_commit\n  exit 1\nfi\nprintf \"Tests for Dexie.js passed.\\n\"\n\n#\n# Addons\n#\n\n# Build, test and eventually release addons\nfor addon in \"${addons[@]}\"\ndo\n    dir=\"${ADDONS_DIR}${addon}\"\n    cd ${dir}\n\n    addonNpmName=$(node -p \"require('./package').name\")\n    addonPublishedVersion=$(pnpm view $addonNpmName version)\n    addonLocalVersion=$(node -p \"require('./package').version\")\n\n    printf \"Installing dependencies for ${addonNpmName}\"\n    pnpm install\n\n    printf \"Building and testing ${addon} (will retry up to 4 times)\\n\"\n\n    n=0\n    until [ $n -ge 4 ]\n    do\n      pnpm build\n      pnpm run test && break\n      n=$[$n+1]\n      printf \"${addon} Tests failed\\n\"\n      if [ $n -lt 4 ]; then\n        printf \"Retrying (this will be retry no ${n})...\"\n      fi\n    done\n    if  [ $n -ge 4 ]; then\n      printf \"${addon} tests failed 3 times. Quitting!\"\n      git reset --hard $previous_commit\n      exit 1\n    fi\n\n    printf \"${addon} Tests passed.\\n\"\n    \n    if [ \"${autoPublishAddons}\" = \"Y\" ]; then\n      if ! [ \"${addonPublishedVersion}\" = \"${addonLocalVersion}\" ]; then\n        printf \"Publishing ${addonNpmName} ${addonLocalVersion} on npm\\n\"\n        #echo \"Would now invoke pnpm publish from $(pwd)!\"\n        pnpm publish --no-git-checks\n      fi\n    fi\n    cd -\ndone\n\n# Force adding/removing dist files\ngit add -A --no-ignore-removal -f dist/ 2>/dev/null\ngit add -A --no-ignore-removal -f addons/*/dist/ 2>/dev/null\ngit add -A --no-ignore-removal -f test/bundle.js 2>/dev/null\n\n# Commit all changes (still locally)\ngit commit -am \"Build output\" 2>/dev/null\n# Tag the release\ngit tag -f -a -m \"$next_ref\" $next_ref\n# Now, push the changes to the releases branch\n#echo \"Would now git push to releases\"\ngit push origin master$master_suffix:releases$master_suffix --follow-tags\n\nprintf \"Successful push to master$master_suffix:releases$master_suffix\\n\\n\"\n\n#echo \"Would now invoke pnpm publish --tag $NPMTAG from $(pwd)\"\npnpm publish --tag $NPMTAG --no-git-checks\n\nprintf \"Successful publish to npm.\\n\\n\"\n\n# Push the update of package.json to master\nprintf \"Pushing Release-commit to master$master_suffix (with updated version in package.json)\\n\"\n#echo \"Would now git push new package.json to master\"\ngit push origin $master_release_commit:master$master_suffix\n\nprintf \"Resetting to origin/master$master_suffix\\n\"\n#echo \"Would now git reset --hard\"\ngit reset --hard origin/master$master_suffix\n\nprintf \"Done.\"\n"
  },
  {
    "path": "tools/replaceVersionAndDate.js",
    "content": "const fs = require('fs');\nconst files = process.argv.slice(2);\nconst version = require('../package.json').version;\n\nfiles.forEach(file => {\n    let fileContent = fs.readFileSync(file, \"utf-8\");\n    fileContent = fileContent\n        .replace(/{version}/g, version)\n        .replace(/{date}/g, new Date().toDateString());\n    fs.writeFileSync(file, fileContent, \"utf-8\");\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"./src/tsconfig.json\",\n}\n"
  }
]