diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..e0e15885 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,42 @@ +module.exports = { + "env": { + "browser": true, + "es2021": true, + "node": true + }, + "extends": "standard-with-typescript", + "plugins": ['unused-imports'], + "overrides": [ + { + "env": { + "node": true + }, + "files": [ + ".eslintrc.{js,cjs}" + ], + "parserOptions": { + "sourceType": "script" + } + } + ], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + 'no-async-promise-executor': 'warn', + '@typescript-eslint/no-empty-interface': 'warn', + '@typescript-eslint/restrict-template-expressions': 'warn', + '@typescript-eslint/no-confusing-void-expression': 'warn', + '@typescript-eslint/return-await': 'warn', + '@typescript-eslint/strict-boolean-expressions': 'warn', + '@typescript-eslint/consistent-type-assertions': 'warn', + '@typescript-eslint/ban-types': 'warn', + '@typescript-eslint/await-thenable': 'warn', + '@typescript-eslint/explicit-function-return-type': 'warn', + "@typescript-eslint/no-unsafe-argument": 'warn', + '@typescript-eslint/no-non-null-assertion': 'warn', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }], + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83d8f5d4..c58e737f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,10 +44,11 @@ jobs: GITHUB_COMMITER_EMAIL: ${{ secrets.GITHUB_COMMITER_EMAIL }} CI: true run: | - npm install + npm install --force chmod +x coverage.sh ./coverage.sh npm run build + npm run lint npm run coverage - name: Coverage Comment diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7571659..e9ab1eb3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: GITHUB_COMMITER_EMAIL: ${{ secrets.GITHUB_COMMITER_EMAIL }} CI: true run: | - npm install + npm install --force npm run build npm run release npm run docs diff --git a/package-lock.json b/package-lock.json index f08dac99..e92a7d9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,16 +34,15 @@ "@types/node": "^20.5.0", "@types/sinon": "^17.0.2", "@types/uuid": "^9.0.2", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.55.0", + "@typescript-eslint/eslint-plugin": "^6.15.0", "@vitest/coverage-istanbul": "^1.0.4", "concurrently": "^8.2.0", - "eslint": "^8.47.0", - "eslint-config-next": "^12.0.0", - "eslint-config-standard-with-typescript": "^34.0.1", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.6.1", + "eslint": "^8.56.0", + "eslint-config-standard-with-typescript": "^43.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.5.0", "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-unused-imports": "^3.0.0", "fake-indexeddb": "^4.0.2", "file-loader": "^6.2.0", "husky": "^7.0.4", @@ -77,7 +76,7 @@ "typedoc-plugin-rename-defaults": "^0.6.5", "typedoc-plugin-superstruct": "^1.0.0", "typedoc-theme-hierarchy": "^4.0.0", - "typescript": "^5.1.6", + "typescript": "^5.3.3", "vitest": "^1.0.4" } }, @@ -2682,35 +2681,6 @@ "sparse-bitfield": "^3.0.3" } }, - "node_modules/@next/eslint-plugin-next": { - "version": "12.3.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-12.3.4.tgz", - "integrity": "sha512-BFwj8ykJY+zc1/jWANsDprDIu2MgwPOIKxNVnrKvPs+f5TPegrVnem8uScND+1veT4B7F6VeqgaNLFW1Hzl9Og==", - "dev": true, - "dependencies": { - "glob": "7.1.7" - } - }, - "node_modules/@next/eslint-plugin-next/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@noble/ciphers": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz", @@ -3939,12 +3909,6 @@ "win32" ] }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz", - "integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==", - "dev": true - }, "node_modules/@scure/base": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", @@ -4966,32 +4930,33 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz", + "integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/type-utils": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -5000,25 +4965,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz", + "integrity": "sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -5027,16 +4993,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.15.0.tgz", + "integrity": "sha512-+BdvxYBltqrmgCNu4Li+fGDIkW9n//NrruzG9X1vBzaNK+ExVXPoGB71kneaVw/Jp+4rH/vaMAGC6JfMbHstVg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -5044,25 +5010,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz", + "integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/utils": "6.15.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -5071,12 +5037,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz", + "integrity": "sha512-yXjbt//E4T/ee8Ia1b5mGlbNj9fB9lJP4jqLbZualwpP2BCQ5is6BcWwxpIsY4XKAhmdv3hrW92GdtJbatC6dQ==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -5084,21 +5050,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.15.0.tgz", + "integrity": "sha512-7mVZJN7Hd15OmGuWrp2T9UvqR2Ecg+1j/Bp1jXUEY2GZKV6FXlOIoqVDmLpBiEiq3katvj/2n2mR0SDwtloCew==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -5111,42 +5077,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz", + "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "semver": "^7.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz", + "integrity": "sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.15.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -5774,15 +5739,6 @@ "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", "dev": true }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -5898,19 +5854,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", - "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" - } - }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", @@ -5966,12 +5909,6 @@ "node": "*" } }, - "node_modules/ast-types-flow": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", - "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "dev": true - }, "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", @@ -5998,15 +5935,6 @@ "node": ">=0.8" } }, - "node_modules/asynciterator.prototype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", - "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -6023,15 +5951,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axe-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", - "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/axios": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", @@ -6042,15 +5961,6 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/axobject-query": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", - "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", - "dev": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -7367,12 +7277,6 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true - }, "node_modules/dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", @@ -7675,15 +7579,6 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", "dev": true }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/detect-indent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", @@ -8141,28 +8036,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, - "node_modules/es-iterator-helpers": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", - "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", - "dev": true, - "dependencies": { - "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.1", - "es-set-tostringtag": "^2.0.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.2.1", - "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.0.1" - } - }, "node_modules/es-module-lexer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", @@ -8322,36 +8195,22 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-next": { - "version": "12.3.4", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-12.3.4.tgz", - "integrity": "sha512-WuT3gvgi7Bwz00AOmKGhOeqnyA5P29Cdyr0iVjLyfDbk+FANQKcOjFUTZIdyYfe5Tq1x4TGcmoe4CwctGvFjHQ==", + "node_modules/eslint-compat-utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", + "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", "dev": true, - "dependencies": { - "@next/eslint-plugin-next": "12.3.4", - "@rushstack/eslint-patch": "^1.1.3", - "@typescript-eslint/parser": "^5.21.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-import-resolver-typescript": "^2.7.1", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.31.7", - "eslint-plugin-react-hooks": "^4.5.0" + "engines": { + "node": ">=12" }, "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0", - "typescript": ">=3.3.1" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": ">=6.0.0" } }, "node_modules/eslint-config-standard": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", - "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", "dev": true, "funding": [ { @@ -8367,27 +8226,30 @@ "url": "https://feross.org/support" } ], + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "eslint": "^8.0.1", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", "eslint-plugin-promise": "^6.0.0" } }, "node_modules/eslint-config-standard-with-typescript": { - "version": "34.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-34.0.1.tgz", - "integrity": "sha512-J7WvZeLtd0Vr9F+v4dZbqJCLD16cbIy4U+alJMq4MiXdpipdBM3U5NkXaGUjePc4sb1ZE01U9g6VuTBpHHz1fg==", + "version": "43.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-43.0.0.tgz", + "integrity": "sha512-AT0qK01M5bmsWiE3UZvaQO5da1y1n6uQckAKqGNe6zPW5IOzgMLXZxw77nnFm+C11nxAZXsCPrbsgJhSrGfX6Q==", "dev": true, "dependencies": { - "@typescript-eslint/parser": "^5.43.0", - "eslint-config-standard": "17.0.0" + "@typescript-eslint/parser": "^6.4.0", + "eslint-config-standard": "17.1.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.43.0", + "@typescript-eslint/eslint-plugin": "^6.4.0", "eslint": "^8.0.1", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", "eslint-plugin-promise": "^6.0.0", "typescript": "*" } @@ -8412,46 +8274,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-import-resolver-typescript": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.1.tgz", - "integrity": "sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ==", - "dev": true, - "dependencies": { - "debug": "^4.3.4", - "glob": "^7.2.0", - "is-glob": "^4.0.3", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*" - } - }, - "node_modules/eslint-import-resolver-typescript/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/eslint-module-utils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", @@ -8478,47 +8300,24 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", - "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "node_modules/eslint-plugin-es-x": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", + "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", "dev": true, "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.6.0", + "eslint-compat-utils": "^0.1.2" }, "engines": { - "node": ">=8.10.0" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" + "eslint": ">=8" } }, "node_modules/eslint-plugin-import": { @@ -8582,53 +8381,25 @@ "semver": "bin/semver.js" } }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", - "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.23.2", - "aria-query": "^5.3.0", - "array-includes": "^3.1.7", - "array.prototype.flatmap": "^1.3.2", - "ast-types-flow": "^0.0.8", - "axe-core": "=4.7.0", - "axobject-query": "^3.2.1", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "es-iterator-helpers": "^1.0.15", - "hasown": "^2.0.0", - "jsx-ast-utils": "^3.3.5", - "language-tags": "^1.0.9", - "minimatch": "^3.1.2", - "object.entries": "^1.1.7", - "object.fromentries": "^2.0.7" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, "node_modules/eslint-plugin-n": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", - "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.5.0.tgz", + "integrity": "sha512-Hw02Bj1QrZIlKyj471Tb1jSReTl4ghIMHGuBGiMVmw+s0jOPbI4CBuYpGbZr+tdQ+VAvSK6FDSta3J4ib/SKHQ==", "dev": true, "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" + "resolve": "^1.22.2", + "semver": "^7.5.3" }, "engines": { - "node": ">=12.22.0" + "node": ">=16.0.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" @@ -8649,84 +8420,34 @@ "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/eslint-plugin-react": { - "version": "7.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", - "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "node_modules/eslint-plugin-unused-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz", + "integrity": "sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==", "dev": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.12", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" + "eslint-rule-composer": "^0.3.0" }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "engines": { - "node": ">=10" + "@typescript-eslint/eslint-plugin": "^6.0.0", + "eslint": "^8.0.0" }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } } }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=4.0.0" } }, "node_modules/eslint-scope": { @@ -8734,6 +8455,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -8747,37 +8469,11 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "peer": true, "engines": { "node": ">=4.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -9682,6 +9378,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/git-log-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", @@ -10517,21 +10225,6 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -10646,18 +10339,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -11151,19 +10832,6 @@ "node": ">=8" } }, - "node_modules/iterator.prototype": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", - "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" - } - }, "node_modules/jackspeak": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", @@ -11533,21 +11201,6 @@ "npm": ">=6" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, "node_modules/just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -11591,24 +11244,6 @@ "node": ">=0.10.0" } }, - "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true - }, - "node_modules/language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", - "dev": true, - "dependencies": { - "language-subtag-registry": "^0.3.20" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/lerna": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/lerna/-/lerna-8.0.1.tgz", @@ -12753,18 +12388,6 @@ "resolved": "https://registry.npmjs.org/looper/-/looper-2.0.0.tgz", "integrity": "sha512-6DzMHJcjbQX/UPHc1rRCBfKlLwDkvuGZ715cIR36wSdYqWXFT35uLXq5P/2orl3tz+t+VOVPxw4yPinQlUDGDQ==" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -13599,12 +13222,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -17066,15 +16683,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -17123,20 +16731,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/object.fromentries": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", @@ -17166,19 +16760,6 @@ "get-intrinsic": "^1.2.1" } }, - "node_modules/object.hasown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", - "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", @@ -18391,23 +17972,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -18963,26 +18527,6 @@ "esprima": "~4.0.0" } }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", - "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -19004,18 +18548,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/registry-auth-token": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", @@ -19091,6 +18623,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -20996,26 +20537,6 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "node_modules/string.prototype.matchall": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", - "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "regexp.prototype.flags": "^1.5.0", - "set-function-name": "^2.0.0", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/string.prototype.trim": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", @@ -21613,6 +21134,18 @@ "node": ">=8" } }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -21651,27 +21184,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/tuf-js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", @@ -22118,9 +21630,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -22884,38 +22396,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-builtin-type": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", - "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", - "dev": true, - "dependencies": { - "function.prototype.name": "^1.1.5", - "has-tostringtag": "^1.0.0", - "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.0.2", - "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/which-collection": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", @@ -23278,7 +22758,7 @@ }, "packages/database": { "name": "@pluto-encrypted/database", - "version": "1.2.2", + "version": "1.2.4", "license": "Apache-2.0", "dependencies": { "@atala/prism-wallet-sdk": "^3.2.0", @@ -23289,9 +22769,9 @@ }, "devDependencies": { "@pluto-encrypted/encryption": "^1.0.1", - "@pluto-encrypted/indexdb": "1.3.1", - "@pluto-encrypted/inmemory": "1.3.1", - "@pluto-encrypted/leveldb": "1.3.1", + "@pluto-encrypted/indexdb": "1.3.2", + "@pluto-encrypted/inmemory": "1.3.2", + "@pluto-encrypted/leveldb": "1.3.2", "@vitest/coverage-v8": "^1.0.4", "level": "^6.0.1" }, @@ -23319,7 +22799,7 @@ }, "packages/indexdb": { "name": "@pluto-encrypted/indexdb", - "version": "1.3.1", + "version": "1.3.2", "license": "Apache-2.0", "dependencies": { "@pluto-encrypted/encryption": "^1.0.1", @@ -23331,7 +22811,7 @@ "devDependencies": { "@pluto-encrypted/encryption": "1.2.0", "@pluto-encrypted/shared": "1.2.0", - "@pluto-encrypted/test-suite": "1.0.0" + "@pluto-encrypted/test-suite": "1.0.1" } }, "packages/indexdb/node_modules/array-push-at-sort-position": { @@ -23340,7 +22820,7 @@ }, "packages/inmemory": { "name": "@pluto-encrypted/inmemory", - "version": "1.3.1", + "version": "1.3.2", "license": "Apache-2.0", "dependencies": { "@pluto-encrypted/encryption": "^1.0.1", @@ -23352,7 +22832,7 @@ "devDependencies": { "@pluto-encrypted/encryption": "1.2.0", "@pluto-encrypted/shared": "1.2.0", - "@pluto-encrypted/test-suite": "1.0.0" + "@pluto-encrypted/test-suite": "1.0.1" } }, "packages/inmemory/node_modules/array-push-at-sort-position": { @@ -23361,7 +22841,7 @@ }, "packages/leveldb": { "name": "@pluto-encrypted/leveldb", - "version": "1.3.1", + "version": "1.3.2", "license": "Apache-2.0", "dependencies": { "@pluto-encrypted/encryption": "^1.0.1", @@ -23377,7 +22857,7 @@ "devDependencies": { "@pluto-encrypted/encryption": "1.2.0", "@pluto-encrypted/shared": "1.2.0", - "@pluto-encrypted/test-suite": "1.0.0" + "@pluto-encrypted/test-suite": "1.0.1" } }, "packages/leveldb/node_modules/array-push-at-sort-position": { @@ -23395,7 +22875,7 @@ }, "packages/test-suite": { "name": "@pluto-encrypted/test-suite", - "version": "1.0.0", + "version": "1.0.1", "license": "Apache-2.0", "dependencies": { "@faker-js/faker": "^8.3.1", diff --git a/package.json b/package.json index c2f12c6f..1a064669 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "private": "true", "scripts": { + "lint": "npx eslint packages/*/src/**/*.ts --fix", "clean-packages": "npx lerna run clean-packages && rm -rf node_modules", "build": "npx lerna run build --stream --include-dependencies", "build-and-test": "npm run build && npm run test", @@ -39,16 +40,15 @@ "@types/node": "^20.5.0", "@types/sinon": "^17.0.2", "@types/uuid": "^9.0.2", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.55.0", + "@typescript-eslint/eslint-plugin": "^6.15.0", "@vitest/coverage-istanbul": "^1.0.4", "concurrently": "^8.2.0", - "eslint": "^8.47.0", - "eslint-config-next": "^12.0.0", - "eslint-config-standard-with-typescript": "^34.0.1", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.6.1", + "eslint": "^8.56.0", + "eslint-config-standard-with-typescript": "^43.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.5.0", "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-unused-imports": "^3.0.0", "fake-indexeddb": "^4.0.2", "file-loader": "^6.2.0", "husky": "^7.0.4", @@ -82,7 +82,7 @@ "typedoc-plugin-rename-defaults": "^0.6.5", "typedoc-plugin-superstruct": "^1.0.0", "typedoc-theme-hierarchy": "^4.0.0", - "typescript": "^5.1.6", + "typescript": "^5.3.3", "vitest": "^1.0.4" }, "dependencies": { diff --git a/packages/database/src/index.ts b/packages/database/src/index.ts index 74abfb65..68fb5d3d 100644 --- a/packages/database/src/index.ts +++ b/packages/database/src/index.ts @@ -2,72 +2,64 @@ * @packageDocumentation * @module database */ -import { Domain } from "@atala/prism-wallet-sdk"; +import { Domain } from '@atala/prism-wallet-sdk' import { - MangoQuerySelector, PROMISE_RESOLVE_FALSE, RxCollectionCreator, - RxDatabase, - RxDatabaseCreator, - RxDumpDatabase, - RxError, - RxQuery, - RxStorage, + type MangoQuerySelector, type RxCollectionCreator, + type RxDatabase, + type RxDatabaseCreator, + type RxDumpDatabase, + type RxError, + type RxStorage, addRxPlugin, createRxDatabase, - flatClone, - getFromMapOrCreate, - getFromMapOrThrow, removeRxDatabase -} from "rxdb"; -import { RxDBEncryptedMigrationPlugin } from "@pluto-encrypted/encryption"; -import { BulkWriteRow, RxCollection, RxDocument, RxDocumentData } from "rxdb/dist/types/types"; -import { RxDBJsonDumpPlugin } from "rxdb/plugins/json-dump"; -import { RxDBQueryBuilderPlugin } from "rxdb/plugins/query-builder"; -import { v4 as uuidv4 } from "uuid"; +} from 'rxdb' +import { RxDBEncryptedMigrationPlugin } from '@pluto-encrypted/encryption' +import { RxDBJsonDumpPlugin } from 'rxdb/plugins/json-dump' +import { RxDBQueryBuilderPlugin } from 'rxdb/plugins/query-builder' +import { v4 as uuidv4 } from 'uuid' import CredentialSchema, { - CredentialCollection, + type CredentialCollection, CredentialMethods -} from "./schemas/Credential"; +} from './schemas/Credential' import CredentialRequestMetadataSchema, { - CredentialRequestMetadataCollection, + type CredentialRequestMetadataCollection, CredentialRequestMetadataMethods -} from "./schemas/CredentialRequestMetadata"; -import DIDSchema, { DIDCollection } from "./schemas/DID"; -import DIDPairSchema, { DIDPairCollection } from "./schemas/DIDPair"; +} from './schemas/CredentialRequestMetadata' +import DIDSchema, { type DIDCollection } from './schemas/DID' +import DIDPairSchema, { type DIDPairCollection } from './schemas/DIDPair' import LinkSecretSchema, { - LinkSecretColletion, + type LinkSecretColletion, LinkSecretMethods -} from "./schemas/LinkSecret"; +} from './schemas/LinkSecret' import MediatorSchema, { - MediatorCollection, + type MediatorCollection, MediatorMethods -} from "./schemas/Mediator"; +} from './schemas/Mediator' import MessageSchema, { - MessageColletion, + type MessageColletion, MessageMethods, - MessageSchemaType -} from "./schemas/Message"; + type MessageSchemaType +} from './schemas/Message' import PrivateKeySchema, { - KeySpec, PrivateKeyColletion, PrivateKeyDocument, + type KeySpec, type PrivateKeyColletion, type PrivateKeyDocument, PrivateKeyMethods -} from "./schemas/PrivateKey"; -import { PlutoCollections } from "./types"; -import { DATA_MIGRATOR_BY_COLLECTION } from "@pluto-encrypted/encryption"; -import { EncryptedDataMigrator } from "@pluto-encrypted/encryption"; -import { mustMigrate } from "@pluto-encrypted/encryption"; - -export * from "./schemas/Credential"; -export * from "./schemas/CredentialRequestMetadata"; -export * from "./schemas/DID"; -export * from "./schemas/DIDPair"; -export * from "./schemas/LinkSecret"; -export * from "./schemas/Mediator"; -export * from "./schemas/Message"; -export * from "./schemas/PrivateKey"; +} from './schemas/PrivateKey' +import { type PlutoCollections } from './types' + +export * from './schemas/Credential' +export * from './schemas/CredentialRequestMetadata' +export * from './schemas/DID' +export * from './schemas/DIDPair' +export * from './schemas/LinkSecret' +export * from './schemas/Mediator' +export * from './schemas/Message' +export * from './schemas/PrivateKey' export type * from './types' export type { Domain as WALLET_SDK_DOMAIN } from '@atala/prism-wallet-sdk' -export type ValuesOf = T[keyof T]; -export type PlutoDatabase = RxDatabase; +export type ValuesOf = T[keyof T] +export type PlutoDatabase = RxDatabase /** * Pluto is a storage interface describing storage requirements of the edge agents @@ -76,43 +68,43 @@ export type PlutoDatabase = RxDatabase; * */ export class Database implements Domain.Pluto { - private _db!: RxDatabase; + private _db!: RxDatabase - protected get db() { + protected get db () { if (!this._db) { - throw new Error("Start Pluto first."); + throw new Error('Start Pluto first.') } - return this._db; + return this._db } - constructor(private dbOptions: RxDatabaseCreator) { - addRxPlugin(RxDBQueryBuilderPlugin); - addRxPlugin(RxDBJsonDumpPlugin); - addRxPlugin(RxDBEncryptedMigrationPlugin); + constructor (private readonly dbOptions: RxDatabaseCreator) { + addRxPlugin(RxDBQueryBuilderPlugin) + addRxPlugin(RxDBJsonDumpPlugin) + addRxPlugin(RxDBEncryptedMigrationPlugin) } - async backup() { - return this.db.exportJSON(); + async backup () { + return await this.db.exportJSON() } - get collections(): PlutoCollections { + get collections (): PlutoCollections { return this.db.collections } /** * CredentialRequestMetadatas * Stores anoncreds credential metadata + exposes orm functions - * + * * Count all Credential Metadatas with optional query * ```ts * await db.credentialmetadatas.count({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all credential metadatas matching the query * ```ts * await db.credentialmetadatas.find({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all credential metadatas by id * ```ts * await db.credentialmetadatas.findByIds([id]) @@ -121,30 +113,30 @@ export class Database implements Domain.Pluto { * ```ts * await db.credentialmetadatas.findOne({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Remove any credential metadatas matching the query * ```ts * await db.credentialmetadatas.remove({selector: {id: {$eq: 1}}}) * ``` */ - get credentialrequestmetadatas(): CredentialRequestMetadataCollection { + get credentialrequestmetadatas (): CredentialRequestMetadataCollection { return this.db.collections.credentialrequestmetadatas } /** - * LinkSecrets + * LinkSecrets * Stores anoncreds link secrets + exposes orm functions - * + * * Count all LinkSecrets with optional query * ```ts * await db.linksecrets.count({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all LinkSecrets matching the query * ```ts * await db.linksecrets.find({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all LinkSecrets by id * ```ts * await db.linksecrets.findByIds([id]) @@ -153,30 +145,30 @@ export class Database implements Domain.Pluto { * ```ts * await db.linksecrets.findOne({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Remove any LinkSecrets matching the query * ```ts * await db.linksecrets.remove({selector: {id: {$eq: 1}}}) * ``` */ - get linksecrets(): LinkSecretColletion { + get linksecrets (): LinkSecretColletion { return this.db.collections.linksecrets } /** - * DIDPairs + * DIDPairs * Stores groups of dids, also known as connections + exposes orm functions - * + * * Count all DIDPairs with optional query * ```ts * await db.didpairs.count({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all DIDPairs matching the query * ```ts * await db.didpairs.find({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all DIDPairs by id * ```ts * await db.didpairs.findByIds([id]) @@ -185,30 +177,30 @@ export class Database implements Domain.Pluto { * ```ts * await db.didpairs.findOne({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Remove any DIDPairs matching the query * ```ts * await db.didpairs.remove({selector: {id: {$eq: 1}}}) * ``` */ - get didpairs(): DIDPairCollection { + get didpairs (): DIDPairCollection { return this.db.collections.didpairs } /** - * Credentials + * Credentials * Stores credentials, both anoncreda and prism/jwt + exposes orm functions - * + * * Count all Credentials with optional query * ```ts * await db.credentials.count({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all Credentials matching the query * ```ts * await db.credentials.find({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all Credentials by id * ```ts * await db.credentials.findByIds([id]) @@ -217,30 +209,30 @@ export class Database implements Domain.Pluto { * ```ts * await db.credentials.findOne({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Remove any Credentials matching the query * ```ts * await db.credentials.remove({selector: {id: {$eq: 1}}}) * ``` */ - get credentials(): CredentialCollection { + get credentials (): CredentialCollection { return this.db.collections.credentials } /** - * Mediators + * Mediators * Stores mediators + exposes orm functions - * + * * Count all Mediators with optional query * ```ts * await db.mediators.count({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all Mediators matching the query * ```ts * await db.mediators.find({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all Mediators by id * ```ts * await db.mediators.findByIds([id]) @@ -249,30 +241,30 @@ export class Database implements Domain.Pluto { * ```ts * await db.mediators.findOne({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Remove any Mediators matching the query * ```ts * await db.mediators.remove({selector: {id: {$eq: 1}}}) * ``` */ - get mediators(): MediatorCollection { + get mediators (): MediatorCollection { return this.db.collections.mediators } /** - * DIDs + * DIDs * Stores dids + exposes orm functions - * + * * Count all DIDS with optional query * ```ts * await db.dids.count({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all DIDS matching the query * ```ts * await db.dids.find({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all DIDS by id * ```ts * await db.dids.findByIds([id]) @@ -281,30 +273,30 @@ export class Database implements Domain.Pluto { * ```ts * await db.dids.findOne({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Remove any DIDS matching the query * ```ts * await db.dids.remove({selector: {id: {$eq: 1}}}) * ``` */ - get dids(): DIDCollection { + get dids (): DIDCollection { return this.db.collections.dids } /** - * PrivateKeys + * PrivateKeys * Stores privateKeys + exposes orm functions - * + * * Count all PrivateKeys with optional query * ```ts * await db.privatekeys.count({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all PrivateKeys matching the query * ```ts * await db.privatekeys.find({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all PrivateKeys by id * ```ts * await db.privatekeys.findByIds([id]) @@ -313,30 +305,30 @@ export class Database implements Domain.Pluto { * ```ts * await db.privatekeys.findOne({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Remove any PrivateKeys matching the query * ```ts * await db.privatekeys.remove({selector: {id: {$eq: 1}}}) * ``` */ - get privatekeys(): PrivateKeyColletion { + get privatekeys (): PrivateKeyColletion { return this.db.collections.privatekeys } /** - * Messages + * Messages * Stores Messages + exposes orm functions - * + * * Count all Messages with optional query * ```ts * await db.messages.count({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all Messages matching the query * ```ts * await db.messages.find({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Find all Messages by id * ```ts * await db.messages.findByIds([id]) @@ -345,13 +337,13 @@ export class Database implements Domain.Pluto { * ```ts * await db.messages.findOne({selector: {id: {$eq: 1}}}) //Query is optional * ``` - * + * * Remove any Messages matching the query * ```ts * await db.messages.remove({selector: {id: {$eq: 1}}}) * ``` */ - get messages(): MessageColletion { + get messages (): MessageColletion { return this.db.collections.messages } @@ -359,96 +351,96 @@ export class Database implements Domain.Pluto { * Use with caution, this will remove all entries from database * and then destroy the database itself. */ - async clear() { + async clear () { const storages = Array.from(this.db.storageInstances.values()) - for (let storage of storages) { + for (const storage of storages) { await storage.cleanup(Infinity) } - await removeRxDatabase(this.dbOptions.name, this.db.storage); + await removeRxDatabase(this.dbOptions.name, this.db.storage) } /** * Creates a database instance. - * @param options + * @param options * @returns Database */ - static async createEncrypted( + static async createEncrypted ( options: { - name: string, - encryptionKey: Uint8Array, - importData?: RxDumpDatabase, - storage: RxStorage, - autoStart?: boolean, + name: string + encryptionKey: Uint8Array + importData?: RxDumpDatabase + storage: RxStorage + autoStart?: boolean collections?: Partial<{ - messages: RxCollectionCreator; - dids: RxCollectionCreator; - didpairs: RxCollectionCreator; - mediators: RxCollectionCreator; - privatekeys: RxCollectionCreator; - credentials: RxCollectionCreator; - credentialrequestmetadatas: RxCollectionCreator; - linksecrets: RxCollectionCreator; + messages: RxCollectionCreator + dids: RxCollectionCreator + didpairs: RxCollectionCreator + mediators: RxCollectionCreator + privatekeys: RxCollectionCreator + credentials: RxCollectionCreator + credentialrequestmetadatas: RxCollectionCreator + linksecrets: RxCollectionCreator }> } ) { try { - const { name, storage, encryptionKey, importData, autoStart = true, collections } = options; + const { name, storage, encryptionKey, importData, autoStart = true, collections } = options if (!storage) { - throw new Error("Please provide a valid storage."); + throw new Error('Please provide a valid storage.') } const database = new Database({ ignoreDuplicate: true, - name: name, - storage: storage, - password: Buffer.from(encryptionKey).toString('hex'), - }); + name, + storage, + password: Buffer.from(encryptionKey).toString('hex') + }) if (autoStart) { await database.start(collections) } if (importData) { - await database.db.importJSON(importData); + await database.db.importJSON(importData) } - return database; + return database } catch (err) { /* istanbul ignore else */ - if ((err as RxError).code === "DB1") { - throw new Error("Invalid Authentication"); + if ((err as RxError).code === 'DB1') { + throw new Error('Invalid Authentication') } else { /* istanbul ignore next */ - throw err; + throw err } } } /** * Get a Message by its id - * @param id + * @param id * @returns [Message](https://input-output-hk.github.io/atala-prism-wallet-sdk-ts/classes/Domain.Message.html) */ - async getMessage(id: string): Promise { + async getMessage (id: string): Promise { const message = await this.db.messages.findOne({ selector: { id: { $eq: id } } - }).exec(); + }).exec() if (message) { - return message.toDomainMessage(); + return message.toDomainMessage() } - return null; + return null } /** * Stores a message - * @param [Message](https://input-output-hk.github.io/atala-prism-wallet-sdk-ts/classes/Domain.Message.html) + * @param [Message](https://input-output-hk.github.io/atala-prism-wallet-sdk-ts/classes/Domain.Message.html) * @returns void */ - async storeMessage(message: Domain.Message): Promise { + async storeMessage (message: Domain.Message): Promise { const existing = await this.db.messages .findOne({ selector: { @@ -456,43 +448,43 @@ export class Database implements Domain.Pluto { $eq: message.id } } - }).exec(); + }).exec() if (existing) { await existing.patch({ ...message, to: message.to?.toString(), - from: message.from?.toString(), - }); + from: message.from?.toString() + }) } else { await this.db.messages.insert({ ...message, to: message.to?.toString(), - from: message.from?.toString(), - }); + from: message.from?.toString() + }) } } /** * Stores multiple messages in 1 call - * @param [Message[]](https://input-output-hk.github.io/atala-prism-wallet-sdk-ts/classes/Domain.Message.html) + * @param [Message[]](https://input-output-hk.github.io/atala-prism-wallet-sdk-ts/classes/Domain.Message.html) * @returns void */ - async storeMessages(messages: Domain.Message[]): Promise { - for (let message of messages) { + async storeMessages (messages: Domain.Message[]): Promise { + for (const message of messages) { await this.storeMessage(message) } } /** * Get all the stored messages - * @returns [Message[]](https://input-output-hk.github.io/atala-prism-wallet-sdk-ts/classes/Domain.Message.html) + * @returns [Message[]](https://input-output-hk.github.io/atala-prism-wallet-sdk-ts/classes/Domain.Message.html) */ - async getAllMessages(): Promise { - const messages = await this.db.messages.find().exec(); - return messages.map((message) => message.toDomainMessage()); + async getAllMessages (): Promise { + const messages = await this.db.messages.find().exec() + return messages.map((message) => message.toDomainMessage()) } - private getDefaultCollections() { + private getDefaultCollections () { return { messages: { schema: MessageSchema, @@ -523,47 +515,47 @@ export class Database implements Domain.Pluto { linksecrets: { schema: LinkSecretSchema, methods: LinkSecretMethods - }, + } } } /** * Start the database and build collections */ - async start(collections?: Partial<{ - messages: RxCollectionCreator; - dids: RxCollectionCreator; - didpairs: RxCollectionCreator; - mediators: RxCollectionCreator; - privatekeys: RxCollectionCreator; - credentials: RxCollectionCreator; - credentialrequestmetadatas: RxCollectionCreator; - linksecrets: RxCollectionCreator; + async start (collections?: Partial<{ + messages: RxCollectionCreator + dids: RxCollectionCreator + didpairs: RxCollectionCreator + mediators: RxCollectionCreator + privatekeys: RxCollectionCreator + credentials: RxCollectionCreator + credentialrequestmetadatas: RxCollectionCreator + linksecrets: RxCollectionCreator }>): Promise { - const { dbOptions } = this; + const { dbOptions } = this const database = await createRxDatabase({ ...dbOptions, multiInstance: false - }); + }) await database.addCollections({ ...this.getDefaultCollections(), - ...(collections || {}) - }); + ...(collections ?? {}) as any + }) - this._db = database; + this._db = database } /** * Stores a prismDID and its privateKey - * @param did - * @param keyPathIndex - * @param privateKey - * @param privateKeyMetaId - * @param alias + * @param did + * @param keyPathIndex + * @param privateKey + * @param privateKeyMetaId + * @param alias */ - async storePrismDID( + async storePrismDID ( did: Domain.DID, keyPathIndex: number, privateKey: Domain.PrivateKey, @@ -575,22 +567,22 @@ export class Database implements Domain.Pluto { method: did.method, methodId: did.methodId, schema: did.schema, - alias: alias, - }); + alias + }) await this.storePrivateKeys( privateKey, did, keyPathIndex, privateKeyMetaId ?? null - ); + ) } /** * Stores a peerdid with its privateKeys - * @param did - * @param privateKeys + * @param did + * @param privateKeys */ - async storePeerDID( + async storePeerDID ( did: Domain.DID, privateKeys: Domain.PrivateKey[] ): Promise { @@ -598,41 +590,41 @@ export class Database implements Domain.Pluto { did: did.toString(), method: did.method, methodId: did.methodId, - schema: did.schema, - }); - for (let prv of privateKeys) { + schema: did.schema + }) + for (const prv of privateKeys) { await this.db.privatekeys.insert({ id: uuidv4(), did: did.toString(), type: prv.type, - keySpecification: Array.from(prv.keySpecification).reduce( + keySpecification: Array.from(prv.keySpecification).reduce( (all, [key, value]) => [ ...all, { - type: "string", + type: 'string', name: key, - value: `${value}`, - }, + value: `${value}` + } ], [ { - type: "string", - name: "raw", - value: Buffer.from(prv.raw).toString("hex"), - }, - ] as KeySpec[] - ), + type: 'string', + name: 'raw', + value: Buffer.from(prv.raw).toString('hex') + } + ] + ) }) } } /** * Stores a didpair - * @param host - * @param receiver - * @param name + * @param host + * @param receiver + * @param name */ - async storeDIDPair( + async storeDIDPair ( host: Domain.DID, receiver: Domain.DID, name: string @@ -640,83 +632,82 @@ export class Database implements Domain.Pluto { await this.db.didpairs.insert({ hostDID: host.toString(), receiverDID: receiver.toString(), - name, - }); + name + }) } /** * Stores privateKeys references to an existing DID - * @param privateKey - * @param did - * @param keyPathIndex - * @param _metaId + * @param privateKey + * @param did + * @param keyPathIndex + * @param _metaId */ - async storePrivateKeys( + async storePrivateKeys ( privateKey: Domain.PrivateKey, did: Domain.DID, - keyPathIndex: number, - _metaId?: string | null + keyPathIndex: number ): Promise { await this.db.privatekeys.insert({ id: uuidv4(), did: did.toString(), type: privateKey.type, - keySpecification: Array.from(privateKey.keySpecification).reduce( + keySpecification: Array.from(privateKey.keySpecification).reduce( (all, [key, value]) => [ ...all, { - type: "string", + type: 'string', name: key, - value: `${value}`, - }, + value: `${value}` + } ], [ { - type: "string", - name: "raw", - value: Buffer.from(privateKey.raw).toString("hex"), + type: 'string', + name: 'raw', + value: Buffer.from(privateKey.raw).toString('hex') }, { - type: "number", - name: "index", - value: `${keyPathIndex}`, - }, - ] as KeySpec[] - ), - }); + type: 'number', + name: 'index', + value: `${keyPathIndex}` + } + ] + ) + }) } /** * Gets all the stores didPairs * @returns [Domain.DIDPair[]](https://input-output-hk.github.io/atala-prism-wallet-sdk-ts/classes/Domain.DIDPair.html) */ - async getAllDidPairs(): Promise { - const { DID, DIDPair } = Domain; + async getAllDidPairs (): Promise { + const { DID, DIDPair } = Domain const results = await this.db.didpairs.find().exec() return results.map( ({ hostDID, receiverDID, name }) => new DIDPair(DID.fromString(hostDID), DID.fromString(receiverDID), name) - ); + ) } /** * Get a did pair (connection) by one of its dids - * @param did + * @param did * @returns [Domain.DIDPair](https://input-output-hk.github.io/atala-prism-wallet-sdk-ts/classes/Domain.DIDPair.html) */ - async getPairByDID(did: Domain.DID): Promise { - const { DID, DIDPair } = Domain; + async getPairByDID (did: Domain.DID): Promise { + const { DID, DIDPair } = Domain const didPair = await this.db.didpairs .findOne({ selector: { $or: [ { - hostDID: did.toString(), + hostDID: did.toString() }, { - receiverDID: did.toString(), - }, - ], + receiverDID: did.toString() + } + ] } }).exec() return didPair @@ -725,21 +716,21 @@ export class Database implements Domain.Pluto { DID.fromString(didPair.receiverDID), didPair.name ) - : null; + : null } - async getPairByName(name: string): Promise { - const { DID, DIDPair } = Domain; + async getPairByName (name: string): Promise { + const { DID, DIDPair } = Domain const didPair = await this.db.didpairs .findOne({ selector: { $and: [ { - name, - }, - ], + name + } + ] } - }).exec(); + }).exec() return didPair ? new DIDPair( @@ -747,16 +738,16 @@ export class Database implements Domain.Pluto { DID.fromString(didPair.receiverDID), didPair.name ) - : null; + : null } - private getPrivateKeyFromDB( + private getPrivateKeyFromDB ( privateKey: PrivateKeyDocument ): Domain.PrivateKey { - return privateKey.toDomainPrivateKey(); + return privateKey.toDomainPrivateKey() } - async getDIDPrivateKeysByDID(did: Domain.DID): Promise { + async getDIDPrivateKeysByDID (did: Domain.DID): Promise { const privateKeys = await this.db.privatekeys .find({ selector: { @@ -765,10 +756,12 @@ export class Database implements Domain.Pluto { } } }).exec() - return privateKeys.map(this.getPrivateKeyFromDB); + return privateKeys.map((privateKey) => { + return this.getPrivateKeyFromDB(privateKey) + }) } - async getDIDPrivateKeyByID(id: string): Promise { + async getDIDPrivateKeyByID (id: string): Promise { const privateKey = await this.db.privatekeys.findOne({ selector: { id: { @@ -776,10 +769,10 @@ export class Database implements Domain.Pluto { } } }).exec() - return privateKey ? this.getPrivateKeyFromDB(privateKey) : null; + return privateKey ? this.getPrivateKeyFromDB(privateKey) : null } - async storeMediator( + async storeMediator ( mediator: Domain.DID, host: Domain.DID, routing: Domain.DID @@ -788,28 +781,28 @@ export class Database implements Domain.Pluto { id: uuidv4(), mediatorDID: mediator.toString(), hostDID: host.toString(), - routingDID: routing.toString(), - }); + routingDID: routing.toString() + }) } - async getAllPrismDIDs(): Promise { + async getAllPrismDIDs (): Promise { const dids = await this.db.dids.find({ selector: { method: { - $eq: "prism" + $eq: 'prism' } } - }).exec(); + }).exec() - const prismDIDInfo: Domain.PrismDIDInfo[] = []; + const prismDIDInfo: Domain.PrismDIDInfo[] = [] - for (let did of dids) { + for (const did of dids) { const didPrivateKeys = await this.getDIDPrivateKeysByDID( Domain.DID.fromString(did.did) - ); + ) - for (let privateKey of didPrivateKeys) { - const indexProp = privateKey.getProperty(Domain.KeyProperties.index)!; + for (const privateKey of didPrivateKeys) { + const indexProp = privateKey.getProperty(Domain.KeyProperties.index)! prismDIDInfo.push( new Domain.PrismDIDInfo( @@ -817,187 +810,186 @@ export class Database implements Domain.Pluto { parseInt(indexProp), did.alias ) - ); + ) } } - return prismDIDInfo; + return prismDIDInfo } - async getDIDInfoByDID(did: Domain.DID): Promise { - + async getDIDInfoByDID (did: Domain.DID): Promise { const didDB = await this.db.dids .findOne({ selector: { did: did.toString() } - }).exec(); + }).exec() if (didDB) { const privateKeys = await this.getDIDPrivateKeysByDID( Domain.DID.fromString(didDB.did) - ); + ) /* istanbul ignore if */ - if (!privateKeys.length) { + if (privateKeys.length === 0) { throw new Error( - "Imposible to recover PrismDIDInfo without its privateKey data." - ); + 'Imposible to recover PrismDIDInfo without its privateKey data.' + ) } const indexProp = privateKeys .at(0)! - .getProperty(Domain.KeyProperties.index); - const index = indexProp ? parseInt(indexProp) : undefined; + .getProperty(Domain.KeyProperties.index) + const index = indexProp ? parseInt(indexProp) : undefined return new Domain.PrismDIDInfo( Domain.DID.fromString(didDB.did), index, didDB.alias - ); + ) } - return null; + return null } - async getDIDInfoByAlias(alias: string): Promise { + async getDIDInfoByAlias (alias: string): Promise { const dids = await this.db.dids.find({ selector: { alias: { $eq: alias } } - }).exec(); - const prismDIDInfo: Domain.PrismDIDInfo[] = []; - for (let did of dids) { + }).exec() + const prismDIDInfo: Domain.PrismDIDInfo[] = [] + for (const did of dids) { const didPrivateKeys = await this.getDIDPrivateKeysByDID( Domain.DID.fromString(did.did) - ); - for (let privateKey of didPrivateKeys) { - const indexProp = privateKey.getProperty(Domain.KeyProperties.index)!; + ) + for (const privateKey of didPrivateKeys) { + const indexProp = privateKey.getProperty(Domain.KeyProperties.index)! prismDIDInfo.push( new Domain.PrismDIDInfo( Domain.DID.fromString(did.did), parseInt(indexProp), did.alias ) - ); + ) } } - return prismDIDInfo; + return prismDIDInfo } - async getAllMessagesByDID(did: Domain.DID): Promise { + async getAllMessagesByDID (did: Domain.DID): Promise { const messages = await this.db.messages .find({ selector: { $or: [ { - to: did.toString(), + to: did.toString() }, { - from: did.toString(), - }, - ], + from: did.toString() + } + ] } }).exec() - return messages.map((message) => message.toDomainMessage()); + return messages.map((message) => message.toDomainMessage()) } - async getAllMessagesSent(): Promise { + async getAllMessagesSent (): Promise { const messages = await this.db.messages .find({ selector: { $or: [ { - direction: Domain.MessageDirection.SENT, - }, - ], + direction: Domain.MessageDirection.SENT + } + ] } }).exec() - return messages.map((message) => message.toDomainMessage()); + return messages.map((message) => message.toDomainMessage()) } - async getAllMessagesReceived(): Promise { + async getAllMessagesReceived (): Promise { const messages = await this.db.messages .find({ selector: { $or: [ { - direction: Domain.MessageDirection.RECEIVED, - }, - ], + direction: Domain.MessageDirection.RECEIVED + } + ] } }).exec() - return messages.map((message) => message.toDomainMessage()); + return messages.map((message) => message.toDomainMessage()) } - async getAllMessagesSentTo(did: Domain.DID): Promise { + async getAllMessagesSentTo (did: Domain.DID): Promise { const messages = await this.db.messages .find({ selector: { $and: [ { - to: did.toString(), + to: did.toString() }, { - direction: Domain.MessageDirection.SENT, - }, - ], + direction: Domain.MessageDirection.SENT + } + ] } }).exec() - return messages.map((message) => message.toDomainMessage()); + return messages.map((message) => message.toDomainMessage()) } - async getAllMessagesReceivedFrom(did: Domain.DID): Promise { + async getAllMessagesReceivedFrom (did: Domain.DID): Promise { const messages = await this.db.messages .find({ selector: { $and: [ { - from: did.toString(), + from: did.toString() }, { - direction: Domain.MessageDirection.RECEIVED, - }, - ], + direction: Domain.MessageDirection.RECEIVED + } + ] } - }).exec(); - return messages.map((message) => message.toDomainMessage()); + }).exec() + return messages.map((message) => message.toDomainMessage()) } - async getAllMessagesOfType( + async getAllMessagesOfType ( type: string, relatedWithDID?: Domain.DID | undefined ): Promise { - const query: MangoQuerySelector[] = [ + const query: Array> = [ { - piuri: type, - }, - ]; + piuri: type + } + ] if (relatedWithDID) { query.push({ $or: [ { - from: relatedWithDID.toString(), + from: relatedWithDID.toString() }, { - to: relatedWithDID.toString(), - }, - ], - }); + to: relatedWithDID.toString() + } + ] + }) } const messages = await this.db.messages .find({ selector: { - $and: query, + $and: query } }).exec() - return messages.map((message) => message.toDomainMessage()); + return messages.map((message) => message.toDomainMessage()) } - async getAllMessagesByFromToDID( + async getAllMessagesByFromToDID ( from: Domain.DID, to: Domain.DID ): Promise { @@ -1006,128 +998,129 @@ export class Database implements Domain.Pluto { selector: { $or: [ { - from: from.toString(), + from: from.toString() }, { - to: to.toString(), - }, - ], + to: to.toString() + } + ] } }).exec() - return messages.map((message) => message.toDomainMessage()); + return messages.map((message) => message.toDomainMessage()) } - async getPrismDIDKeyPathIndex(did: Domain.DID): Promise { - const [key] = await this.getDIDPrivateKeysByDID(did); + async getPrismDIDKeyPathIndex (did: Domain.DID): Promise { + const [key] = await this.getDIDPrivateKeysByDID(did) if (!key) { - return null; + return null } - return parseInt(key.index); + return parseInt(key.index) } - async getPrismLastKeyPathIndex(): Promise { - const results = await this.getAllPrismDIDs(); + async getPrismLastKeyPathIndex (): Promise { + const results = await this.getAllPrismDIDs() if (!results || results.length === 0) { - return 0; + return 0 } - return Math.max(...results.map((result) => result.keyPathIndex)); + return Math.max(...results.map((result) => result.keyPathIndex)) } - async getAllPeerDIDs(): Promise { - const peerDIDs: Domain.PeerDID[] = []; + async getAllPeerDIDs (): Promise { + const peerDIDs: Domain.PeerDID[] = [] const dids = await this.db.dids.find({ selector: { method: { $eq: 'peer' } } - }).exec(); - for (let did of dids) { - const peerDID = Domain.DID.fromString(did.did); - const keys = await this.getDIDPrivateKeysByDID(peerDID); + }).exec() + for (const did of dids) { + const peerDID = Domain.DID.fromString(did.did) + const keys = await this.getDIDPrivateKeysByDID(peerDID) peerDIDs.push( new Domain.PeerDID( peerDID, keys.map((key) => ({ keyCurve: { - curve: key.curve as any, + curve: key.curve as any }, - value: key.raw, + value: key.raw })) ) - ); + ) } - return peerDIDs; + return peerDIDs } - async storeCredential(credential: Domain.Credential): Promise { + async storeCredential (credential: Domain.Credential): Promise { if (!credential.isStorable || !credential.isStorable()) { - throw new Error("Credential is not storable"); + throw new Error('Credential is not storable') } - const storable = credential.toStorable(); + const storable = credential.toStorable() /* istanbul ignore else -- @preserve */ - if (!storable.id) storable.id = uuidv4(); + if (!storable.id) storable.id = uuidv4() - await this.db.credentials.insert(storable); + await this.db.credentials.insert(storable) } - async getAllMediators(): Promise { + async getAllMediators (): Promise { const mediators = await this.db.mediators.find().exec() - return mediators.map((mediator) => mediator.toDomainMediator()); + return mediators.map((mediator) => mediator.toDomainMediator()) } - async getAllCredentials(): Promise { + async getAllCredentials (): Promise { const credentials = await this.db.credentials.find().exec() return credentials.map( (verifiableCredential) => verifiableCredential.toDomainCredential() - ); + ) } - async getLinkSecret( + async getLinkSecret ( linkSecretName?: string | undefined ): Promise { - const query = linkSecretName ? - { - selector: { - name: { - $eq: linkSecretName + const query = linkSecretName + ? { + selector: { + name: { + $eq: linkSecretName + } } } - } : {} + : {} const linkSecret = await this.db.linksecrets - .findOne(query).exec(); + .findOne(query).exec() if (linkSecret) { - return linkSecret.toDomainLinkSecret(); + return linkSecret.toDomainLinkSecret() } - return null; + return null } - async storeLinkSecret( + async storeLinkSecret ( linkSecret: string, linkSecretName: string ): Promise { await this.db.linksecrets.insert({ name: linkSecretName, - secret: linkSecret, - }); + secret: linkSecret + }) } - async storeCredentialMetadata( + async storeCredentialMetadata ( metadata: Domain.Anoncreds.CredentialRequestMeta, linkSecret: string ): Promise { await this.db.credentialrequestmetadatas.insert({ ...metadata, id: uuidv4(), - link_secret_name: linkSecret, - }); + link_secret_name: linkSecret + }) } - async fetchCredentialMetadata( + async fetchCredentialMetadata ( linkSecretName: string ): Promise { const credentialRequestMetadata = await this.db.credentialrequestmetadatas @@ -1137,11 +1130,11 @@ export class Database implements Domain.Pluto { $eq: linkSecretName } } - }).exec(); + }).exec() if (credentialRequestMetadata) { - return credentialRequestMetadata.toDomainCredentialRequestMetadata(); + return credentialRequestMetadata.toDomainCredentialRequestMetadata() } - return null; + return null } } diff --git a/packages/database/src/schemas/Credential.ts b/packages/database/src/schemas/Credential.ts index 45c5b3c2..2e89545f 100644 --- a/packages/database/src/schemas/Credential.ts +++ b/packages/database/src/schemas/Credential.ts @@ -2,94 +2,94 @@ import { AnonCredsCredential, AnonCredsCredentialProperties, AnonCredsRecoveryId, - Domain, + type Domain, JWTCredential, - JWTVerifiableCredentialRecoveryId, -} from "@atala/prism-wallet-sdk"; -import type { GenericORMType, Schema } from "../types"; -import { RxCollection, RxDocument } from "rxdb"; + JWTVerifiableCredentialRecoveryId +} from '@atala/prism-wallet-sdk' +import type { Schema } from '../types' +import { type RxCollection, type RxDocument } from 'rxdb' -export type CredentialSubjectType = { - type: string; - name: string; - value: string; -}; +export interface CredentialSubjectType { + type: string + name: string + value: string +} -export type CredentialSchemaType = { - id: string; - recoveryId: string; - credentialData: string; - issuer?: string; - subject?: string; - credentialCreated?: string; - credentialUpdated?: string; - credentialSchema?: string; - validUntil?: string; - revoked?: boolean; - availableClaims?: string[]; -}; +export interface CredentialSchemaType { + id: string + recoveryId: string + credentialData: string + issuer?: string + subject?: string + credentialCreated?: string + credentialUpdated?: string + credentialSchema?: string + validUntil?: string + revoked?: boolean + availableClaims?: string[] +} const CredentialSchema: Schema = { version: 0, - primaryKey: "id", - type: "object", + primaryKey: 'id', + type: 'object', properties: { id: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, recoveryId: { - type: "string", + type: 'string' }, credentialData: { - type: "string", + type: 'string' }, issuer: { - type: "string", + type: 'string' }, subject: { - type: "string", + type: 'string' }, credentialCreated: { - type: "string", + type: 'string' }, credentialUpdated: { - type: "string", + type: 'string' }, credentialSchema: { - type: "string", + type: 'string' }, validUntil: { - type: "string", + type: 'string' }, revoked: { - type: "boolean", + type: 'boolean' }, availableClaims: { - type: "array", + type: 'array', items: { - type: "string", - }, - }, + type: 'string' + } + } }, - encrypted: ["credentialData"], - required: ["id", "recoveryId", "credentialData"], -}; + encrypted: ['credentialData'], + required: ['id', 'recoveryId', 'credentialData'] +} -export type CredentialDocument = RxDocument; -export type CredentialMethodTypes = { - toDomainCredential: (this: CredentialSchemaType) => Domain.Credential; -}; +export type CredentialDocument = RxDocument +export interface CredentialMethodTypes { + toDomainCredential: (this: CredentialSchemaType) => Domain.Credential +} export const CredentialMethods: CredentialMethodTypes = { - toDomainCredential: function toDomainCredential(this: CredentialSchemaType) { + toDomainCredential: function toDomainCredential (this: CredentialSchemaType) { if (this.recoveryId === JWTVerifiableCredentialRecoveryId) { - const jwtString = Buffer.from(this.credentialData).toString(); - const jwtObj = JSON.parse(jwtString); - return JWTCredential.fromJWT(jwtObj, jwtString); + const jwtString = Buffer.from(this.credentialData).toString() + const jwtObj = JSON.parse(jwtString) + return JWTCredential.fromJWT(jwtObj, jwtString) } else if (this.recoveryId === AnonCredsRecoveryId) { - const credentialData = Buffer.from(this.credentialData).toString(); - const credentialJson = JSON.parse(credentialData); + const credentialData = Buffer.from(this.credentialData).toString() + const credentialJson = JSON.parse(credentialData) return new AnonCredsCredential({ schema_id: credentialJson[AnonCredsCredentialProperties.schemaId], cred_def_id: @@ -98,19 +98,19 @@ export const CredentialMethods: CredentialMethodTypes = { signature: credentialJson[AnonCredsCredentialProperties.signasture], signature_correctness_proof: credentialJson[ - AnonCredsCredentialProperties.signatureCorrectnessProof - ], - }); + AnonCredsCredentialProperties.signatureCorrectnessProof + ] + }) } else { - throw new Error("Unsupported key type from db storage"); + throw new Error('Unsupported key type from db storage') } - }, -}; + } +} export type CredentialCollection = RxCollection< - CredentialSchemaType, - CredentialMethodTypes, - CredentialDocument ->; +CredentialSchemaType, +CredentialMethodTypes, +CredentialDocument +> -export default CredentialSchema; +export default CredentialSchema diff --git a/packages/database/src/schemas/CredentialRequestMetadata.ts b/packages/database/src/schemas/CredentialRequestMetadata.ts index 67ac6bcd..87d712b1 100644 --- a/packages/database/src/schemas/CredentialRequestMetadata.ts +++ b/packages/database/src/schemas/CredentialRequestMetadata.ts @@ -1,71 +1,70 @@ import { - Domain -} from "@atala/prism-wallet-sdk"; -import type { GenericORMType, Schema } from "../types"; -import { RxCollection, RxDocument } from "rxdb"; + type Domain +} from '@atala/prism-wallet-sdk' +import type { Schema } from '../types' +import { type RxCollection, type RxDocument } from 'rxdb' -export type CredentialRequestMetadataSchemaType = { - id: string; +export interface CredentialRequestMetadataSchemaType { + id: string link_secret_blinding_data: { - v_prime: string; - }; - link_secret_name: string; - nonce: string; -}; - + v_prime: string + } + link_secret_name: string + nonce: string +} const CredentialRequestMetadataSchema: Schema = { version: 0, - primaryKey: "id", - type: "object", + primaryKey: 'id', + type: 'object', properties: { id: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, link_secret_blinding_data: { - type: "object", + type: 'object', properties: { v_prime: { - type: "string", - }, - }, + type: 'string' + } + } }, link_secret_name: { - type: "string", + type: 'string' }, nonce: { - type: "string", - }, + type: 'string' + } }, - encrypted: ["link_secret_blinding_data", "nonce"], - required: ["id", "link_secret_blinding_data", "link_secret_name", "nonce"], -}; + encrypted: ['link_secret_blinding_data', 'nonce'], + required: ['id', 'link_secret_blinding_data', 'link_secret_name', 'nonce'] +} export type CredentialRequestMetadataDocument = - RxDocument; + RxDocument -export type CredentialRequestMetadataMethodTypes = { +export interface CredentialRequestMetadataMethodTypes { toDomainCredentialRequestMetadata: ( this: CredentialRequestMetadataSchemaType - ) => Domain.Anoncreds.CredentialRequestMeta; -}; + ) => Domain.Anoncreds.CredentialRequestMeta +} export const CredentialRequestMetadataMethods: CredentialRequestMetadataMethodTypes = { toDomainCredentialRequestMetadata: - function toDomainCredentialRequestMetadata( + function toDomainCredentialRequestMetadata ( this: CredentialRequestMetadataSchemaType ) { - return this; - }, -}; + return this + } +} export type CredentialRequestMetadataCollection = RxCollection< - CredentialRequestMetadataSchemaType, - CredentialRequestMetadataMethodTypes, - CredentialRequestMetadataDocument ->; +CredentialRequestMetadataSchemaType, +CredentialRequestMetadataMethodTypes, +CredentialRequestMetadataDocument +> -export default CredentialRequestMetadataSchema; +export default CredentialRequestMetadataSchema diff --git a/packages/database/src/schemas/DID.ts b/packages/database/src/schemas/DID.ts index e549af6d..e5cc00ec 100644 --- a/packages/database/src/schemas/DID.ts +++ b/packages/database/src/schemas/DID.ts @@ -1,45 +1,45 @@ -import { RxCollection, RxDocument } from "rxdb"; -import type { GenericORMType, Schema } from "../types"; +import { type RxCollection, type RxDocument } from 'rxdb' +import type { Schema } from '../types' -export type DIDSchemaType = { - schema: string; - method: string; - methodId: string; - alias?: string; - did: string; -}; +export interface DIDSchemaType { + schema: string + method: string + methodId: string + alias?: string + did: string +} const DIDSchema: Schema = { version: 0, - primaryKey: "did", - type: "object", + primaryKey: 'did', + type: 'object', properties: { method: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, methodId: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, schema: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, alias: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, did: { - type: "string", - maxLength: 60, - }, + type: 'string', + maxLength: 60 + } }, encrypted: [], - required: ["method", "methodId", "did", "schema"], -}; -export type DIDDocument = RxDocument; + required: ['method', 'methodId', 'did', 'schema'] +} +export type DIDDocument = RxDocument export type DIDCollection = RxCollection -export default DIDSchema; +export default DIDSchema diff --git a/packages/database/src/schemas/DIDPair.ts b/packages/database/src/schemas/DIDPair.ts index 05d53c0c..97f7d102 100644 --- a/packages/database/src/schemas/DIDPair.ts +++ b/packages/database/src/schemas/DIDPair.ts @@ -1,33 +1,33 @@ -import { RxCollection, RxDocument } from "rxdb"; -import type { GenericORMType, Schema } from "../types"; +import { type RxCollection, type RxDocument } from 'rxdb' +import type { Schema } from '../types' -export type DIDPairSchemaType = { - hostDID: string; - receiverDID: string; - name: string; -}; +export interface DIDPairSchemaType { + hostDID: string + receiverDID: string + name: string +} const DIDPairSchema: Schema = { version: 0, - primaryKey: "name", - type: "object", + primaryKey: 'name', + type: 'object', properties: { hostDID: { - type: "string", + type: 'string' }, name: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, receiverDID: { - type: "string", - }, + type: 'string' + } }, encrypted: [], - required: ["name", "hostDID", "receiverDID"], -}; + required: ['name', 'hostDID', 'receiverDID'] +} -export type DIDPairDocument = RxDocument; +export type DIDPairDocument = RxDocument export type DIDPairCollection = RxCollection -export default DIDPairSchema; +export default DIDPairSchema diff --git a/packages/database/src/schemas/LinkSecret.ts b/packages/database/src/schemas/LinkSecret.ts index 708af9a9..5ef3d830 100644 --- a/packages/database/src/schemas/LinkSecret.ts +++ b/packages/database/src/schemas/LinkSecret.ts @@ -1,48 +1,47 @@ -import { Domain } from "@atala/prism-wallet-sdk"; -import type { GenericORMType, Schema } from "../types"; -import { RxCollection, RxDocument } from "rxdb"; - -export type LinkSecretSchemaType = { - readonly name: string; - readonly secret: string; -}; +import { type Domain } from '@atala/prism-wallet-sdk' +import type { Schema } from '../types' +import { type RxCollection, type RxDocument } from 'rxdb' +export interface LinkSecretSchemaType { + readonly name: string + readonly secret: string +} const LinkSecretSchema: Schema = { version: 0, - primaryKey: "name", - type: "object", + primaryKey: 'name', + type: 'object', properties: { name: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, secret: { - type: "string", - }, + type: 'string' + } }, - encrypted: ["secret"], - required: ["name", "secret"], -}; + encrypted: ['secret'], + required: ['name', 'secret'] +} export type LinkSecretDocument = RxDocument< - LinkSecretSchemaType, - LinkSecretMethodTypes ->; +LinkSecretSchemaType, +LinkSecretMethodTypes +> -export type LinkSecretMethodTypes = { - toDomainLinkSecret: (this: LinkSecretDocument) => Domain.Anoncreds.LinkSecret; -}; +export interface LinkSecretMethodTypes { + toDomainLinkSecret: (this: LinkSecretDocument) => Domain.Anoncreds.LinkSecret +} export type LinkSecretColletion = RxCollection< - LinkSecretSchemaType, - LinkSecretMethodTypes, - LinkSecretDocument ->; +LinkSecretSchemaType, +LinkSecretMethodTypes, +LinkSecretDocument +> export const LinkSecretMethods: LinkSecretMethodTypes = { - toDomainLinkSecret: function toDomainLinkSecret(this: LinkSecretDocument) { - return this.secret; - }, -}; -export default LinkSecretSchema; + toDomainLinkSecret: function toDomainLinkSecret (this: LinkSecretDocument) { + return this.secret + } +} +export default LinkSecretSchema diff --git a/packages/database/src/schemas/Mediator.ts b/packages/database/src/schemas/Mediator.ts index 1be69120..001c41c7 100644 --- a/packages/database/src/schemas/Mediator.ts +++ b/packages/database/src/schemas/Mediator.ts @@ -1,56 +1,56 @@ -import { RxCollection, RxDocument, RxDocumentBase } from "rxdb"; -import type { GenericORMType, Schema } from "../types"; -import { Domain } from "@atala/prism-wallet-sdk"; +import { type RxCollection, type RxDocument } from 'rxdb' +import type { Schema } from '../types' +import { Domain } from '@atala/prism-wallet-sdk' -export type MediatorSchemaType = { - id: string; - mediatorDID: string; - hostDID: string; - routingDID: string; -}; +export interface MediatorSchemaType { + id: string + mediatorDID: string + hostDID: string + routingDID: string +} const MediatorSchema: Schema = { version: 0, - primaryKey: "id", - type: "object", + primaryKey: 'id', + type: 'object', properties: { id: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, mediatorDID: { - type: "string", + type: 'string' }, hostDID: { - type: "string", + type: 'string' }, routingDID: { - type: "string", - }, + type: 'string' + } }, encrypted: [], - required: ["id", "mediatorDID"], -}; + required: ['id', 'mediatorDID'] +} -export type MediatorDocument = RxDocument; -export type MediatorMethodTypes = { - toDomainMediator: (this: RxDocument) => Domain.Mediator; -}; +export type MediatorDocument = RxDocument +export interface MediatorMethodTypes { + toDomainMediator: (this: RxDocument) => Domain.Mediator +} export type MediatorCollection = RxCollection< - MediatorSchemaType, - MediatorMethodTypes, - MediatorDocument ->; +MediatorSchemaType, +MediatorMethodTypes, +MediatorDocument +> export const MediatorMethods: MediatorMethodTypes = { - toDomainMediator: function toDomainMediator(this: RxDocument) { + toDomainMediator: function toDomainMediator (this: RxDocument) { const mediator = this.toJSON() return { hostDID: Domain.DID.fromString(mediator.hostDID), routingDID: Domain.DID.fromString(mediator.routingDID), - mediatorDID: Domain.DID.fromString(mediator.mediatorDID), - }; - }, -}; + mediatorDID: Domain.DID.fromString(mediator.mediatorDID) + } + } +} -export default MediatorSchema; +export default MediatorSchema diff --git a/packages/database/src/schemas/Message.ts b/packages/database/src/schemas/Message.ts index f13ef055..4969959d 100644 --- a/packages/database/src/schemas/Message.ts +++ b/packages/database/src/schemas/Message.ts @@ -1,128 +1,125 @@ -import { Domain } from "@atala/prism-wallet-sdk"; -import type { GenericORMType, Schema } from "../types"; -import { RxCollection, RxCollectionCreator, RxDocument } from "rxdb"; - -export type MessageSchemaType = { - readonly body: string; - readonly id: string; - readonly piuri: string; - readonly from?: string | undefined; - readonly to?: string | undefined; - readonly attachments: Domain.AttachmentDescriptor[]; - readonly thid?: string; - readonly extraHeaders: string[]; - readonly createdTime: string; - readonly expiresTimePlus: string; - readonly ack: string[]; - readonly direction: Domain.MessageDirection; - readonly fromPrior?: string | undefined; - readonly pthid?: string | undefined; -}; +import { Domain } from '@atala/prism-wallet-sdk' +import type { Schema } from '../types' +import { type RxCollection, type RxDocument } from 'rxdb' +export interface MessageSchemaType { + readonly body: string + readonly id: string + readonly piuri: string + readonly from?: string | undefined + readonly to?: string | undefined + readonly attachments: Domain.AttachmentDescriptor[] + readonly thid?: string + readonly extraHeaders: string[] + readonly createdTime: string + readonly expiresTimePlus: string + readonly ack: string[] + readonly direction: Domain.MessageDirection + readonly fromPrior?: string | undefined + readonly pthid?: string | undefined +} const MessageSchema: Schema = { version: 0, - primaryKey: "id", - type: "object", + primaryKey: 'id', + type: 'object', properties: { id: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, body: { - type: "string", + type: 'string' }, piuri: { - type: "string", + type: 'string' }, attachments: { - type: "array", + type: 'array', items: { - type: "object", + type: 'object', properties: { id: { - type: "id", - maxLength: 60, + type: 'id', + maxLength: 60 }, description: { - type: "string", + type: 'string' }, byteCount: { - type: "number", + type: 'number' }, lastModTime: { - type: "string", + type: 'string' }, format: { - type: "string", + type: 'string' }, filename: { - type: "array", + type: 'array', items: { - type: "string", - }, + type: 'string' + } }, mediaType: { - type: "string", + type: 'string' }, data: { - type: "object", - }, - }, - }, + type: 'object' + } + } + } }, extraHeaders: { - type: "array", + type: 'array' }, createdTime: { - type: "string", + type: 'string' }, expiresTimePlus: { - type: "string", + type: 'string' }, ack: { - type: "array", + type: 'array' }, direction: { - type: "number", + type: 'number' }, from: { - type: "string", + type: 'string' }, to: { - type: "string", + type: 'string' }, thid: { - type: "string", + type: 'string' }, fromPrior: { - type: "string", + type: 'string' }, pthid: { - type: "string", - }, + type: 'string' + } }, - encrypted: ["thid", "attachments", "body"], - required: ["id"], -}; -export type MessageDocument = RxDocument; + encrypted: ['thid', 'attachments', 'body'], + required: ['id'] +} +export type MessageDocument = RxDocument -export type MessageMethodTypes = { - toDomainMessage: (this: MessageDocument) => Domain.Message; -}; +export interface MessageMethodTypes { + toDomainMessage: (this: MessageDocument) => Domain.Message +} export type MessageColletion = RxCollection< - MessageSchemaType, - MessageMethodTypes, - MessageDocument ->; - - +MessageSchemaType, +MessageMethodTypes, +MessageDocument +> export const MessageMethods: MessageMethodTypes = { - toDomainMessage: function toDomainMessage(this: MessageDocument) { - return Domain.Message.fromJson(JSON.stringify(this)); - }, -}; + toDomainMessage: function toDomainMessage (this: MessageDocument) { + return Domain.Message.fromJson(JSON.stringify(this)) + } +} -export default MessageSchema; +export default MessageSchema diff --git a/packages/database/src/schemas/PrivateKey.ts b/packages/database/src/schemas/PrivateKey.ts index 762f6736..2105fa1d 100644 --- a/packages/database/src/schemas/PrivateKey.ts +++ b/packages/database/src/schemas/PrivateKey.ts @@ -3,88 +3,88 @@ import { KeyProperties, Secp256k1PrivateKey, X25519PrivateKey, - Domain, -} from "@atala/prism-wallet-sdk"; -import type { GenericORMType, Schema } from "../types"; -import { RxCollection, RxDocument } from "rxdb"; - -export type KeySpec = { - name: string; - type: string; - value: string; -}; - -export type KeySchemaType = { - id: string; - type: string; - did: string; - keySpecification: KeySpec[]; -}; + Domain +} from '@atala/prism-wallet-sdk' +import type { Schema } from '../types' +import { type RxCollection, type RxDocument } from 'rxdb' + +export interface KeySpec { + name: string + type: string + value: string +} + +export interface KeySchemaType { + id: string + type: string + did: string + keySpecification: KeySpec[] +} const PrivateKeySchema: Schema = { version: 0, - primaryKey: "id", - type: "object", + primaryKey: 'id', + type: 'object', properties: { id: { - type: "string", - maxLength: 60, + type: 'string', + maxLength: 60 }, did: { - type: "string", + type: 'string' }, type: { - type: "string", + type: 'string' }, keySpecification: { - type: "array", + type: 'array', items: { - type: "object", + type: 'object', properties: { name: { - type: "string", + type: 'string' }, type: { - type: "string", + type: 'string' }, value: { - type: "string", - }, - }, - }, - }, + type: 'string' + } + } + } + } }, - encrypted: ["keySpecification", "type"], - required: ["keySpecification", "did", "type", "id"], -}; + encrypted: ['keySpecification', 'type'], + required: ['keySpecification', 'did', 'type', 'id'] +} -export type PrivateKeyMethodTypes = { - toDomainPrivateKey: (this: PrivateKeyDocument) => Domain.PrivateKey; -}; +export interface PrivateKeyMethodTypes { + toDomainPrivateKey: (this: PrivateKeyDocument) => Domain.PrivateKey +} export type PrivateKeyColletion = RxCollection< - KeySchemaType, - PrivateKeyMethodTypes, - PrivateKeyDocument ->; +KeySchemaType, +PrivateKeyMethodTypes, +PrivateKeyDocument +> export type PrivateKeyDocument = RxDocument< - KeySchemaType, - PrivateKeyMethodTypes ->; +KeySchemaType, +PrivateKeyMethodTypes +> export const PrivateKeyMethods: PrivateKeyMethodTypes = { - toDomainPrivateKey: function toDomainPrivateKey(this: PrivateKeyDocument) { - const { type, keySpecification } = this; + toDomainPrivateKey: function toDomainPrivateKey (this: PrivateKeyDocument) { + const { type, keySpecification } = this const curve = keySpecification.find( (item) => item.name === KeyProperties.curve - ); + ) const raw = keySpecification.find( (item) => item.name === KeyProperties.rawKey - ); + ) if (!(type in Domain.KeyTypes)) { - throw new Error(`Invalid KeyType ${type || "undefined"}`); + throw new Error(`Invalid KeyType ${type || 'undefined'}`) } if (!curve) { - throw new Error("Undefined key curve"); + throw new Error('Undefined key curve') } if ( @@ -92,75 +92,75 @@ export const PrivateKeyMethods: PrivateKeyMethodTypes = { curve.value !== Domain.Curve.ED25519 && curve.value !== Domain.Curve.X25519 ) { - throw new Error(`Invalid key curve ${curve.value}`); + throw new Error(`Invalid key curve ${curve.value}`) } if (!raw) { - throw new Error("Undefined key raw"); + throw new Error('Undefined key raw') } /* istanbul ignore else -- @preserve */ if (curve.value === Domain.Curve.SECP256K1) { const index = keySpecification.find( (item) => item.name === KeyProperties.index - ); + ) const seed = keySpecification.find( (item) => item.name === KeyProperties.seed - ); + ) const privateKey = new Secp256k1PrivateKey( - Buffer.from(raw.value, "hex") - ); + Buffer.from(raw.value, 'hex') + ) - privateKey.keySpecification.set(Domain.KeyProperties.rawKey, raw.value); + privateKey.keySpecification.set(Domain.KeyProperties.rawKey, raw.value) privateKey.keySpecification.set( Domain.KeyProperties.curve, Domain.Curve.SECP256K1 - ); + ) if (index) { privateKey.keySpecification.set( Domain.KeyProperties.index, index.value - ); + ) } if (seed) { privateKey.keySpecification.set( Domain.KeyProperties.seed, seed.value - ); + ) } - return privateKey; + return privateKey } else if (curve.value === Domain.Curve.ED25519) { - const privateKey = new Ed25519PrivateKey(Buffer.from(raw.value, "hex")); + const privateKey = new Ed25519PrivateKey(Buffer.from(raw.value, 'hex')) - privateKey.keySpecification.set(Domain.KeyProperties.rawKey, raw.value); + privateKey.keySpecification.set(Domain.KeyProperties.rawKey, raw.value) privateKey.keySpecification.set( Domain.KeyProperties.curve, Domain.Curve.ED25519 - ); + ) - return privateKey; + return privateKey } else if (curve.value === Domain.Curve.X25519) { - const privateKey = new X25519PrivateKey(Buffer.from(raw.value, "hex")); + const privateKey = new X25519PrivateKey(Buffer.from(raw.value, 'hex')) - privateKey.keySpecification.set(Domain.KeyProperties.rawKey, raw.value); + privateKey.keySpecification.set(Domain.KeyProperties.rawKey, raw.value) privateKey.keySpecification.set( Domain.KeyProperties.curve, Domain.Curve.X25519 - ); + ) - return privateKey; + return privateKey } else { /* istanbul ignore next -- @preserve */ - throw new Error(`Invalid key ${curve.value} ${type}`); + throw new Error(`Invalid key ${curve.value} ${type}`) } - }, -}; + } +} -export default PrivateKeySchema; +export default PrivateKeySchema diff --git a/packages/database/src/types.ts b/packages/database/src/types.ts index ae794f40..7a15101d 100644 --- a/packages/database/src/types.ts +++ b/packages/database/src/types.ts @@ -1,46 +1,44 @@ -import { KeyFunctionMap, RxCollectionBase, RxJsonSchema, RxQuery, RxStorageInstanceCreationParams } from "rxdb"; -import { MangoQuery, MangoQueryNoLimit, MangoQuerySelectorAndIndex, RxCollection, RxDocument, RxDumpDatabase, RxStorage } from "rxdb/dist/types/types"; -import { CredentialCollection } from "./schemas/Credential"; -import { CredentialRequestMetadataCollection } from "./schemas/CredentialRequestMetadata"; -import { DIDCollection } from "./schemas/DID"; -import { DIDPairCollection } from "./schemas/DIDPair"; -import { LinkSecretColletion } from "./schemas/LinkSecret"; -import { MediatorCollection } from "./schemas/Mediator"; -import { MessageColletion } from "./schemas/Message"; -import { PrivateKeyColletion } from "./schemas/PrivateKey"; - +import { type RxJsonSchema } from 'rxdb' +import { type MangoQuery, type MangoQueryNoLimit, type RxCollection, type RxDocument, type RxDumpDatabase, type RxStorage } from 'rxdb/dist/types/types' +import { type CredentialCollection } from './schemas/Credential' +import { type CredentialRequestMetadataCollection } from './schemas/CredentialRequestMetadata' +import { type DIDCollection } from './schemas/DID' +import { type DIDPairCollection } from './schemas/DIDPair' +import { type LinkSecretColletion } from './schemas/LinkSecret' +import { type MediatorCollection } from './schemas/Mediator' +import { type MessageColletion } from './schemas/Message' +import { type PrivateKeyColletion } from './schemas/PrivateKey' export type NoKeys = { - [P in Exclude]: T[P]; -}; + [P in Exclude]: T[P]; +} export type Schema = RxJsonSchema & { - encrypted: (keyof T)[]; -}; + encrypted: Array +} export interface GenericORMType extends RxCollection { - count: (this: RxCollection, query: MangoQuery | undefined) => Promise, - findByIds(this: RxCollection, ids: string[]): Map; - find(queryObj?: MangoQueryNoLimit | string): RxDocument[]; - findOne(queryObj?: MangoQueryNoLimit | string): RxDocument | null; - remove(queryObj?: MangoQueryNoLimit | string): RxDocument[] + count: (this: RxCollection, query: MangoQuery | undefined) => Promise + findByIds: (this: RxCollection, ids: string[]) => Map + find: (queryObj?: MangoQueryNoLimit | string) => Array> + findOne: (queryObj?: MangoQueryNoLimit | string) => RxDocument | null + remove: (queryObj?: MangoQueryNoLimit | string) => Array> } +export interface PlutoCollections { + messages: MessageColletion + dids: DIDCollection + didpairs: DIDPairCollection + mediators: MediatorCollection + privatekeys: PrivateKeyColletion + credentials: CredentialCollection + credentialrequestmetadatas: CredentialRequestMetadataCollection + linksecrets: LinkSecretColletion +} -export type PlutoCollections = { - messages: MessageColletion; - dids: DIDCollection; - didpairs: DIDPairCollection; - mediators: MediatorCollection; - privatekeys: PrivateKeyColletion; - credentials: CredentialCollection; - credentialrequestmetadatas: CredentialRequestMetadataCollection; - linksecrets: LinkSecretColletion; -}; - -export type createEncryptedOptions = { - name: string, - encryptionKey: Uint8Array, - importData?: RxDumpDatabase, - storage: RxStorage -} \ No newline at end of file +export interface createEncryptedOptions { + name: string + encryptionKey: Uint8Array + importData?: RxDumpDatabase + storage: RxStorage +} diff --git a/packages/encryption/src/index.ts b/packages/encryption/src/index.ts index 4424281a..c9178a49 100644 --- a/packages/encryption/src/index.ts +++ b/packages/encryption/src/index.ts @@ -1,7 +1,7 @@ /** * @packageDocumentation * @module encryption - * @description This package is an rxdb encryption layer, a replacement for the vulnerable crypto-js dependency provided by the free version of rxDB. + * @description This package is an rxdb encryption layer, a replacement for the vulnerable crypto-js dependency provided by the free version of rxDB. * The package can be used outside or Pluto as its fully compatible with RXDB. * @examples In order to use this package in any RXDB environment type the following code. * Install the package with npm @@ -12,7 +12,7 @@ * ```bash * npm i @pluto-encrypted/encryption --save * ``` - * + * * Integrate in your existing RXDB storage. * ```typescript * import { wrappedKeyEncryptionStorage } from "@pluto-encrypted/encryption"; @@ -24,277 +24,273 @@ * ``` */ import { - InternalStoreDocType, - RxAttachmentWriteData, - RxDocumentData, - RxDocumentWriteData, - RxError, - RxJsonSchema, - RxStorage, - RxStorageInstanceCreationParams, - b64DecodeUnicode, - b64EncodeUnicode, - clone, - ensureNotFalsy, - flatClone, - getProperty, - hasEncryption, - newRxError, - newRxTypeError, - setProperty, - wrapRxStorageInstance -} from "rxdb"; - -import { sha256 } from '@noble/hashes/sha256'; -import { chacha20poly1305 } from '@noble/ciphers/chacha'; -import { CipherWithOutput } from "@noble/ciphers/utils"; - -export type { RxStorage } from "rxdb"; -export type { CipherWithOutput } from "@noble/ciphers/utils" -export const MINIMUM_PASSWORD_LENGTH: 8 = 8; - -//We must keep nonce static to be able to restore the database later, user only has the password + type InternalStoreDocType, + type RxAttachmentWriteData, + type RxDocumentData, + type RxDocumentWriteData, + type RxError, + type RxJsonSchema, + type RxStorage, + type RxStorageInstanceCreationParams, + b64DecodeUnicode, + b64EncodeUnicode, + clone, + ensureNotFalsy, + flatClone, + getProperty, + hasEncryption, + newRxError, + newRxTypeError, + setProperty, + wrapRxStorageInstance +} from 'rxdb' + +import { sha256 } from '@noble/hashes/sha256' +import { chacha20poly1305 } from '@noble/ciphers/chacha' +import { type CipherWithOutput } from '@noble/ciphers/utils' + +export type { RxStorage } from 'rxdb' +export type { CipherWithOutput } from '@noble/ciphers/utils' +export const MINIMUM_PASSWORD_LENGTH: 8 = 8 + +// We must keep nonce static to be able to restore the database later, user only has the password const nonce = Buffer.from('b47e1d4e5f7377c2e80a19b8', 'hex') -export function encryptString(chacha: CipherWithOutput, value: string): string { - try { - const encrypted = chacha.encrypt(Buffer.from(value)); - return Buffer.from(encrypted).toString('hex'); - } catch (err) { - if ((err as RxError).code === "DB1" || (err as RxError).message === "invalid tag") { - throw new Error("Invalid Authentication"); - } else { - /* istanbul ignore next */ - - throw err; - } +export function encryptString (chacha: CipherWithOutput, value: string): string { + try { + const encrypted = chacha.encrypt(Buffer.from(value)) + return Buffer.from(encrypted).toString('hex') + } catch (err) { + if ((err as RxError).code === 'DB1' || (err as RxError).message === 'invalid tag') { + throw new Error('Invalid Authentication') + } else { + /* istanbul ignore next */ + + throw err } + } } -export function decryptString(chacha: CipherWithOutput, cipherText: string): string { - try { - /** +export function decryptString (chacha: CipherWithOutput, cipherText: string): string { + try { + /** * Trying to decrypt non-strings * will cause no errors and will be hard to debug. * So instead we do this check here. */ - if (typeof cipherText !== 'string') { - throw newRxError('SNH', { - args: { - cipherText - } - }); - } - const decrypted = chacha.decrypt(Buffer.from(cipherText, 'hex')); - const ret = Buffer.from(decrypted).toString(); - return ret; - } catch (err) { - if ((err as RxError).code === "DB1" || (err as RxError).message === "invalid tag") { - throw new Error("Invalid Authentication"); - } else { - /* istanbul ignore next */ - - throw err; + if (typeof cipherText !== 'string') { + throw newRxError('SNH', { + args: { + cipherText } + }) } + const decrypted = chacha.decrypt(Buffer.from(cipherText, 'hex')) + const ret = Buffer.from(decrypted).toString() + return ret + } catch (err) { + if ((err as RxError).code === 'DB1' || (err as RxError).message === 'invalid tag') { + throw new Error('Invalid Authentication') + } else { + /* istanbul ignore next */ + + throw err + } + } } export type InternalStorePasswordDocType = InternalStoreDocType<{ - hash: string; -}>; + hash: string +}> -export type EncryptableStorageType = { - storage: RxStorage; +export interface EncryptableStorageType { + storage: RxStorage } - /** * Create encrypted storage for pluto-encrypted * @param args { storage: RxStorage; } * @returns RxStorage */ -export function wrappedKeyEncryptionStorage( - args: EncryptableStorageType +export function wrappedKeyEncryptionStorage ( + args: EncryptableStorageType ): RxStorage { - return Object.assign( - {}, - args.storage, - { - async createStorageInstance( - params: RxStorageInstanceCreationParams - ) { - try { - if (typeof params.password !== 'undefined') { - validatePassword(params.password as any); - } - - if (!hasEncryption(params.schema)) { - const retInstance = await args.storage.createStorageInstance(params); - return retInstance; - } - - if (!params.password) { - throw newRxError('EN3', { - database: params.databaseName, - collection: params.collectionName, - schema: params.schema - }); - } - - const password = params.password; - - const hashedPassword = sha256(password) - const chacha = chacha20poly1305(hashedPassword, nonce); - - const schemaWithoutEncrypted: RxJsonSchema> = clone(params.schema); - delete schemaWithoutEncrypted.encrypted; - if (schemaWithoutEncrypted.attachments) { - schemaWithoutEncrypted.attachments.encrypted = false; - } - - const instance = await args.storage.createStorageInstance( - Object.assign( - {}, - params, - { - schema: schemaWithoutEncrypted - } - ) - ); - - function modifyToStorage(docData: RxDocumentWriteData) { - try { - docData = cloneWithoutAttachments(docData); - ensureNotFalsy(params.schema.encrypted) - .forEach(path => { - const value = getProperty(docData, path); - if (typeof value === 'undefined') { - return; - } - - const stringValue = JSON.stringify(value); - const encrypted = encryptString(chacha, stringValue); - setProperty(docData, path, encrypted); - }); - - // handle attachments - if ( - params.schema.attachments && + return Object.assign( + {}, + args.storage, + { + async createStorageInstance( + params: RxStorageInstanceCreationParams + ) { + try { + if (typeof params.password !== 'undefined') { + validatePassword(params.password) + } + + if (!hasEncryption(params.schema)) { + const retInstance = await args.storage.createStorageInstance(params) + return retInstance + } + + if (!params.password) { + throw newRxError('EN3', { + database: params.databaseName, + collection: params.collectionName, + schema: params.schema + }) + } + + const password = params.password + + const hashedPassword = sha256(password) + const chacha = chacha20poly1305(hashedPassword, nonce) + + const schemaWithoutEncrypted: RxJsonSchema> = clone(params.schema) + delete schemaWithoutEncrypted.encrypted + if (schemaWithoutEncrypted.attachments) { + schemaWithoutEncrypted.attachments.encrypted = false + } + + const instance = await args.storage.createStorageInstance( + Object.assign( + {}, + params, + { + schema: schemaWithoutEncrypted + } + ) + ) + + function modifyToStorage (docData: RxDocumentWriteData) { + try { + docData = cloneWithoutAttachments(docData) + ensureNotFalsy(params.schema.encrypted) + .forEach(path => { + const value = getProperty(docData, path) + if (typeof value === 'undefined') { + return + } + + const stringValue = JSON.stringify(value) + const encrypted = encryptString(chacha, stringValue) + setProperty(docData, path, encrypted) + }) + + // handle attachments + if ( + params.schema.attachments && params.schema.attachments.encrypted - ) { - const newAttachments: typeof docData._attachments = {}; - Object.entries(docData._attachments).forEach(([id, attachment]) => { - const useAttachment: RxAttachmentWriteData = flatClone(attachment) as any; - if (useAttachment.data) { - const dataString = useAttachment.data; - useAttachment.data = b64EncodeUnicode(encryptString(chacha, dataString)); - } - newAttachments[id] = useAttachment; - }); - docData._attachments = newAttachments; - } - return docData; - } catch (err) { - if ((err as RxError).code === "DB1" || (err as RxError).message === "invalid tag") { - throw new Error("Invalid Authentication"); - } else { - /* istanbul ignore next */ - - throw err; - } - } - } - function modifyFromStorage(docData: RxDocumentData): Promise> { - try { - docData = cloneWithoutAttachments(docData); - ensureNotFalsy(params.schema.encrypted) - .forEach(path => { - const value = getProperty(docData, path); - if (typeof value === 'undefined') { - return; - } - const decrypted = decryptString(chacha, value); - const decryptedParsed = JSON.parse(decrypted); - setProperty(docData, path, decryptedParsed); - }); - return docData; - } catch (err) { - if ((err as RxError).code === "DB1" || (err as RxError).message === "invalid tag") { - throw new Error("Invalid Authentication"); - } else { - /* istanbul ignore next */ - - throw err; - } - } - } + ) { + const newAttachments: typeof docData._attachments = {} + Object.entries(docData._attachments).forEach(([id, attachment]) => { + const useAttachment: RxAttachmentWriteData = flatClone(attachment) as any + if (useAttachment.data) { + const dataString = useAttachment.data + useAttachment.data = b64EncodeUnicode(encryptString(chacha, dataString)) + } + newAttachments[id] = useAttachment + }) + docData._attachments = newAttachments + } + return docData + } catch (err) { + if ((err as RxError).code === 'DB1' || (err as RxError).message === 'invalid tag') { + throw new Error('Invalid Authentication') + } else { + /* istanbul ignore next */ + + throw err + } + } + } + async function modifyFromStorage (docData: RxDocumentData): Promise> { + try { + docData = cloneWithoutAttachments(docData) + ensureNotFalsy(params.schema.encrypted) + .forEach(path => { + const value = getProperty(docData, path) + if (typeof value === 'undefined') { + return + } + const decrypted = decryptString(chacha, value) + const decryptedParsed = JSON.parse(decrypted) + setProperty(docData, path, decryptedParsed) + }) + return docData + } catch (err) { + if ((err as RxError).code === 'DB1' || (err as RxError).message === 'invalid tag') { + throw new Error('Invalid Authentication') + } else { + /* istanbul ignore next */ + + throw err + } + } + } - function modifyAttachmentFromStorage(attachmentData: string): string { - try { - if ( - params.schema.attachments && + function modifyAttachmentFromStorage (attachmentData: string): string { + try { + if ( + params.schema.attachments && params.schema.attachments.encrypted - ) { - const decrypted = decryptString(chacha, b64DecodeUnicode(attachmentData)); - return decrypted; - } else { - return attachmentData; - } - } catch (err) { - if ((err as RxError).code === "DB1" || (err as RxError).message === "invalid tag") { - throw new Error("Invalid Authentication"); - } else { - /* istanbul ignore next */ - - throw err; - } - } - } - - - return wrapRxStorageInstance( - instance, - modifyToStorage, - modifyFromStorage, - modifyAttachmentFromStorage - ); - } catch (err) { - if ((err as RxError).code === "DB1" || (err as RxError).message === "invalid tag") { - throw new Error("Invalid Authentication"); - } else { - /* istanbul ignore next */ - - throw err; - } - } + ) { + const decrypted = decryptString(chacha, b64DecodeUnicode(attachmentData)) + return decrypted + } else { + return attachmentData + } + } catch (err) { + if ((err as RxError).code === 'DB1' || (err as RxError).message === 'invalid tag') { + throw new Error('Invalid Authentication') + } else { + /* istanbul ignore next */ + + throw err + } } + } + + return wrapRxStorageInstance( + instance, + modifyToStorage, + modifyFromStorage, + modifyAttachmentFromStorage + ) + } catch (err) { + if ((err as RxError).code === 'DB1' || (err as RxError).message === 'invalid tag') { + throw new Error('Invalid Authentication') + } else { + /* istanbul ignore next */ + + throw err + } } - ); + } + } + ) } -function cloneWithoutAttachments(data: RxDocumentWriteData): RxDocumentData { - const attachments = data._attachments; - data = flatClone(data); - delete (data as any)._attachments; - data = clone(data); - data._attachments = attachments; - return data as any; +function cloneWithoutAttachments (data: RxDocumentWriteData): RxDocumentData { + const attachments = data._attachments + data = flatClone(data) + delete (data as any)._attachments + data = clone(data) + data._attachments = attachments + return data as any } -function validatePassword(password: string) { - if (typeof password !== 'string') { - throw newRxTypeError('EN1', { - password - }); - } - if (password.length < MINIMUM_PASSWORD_LENGTH) { - throw newRxError('EN2', { - minPassLength: MINIMUM_PASSWORD_LENGTH, - password - }); - } +function validatePassword (password: string) { + if (typeof password !== 'string') { + throw newRxTypeError('EN1', { + password + }) + } + if (password.length < MINIMUM_PASSWORD_LENGTH) { + throw newRxError('EN2', { + minPassLength: MINIMUM_PASSWORD_LENGTH, + password + }) + } } - export * from './migration' - diff --git a/packages/encryption/src/migration/index.ts b/packages/encryption/src/migration/index.ts index f22a3c92..ba94abdb 100644 --- a/packages/encryption/src/migration/index.ts +++ b/packages/encryption/src/migration/index.ts @@ -1,241 +1,220 @@ import { - combineLatest, - Observable -} from 'rxjs'; + combineLatest, + type Observable + , + BehaviorSubject +} from 'rxjs' import { - shareReplay, - switchMap -} from 'rxjs/operators'; -import { INTERNAL_CONTEXT_COLLECTION, InternalStoreCollectionDocType, NumberFunctionMap, PROMISE_RESOLVE_NULL, RxDocumentData, RxJsonSchema, RxSchema, RxStorageInstance, RxStorageInstanceCreationParams, WithAttachmentsData, clone, createRevision, createRxSchema, deepEqual, flatClone, getDefaultRxDocumentMeta, getPreviousVersions, getPrimaryKeyOfInternalDocument, getWrappedStorageInstance, newRxError, normalizeMangoQuery, now, overwritable, runAsyncPluginHooks, runPluginHooks, toPromise } from 'rxdb'; + shareReplay, + switchMap +} from 'rxjs/operators' +import { INTERNAL_CONTEXT_COLLECTION, type InternalStoreCollectionDocType, type NumberFunctionMap, PROMISE_RESOLVE_NULL, type RxDocumentData, type RxJsonSchema, type RxSchema, type RxStorageInstance, type RxStorageInstanceCreationParams, type WithAttachmentsData, clone, createRevision, createRxSchema, deepEqual, flatClone, getDefaultRxDocumentMeta, getPreviousVersions, getPrimaryKeyOfInternalDocument, getWrappedStorageInstance, normalizeMangoQuery, now, overwritable, runAsyncPluginHooks, runPluginHooks, toPromise, type MigrationState, type RxStorage, getFromMapOrCreate, PROMISE_RESOLVE_FALSE, type RxCollection, RXJS_SHARE_REPLAY_DEFAULTS, type RxPlugin } from 'rxdb' -import { MigrationState, RxStorage } from 'rxdb'; -import { - BehaviorSubject, -} from 'rxjs'; - - - -import { getFromMapOrCreate, PROMISE_RESOLVE_FALSE, RxCollection, RXJS_SHARE_REPLAY_DEFAULTS, RxPlugin } from 'rxdb'; -import { AllMigrationStates, RxDatabase, RxDocument } from 'rxdb/dist/types/types'; +import { type AllMigrationStates, type RxDatabase, type RxDocument } from 'rxdb/dist/types/types' export interface OldRxCollection { - version: number; - schema: RxSchema; - storageInstance: RxStorageInstance; - dataMigrator: EncryptedDataMigrator; - newestCollection: RxCollection; - database: RxDatabase; - _migrate?: boolean; - _migratePromise?: Promise; + version: number + schema: RxSchema + storageInstance: RxStorageInstance + dataMigrator: EncryptedDataMigrator + newestCollection: RxCollection + database: RxDatabase + _migrate?: boolean + _migratePromise?: Promise } -export const MIGRATION_DEFAULT_BATCH_SIZE = 200; +export const MIGRATION_DEFAULT_BATCH_SIZE = 200 export class EncryptedDataMigrator { - - constructor( - public newestCollection: RxCollection, - public migrationStrategies: NumberFunctionMap - ) { - this.currentSchema = newestCollection.schema; - this.database = newestCollection.database; - this.name = newestCollection.name; - } - - public currentSchema: RxSchema; - public database: RxDatabase; - public name: string; - - async countAllDocuments( - storage: RxStorage, - storageInstance: RxStorageInstance, - schema: RxJsonSchema - ): Promise { - const getAllQueryPrepared = storage.statics.prepareQuery( - storageInstance.schema, - normalizeMangoQuery( - schema, - {} - ) - ); - const queryResult = await storageInstance.count(getAllQueryPrepared); - return queryResult.count; + constructor ( + public newestCollection: RxCollection, + public migrationStrategies: NumberFunctionMap + ) { + this.currentSchema = newestCollection.schema + this.database = newestCollection.database + this.name = newestCollection.name + } + + public currentSchema: RxSchema + public database: RxDatabase + public name: string + + async countAllDocuments ( + storage: RxStorage, + storageInstance: RxStorageInstance, + schema: RxJsonSchema + ): Promise { + const getAllQueryPrepared = storage.statics.prepareQuery( + storageInstance.schema, + normalizeMangoQuery( + schema, + {} + ) + ) + const queryResult = await storageInstance.count(getAllQueryPrepared) + return queryResult.count + } + + async migratePromise (batchSize: number): Promise { + const migrationNeeded = await mustMigrate(this) + if (!migrationNeeded) { + return false } - - async migratePromise(batchSize: number): Promise { - return new Promise(async (resolve, reject) => { - const migrationNeeded = await mustMigrate(this) - if (!migrationNeeded) { - return resolve(false) - } - try { - const oldCollections = await _getOldCollections(this) - while (oldCollections.length > 0) { - const oldCollection = oldCollections.shift() - if (oldCollection) { - let row: RxDocument - let notMatching: any[] = [] - while ((row = await getBatchOfOldCollection(oldCollection, batchSize, notMatching)).length > 0) { - await _migrateDocuments(oldCollection, row) - notMatching.push(...row.map((document) => document[oldCollection.schema.primaryPath])) - } - await deleteOldCollection(oldCollection) - } - } - return resolve(true) - } catch (err) { - - return reject(err); - } - }) - + const oldCollections = await _getOldCollections(this) + while (oldCollections.length > 0) { + const oldCollection = oldCollections.shift() + if (oldCollection) { + let row: RxDocument + const notMatching: any[] = [] + while ((row = await getBatchOfOldCollection(oldCollection, batchSize, notMatching)).length > 0) { + await _migrateDocuments(oldCollection, row) + notMatching.push(...row.map((document) => document[oldCollection.schema.primaryPath])) + } + await deleteOldCollection(oldCollection) + } } + return true + } } - - - -export async function createOldCollection( - version: number, - schemaObj: RxJsonSchema, - dataMigrator: EncryptedDataMigrator +export async function createOldCollection ( + version: number, + schemaObj: RxJsonSchema, + dataMigrator: EncryptedDataMigrator ): Promise { - const database = dataMigrator.newestCollection.database; - const storageInstanceCreationParams: RxStorageInstanceCreationParams = { - databaseInstanceToken: database.token, - databaseName: database.name, - collectionName: dataMigrator.newestCollection.name, - schema: schemaObj, - options: dataMigrator.newestCollection.instanceCreationOptions, - multiInstance: database.multiInstance, - devMode: overwritable.isDevMode(), - password: database.password - }; - runPluginHooks( - 'preCreateRxStorageInstance', - storageInstanceCreationParams - ); - - const storageInstance = await database.storage.createStorageInstance( - storageInstanceCreationParams - ); - const ret: OldRxCollection = { - version, - dataMigrator, - newestCollection: dataMigrator.newestCollection, - database, - schema: createRxSchema(schemaObj, database.hashFunction, false), - storageInstance - }; - - ret.storageInstance = getWrappedStorageInstance( - ret.database, - storageInstance, - schemaObj - ); - - return ret; + const database = dataMigrator.newestCollection.database + const storageInstanceCreationParams: RxStorageInstanceCreationParams = { + databaseInstanceToken: database.token, + databaseName: database.name, + collectionName: dataMigrator.newestCollection.name, + schema: schemaObj, + options: dataMigrator.newestCollection.instanceCreationOptions, + multiInstance: database.multiInstance, + devMode: overwritable.isDevMode(), + password: database.password + } + runPluginHooks( + 'preCreateRxStorageInstance', + storageInstanceCreationParams + ) + + const storageInstance = await database.storage.createStorageInstance( + storageInstanceCreationParams + ) + const ret: OldRxCollection = { + version, + dataMigrator, + newestCollection: dataMigrator.newestCollection, + database, + schema: createRxSchema(schemaObj, database.hashFunction, false), + storageInstance + } + + ret.storageInstance = getWrappedStorageInstance( + ret.database, + storageInstance, + schemaObj + ) + + return ret } - -export async function getOldCollectionDocs( - dataMigrator: EncryptedDataMigrator -): Promise[]> { - - const collectionDocKeys = getPreviousVersions(dataMigrator.currentSchema.jsonSchema) - .map(version => dataMigrator.name + '-' + version); - - const docs = await dataMigrator.database.internalStore.findDocumentsById( - collectionDocKeys.map(key => getPrimaryKeyOfInternalDocument( - key, - INTERNAL_CONTEXT_COLLECTION - )), - false - ) - return Object.values(docs); +export async function getOldCollectionDocs ( + dataMigrator: EncryptedDataMigrator +): Promise>> { + const collectionDocKeys = getPreviousVersions(dataMigrator.currentSchema.jsonSchema) + .map(version => dataMigrator.name + '-' + version) + + const docs = await dataMigrator.database.internalStore.findDocumentsById( + collectionDocKeys.map(key => getPrimaryKeyOfInternalDocument( + key, + INTERNAL_CONTEXT_COLLECTION + )), + false + ) + return Object.values(docs) } /** * get an array with OldCollection-instances from all existing old storage-instances */ -export async function _getOldCollections( - dataMigrator: EncryptedDataMigrator +export async function _getOldCollections ( + dataMigrator: EncryptedDataMigrator ): Promise { - const oldColDocs = await getOldCollectionDocs(dataMigrator); - - return Promise.all( - oldColDocs - .map(colDoc => { - return createOldCollection( - colDoc.data.schema.version, - colDoc.data.schema, - dataMigrator - ); - }) - .filter(colDoc => colDoc !== null) - ); + const oldColDocs = await getOldCollectionDocs(dataMigrator) + + return await Promise.all( + oldColDocs + .map(async colDoc => { + return await createOldCollection( + colDoc.data.schema.version, + colDoc.data.schema, + dataMigrator + ) + }) + .filter(colDoc => colDoc !== null) + ) } - /** * returns true if a migration is needed */ -export function mustMigrate(dataMigrator: EncryptedDataMigrator): Promise { - if (dataMigrator.currentSchema.version === 0) { - return PROMISE_RESOLVE_FALSE; - } - return getOldCollectionDocs(dataMigrator) - .then(oldColDocs => { - if (oldColDocs.length === 0) { - return false; - } else { - return true; - } - }); +export async function mustMigrate (dataMigrator: EncryptedDataMigrator): Promise { + if (dataMigrator.currentSchema.version === 0) { + return await PROMISE_RESOLVE_FALSE + } + return await getOldCollectionDocs(dataMigrator) + .then(oldColDocs => { + if (oldColDocs.length === 0) { + return false + } else { + return true + } + }) } -export function runStrategyIfNotNull( - oldCollection: OldRxCollection, - version: number, - docOrNull: any | null +export async function runStrategyIfNotNull ( + oldCollection: OldRxCollection, + version: number, + docOrNull: any | null ): Promise { - if (docOrNull === null) { - return PROMISE_RESOLVE_NULL; - } else { - const migrationStrategy = oldCollection.dataMigrator.migrationStrategies[version]; - if (!migrationStrategy) { - return docOrNull - } - const ret = migrationStrategy(docOrNull, oldCollection); - const retPromise = toPromise(ret); - return retPromise; + if (docOrNull === null) { + return await PROMISE_RESOLVE_NULL + } else { + const migrationStrategy = oldCollection.dataMigrator.migrationStrategies[version] + if (!migrationStrategy) { + return docOrNull } + const ret = migrationStrategy(docOrNull, oldCollection) + const retPromise = toPromise(ret) + return await retPromise + } } -export async function getBatchOfOldCollection( - oldCollection: OldRxCollection, - batchSize: number, - notMatching: any[] +export async function getBatchOfOldCollection ( + oldCollection: OldRxCollection, + batchSize: number, + notMatching: any[] ): Promise { - const storage = oldCollection.database.storage; - const storageInstance = oldCollection.storageInstance; - const query = {} - if (notMatching.length > 0) { - query[oldCollection.schema.primaryPath] = { - $ne: notMatching - } + const storage = oldCollection.database.storage + const storageInstance = oldCollection.storageInstance + const query = {} + if (notMatching.length > 0) { + query[oldCollection.schema.primaryPath] = { + $ne: notMatching } - const preparedQuery = storage.statics.prepareQuery( - storageInstance.schema, - { - selector: query, - sort: [{ [oldCollection.schema.primaryPath]: 'asc' } as any], - limit: batchSize, - skip: notMatching.length - } - ); - const results = await storageInstance.query(preparedQuery); - const documents = results.documents.map((doc) => flatClone(doc)) - return documents; + } + const preparedQuery = storage.statics.prepareQuery( + storageInstance.schema, + { + selector: query, + sort: [{ [oldCollection.schema.primaryPath]: 'asc' } as any], + limit: batchSize, + skip: notMatching.length + } + ) + const results = await storageInstance.query(preparedQuery) + const documents = results.documents.map((doc) => flatClone(doc)) + return documents } /** @@ -244,298 +223,288 @@ export async function getBatchOfOldCollection( * @throws Error if final doc does not match final schema or migrationStrategy crashes * @return final object or null if migrationStrategy deleted it */ -export function migrateDocumentData( - oldCollection: OldRxCollection, - docData: any +export async function migrateDocumentData ( + oldCollection: OldRxCollection, + docData: any ): Promise { - /** + /** * We cannot deep-clone Blob or Buffer * so we just flat clone it here * and attach it to the deep cloned document data. */ - const attachmentsBefore = flatClone(docData._attachments); - const mutateableDocData = clone(docData); - mutateableDocData._attachments = attachmentsBefore; - - let nextVersion = oldCollection.version + 1; - - // run the document through migrationStrategies - let currentPromise = Promise.resolve(mutateableDocData); - while (nextVersion <= oldCollection.newestCollection.schema.version) { - const version = nextVersion; - currentPromise = currentPromise.then(docOrNull => runStrategyIfNotNull( - oldCollection, - version, - docOrNull - )); - nextVersion++; + const attachmentsBefore = flatClone(docData._attachments) + const mutateableDocData = clone(docData) + mutateableDocData._attachments = attachmentsBefore + + let nextVersion = oldCollection.version + 1 + + // run the document through migrationStrategies + let currentPromise = Promise.resolve(mutateableDocData) + while (nextVersion <= oldCollection.newestCollection.schema.version) { + const version = nextVersion + currentPromise = currentPromise.then(async docOrNull => await runStrategyIfNotNull( + oldCollection, + version, + docOrNull + )) + nextVersion++ + } + + return await currentPromise.then(doc => { + if (doc === null) { + return PROMISE_RESOLVE_NULL } - return currentPromise.then(doc => { - if (doc === null) { - return PROMISE_RESOLVE_NULL; - } - - /** + /** * Add _meta field if missing. * We need this to migration documents from pre-12.0.0 state * to version 12.0.0. Therefore we need to add the _meta field if it is missing. * TODO remove this in the major version 13.0.0 */ - if (!doc._meta) { - doc._meta = getDefaultRxDocumentMeta(); - } - return doc; - }); + if (!doc._meta) { + doc._meta = getDefaultRxDocumentMeta() + } + return doc + }) } - -export function isDocumentDataWithoutRevisionEqual(doc1: T, doc2: T): boolean { - const doc1NoRev = Object.assign({}, doc1, { - _attachments: undefined, - _rev: undefined - }); - const doc2NoRev = Object.assign({}, doc2, { - _attachments: undefined, - _rev: undefined - }); - return deepEqual(doc1NoRev, doc2NoRev); +export function isDocumentDataWithoutRevisionEqual (doc1: T, doc2: T): boolean { + const doc1NoRev = Object.assign({}, doc1, { + _attachments: undefined, + _rev: undefined + }) + const doc2NoRev = Object.assign({}, doc2, { + _attachments: undefined, + _rev: undefined + }) + return deepEqual(doc1NoRev, doc2NoRev) } /** * transform documents data and save them to the new collection * @return status-action with status and migrated document */ -export async function _migrateDocuments( - oldCollection: OldRxCollection, - documentsData: RxDocumentData[] -): Promise<{ type: string; doc: any; }[]> { - /** +export async function _migrateDocuments ( + oldCollection: OldRxCollection, + documentsData: Array> +): Promise> { + /** * Required in case the hooks mutate the document * data which would then wrongly cause conflicts * because we would send the mutated document * as writeRow.previous. */ - const previousDocumentData = clone(documentsData); - - // run hooks that might mutate documentsData - await Promise.all( - documentsData.map(docData => runAsyncPluginHooks( - 'preMigrateDocument', - { - docData, - oldCollection - } - )) - ); - // run the migration strategies on each document - const migratedDocuments: (any | null)[] = await Promise.all( - documentsData.map(docData => migrateDocumentData(oldCollection, docData)) - ); - - const bulkWriteToStorageInput: RxDocumentData[] = []; - const actions: any[] = []; - - documentsData.forEach((docData, idx) => { - const migratedDocData: any | null = migratedDocuments[idx]; - const action = { - res: null as any, - type: '', - migrated: migratedDocData, - doc: docData, - oldCollection, - newestCollection: oldCollection.newestCollection - }; - actions.push(action); - - /** + const previousDocumentData = clone(documentsData) + + // run hooks that might mutate documentsData + await Promise.all( + documentsData.map(async docData => await runAsyncPluginHooks( + 'preMigrateDocument', + { + docData, + oldCollection + } + )) + ) + // run the migration strategies on each document + const migratedDocuments: Array = await Promise.all( + documentsData.map(async docData => await migrateDocumentData(oldCollection, docData)) + ) + + const bulkWriteToStorageInput: Array> = [] + const actions: any[] = [] + + documentsData.forEach((docData, idx) => { + const migratedDocData: any | null = migratedDocuments[idx] + const action = { + res: null as any, + type: '', + migrated: migratedDocData, + doc: docData, + oldCollection, + newestCollection: oldCollection.newestCollection + } + actions.push(action) + + /** * Deterministically handle the revision * so migrating the same data on multiple instances * will result in the same output. */ - if (isDocumentDataWithoutRevisionEqual(docData, migratedDocData)) { - /** + if (isDocumentDataWithoutRevisionEqual(docData, migratedDocData)) { + /** * Data not changed by migration strategies, keep the same revision. * This ensures that other replicated instances that did not migrate already * will still have the same document. */ - migratedDocData._rev = docData._rev; - } else if (migratedDocData !== null) { - /** + migratedDocData._rev = docData._rev + } else if (migratedDocData !== null) { + /** * data changed, increase revision height * so replicating instances use our new document data */ - migratedDocData._rev = createRevision( - oldCollection.newestCollection.database.token, - docData - ); - } - + migratedDocData._rev = createRevision( + oldCollection.newestCollection.database.token, + docData + ) + } - if (migratedDocData) { - /** + if (migratedDocData) { + /** * save to newest collection * notice that this data also contains the attachments data */ - const attachmentsBefore = migratedDocData._attachments; - const saveData: WithAttachmentsData = migratedDocData; - saveData._attachments = attachmentsBefore; - saveData._meta.lwt = now(); - bulkWriteToStorageInput.push(saveData); - action.res = saveData; - action.type = 'success'; - } else { - /** + const attachmentsBefore = migratedDocData._attachments + const saveData: WithAttachmentsData = migratedDocData + saveData._attachments = attachmentsBefore + saveData._meta.lwt = now() + bulkWriteToStorageInput.push(saveData) + action.res = saveData + action.type = 'success' + } else { + /** * Migration strategy returned null * which means we should not migrate this document, * just drop it. */ - action.type = 'deleted'; - } - }); + action.type = 'deleted' + } + }) - /** + /** * Write the documents to the newest collection. * We need to add as revision * because we provide the _rev by our own * to have deterministic revisions in case the migration * runs on multiple nodes which must lead to the equal storage state. */ - if (bulkWriteToStorageInput.length) { - /** + if (bulkWriteToStorageInput.length > 0) { + /** * To ensure that we really keep that revision, we * hackly insert this document via the RxStorageInstance.originalStorageInstance * so that getWrappedStorageInstance() does not overwrite its own revision. */ - const originalStorageInstance = oldCollection.newestCollection.storageInstance.originalStorageInstance; - await originalStorageInstance.bulkWrite( - bulkWriteToStorageInput.map(document => ({ document })), - 'data-migrator-import' - ); - } - - // run hooks - await Promise.all( - actions.map(action => runAsyncPluginHooks( - 'postMigrateDocument', - action - )) - ); - - - // remove the documents from the old collection storage instance - const bulkDeleteInputData = documentsData.map((docData, idx) => { - const writeDeleted = flatClone(docData); - writeDeleted._deleted = true; - writeDeleted._attachments = {}; - return { - previous: previousDocumentData[idx], - document: writeDeleted - }; - }); - - if (bulkDeleteInputData.length) { - await oldCollection.storageInstance.bulkWrite( - bulkDeleteInputData, - 'data-migrator-delete' - ); + const originalStorageInstance = oldCollection.newestCollection.storageInstance.originalStorageInstance + await originalStorageInstance.bulkWrite( + bulkWriteToStorageInput.map(document => ({ document })), + 'data-migrator-import' + ) + } + + // run hooks + await Promise.all( + actions.map(async action => await runAsyncPluginHooks( + 'postMigrateDocument', + action + )) + ) + + // remove the documents from the old collection storage instance + const bulkDeleteInputData = documentsData.map((docData, idx) => { + const writeDeleted = flatClone(docData) + writeDeleted._deleted = true + writeDeleted._attachments = {} + return { + previous: previousDocumentData[idx], + document: writeDeleted } + }) + if (bulkDeleteInputData.length > 0) { + await oldCollection.storageInstance.bulkWrite( + bulkDeleteInputData, + 'data-migrator-delete' + ) + } - - - return actions; + return actions } - /** * deletes this.storageInstance and removes it from the database.collectionsCollection */ -export async function deleteOldCollection( - oldCollection: OldRxCollection +export async function deleteOldCollection ( + oldCollection: OldRxCollection ): Promise { - await oldCollection.storageInstance.remove(); + await oldCollection.storageInstance.remove() + const removeCollection = await oldCollection.database.removeCollectionDoc( + oldCollection.dataMigrator.name, + oldCollection.schema + ) - const removeCollection = await oldCollection.database.removeCollectionDoc( - oldCollection.dataMigrator.name, - oldCollection.schema - ) - - return removeCollection + return removeCollection } -export const DATA_MIGRATOR_BY_COLLECTION: WeakMap = new WeakMap(); -export type MigrationStateWithCollection = { - collection: RxCollection; - state: MigrationState; -}; - -export const DATA_MIGRATION_STATE_SUBJECT_BY_DATABASE = new WeakMap[]>>(); +export const DATA_MIGRATOR_BY_COLLECTION = new WeakMap() +export interface MigrationStateWithCollection { + collection: RxCollection + state: MigrationState +} -export function getMigrationStateByDatabase(database: RxDatabase): BehaviorSubject[]> { +export const DATA_MIGRATION_STATE_SUBJECT_BY_DATABASE = new WeakMap>>>() - return getFromMapOrCreate( - DATA_MIGRATION_STATE_SUBJECT_BY_DATABASE, - database, - () => new BehaviorSubject[]>([]) - ); +export function getMigrationStateByDatabase (database: RxDatabase): BehaviorSubject>> { + return getFromMapOrCreate( + DATA_MIGRATION_STATE_SUBJECT_BY_DATABASE, + database, + () => new BehaviorSubject>>([]) + ) } /** * Complete on database destroy * so people do not have to unsubscribe */ -export function onDatabaseDestroy(database: RxDatabase) { - - const subject = DATA_MIGRATION_STATE_SUBJECT_BY_DATABASE.get(database); - if (subject) { - subject.complete(); - } +export function onDatabaseDestroy (database: RxDatabase) { + const subject = DATA_MIGRATION_STATE_SUBJECT_BY_DATABASE.get(database) + if (subject) { + subject.complete() + } } export const RxDBEncryptedMigrationPlugin: RxPlugin = { - name: 'encrypted-migration', - rxdb: true, - hooks: { - preDestroyRxDatabase: { - after: onDatabaseDestroy - } + name: 'encrypted-migration', + rxdb: true, + hooks: { + preDestroyRxDatabase: { + after: onDatabaseDestroy + } + }, + prototypes: { + RxDatabase: (proto: any) => { + proto.migrationStates = function (this: RxDatabase): Observable { + return getMigrationStateByDatabase(this).pipe( + switchMap(list => combineLatest(list)), + shareReplay(RXJS_SHARE_REPLAY_DEFAULTS) + ) + } }, - prototypes: { - RxDatabase: (proto: any) => { - proto.migrationStates = function (this: RxDatabase): Observable { - return getMigrationStateByDatabase(this).pipe( - switchMap(list => combineLatest(list)), - shareReplay(RXJS_SHARE_REPLAY_DEFAULTS) - ); - }; - }, - RxCollection: (proto: any) => { - proto.getDataMigrator = function (this: RxCollection): EncryptedDataMigrator { - return getFromMapOrCreate( - DATA_MIGRATOR_BY_COLLECTION, - this, - () => new EncryptedDataMigrator( - this.asRxCollection, - this.migrationStrategies - ) - ); - }; - proto.migrationNeeded = function (this: RxCollection) { - if (this.schema.version === 0) { - return PROMISE_RESOLVE_FALSE; - } - const dataMigrator = getFromMapOrCreate( - DATA_MIGRATOR_BY_COLLECTION, - this, - () => new EncryptedDataMigrator( - this.asRxCollection, - this.migrationStrategies - ) - ); - return mustMigrate(dataMigrator); - }; + RxCollection: (proto: any) => { + proto.getDataMigrator = function (this: RxCollection): EncryptedDataMigrator { + return getFromMapOrCreate( + DATA_MIGRATOR_BY_COLLECTION, + this, + () => new EncryptedDataMigrator( + this.asRxCollection, + this.migrationStrategies + ) + ) + } + proto.migrationNeeded = async function (this: RxCollection) { + if (this.schema.version === 0) { + return await PROMISE_RESOLVE_FALSE } + const dataMigrator = getFromMapOrCreate( + DATA_MIGRATOR_BY_COLLECTION, + this, + () => new EncryptedDataMigrator( + this.asRxCollection, + this.migrationStrategies + ) + ) + return await mustMigrate(dataMigrator) + } } -}; \ No newline at end of file + } +} diff --git a/packages/indexdb/src/index.ts b/packages/indexdb/src/index.ts index d6217477..0bf5a3f1 100644 --- a/packages/indexdb/src/index.ts +++ b/packages/indexdb/src/index.ts @@ -4,7 +4,7 @@ * @description This is a RXDB IndexDB storage that supports encryption middleware. * In order to use this in your pluto-encrypted database you must write the following code: * Creating a IndexDB compatible storage is very simple. - * + * * ```typescript * import IndexDB from "@pluto-encrypted/indexdb"; * import { Database } from "@pluto-encrypted/database"; @@ -17,12 +17,12 @@ * }); * ``` */ -import { wrappedKeyEncryptionStorage } from "@pluto-encrypted/encryption"; -import { RxStorage } from "rxdb"; -import { getRxStorageDexie } from "./storage-dexie"; +import { wrappedKeyEncryptionStorage } from '@pluto-encrypted/encryption' +import { type RxStorage } from 'rxdb' +import { getRxStorageDexie } from './storage-dexie' const storage: RxStorage = wrappedKeyEncryptionStorage({ - storage: getRxStorageDexie(), + storage: getRxStorageDexie() }) -export default storage; \ No newline at end of file +export default storage diff --git a/packages/indexdb/src/storage-dexie/dexie-helper.ts b/packages/indexdb/src/storage-dexie/dexie-helper.ts index 31c631cb..b3a5f65f 100644 --- a/packages/indexdb/src/storage-dexie/dexie-helper.ts +++ b/packages/indexdb/src/storage-dexie/dexie-helper.ts @@ -1,16 +1,16 @@ -import { Dexie } from 'dexie'; -import { RxDocumentData, RxJsonSchema, RxStorageDefaultStatics, flatClone, getFromMapOrCreate, getPrimaryFieldOfPrimaryKey, getSchemaByObjectPath, newRxError, toArray } from "rxdb"; -import { DexieSettings } from "rxdb/dist/types/types"; +import { Dexie } from 'dexie' +import { type RxDocumentData, type RxJsonSchema, RxStorageDefaultStatics, getPrimaryFieldOfPrimaryKey, getSchemaByObjectPath, newRxError, toArray } from 'rxdb' +import { type DexieSettings } from 'rxdb/dist/types/types' import type { - Table as DexieTable -} from 'dexie'; -export const DEXIE_DOCS_TABLE_NAME = 'docs'; -export const DEXIE_DELETED_DOCS_TABLE_NAME = 'deleted-docs'; -export const DEXIE_CHANGES_TABLE_NAME = 'changes'; + Table as DexieTable +} from 'dexie' +export const DEXIE_DOCS_TABLE_NAME = 'docs' +export const DEXIE_DELETED_DOCS_TABLE_NAME = 'deleted-docs' +export const DEXIE_CHANGES_TABLE_NAME = 'changes' -export const RX_STORAGE_NAME_DEXIE = 'dexie'; +export const RX_STORAGE_NAME_DEXIE = 'dexie' -export const RxStorageDexieStatics = RxStorageDefaultStatics; +export const RxStorageDexieStatics = RxStorageDefaultStatics /** * The internals is a Promise that resolves * when the database has fully opened @@ -18,231 +18,228 @@ export const RxStorageDexieStatics = RxStorageDefaultStatics; * @link https://dexie.org/docs/Dexie/Dexie.on.ready * */ -export type DexieStorageInternals = { - dexieDb: Dexie; - /** +export interface DexieStorageInternals { + dexieDb: Dexie + /** * Contains all normal non-deleted documents */ - dexieTable: DexieTable; - /** + dexieTable: DexieTable + /** * Contains all docs with _deleted: true * We need this because with dexie it is not possible to use a boolean field as index * which could be used to filter out deleted docs in queries. */ - dexieDeletedTable: DexieTable; -}; - -const DEXIE_STATE_DB_BY_NAME: Map = new Map(); -const REF_COUNT_PER_DEXIE_DB: Map = new Map(); -export async function getDexieDbWithTables( - databaseName: string, - collectionName: string, - settings: DexieSettings, - schema: RxJsonSchema + dexieDeletedTable: DexieTable +} + +const DEXIE_STATE_DB_BY_NAME = new Map() +const REF_COUNT_PER_DEXIE_DB = new Map() +export async function getDexieDbWithTables ( + databaseName: string, + collectionName: string, + settings: DexieSettings, + schema: RxJsonSchema ): Promise { - const primaryPath = getPrimaryFieldOfPrimaryKey(schema.primaryKey); - const dexieDbName = `rxdb-dexie-${databaseName}--${schema.version}--${collectionName}`; - const useSettings = { ...settings, autoOpen: false }; - const dexieDb = new Dexie(dexieDbName, useSettings); + const primaryPath = getPrimaryFieldOfPrimaryKey(schema.primaryKey) + const dexieDbName = `rxdb-dexie-${databaseName}--${schema.version}--${collectionName}` + const useSettings = { ...settings, autoOpen: false } + const dexieDb = new Dexie(dexieDbName, useSettings) - const dexieStoresSettings = { - [DEXIE_DOCS_TABLE_NAME]: getDexieStoreSchema(schema), - [DEXIE_CHANGES_TABLE_NAME]: '++sequence, id', - [DEXIE_DELETED_DOCS_TABLE_NAME]: `${primaryPath},_meta.lwt,[_meta.lwt+${primaryPath}]` - }; + const dexieStoresSettings = { + [DEXIE_DOCS_TABLE_NAME]: getDexieStoreSchema(schema), + [DEXIE_CHANGES_TABLE_NAME]: '++sequence, id', + [DEXIE_DELETED_DOCS_TABLE_NAME]: `${primaryPath},_meta.lwt,[_meta.lwt+${primaryPath}]` + } - dexieDb.version(1).stores(dexieStoresSettings); + dexieDb.version(1).stores(dexieStoresSettings) - await dexieDb.open(); + await dexieDb.open() - const state: DexieStorageInternals = { - dexieDb, - dexieTable: dexieDb[DEXIE_DOCS_TABLE_NAME], - dexieDeletedTable: dexieDb[DEXIE_DELETED_DOCS_TABLE_NAME] - }; + const state: DexieStorageInternals = { + dexieDb, + dexieTable: dexieDb[DEXIE_DOCS_TABLE_NAME], + dexieDeletedTable: dexieDb[DEXIE_DELETED_DOCS_TABLE_NAME] + } - DEXIE_STATE_DB_BY_NAME.set(dexieDbName, state); - REF_COUNT_PER_DEXIE_DB.set(state, 0); + DEXIE_STATE_DB_BY_NAME.set(dexieDbName, state) + REF_COUNT_PER_DEXIE_DB.set(state, 0) - return state; + return state } -export async function closeDexieDb(statePromise: DexieStorageInternals) { - const state = await statePromise; - const prevCount = REF_COUNT_PER_DEXIE_DB.get(statePromise); - const newCount = (prevCount as any) - 1; - if (newCount === 0) { - state.dexieDb.close(); - REF_COUNT_PER_DEXIE_DB.delete(statePromise); - } else { - REF_COUNT_PER_DEXIE_DB.set(statePromise, newCount); - } +export async function closeDexieDb (statePromise: DexieStorageInternals) { + const state = await statePromise + const prevCount = REF_COUNT_PER_DEXIE_DB.get(statePromise) + const newCount = (prevCount as any) - 1 + if (newCount === 0) { + state.dexieDb.close() + REF_COUNT_PER_DEXIE_DB.delete(statePromise) + } else { + REF_COUNT_PER_DEXIE_DB.set(statePromise, newCount) + } } -export function ensureNoBooleanIndex(schema: RxJsonSchema) { - if (!schema.indexes) { - return; - } - const checkedFields = new Set(); - schema.indexes.forEach(index => { - const fields = toArray(index); - fields.forEach(field => { - if (checkedFields.has(field)) { - return; - } - checkedFields.add(field); - const schemaObj = getSchemaByObjectPath(schema, field); - if (schemaObj.type === 'boolean') { - throw newRxError('DXE1', { - schema, - index, - field - }); - } - }); - }); +export function ensureNoBooleanIndex (schema: RxJsonSchema) { + if (!schema.indexes) { + return + } + const checkedFields = new Set() + schema.indexes.forEach(index => { + const fields = toArray(index) + fields.forEach(field => { + if (checkedFields.has(field)) { + return + } + checkedFields.add(field) + const schemaObj = getSchemaByObjectPath(schema, field) + if (schemaObj.type === 'boolean') { + throw newRxError('DXE1', { + schema, + index, + field + }) + } + }) + }) } - - /** * It is not possible to set non-javascript-variable-syntax * keys as IndexedDB indexes. So we have to substitute the pipe-char * which comes from the key-compression plugin. */ -export const DEXIE_PIPE_SUBSTITUTE = '__'; -export function dexieReplaceIfStartsWithPipe(str: string): string { - const split = str.split('.'); - if (split.length > 1) { - return split.map(part => dexieReplaceIfStartsWithPipe(part)).join('.'); - } - - if (str.startsWith('|')) { - const withoutFirst = str.substring(1); - return DEXIE_PIPE_SUBSTITUTE + withoutFirst; - } else { - return str; - } +export const DEXIE_PIPE_SUBSTITUTE = '__' +export function dexieReplaceIfStartsWithPipe (str: string): string { + const split = str.split('.') + if (split.length > 1) { + return split.map(part => dexieReplaceIfStartsWithPipe(part)).join('.') + } + + if (str.startsWith('|')) { + const withoutFirst = str.substring(1) + return DEXIE_PIPE_SUBSTITUTE + withoutFirst + } else { + return str + } } -export function dexieReplaceIfStartsWithPipeRevert(str: string): string { - const split = str.split('.'); - if (split.length > 1) { - return split.map(part => dexieReplaceIfStartsWithPipeRevert(part)).join('.'); - } - - if (str.startsWith(DEXIE_PIPE_SUBSTITUTE)) { - const withoutFirst = str.substring(DEXIE_PIPE_SUBSTITUTE.length); - return '|' + withoutFirst; - } else { - return str; - } +export function dexieReplaceIfStartsWithPipeRevert (str: string): string { + const split = str.split('.') + if (split.length > 1) { + return split.map(part => dexieReplaceIfStartsWithPipeRevert(part)).join('.') + } + + if (str.startsWith(DEXIE_PIPE_SUBSTITUTE)) { + const withoutFirst = str.substring(DEXIE_PIPE_SUBSTITUTE.length) + return '|' + withoutFirst + } else { + return str + } } /** * @recursive */ -export function fromStorageToDexie(documentData: RxDocumentData): any { - if (!documentData || typeof documentData === 'string' || typeof documentData === 'number' || typeof documentData === 'boolean') { - return documentData; - } else if (Array.isArray(documentData)) { - return documentData.map(row => fromStorageToDexie(row)); - } else if (typeof documentData === 'object') { - const ret: any = {}; - Object.entries(documentData).forEach(([key, value]) => { - if (typeof value === 'object') { - value = fromStorageToDexie(value); - } - ret[dexieReplaceIfStartsWithPipe(key)] = value; - }); - return ret; - } +export function fromStorageToDexie (documentData: RxDocumentData): any { + if (!documentData || typeof documentData === 'string' || typeof documentData === 'number' || typeof documentData === 'boolean') { + return documentData + } else if (Array.isArray(documentData)) { + return documentData.map(row => fromStorageToDexie(row)) + } else if (typeof documentData === 'object') { + const ret: any = {} + Object.entries(documentData).forEach(([key, value]) => { + if (typeof value === 'object') { + value = fromStorageToDexie(value) + } + ret[dexieReplaceIfStartsWithPipe(key)] = value + }) + return ret + } } -export function fromDexieToStorage(documentData: any): RxDocumentData { - if (!documentData || typeof documentData === 'string' || typeof documentData === 'number' || typeof documentData === 'boolean') { - return documentData; - } else if (Array.isArray(documentData)) { - return documentData.map(row => fromDexieToStorage(row)); - } else if (typeof documentData === 'object') { - const ret: any = {}; - Object.entries(documentData).forEach(([key, value]) => { - if (typeof value === 'object' || Array.isArray(documentData)) { - value = fromDexieToStorage(value); - } - ret[dexieReplaceIfStartsWithPipeRevert(key)] = value; - }); - return ret; - } +export function fromDexieToStorage (documentData: any): RxDocumentData { + if (!documentData || typeof documentData === 'string' || typeof documentData === 'number' || typeof documentData === 'boolean') { + return documentData + } else if (Array.isArray(documentData)) { + return documentData.map(row => fromDexieToStorage(row)) + } else if (typeof documentData === 'object') { + const ret: any = {} + Object.entries(documentData).forEach(([key, value]) => { + if (typeof value === 'object' || Array.isArray(documentData)) { + value = fromDexieToStorage(value) + } + ret[dexieReplaceIfStartsWithPipeRevert(key)] = value + }) + return ret + } } - /** * Creates a string that can be used to create the dexie store. * @link https://dexie.org/docs/API-Reference#quick-reference */ -export function getDexieStoreSchema( - rxJsonSchema: RxJsonSchema +export function getDexieStoreSchema ( + rxJsonSchema: RxJsonSchema ): string { - let parts: string[][] = []; + let parts: string[][] = [] - /** + /** * First part must be the primary key * @link https://github.com/dexie/Dexie.js/issues/1307#issuecomment-846590912 */ - const primaryKey = getPrimaryFieldOfPrimaryKey(rxJsonSchema.primaryKey); - parts.push([primaryKey]); - - // add other indexes - if (rxJsonSchema.indexes) { - rxJsonSchema.indexes.forEach(index => { - const arIndex = toArray(index); - parts.push(arIndex); - }); - } + const primaryKey = getPrimaryFieldOfPrimaryKey(rxJsonSchema.primaryKey) + parts.push([primaryKey]) + + // add other indexes + if (rxJsonSchema.indexes) { + rxJsonSchema.indexes.forEach(index => { + const arIndex = toArray(index) + parts.push(arIndex) + }) + } - // we also need the _meta.lwt+primaryKey index for the getChangedDocumentsSince() method. - parts.push(['_meta.lwt', primaryKey]); + // we also need the _meta.lwt+primaryKey index for the getChangedDocumentsSince() method. + parts.push(['_meta.lwt', primaryKey]) - /** + /** * It is not possible to set non-javascript-variable-syntax * keys as IndexedDB indexes. So we have to substitute the pipe-char * which comes from the key-compression plugin. */ - parts = parts.map(part => { - return part.map(str => dexieReplaceIfStartsWithPipe(str)); - }); - - return parts.map(part => { - if (part.length === 1) { - return part[0]; - } else { - return '[' + part.join('+') + ']'; - } - }).join(', '); + parts = parts.map(part => { + return part.map(str => dexieReplaceIfStartsWithPipe(str)) + }) + + return parts.map(part => { + if (part.length === 1) { + return part[0] + } else { + return '[' + part.join('+') + ']' + } + }).join(', ') } /** * Returns all documents in the database. * Non-deleted plus deleted ones. */ -export async function getDocsInDb( - internals: DexieStorageInternals, - docIds: string[] -): Promise[]> { - const state = await internals; - const [ - nonDeletedDocsInDb, - deletedDocsInDb - ] = await Promise.all([ - state.dexieTable.bulkGet(docIds), - state.dexieDeletedTable.bulkGet(docIds) - ]); - const docsInDb = deletedDocsInDb.slice(0); - nonDeletedDocsInDb.forEach((doc, idx) => { - if (doc) { - docsInDb[idx] = doc; - } - }); - return docsInDb; +export async function getDocsInDb ( + internals: DexieStorageInternals, + docIds: string[] +): Promise>> { + const state = await internals + const [ + nonDeletedDocsInDb, + deletedDocsInDb + ] = await Promise.all([ + state.dexieTable.bulkGet(docIds), + state.dexieDeletedTable.bulkGet(docIds) + ]) + const docsInDb = deletedDocsInDb.slice(0) + nonDeletedDocsInDb.forEach((doc, idx) => { + if (doc) { + docsInDb[idx] = doc + } + }) + return docsInDb } diff --git a/packages/indexdb/src/storage-dexie/dexie-query.ts b/packages/indexdb/src/storage-dexie/dexie-query.ts index 9584996b..291b5147 100644 --- a/packages/indexdb/src/storage-dexie/dexie-query.ts +++ b/packages/indexdb/src/storage-dexie/dexie-query.ts @@ -14,179 +14,177 @@ // } from './dexie-helper'; // import type { RxStorageInstanceDexie } from './rx-storage-instance-dexie'; -import { DefaultPreparedQuery, INDEX_MIN, RxDocumentData, RxQueryPlan, RxSchema, RxStorageQueryResult, getQueryMatcher, getSortComparator } from "rxdb"; -import { QueryMatcher, RxJsonSchema } from "rxdb/dist/types/types"; -import { DEXIE_DOCS_TABLE_NAME, dexieReplaceIfStartsWithPipe, fromDexieToStorage } from "./dexie-helper"; -import { RxStorageInstanceDexie } from "./rx-storage-instance-dexie"; -import { fixTxPipe } from "@pluto-encrypted/shared"; - -export function mapKeyForKeyRange(k: any) { - if (k === INDEX_MIN) { - return -Infinity; - } else { - return k; - } +import { type DefaultPreparedQuery, INDEX_MIN, type RxDocumentData, type RxQueryPlan, type RxStorageQueryResult, getQueryMatcher, getSortComparator } from 'rxdb' +import { type QueryMatcher, type RxJsonSchema } from 'rxdb/dist/types/types' +import { DEXIE_DOCS_TABLE_NAME, dexieReplaceIfStartsWithPipe, fromDexieToStorage } from './dexie-helper' +import { type RxStorageInstanceDexie } from './rx-storage-instance-dexie' +import { fixTxPipe } from '@pluto-encrypted/shared' + +export function mapKeyForKeyRange (k: any) { + if (k === INDEX_MIN) { + return -Infinity + } else { + return k + } } -export function getKeyRangeByQueryPlan( - queryPlan: RxQueryPlan, - IDBKeyRange?: any +export function getKeyRangeByQueryPlan ( + queryPlan: RxQueryPlan, + IDBKeyRange?: any ) { - if (!IDBKeyRange) { - if (typeof window === 'undefined') { - throw new Error('IDBKeyRange missing'); - } else { - IDBKeyRange = window.IDBKeyRange; - } + if (!IDBKeyRange) { + if (typeof window === 'undefined') { + throw new Error('IDBKeyRange missing') + } else { + IDBKeyRange = window.IDBKeyRange } + } - const startKeys = queryPlan.startKeys.map(mapKeyForKeyRange); - const endKeys = queryPlan.endKeys.map(mapKeyForKeyRange); + const startKeys = queryPlan.startKeys.map(mapKeyForKeyRange) + const endKeys = queryPlan.endKeys.map(mapKeyForKeyRange) - let ret: any; - /** + let ret: any + /** * If index has only one field, * we have to pass the keys directly, not the key arrays. */ - if (queryPlan.index.length === 1) { - const equalKeys = startKeys[0] === endKeys[0]; - ret = IDBKeyRange.bound( - startKeys[0], - endKeys[0], - equalKeys ? false : !queryPlan.inclusiveStart, - equalKeys ? false : !queryPlan.inclusiveEnd - ); - } else { - ret = IDBKeyRange.bound( - startKeys, - endKeys, - !queryPlan.inclusiveStart, - !queryPlan.inclusiveEnd - ); - } - return ret; + if (queryPlan.index.length === 1) { + const equalKeys = startKeys[0] === endKeys[0] + ret = IDBKeyRange.bound( + startKeys[0], + endKeys[0], + equalKeys ? false : !queryPlan.inclusiveStart, + equalKeys ? false : !queryPlan.inclusiveEnd + ) + } else { + ret = IDBKeyRange.bound( + startKeys, + endKeys, + !queryPlan.inclusiveStart, + !queryPlan.inclusiveEnd + ) + } + return ret } - /** * Runs mango queries over the Dexie.js database. */ -export async function dexieQuery( - instance: RxStorageInstanceDexie, - preparedQuery: DefaultPreparedQuery, - schema: Readonly>> +export async function dexieQuery ( + instance: RxStorageInstanceDexie, + preparedQuery: DefaultPreparedQuery, + schema: Readonly>> ): Promise> { - const state = await instance.internals; - const query = preparedQuery.query; - - const skip = query.skip ? query.skip : 0; - const limit = query.limit ? query.limit : Infinity; - const skipPlusLimit = skip + limit; - const queryPlan = preparedQuery.queryPlan; - - let queryMatcher: QueryMatcher> = getQueryMatcher( - instance.schema, - preparedQuery.query - ); - - let rows: any[] = []; - - const queryPlanFields: string[] = queryPlan.index; - let indexes: string[] = [] - if (queryPlanFields.length === 1) { - indexes.push(dexieReplaceIfStartsWithPipe(fixTxPipe(queryPlanFields[0]!))) + const state = await instance.internals + const query = preparedQuery.query + + const skip = query.skip ? query.skip : 0 + const limit = query.limit ? query.limit : Infinity + const skipPlusLimit = skip + limit + const queryPlan = preparedQuery.queryPlan + + const queryMatcher: QueryMatcher> = getQueryMatcher( + instance.schema, + preparedQuery.query + ) + + let rows: any[] = [] + + const queryPlanFields: string[] = queryPlan.index + const indexes: string[] = [] + if (queryPlanFields.length === 1) { + indexes.push(dexieReplaceIfStartsWithPipe(fixTxPipe(queryPlanFields[0]!))) + } else { + indexes.push(...queryPlanFields.map(field => dexieReplaceIfStartsWithPipe(fixTxPipe(field)))) + } + + const shouldAddCompoundIndexes = schema.indexes?.find((index) => { + if (typeof index === 'string') { + return indexes.find((index2) => index2 === index) } else { - indexes.push(...queryPlanFields.map(field => dexieReplaceIfStartsWithPipe(fixTxPipe(field)))) + return index.find((subIndex) => { + return subIndex === index.find((indexValue) => indexValue === subIndex) + }) } + }) - const shouldAddCompoundIndexes = schema.indexes?.find((index) => { - if (typeof index === "string") { - return indexes.find((index2) => index2 === index) - } else { - return index.find((subIndex) => subIndex === subIndex) - } - }); - - if (shouldAddCompoundIndexes) { - indexes.splice(0, indexes.length) - if (typeof shouldAddCompoundIndexes === "string") { - indexes.push(shouldAddCompoundIndexes) - } else { - indexes.push(...shouldAddCompoundIndexes) - } - + if (shouldAddCompoundIndexes) { + indexes.splice(0, indexes.length) + if (typeof shouldAddCompoundIndexes === 'string') { + indexes.push(shouldAddCompoundIndexes) + } else { + indexes.push(...shouldAddCompoundIndexes) } + } - const results = await state.dexieTable.toArray() - for (let item of results) { - const docData = fromDexieToStorage(item); + const results = await state.dexieTable.toArray() + for (const item of results) { + const docData = fromDexieToStorage(item) - if ( - queryMatcher(docData) - ) { - rows.push(docData); - } + if ( + queryMatcher(docData) + ) { + rows.push(docData) } + } + const sortComparator = getSortComparator(instance.schema, preparedQuery.query) + rows = rows.sort(sortComparator) - const sortComparator = getSortComparator(instance.schema, preparedQuery.query); - rows = rows.sort(sortComparator); - - // apply skip and limit boundaries. - rows = rows.slice(skip, skipPlusLimit); - return { - documents: rows - }; + // apply skip and limit boundaries. + rows = rows.slice(skip, skipPlusLimit) + return { + documents: rows + } } - -export async function dexieCount( - instance: RxStorageInstanceDexie, - preparedQuery: DefaultPreparedQuery +export async function dexieCount ( + instance: RxStorageInstanceDexie, + preparedQuery: DefaultPreparedQuery ): Promise { - const state = await instance.internals; - const queryPlan = preparedQuery.queryPlan; - const queryPlanFields: string[] = queryPlan.index; - - const keyRange = getKeyRangeByQueryPlan( - queryPlan, - (state.dexieDb as any)._options.IDBKeyRange - ); - let count: number = -1; - await state.dexieDb.transaction( - 'r', - state.dexieTable, - async (dexieTx) => { - const tx = (dexieTx as any).idbtrans; - const store = tx.objectStore(DEXIE_DOCS_TABLE_NAME); - let index: any; - if ( - queryPlanFields.length === 1 && - queryPlanFields[0] === instance.primaryPath - ) { - index = store; - } else { - let indexName: string; - if (queryPlanFields.length === 1) { - indexName = dexieReplaceIfStartsWithPipe(queryPlanFields[0]!); - } else { - indexName = '[' + - queryPlanFields - .map(field => dexieReplaceIfStartsWithPipe(field)) - .join('+') - + ']'; - } - index = store.index(indexName); - } - - const request = index.count(keyRange); - count = await new Promise((res, rej) => { - request.onsuccess = function () { - res(request.result); - }; - request.onerror = (err: any) => rej(err); - }); + const state = await instance.internals + const queryPlan = preparedQuery.queryPlan + const queryPlanFields: string[] = queryPlan.index + + const keyRange = getKeyRangeByQueryPlan( + queryPlan, + (state.dexieDb as any)._options.IDBKeyRange + ) + let count: number = -1 + await state.dexieDb.transaction( + 'r', + state.dexieTable, + async (dexieTx) => { + const tx = (dexieTx as any).idbtrans + const store = tx.objectStore(DEXIE_DOCS_TABLE_NAME) + let index: any + if ( + queryPlanFields.length === 1 && + queryPlanFields[0] === instance.primaryPath + ) { + index = store + } else { + let indexName: string + if (queryPlanFields.length === 1) { + indexName = dexieReplaceIfStartsWithPipe(queryPlanFields[0]!) + } else { + indexName = '[' + + queryPlanFields + .map(field => dexieReplaceIfStartsWithPipe(field)) + .join('+') + + ']' } - ); - return count; + index = store.index(indexName) + } + + const request = index.count(keyRange) + count = await new Promise((resolve, reject) => { + request.onsuccess = function () { + resolve(request.result) + } + request.onerror = (err: any) => { reject(err) } + }) + } + ) + return count } diff --git a/packages/indexdb/src/storage-dexie/index.ts b/packages/indexdb/src/storage-dexie/index.ts index d401d969..a0d0598f 100644 --- a/packages/indexdb/src/storage-dexie/index.ts +++ b/packages/indexdb/src/storage-dexie/index.ts @@ -1,4 +1,4 @@ -export * from './rx-storage-dexie'; -export * from './rx-storage-instance-dexie'; -export * from './dexie-helper'; -export * from './dexie-query'; +export * from './rx-storage-dexie' +export * from './rx-storage-instance-dexie' +export * from './dexie-helper' +export * from './dexie-query' diff --git a/packages/indexdb/src/storage-dexie/rx-storage-dexie.ts b/packages/indexdb/src/storage-dexie/rx-storage-dexie.ts index c70b9ced..cf666290 100644 --- a/packages/indexdb/src/storage-dexie/rx-storage-dexie.ts +++ b/packages/indexdb/src/storage-dexie/rx-storage-dexie.ts @@ -1,37 +1,34 @@ -import { RxStorage, RxStorageInstanceCreationParams, ensureRxStorageInstanceParamsAreCorrect, newRxError } from "rxdb"; -import { DexieSettings } from "rxdb/dist/types/types"; - - -import { DexieStorageInternals, RX_STORAGE_NAME_DEXIE, RxStorageDexieStatics, ensureNoBooleanIndex } from "./dexie-helper"; -import { RxStorageInstanceDexie, createDexieStorageInstance } from "./rx-storage-instance-dexie"; +import { type RxStorage, type RxStorageInstanceCreationParams, ensureRxStorageInstanceParamsAreCorrect, newRxError } from 'rxdb' +import { type DexieSettings } from 'rxdb/dist/types/types' +import { type DexieStorageInternals, RX_STORAGE_NAME_DEXIE, RxStorageDexieStatics, ensureNoBooleanIndex } from './dexie-helper' +import { type RxStorageInstanceDexie, createDexieStorageInstance } from './rx-storage-instance-dexie' export class RxStorageDexie implements RxStorage { - public name = RX_STORAGE_NAME_DEXIE; - public statics = RxStorageDexieStatics; - - constructor( - public settings: DexieSettings - ) { } - - public async createStorageInstance( - params: RxStorageInstanceCreationParams - ): Promise> { - if (params.schema.keyCompression) { - throw newRxError('UT5', { args: { databaseName: params.databaseName, collectionName: params.collectionName } }) - } + public name = RX_STORAGE_NAME_DEXIE + public statics = RxStorageDexieStatics + + constructor ( + public settings: DexieSettings + ) { } + + public async createStorageInstance( + params: RxStorageInstanceCreationParams + ): Promise> { + if (params.schema.keyCompression) { + throw newRxError('UT5', { args: { databaseName: params.databaseName, collectionName: params.collectionName } }) + } - ensureRxStorageInstanceParamsAreCorrect(params); - ensureNoBooleanIndex(params.schema); + ensureRxStorageInstanceParamsAreCorrect(params) + ensureNoBooleanIndex(params.schema) - const instance = await createDexieStorageInstance(this, params, this.settings); - return instance - } + const instance = await createDexieStorageInstance(this, params, this.settings) + return instance + } } - -export function getRxStorageDexie( - settings: DexieSettings = {} +export function getRxStorageDexie ( + settings: DexieSettings = {} ): RxStorageDexie { - return new RxStorageDexie(settings);; + return new RxStorageDexie(settings) } diff --git a/packages/indexdb/src/storage-dexie/rx-storage-instance-dexie.ts b/packages/indexdb/src/storage-dexie/rx-storage-instance-dexie.ts index e72405bd..8cb42976 100644 --- a/packages/indexdb/src/storage-dexie/rx-storage-instance-dexie.ts +++ b/packages/indexdb/src/storage-dexie/rx-storage-instance-dexie.ts @@ -1,340 +1,338 @@ import { - Subject, - Observable -} from 'rxjs'; + Subject, + type Observable +} from 'rxjs' -import { now, RxStorageInstance, RxStorageDefaultCheckpoint, StringKeys, RxDocumentData, EventBulk, RxStorageChangeEvent, RxJsonSchema, getPrimaryFieldOfPrimaryKey, BulkWriteRow, RxStorageBulkWriteResponse, newRxError, CategorizeBulkWriteRowsOutput, categorizeBulkWriteRows, PROMISE_RESOLVE_VOID, ensureNotFalsy, RxDocumentDataById, DefaultPreparedQuery, RxStorageQueryResult, RxStorageCountResult, RX_META_LWT_MINIMUM, appendToArray, sortDocumentsByLastWriteTime, lastOfArray, RxConflictResultionTask, RxConflictResultionTaskSolution, RxStorageInstanceCreationParams, addRxStorageMultiInstanceSupport } from "rxdb"; -import { DexieSettings } from "rxdb/dist/types/types"; -import { getDocsInDb, fromDexieToStorage, fromStorageToDexie, closeDexieDb, getDexieDbWithTables, RX_STORAGE_NAME_DEXIE, DexieStorageInternals } from "./dexie-helper"; -import { dexieQuery, dexieCount } from "./dexie-query"; -import { RxStorageDexie } from "./rx-storage-dexie"; +import { now, type RxStorageInstance, type RxStorageDefaultCheckpoint, type StringKeys, type RxDocumentData, type EventBulk, type RxStorageChangeEvent, type RxJsonSchema, getPrimaryFieldOfPrimaryKey, type BulkWriteRow, type RxStorageBulkWriteResponse, newRxError, type CategorizeBulkWriteRowsOutput, categorizeBulkWriteRows, PROMISE_RESOLVE_VOID, ensureNotFalsy, type RxDocumentDataById, type DefaultPreparedQuery, type RxStorageQueryResult, type RxStorageCountResult, RX_META_LWT_MINIMUM, appendToArray, sortDocumentsByLastWriteTime, lastOfArray, type RxConflictResultionTask, type RxStorageInstanceCreationParams } from 'rxdb' +import { type DexieSettings } from 'rxdb/dist/types/types' +import { getDocsInDb, fromDexieToStorage, fromStorageToDexie, closeDexieDb, getDexieDbWithTables, type DexieStorageInternals } from './dexie-helper' +import { dexieQuery } from './dexie-query' +import { type RxStorageDexie } from './rx-storage-dexie' -let instanceId = now(); +let instanceId = now() export class RxStorageInstanceDexie implements RxStorageInstance< - RxDocType, - DexieStorageInternals, - DexieSettings, - RxStorageDefaultCheckpoint +RxDocType, +DexieStorageInternals, +DexieSettings, +RxStorageDefaultCheckpoint > { - public readonly primaryPath: StringKeys>; - private changes$: Subject>, RxStorageDefaultCheckpoint>> = new Subject(); - public readonly instanceId = instanceId++; - public closed = false; - - constructor( - public readonly storage: RxStorageDexie, - public readonly databaseName: string, - public readonly collectionName: string, - public readonly schema: Readonly>>, - public readonly internals: DexieStorageInternals, - public readonly options: Readonly, - public readonly settings: DexieSettings - ) { - this.primaryPath = getPrimaryFieldOfPrimaryKey(this.schema.primaryKey); + public readonly primaryPath: StringKeys> + private readonly changes$ = new Subject>, RxStorageDefaultCheckpoint>>() + public readonly instanceId = instanceId++ + public closed = false + + constructor ( + public readonly storage: RxStorageDexie, + public readonly databaseName: string, + public readonly collectionName: string, + public readonly schema: Readonly>>, + public readonly internals: DexieStorageInternals, + public readonly options: Readonly, + public readonly settings: DexieSettings + ) { + this.primaryPath = getPrimaryFieldOfPrimaryKey(this.schema.primaryKey) + } + + async bulkWrite ( + documentWrites: Array>, + context: string + ): Promise> { + ensureNotClosed(this) + + /** + * Check some assumptions to ensure RxDB + * does not call the storage with an invalid write. + */ + documentWrites.forEach(row => { + // ensure revision is set + if ( + !row.document._rev || + ( + row.previous && + !row.previous._rev + ) + ) { + throw newRxError('SNH', { args: { row } }) + } + }) + + const state = await this.internals + const ret: RxStorageBulkWriteResponse = { + success: {}, + error: {} } - async bulkWrite( - documentWrites: BulkWriteRow[], - context: string - ): Promise> { - ensureNotClosed(this); - + const documentKeys: string[] = documentWrites.map(writeRow => writeRow.document[this.primaryPath] as any) + let categorized: CategorizeBulkWriteRowsOutput | undefined + await state.dexieDb.transaction( + 'rw', + state.dexieTable, + state.dexieDeletedTable, + async () => { + const docsInDbMap = new Map>() + const docsInDbWithInternals = await getDocsInDb(this.internals, documentKeys) + docsInDbWithInternals.forEach(docWithDexieInternals => { + const doc = docWithDexieInternals ? fromDexieToStorage(docWithDexieInternals) : docWithDexieInternals + if (doc) { + docsInDbMap.set(doc[this.primaryPath], doc) + } + return doc + }) + + categorized = categorizeBulkWriteRows( + this, + this.primaryPath as any, + docsInDbMap, + documentWrites, + context + ) + ret.error = categorized.errors /** - * Check some assumptions to ensure RxDB - * does not call the storage with an invalid write. - */ - documentWrites.forEach(row => { - // ensure revision is set - if ( - !row.document._rev || - ( - row.previous && - !row.previous._rev - ) - ) { - throw newRxError('SNH', { args: { row } }); - } - }); - - - - const state = await this.internals; - const ret: RxStorageBulkWriteResponse = { - success: {}, - error: {} - }; - - const documentKeys: string[] = documentWrites.map(writeRow => writeRow.document[this.primaryPath] as any); - let categorized: CategorizeBulkWriteRowsOutput | undefined; - await state.dexieDb.transaction( - 'rw', - state.dexieTable, - state.dexieDeletedTable, - async () => { - const docsInDbMap = new Map>(); - const docsInDbWithInternals = await getDocsInDb(this.internals, documentKeys); - docsInDbWithInternals.forEach(docWithDexieInternals => { - const doc = docWithDexieInternals ? fromDexieToStorage(docWithDexieInternals) : docWithDexieInternals; - if (doc) { - docsInDbMap.set(doc[this.primaryPath], doc); - } - return doc; - }); - - categorized = categorizeBulkWriteRows( - this, - this.primaryPath as any, - docsInDbMap, - documentWrites, - context - ); - ret.error = categorized.errors; - - /** * Batch up the database operations * so we can later run them in bulk. */ - const bulkPutDocs: any[] = []; - const bulkRemoveDocs: string[] = []; - const bulkPutDeletedDocs: any[] = []; - const bulkRemoveDeletedDocs: string[] = []; - - categorized.bulkInsertDocs.forEach(row => { - const docId: string = (row.document as any)[this.primaryPath]; - ret.success[docId] = row.document as any; - bulkPutDocs.push(row.document); - }); - categorized.bulkUpdateDocs.forEach(row => { - const docId: string = (row.document as any)[this.primaryPath]; - ret.success[docId] = row.document as any; - if ( - row.document._deleted && - (row.previous && !row.previous._deleted) - ) { - // newly deleted - bulkRemoveDocs.push(docId); - bulkPutDeletedDocs.push(row.document); - } else if ( - row.document._deleted && - row.previous && row.previous._deleted - ) { - // deleted was modified but is still deleted - bulkPutDeletedDocs.push(row.document); - } else if (!row.document._deleted) { - // non-deleted was changed - bulkPutDocs.push(row.document); - } else { - throw newRxError('SNH', { args: { row } }); - } - }); - - await Promise.all([ - bulkPutDocs.length > 0 ? state.dexieTable.bulkPut(bulkPutDocs.map(d => fromStorageToDexie(d))) : PROMISE_RESOLVE_VOID, - bulkRemoveDocs.length > 0 ? state.dexieTable.bulkDelete(bulkRemoveDocs) : PROMISE_RESOLVE_VOID, - bulkPutDeletedDocs.length > 0 ? state.dexieDeletedTable.bulkPut(bulkPutDeletedDocs.map(d => fromStorageToDexie(d))) : PROMISE_RESOLVE_VOID, - bulkRemoveDeletedDocs.length > 0 ? state.dexieDeletedTable.bulkDelete(bulkRemoveDeletedDocs) : PROMISE_RESOLVE_VOID - ]); - }); - - categorized = ensureNotFalsy(categorized); - if (categorized.eventBulk.events.length > 0) { - const lastState = ensureNotFalsy(categorized.newestRow).document; - categorized.eventBulk.checkpoint = { - id: lastState[this.primaryPath], - lwt: lastState._meta.lwt - }; - const endTime = now(); - categorized.eventBulk.events.forEach(event => (event as any).endTime = endTime); - this.changes$.next(categorized.eventBulk); - } - - return ret; + const bulkPutDocs: any[] = [] + const bulkRemoveDocs: string[] = [] + const bulkPutDeletedDocs: any[] = [] + const bulkRemoveDeletedDocs: string[] = [] + + categorized.bulkInsertDocs.forEach(row => { + const docId: string = (row.document as any)[this.primaryPath] + ret.success[docId] = row.document as any + bulkPutDocs.push(row.document) + }) + categorized.bulkUpdateDocs.forEach(row => { + const docId: string = (row.document as any)[this.primaryPath] + ret.success[docId] = row.document as any + if ( + row.document._deleted && + (row.previous && !row.previous._deleted) + ) { + // newly deleted + bulkRemoveDocs.push(docId) + bulkPutDeletedDocs.push(row.document) + } else if ( + row.document._deleted && + row.previous && row.previous._deleted + ) { + // deleted was modified but is still deleted + bulkPutDeletedDocs.push(row.document) + } else if (!row.document._deleted) { + // non-deleted was changed + bulkPutDocs.push(row.document) + } else { + throw newRxError('SNH', { args: { row } }) + } + }) + + await Promise.all([ + bulkPutDocs.length > 0 ? state.dexieTable.bulkPut(bulkPutDocs.map(d => fromStorageToDexie(d))) : PROMISE_RESOLVE_VOID, + bulkRemoveDocs.length > 0 ? state.dexieTable.bulkDelete(bulkRemoveDocs) : PROMISE_RESOLVE_VOID, + bulkPutDeletedDocs.length > 0 ? state.dexieDeletedTable.bulkPut(bulkPutDeletedDocs.map(d => fromStorageToDexie(d))) : PROMISE_RESOLVE_VOID, + bulkRemoveDeletedDocs.length > 0 ? state.dexieDeletedTable.bulkDelete(bulkRemoveDeletedDocs) : PROMISE_RESOLVE_VOID + ]) + }) + + categorized = ensureNotFalsy(categorized) + if (categorized.eventBulk.events.length > 0) { + const lastState = ensureNotFalsy(categorized.newestRow).document + categorized.eventBulk.checkpoint = { + id: lastState[this.primaryPath], + lwt: lastState._meta.lwt + } + const endTime = now() + categorized.eventBulk.events.forEach(event => { + (event as any).endTime = endTime + }) + this.changes$.next(categorized.eventBulk) } - async findDocumentsById( - ids: string[], - deleted: boolean - ): Promise> { - ensureNotClosed(this); - const state = await this.internals; - const ret: RxDocumentDataById = {}; - - await state.dexieDb.transaction( - 'r', - state.dexieTable, - state.dexieDeletedTable, - async () => { - let docsInDb: RxDocumentData[]; - if (deleted) { - docsInDb = await getDocsInDb(this.internals, ids); - } else { - docsInDb = await state.dexieTable.bulkGet(ids); - } - ids.forEach((id, idx) => { - const documentInDb = docsInDb[idx]; - if ( - documentInDb && - (!documentInDb._deleted || deleted) - ) { - ret[id] = fromDexieToStorage(documentInDb); - } - }); - }); - return ret; - } - - query(preparedQuery: DefaultPreparedQuery): Promise> { - ensureNotClosed(this); - return dexieQuery( - this, - preparedQuery, - this.schema - ); - } - async count( - preparedQuery: DefaultPreparedQuery - ): Promise { - const result = await dexieQuery(this, preparedQuery, this.schema); - return { - count: result.documents.length, - mode: 'fast' - }; + return ret + } + + async findDocumentsById ( + ids: string[], + deleted: boolean + ): Promise> { + ensureNotClosed(this) + const state = await this.internals + const ret: RxDocumentDataById = {} + + await state.dexieDb.transaction( + 'r', + state.dexieTable, + state.dexieDeletedTable, + async () => { + let docsInDb: Array> + if (deleted) { + docsInDb = await getDocsInDb(this.internals, ids) + } else { + docsInDb = await state.dexieTable.bulkGet(ids) + } + ids.forEach((id, idx) => { + const documentInDb = docsInDb[idx] + if ( + documentInDb && + (!documentInDb._deleted || deleted) + ) { + ret[id] = fromDexieToStorage(documentInDb) + } + }) + }) + return ret + } + + async query (preparedQuery: DefaultPreparedQuery): Promise> { + ensureNotClosed(this) + return await dexieQuery( + this, + preparedQuery, + this.schema + ) + } + + async count ( + preparedQuery: DefaultPreparedQuery + ): Promise { + const result = await dexieQuery(this, preparedQuery, this.schema) + return { + count: result.documents.length, + mode: 'fast' } - - async getChangedDocumentsSince( - limit: number, - checkpoint?: RxStorageDefaultCheckpoint - ): Promise<{ - documents: RxDocumentData[]; - checkpoint: RxStorageDefaultCheckpoint; + } + + async getChangedDocumentsSince ( + limit: number, + checkpoint?: RxStorageDefaultCheckpoint + ): Promise<{ + documents: Array> + checkpoint: RxStorageDefaultCheckpoint }> { - ensureNotClosed(this); - const sinceLwt = checkpoint ? checkpoint.lwt : RX_META_LWT_MINIMUM; - const sinceId = checkpoint ? checkpoint.id : ''; - const state = await this.internals; - - - const [changedDocsNormal, changedDocsDeleted] = await Promise.all( - [ - state.dexieTable, - state.dexieDeletedTable - ].map(async (table) => { - const query = table - .where('[_meta.lwt+' + this.primaryPath + ']') - .above([sinceLwt, sinceId]) - .limit(limit); - const changedDocuments: RxDocumentData[] = await query.toArray(); - return changedDocuments.map(d => fromDexieToStorage(d)); - }) - ); - let changedDocs = changedDocsNormal!.slice(0); - appendToArray(changedDocs, changedDocsDeleted!); - - changedDocs = sortDocumentsByLastWriteTime(this.primaryPath as any, changedDocs); - changedDocs = changedDocs.slice(0, limit); - - const lastDoc = lastOfArray(changedDocs); - return { - documents: changedDocs, - checkpoint: lastDoc ? { - id: lastDoc[this.primaryPath] as any, - lwt: lastDoc._meta.lwt - } : checkpoint ? checkpoint : { - id: '', - lwt: 0 - } - }; - } - - async remove(): Promise { - return Promise.resolve() - } - - changeStream(): Observable>, RxStorageDefaultCheckpoint>> { - ensureNotClosed(this); - return this.changes$.asObservable(); + ensureNotClosed(this) + const sinceLwt = checkpoint ? checkpoint.lwt : RX_META_LWT_MINIMUM + const sinceId = checkpoint ? checkpoint.id : '' + const state = await this.internals + + const [changedDocsNormal, changedDocsDeleted] = await Promise.all( + [ + state.dexieTable, + state.dexieDeletedTable + ].map(async (table) => { + const query = table + .where('[_meta.lwt+' + this.primaryPath + ']') + .above([sinceLwt, sinceId]) + .limit(limit) + const changedDocuments: Array> = await query.toArray() + return changedDocuments.map(d => fromDexieToStorage(d)) + }) + ) + let changedDocs = changedDocsNormal!.slice(0) + appendToArray(changedDocs, changedDocsDeleted!) + + changedDocs = sortDocumentsByLastWriteTime(this.primaryPath as any, changedDocs) + changedDocs = changedDocs.slice(0, limit) + + const lastDoc = lastOfArray(changedDocs) + return { + documents: changedDocs, + checkpoint: lastDoc + ? { + id: lastDoc[this.primaryPath], + lwt: lastDoc._meta.lwt + } + : checkpoint ?? { + id: '', + lwt: 0 + } } - - async cleanup(minimumDeletedTime: number): Promise { - ensureNotClosed(this); - const state = await this.internals; - await state.dexieDb.transaction( - 'rw', - state.dexieDeletedTable, - async () => { - const maxDeletionTime = now() - Infinity; - const toRemove = await state.dexieDeletedTable - .where('_meta.lwt') - .below(maxDeletionTime) - .toArray(); - const removeIds: string[] = toRemove.map(doc => doc[this.primaryPath]); - await state.dexieDeletedTable.bulkDelete(removeIds); - } - ); - - /** + } + + async remove (): Promise { + await Promise.resolve() + } + + changeStream (): Observable>, RxStorageDefaultCheckpoint>> { + ensureNotClosed(this) + return this.changes$.asObservable() + } + + async cleanup (): Promise { + ensureNotClosed(this) + const state = await this.internals + await state.dexieDb.transaction( + 'rw', + state.dexieDeletedTable, + async () => { + const maxDeletionTime = now() - Infinity + const toRemove = await state.dexieDeletedTable + .where('_meta.lwt') + .below(maxDeletionTime) + .toArray() + const removeIds: string[] = toRemove.map(doc => doc[this.primaryPath]) + await state.dexieDeletedTable.bulkDelete(removeIds) + } + ) + + /** * TODO instead of deleting all deleted docs at once, * only clean up some of them and return false if there are more documents to clean up. * This ensures that when many documents have to be purged, * we do not block the more important tasks too long. */ - return true; - } - - getAttachmentData(_documentId: string, _attachmentId: string, _digest: string): Promise { - ensureNotClosed(this); - throw new Error('Attachments are not implemented in the dexie RxStorage. Make a pull request.'); - } - - close(): Promise { - ensureNotClosed(this); - this.closed = true; - this.changes$.complete(); - closeDexieDb(this.internals); - return PROMISE_RESOLVE_VOID; - } - - conflictResultionTasks(): Observable> { - return new Subject(); - } - async resolveConflictResultionTask(_taskSolution: RxConflictResultionTaskSolution): Promise { } - + return true + } + + async getAttachmentData (): Promise { + ensureNotClosed(this) + throw new Error('Attachments are not implemented in the dexie RxStorage. Make a pull request.') + } + + async close (): Promise { + ensureNotClosed(this) + this.closed = true + this.changes$.complete() + await closeDexieDb(this.internals) + await PROMISE_RESOLVE_VOID + } + + conflictResultionTasks (): Observable> { + return new Subject() + } + + async resolveConflictResultionTask (): Promise { } } - -export async function createDexieStorageInstance( - storage: RxStorageDexie, - params: RxStorageInstanceCreationParams, - settings: DexieSettings +export async function createDexieStorageInstance ( + storage: RxStorageDexie, + params: RxStorageInstanceCreationParams, + settings: DexieSettings ): Promise> { - const internals = await getDexieDbWithTables( - params.databaseName, - params.collectionName, - settings, - params.schema - ) as any; - - const instance = new RxStorageInstanceDexie( - storage, - params.databaseName, - params.collectionName, - params.schema, - internals, - params.options, - settings - ); - - return Promise.resolve(instance); + const internals = await getDexieDbWithTables( + params.databaseName, + params.collectionName, + settings, + params.schema + ) as any + + const instance = new RxStorageInstanceDexie( + storage, + params.databaseName, + params.collectionName, + params.schema, + internals, + params.options, + settings + ) + + return await Promise.resolve(instance) } - - -function ensureNotClosed( - instance: RxStorageInstanceDexie +function ensureNotClosed ( + instance: RxStorageInstanceDexie ) { - if (instance.closed) { - throw new Error('RxStorageInstanceDexie is closed ' + instance.databaseName + '-' + instance.collectionName); - } + if (instance.closed) { + throw new Error('RxStorageInstanceDexie is closed ' + instance.databaseName + '-' + instance.collectionName) + } } diff --git a/packages/inmemory/src/inMemoryStorage/instance.ts b/packages/inmemory/src/inMemoryStorage/instance.ts index b04c6613..15704a14 100644 --- a/packages/inmemory/src/inMemoryStorage/instance.ts +++ b/packages/inmemory/src/inMemoryStorage/instance.ts @@ -1,237 +1,230 @@ - -import { RxStorageInstance, RxStorageDefaultCheckpoint, StringKeys, RxDocumentData, EventBulk, RxStorageChangeEvent, RxJsonSchema, getPrimaryFieldOfPrimaryKey, BulkWriteRow, RxStorageBulkWriteResponse, newRxError, CategorizeBulkWriteRowsOutput, categorizeBulkWriteRows, PROMISE_RESOLVE_VOID, ensureNotFalsy, now, RxDocumentDataById, RxStorageQueryResult, RxStorageCountResult, RxConflictResultionTask, RxConflictResultionTaskSolution, getQueryMatcher, getStartIndexStringFromLowerBound, getStartIndexStringFromUpperBound, MangoQuerySelector, flatClone, getSortComparator } from "rxdb"; +import { type RxStorageInstance, type RxStorageDefaultCheckpoint, type StringKeys, type RxDocumentData, type EventBulk, type RxStorageChangeEvent, type RxJsonSchema, getPrimaryFieldOfPrimaryKey, type BulkWriteRow, type RxStorageBulkWriteResponse, categorizeBulkWriteRows, ensureNotFalsy, now, type RxDocumentDataById, type RxStorageQueryResult, type RxStorageCountResult, type RxConflictResultionTask, getQueryMatcher, getSortComparator } from 'rxdb' import { - Subject, Observable -} from "rxjs"; - -import { InMemoryStorageInternals, InMemorySettings, RxStorageInMemoryType, InMemoryPreparedQuery } from "./types"; -import { conditionMatches } from '@pluto-encrypted/shared' -import { fixTxPipe } from "@pluto-encrypted/shared"; -import { QueryMatcher } from "rxdb/dist/types/types"; + Subject, type Observable +} from 'rxjs' +import { type InMemoryStorageInternals, type InMemorySettings, type RxStorageInMemoryType, type InMemoryPreparedQuery } from './types' +import { fixTxPipe } from '@pluto-encrypted/shared' +import { type QueryMatcher } from 'rxdb/dist/types/types' export class RxStorageIntanceInMemory implements RxStorageInstance< - RxDocType, - InMemoryStorageInternals, - InMemorySettings, - RxStorageDefaultCheckpoint> -{ - public readonly primaryPath: StringKeys>; - public conflictResultionTasks$: Subject> = new Subject() - public changes$: Subject>, RxStorageDefaultCheckpoint>> = new Subject() - public closed: boolean = false; - - constructor( - public readonly storage: RxStorageInMemoryType, - public readonly databaseName: string, - public readonly collectionName: string, - public readonly schema: Readonly>>, - public readonly internals: InMemoryStorageInternals, - public readonly options: Readonly, - ) { - this.primaryPath = getPrimaryFieldOfPrimaryKey(this.schema.primaryKey); +RxDocType, +InMemoryStorageInternals, +InMemorySettings, +RxStorageDefaultCheckpoint> { + public readonly primaryPath: StringKeys> + public conflictResultionTasks$ = new Subject>() + public changes$ = new Subject>, RxStorageDefaultCheckpoint>>() + public closed: boolean = false + + constructor ( + public readonly storage: RxStorageInMemoryType, + public readonly databaseName: string, + public readonly collectionName: string, + public readonly schema: Readonly>>, + public readonly internals: InMemoryStorageInternals, + public readonly options: Readonly + ) { + this.primaryPath = getPrimaryFieldOfPrimaryKey(this.schema.primaryKey) + } + + async bulkWrite ( + documentWrites: Array>, + context: string): Promise> { + const primaryPath = this.primaryPath + const ret: RxStorageBulkWriteResponse = { + success: {}, + error: {} } - async bulkWrite( - documentWrites: BulkWriteRow[], - context: string): Promise> { - - const primaryPath = this.primaryPath; - const ret: RxStorageBulkWriteResponse = { - success: {}, - error: {} - }; - - const documents = this.internals.documents - const fixed = documentWrites.reduce[]>((fixedDocs, currentWriteDoc) => { - const currentId = currentWriteDoc.document[this.primaryPath] as any; - const previousDocument = currentWriteDoc.previous || this.internals.documents.get(currentId) - if (context === "data-migrator-delete") { - if (previousDocument && previousDocument._rev === currentWriteDoc.document._rev) { - fixedDocs.push(currentWriteDoc) - } - } else { - if (previousDocument && previousDocument._rev !== currentWriteDoc.document._rev) { - currentWriteDoc.previous = previousDocument - } else { - currentWriteDoc.previous = undefined - } - fixedDocs.push(currentWriteDoc) - } - return fixedDocs - }, []); - - - const categorized = categorizeBulkWriteRows( - this, - primaryPath as any, - documents as any, - fixed, - context - ); - ret.error = categorized.errors; - - /** - * Do inserts/updates - */ - const bulkInsertDocs = categorized.bulkInsertDocs; - for (let i = 0; i < bulkInsertDocs.length; ++i) { - const writeRow = bulkInsertDocs[i]!; - const docId = writeRow.document[primaryPath]; - await this.internals.bulkPut([writeRow.document], this.collectionName, this.schema) - ret.success[docId as any] = writeRow.document; - } - - const bulkUpdateDocs = categorized.bulkUpdateDocs; - for (let i = 0; i < bulkUpdateDocs.length; ++i) { - const writeRow = bulkUpdateDocs[i]!; - const docId = writeRow.document[primaryPath]; - await this.internals.bulkPut([writeRow.document], this.collectionName, this.schema) - ret.success[docId as any] = writeRow.document; + const documents = this.internals.documents + const fixed = documentWrites.reduce>>((fixedDocs, currentWriteDoc) => { + const currentId = currentWriteDoc.document[this.primaryPath] as any + const previousDocument = currentWriteDoc.previous ?? this.internals.documents.get(currentId) + if (context === 'data-migrator-delete') { + if (previousDocument && previousDocument._rev === currentWriteDoc.document._rev) { + fixedDocs.push(currentWriteDoc) } - - - if (categorized.eventBulk.events.length > 0) { - const lastState = ensureNotFalsy(categorized.newestRow).document; - categorized.eventBulk.checkpoint = { - id: lastState[primaryPath], - lwt: lastState._meta.lwt - }; - const endTime = now(); - categorized.eventBulk.events.forEach(event => (event as any).endTime = endTime); - this.changes$.next(categorized.eventBulk); + } else { + if (previousDocument && previousDocument._rev !== currentWriteDoc.document._rev) { + currentWriteDoc.previous = previousDocument + } else { + currentWriteDoc.previous = undefined } - - return Promise.resolve(ret); + fixedDocs.push(currentWriteDoc) + } + return fixedDocs + }, []) + + const categorized = categorizeBulkWriteRows( + this, + primaryPath as any, + documents as any, + fixed, + context + ) + ret.error = categorized.errors + + /** + * Do inserts/updates + */ + const bulkInsertDocs = categorized.bulkInsertDocs + for (let i = 0; i < bulkInsertDocs.length; ++i) { + const writeRow = bulkInsertDocs[i]! + const docId = writeRow.document[primaryPath] + await this.internals.bulkPut([writeRow.document], this.collectionName, this.schema) + ret.success[docId as any] = writeRow.document } - async findDocumentsById(ids: string[], withDeleted: boolean): Promise> { - return this.internals.bulkGet(ids, withDeleted) + const bulkUpdateDocs = categorized.bulkUpdateDocs + for (let i = 0; i < bulkUpdateDocs.length; ++i) { + const writeRow = bulkUpdateDocs[i]! + const docId = writeRow.document[primaryPath] + await this.internals.bulkPut([writeRow.document], this.collectionName, this.schema) + ret.success[docId as any] = writeRow.document } - async query(preparedQuery: InMemoryPreparedQuery): Promise> { - const { queryPlan, query } = preparedQuery; - const selector = query.selector; - const selectorKeys = Object.keys(selector); - const skip = query.skip ? query.skip : 0; - const limit = query.limit ? query.limit : Infinity; - const skipPlusLimit = skip + limit; - let queryMatcher: QueryMatcher> = getQueryMatcher( - this.schema, - query - ); - - - const queryPlanFields: string[] = queryPlan.index; - let indexes: string[] = [] - if (queryPlanFields.length === 1) { - indexes.push(fixTxPipe(queryPlanFields[0]!)) - } else { - indexes.push(...queryPlanFields.map(field => fixTxPipe(field))) + if (categorized.eventBulk.events.length > 0) { + const lastState = ensureNotFalsy(categorized.newestRow).document + categorized.eventBulk.checkpoint = { + id: lastState[primaryPath], + lwt: lastState._meta.lwt + } + const endTime = now() + categorized.eventBulk.events.forEach(event => { + (event as any).endTime = endTime + }) + this.changes$.next(categorized.eventBulk) + } - } + return await Promise.resolve(ret) + } + + async findDocumentsById (ids: string[], withDeleted: boolean): Promise> { + return this.internals.bulkGet(ids, withDeleted) + } + + async query (preparedQuery: InMemoryPreparedQuery): Promise> { + const { queryPlan, query } = preparedQuery + const selector = query.selector + const selectorKeys = Object.keys(selector) + const skip = query.skip ? query.skip : 0 + const limit = query.limit ? query.limit : Infinity + const skipPlusLimit = skip + limit + const queryMatcher: QueryMatcher> = getQueryMatcher( + this.schema, + query + ) + + const queryPlanFields: string[] = queryPlan.index + const indexes: string[] = [] + if (queryPlanFields.length === 1) { + indexes.push(fixTxPipe(queryPlanFields[0]!)) + } else { + indexes.push(...queryPlanFields.map(field => fixTxPipe(field))) + } - const shouldAddCompoundIndexes = this.schema.indexes?.find((index) => { - if (typeof index === "string") { - return indexes.find((index2) => index2 === index) - } else { - return index.find((subIndex) => subIndex === subIndex) - } - }); - - if (shouldAddCompoundIndexes) { - indexes.splice(0, indexes.length) - indexes.push(this.collectionName) - if (typeof shouldAddCompoundIndexes === "string") { - indexes.push(shouldAddCompoundIndexes) - } else { - indexes.push(...shouldAddCompoundIndexes) - } + const shouldAddCompoundIndexes = this.schema.indexes?.find((index) => { + if (typeof index === 'string') { + return indexes.find((index2) => index2 === index) + } else { + return index.find((subIndex) => { + return subIndex === index.find((indexValue) => indexValue === subIndex) + }) + } + }) + + if (shouldAddCompoundIndexes) { + indexes.splice(0, indexes.length) + indexes.push(this.collectionName) + if (typeof shouldAddCompoundIndexes === 'string') { + indexes.push(shouldAddCompoundIndexes) + } else { + indexes.push(...shouldAddCompoundIndexes) + } + } else { + indexes.unshift(this.collectionName) + } - } else { - indexes.unshift(this.collectionName) - } + const indexName: string = `[${indexes.join('+')}]` + const documentIds = this.internals.index.get(indexName) - const indexName: string = `[${indexes.join('+')}]`; - const documentIds = this.internals.index.get(indexName); + if (!documentIds) { + return { documents: [] } + } - if (!documentIds) { - return { documents: [] } + let documents = documentIds.reduce>>((allDocuments, id) => { + const document = this.internals.data.get(id) + if (document) { + if (selectorKeys.length <= 0) { + return [...allDocuments, document] } - - let documents = documentIds.reduce[]>((allDocuments, id) => { - const document = this.internals.data.get(id); - if (document) { - if (selectorKeys.length <= 0) { - return [...allDocuments, document] - } - const matches = queryMatcher(document) - if (matches) { - return [...allDocuments, document] - } - } - return allDocuments - }, []) - - const sortComparator = getSortComparator(this.schema, preparedQuery.query); - documents = documents.sort(sortComparator); - documents = documents.slice(skip, skipPlusLimit); - return { - documents: documents + const matches = queryMatcher(document) + if (matches) { + return [...allDocuments, document] } + } + return allDocuments + }, []) + + const sortComparator = getSortComparator(this.schema, preparedQuery.query) + documents = documents.sort(sortComparator) + documents = documents.slice(skip, skipPlusLimit) + return { + documents } + } - async count(preparedQuery: any): Promise { - const result = await this.query(preparedQuery); - return { - count: result.documents.length, - mode: 'fast' - }; + async count (preparedQuery: any): Promise { + const result = await this.query(preparedQuery) + return { + count: result.documents.length, + mode: 'fast' } + } - /* istanbul ignore next */ - getAttachmentData(documentId: string, attachmentId: string, digest: string): Promise { - throw new Error("Method not implemented."); - } + /* istanbul ignore next */ + async getAttachmentData (): Promise { + throw new Error('Method not implemented.') + } - /* istanbul ignore next */ - getChangedDocumentsSince(limit: number, checkpoint?: RxStorageDefaultCheckpoint | undefined): Promise<{ documents: RxDocumentData[]; checkpoint: RxStorageDefaultCheckpoint; }> { - throw new Error("Method not implemented."); - } + /* istanbul ignore next */ + async getChangedDocumentsSince (): Promise<{ documents: Array>, checkpoint: RxStorageDefaultCheckpoint }> { + throw new Error('Method not implemented.') + } - /* istanbul ignore next */ - changeStream(): Observable, RxStorageDefaultCheckpoint>> { - return this.changes$.asObservable(); - } - - async cleanup(): Promise { - this.internals.clear() + /* istanbul ignore next */ + changeStream (): Observable, RxStorageDefaultCheckpoint>> { + return this.changes$.asObservable() + } - return true; - } + async cleanup (): Promise { + this.internals.clear() - /* istanbul ignore next */ - async close(): Promise { - if (this.closed) { - return Promise.reject(new Error('already closed')); - } - this.closed = true; + return true + } - this.internals.refCount = this.internals.refCount - 1; + /* istanbul ignore next */ + async close (): Promise { + if (this.closed) { + await Promise.reject(new Error('already closed')); return } + this.closed = true - /* istanbul ignore next */ - async remove(): Promise { - return Promise.resolve() - } + this.internals.refCount = this.internals.refCount - 1 + } - conflictResultionTasks(): Observable> { - return this.conflictResultionTasks$.asObservable(); - } + /* istanbul ignore next */ + async remove (): Promise { + await Promise.resolve() + } - /* istanbul ignore next */ - resolveConflictResultionTask(taskSolution: RxConflictResultionTaskSolution): Promise { - return Promise.resolve() - } + conflictResultionTasks (): Observable> { + return this.conflictResultionTasks$.asObservable() + } -} \ No newline at end of file + /* istanbul ignore next */ + async resolveConflictResultionTask (): Promise { + await Promise.resolve() + } +} diff --git a/packages/inmemory/src/inMemoryStorage/internal.ts b/packages/inmemory/src/inMemoryStorage/internal.ts index d4fc80d3..cb800dd4 100644 --- a/packages/inmemory/src/inMemoryStorage/internal.ts +++ b/packages/inmemory/src/inMemoryStorage/internal.ts @@ -1,90 +1,84 @@ -import { RxDocumentData, RxDocumentDataById, RxJsonSchema } from "rxdb"; -import { InMemoryDataIndex, InMemoryDataStructure, InMemoryStorageInternals, IndexType } from "./types"; -import { getPrivateKeyValue, safeIndexList } from "@pluto-encrypted/shared"; +import { type RxDocumentData, type RxDocumentDataById, type RxJsonSchema } from 'rxdb' +import { type InMemoryDataIndex, type InMemoryDataStructure, type InMemoryStorageInternals, type IndexType } from './types' +import { getPrivateKeyValue, safeIndexList } from '@pluto-encrypted/shared' - - - -function initialiseData(): InMemoryDataStructure { - return new Map() +function initialiseData (): InMemoryDataStructure { + return new Map() } -function initialiseIndex(): InMemoryDataIndex { - return new Map() +function initialiseIndex (): InMemoryDataIndex { + return new Map() } export class InMemoryInternal implements InMemoryStorageInternals { - - public removed = false; - public data: InMemoryDataStructure = initialiseData(); - public index: InMemoryDataIndex = initialiseIndex(); - - constructor(public refCount: number) { } - - get documents() { - return this.data + public removed = false + public data: InMemoryDataStructure = initialiseData() + public index: InMemoryDataIndex = initialiseIndex() + + constructor (public refCount: number) { } + + get documents () { + return this.data + } + + public addIndex (indexName: string, docId: IndexType) { + if (this.index.has(indexName)) { + const values = this.index.get(indexName) ?? [] + const newIndexes = Array.from(new Set([...values, docId])) + this.index.set(indexName, newIndexes) + } else { + this.index.set(indexName, [docId]) } + } - public addIndex(indexName: string, docId: IndexType) { - if (this.index.has(indexName)) { - const values = this.index.get(indexName) || []; - const newIndexes = Array.from(new Set([...values, docId])); - this.index.set(indexName, newIndexes) - } else { - this.index.set(indexName, [docId]) - } + public removeFromIndex (indexName: string, id: string) { + if (this.index.has(indexName)) { + const values = this.index.get(indexName) ?? [] + this.index.set(indexName, values.filter((vId) => vId !== id)) } - - public removeFromIndex(indexName: string, id: string) { - if (this.index.has(indexName)) { - const values = this.index.get(indexName) || []; - this.index.set(indexName, values.filter((vId) => vId !== id)) + } + + clear () { + this.data.clear() + this.index.clear() + } + + async bulkPut (items: Array>, collectionName: string, schema: Readonly>>) { + const primaryKeyKey = typeof schema.primaryKey === 'string' ? schema.primaryKey : schema.primaryKey.key + const saferIndexList = safeIndexList(schema) + + for (const item of items) { + const shouldDelete = item._deleted + const id = getPrivateKeyValue(item, schema) + if (shouldDelete) { + for (const requiredIndexes of saferIndexList) { + const requiredIndex = `[${collectionName}+${requiredIndexes.join('+')}]` + await this.removeFromIndex(requiredIndex, id) } - } - - clear() { - this.data.clear(); - this.index.clear(); - } - - async bulkPut(items: RxDocumentData[], collectionName: string, schema: Readonly>>) { - const primaryKeyKey = typeof schema.primaryKey === "string" ? schema.primaryKey : schema.primaryKey.key; - const saferIndexList = safeIndexList(schema); - - for (let item of items) { - - const shouldDelete = item._deleted; - const id = getPrivateKeyValue(item, schema) - if (shouldDelete) { - for (let requiredIndexes of saferIndexList) { - const requiredIndex = `[${collectionName}+${requiredIndexes.join("+")}]` - await this.removeFromIndex(requiredIndex, id) - } - await this.removeFromIndex(`[${collectionName}+${primaryKeyKey}]`, id) - await this.removeFromIndex('[all]', id) - await this.data.delete(id) - this.documents.delete(id) - } else { - for (let requiredIndexes of saferIndexList) { - const requiredIndex = `[${collectionName}+${requiredIndexes.join("+")}]` - await this.addIndex(requiredIndex, id) - } - await this.addIndex(`[${collectionName}+${primaryKeyKey}]`, id) - await this.addIndex('[all]', id) - await this.data.set(id, item); - this.documents.set(id, item) - } + await this.removeFromIndex(`[${collectionName}+${primaryKeyKey}]`, id) + await this.removeFromIndex('[all]', id) + await this.data.delete(id) + this.documents.delete(id) + } else { + for (const requiredIndexes of saferIndexList) { + const requiredIndex = `[${collectionName}+${requiredIndexes.join('+')}]` + await this.addIndex(requiredIndex, id) } + await this.addIndex(`[${collectionName}+${primaryKeyKey}]`, id) + await this.addIndex('[all]', id) + await this.data.set(id, item) + this.documents.set(id, item) + } } - - bulkGet(docIds: string[], withDeleted: boolean): RxDocumentDataById { - return docIds.reduce>((alldocs, current) => { - const data = this.data.get(current); - if (data) { - alldocs[current] = data - } - return alldocs; - }, {}) - } + } + + bulkGet (docIds: string[]): RxDocumentDataById { + return docIds.reduce>((alldocs, current) => { + const data = this.data.get(current) + if (data) { + alldocs[current] = data + } + return alldocs + }, {}) + } } - diff --git a/packages/inmemory/src/inMemoryStorage/types.ts b/packages/inmemory/src/inMemoryStorage/types.ts index 3ce700d3..a16ab18b 100644 --- a/packages/inmemory/src/inMemoryStorage/types.ts +++ b/packages/inmemory/src/inMemoryStorage/types.ts @@ -1,45 +1,43 @@ -import { DefaultPreparedQuery, RxDocumentData, RxDocumentDataById, RxJsonSchema, RxStorage, } from "rxdb"; +import { type DefaultPreparedQuery, type RxDocumentData, type RxDocumentDataById, type RxJsonSchema, type RxStorage } from 'rxdb' /** * Index of a table can be a string or a number */ -export type IndexType = string | number; +export type IndexType = string | number /** * InMemory internal data structure is a Map with an index * and RxDocumentData from RXDB */ -export type InMemoryDataStructure = Map>; +export type InMemoryDataStructure = Map> /** * Data type for index keystorage * I used this to get faster searches based on what RXDB indexes we were * informed */ -export type InMemoryDataIndex = Map; +export type InMemoryDataIndex = Map /** * Query type for InMemory */ -export type InMemoryPreparedQuery = DefaultPreparedQuery; +export type InMemoryPreparedQuery = DefaultPreparedQuery /** * Main storage interface for InMemoryStorage */ -export type InMemoryStorageInternals = { - data: InMemoryDataStructure; - index: InMemoryDataIndex; - documents: InMemoryDataStructure; - removed: boolean; - refCount: number; - addIndex(indexName: string, docId: IndexType); - removeFromIndex(indexName: string, id: string): void; - bulkPut( - items: any, - collectionName: string, - schema: Readonly>>): any; - bulkGet(docIds: string[], withDeleted: boolean): RxDocumentDataById; - clear(): void; +export interface InMemoryStorageInternals { + data: InMemoryDataStructure + index: InMemoryDataIndex + documents: InMemoryDataStructure + removed: boolean + refCount: number + addIndex: (indexName: string, docId: IndexType) => any + removeFromIndex: (indexName: string, id: string) => void + bulkPut: ( + items: any, + collectionName: string, + schema: Readonly>>) => any + bulkGet: (docIds: string[], withDeleted: boolean) => RxDocumentDataById + clear: () => void } export type RxStorageInMemoryType = RxStorage - -export type InMemorySettings = {} - +export interface InMemorySettings {} diff --git a/packages/inmemory/src/index.ts b/packages/inmemory/src/index.ts index 07608a28..7c5e0f0a 100644 --- a/packages/inmemory/src/index.ts +++ b/packages/inmemory/src/index.ts @@ -4,7 +4,7 @@ * @description This is a RXDB InMemory storage that supports encryption middleware. * In order to use this in your pluto-encrypted database you must write the following code: * Creating a InMemory compatible storage is very simple. - * + * * ```typescript * import InMemory from "@pluto-encrypted/inmemory"; * import { Database } from "@pluto-encrypted/database"; @@ -17,41 +17,40 @@ * }); * ``` */ -import { RxStorage, RxStorageDefaultStatics, RxStorageInstance, RxStorageInstanceCreationParams, newRxError } from "rxdb"; -import { InMemorySettings, InMemoryStorageInternals, RxStorageInMemoryType } from "./inMemoryStorage/types"; -import { RxStorageIntanceInMemory } from "./inMemoryStorage/instance"; -import { InMemoryInternal } from "./inMemoryStorage/internal"; -import { wrappedKeyEncryptionStorage } from "@pluto-encrypted/encryption"; +import { type RxStorage, RxStorageDefaultStatics, type RxStorageInstance, type RxStorageInstanceCreationParams, newRxError } from 'rxdb' +import { type InMemorySettings, type InMemoryStorageInternals, type RxStorageInMemoryType } from './inMemoryStorage/types' +import { RxStorageIntanceInMemory } from './inMemoryStorage/instance' +import { InMemoryInternal } from './inMemoryStorage/internal' +import { wrappedKeyEncryptionStorage } from '@pluto-encrypted/encryption' +const internalInstance = new Map>() -let internalInstance: Map> = new Map() - -function getRxStorageMemory(settings: InMemorySettings = {}): RxStorageInMemoryType { - const inMemoryInstance: RxStorageInMemoryType = { - name: "in-memory", - statics: RxStorageDefaultStatics, - async createStorageInstance(params: RxStorageInstanceCreationParams): Promise, InMemorySettings, any>> { - if (params.schema.keyCompression) { - throw newRxError('UT5', { args: { databaseName: params.databaseName, collectionName: params.collectionName } }) - } - const existingInstance = internalInstance.get(params.databaseName) - if (!existingInstance) { - internalInstance.set(params.databaseName, new InMemoryInternal(0)); - } else { - existingInstance.refCount++; - internalInstance.set(params.databaseName, existingInstance) - } - return new RxStorageIntanceInMemory( - this, - params.databaseName, - params.collectionName, - params.schema, - internalInstance.get(params.databaseName)!, - settings - ) - } +function getRxStorageMemory (settings: InMemorySettings = {}): RxStorageInMemoryType { + const inMemoryInstance: RxStorageInMemoryType = { + name: 'in-memory', + statics: RxStorageDefaultStatics, + async createStorageInstance(params: RxStorageInstanceCreationParams): Promise, InMemorySettings, any>> { + if (params.schema.keyCompression) { + throw newRxError('UT5', { args: { databaseName: params.databaseName, collectionName: params.collectionName } }) + } + const existingInstance = internalInstance.get(params.databaseName) + if (!existingInstance) { + internalInstance.set(params.databaseName, new InMemoryInternal(0)) + } else { + existingInstance.refCount++ + internalInstance.set(params.databaseName, existingInstance) + } + return new RxStorageIntanceInMemory( + this, + params.databaseName, + params.collectionName, + params.schema, + internalInstance.get(params.databaseName)!, + settings + ) } - return inMemoryInstance + } + return inMemoryInstance } /** @@ -59,7 +58,7 @@ function getRxStorageMemory(settings: InMemorySettings = {}): RxStora * @description Use this as storage in our RXDB database. For now there is no initialisation settings, so you can use it out of the box. */ const storage: RxStorage = wrappedKeyEncryptionStorage({ - storage: getRxStorageMemory() + storage: getRxStorageMemory() }) -export default storage; \ No newline at end of file +export default storage diff --git a/packages/leveldb/src/index.ts b/packages/leveldb/src/index.ts index a6aba419..a35906a3 100644 --- a/packages/leveldb/src/index.ts +++ b/packages/leveldb/src/index.ts @@ -4,7 +4,7 @@ * @description This is a RXDB LevelDB storage that supports encryption middleware. * In order to use this in your pluto-encrypted database you must write the following code: * Creating a LevelDB compatible storage is very simple. - * + * * ```typescript * import { createLevelDBStorage } from "@pluto-encrypted/leveldb"; * import { Database } from "@pluto-encrypted/database"; @@ -13,94 +13,87 @@ * const database = db = await Database.createEncrypted({ * name: `my-db`, * encryptionKey: defaultPassword, - * storage: createLevelDBStorage({ + * storage: createLevelDBStorage({ * dbName: "demo", - * dbPath: "/tmp/demo" + * dbPath: "/tmp/demo" * }) * }); * ``` */ -import { RxStorage, RxStorageDefaultStatics, RxStorageInstance, RxStorageInstanceCreationParams, newRxError } from "rxdb"; -import { LevelDBInternalConstructor, LevelDBSettings, LevelDBStorageInternals, RxStorageLevelDBType } from "./leveldb/types"; +import { RxStorageDefaultStatics, type RxStorageInstance, type RxStorageInstanceCreationParams, newRxError } from 'rxdb' +import { type LevelDBInternalConstructor, type LevelDBSettings, type LevelDBStorageInternals, type RxStorageLevelDBType } from './leveldb/types' -import { RxStorageIntanceLevelDB } from "./leveldb/instance"; -import { LevelDBInternal } from "./leveldb/internal"; -import { wrappedKeyEncryptionStorage } from "@pluto-encrypted/encryption"; +import { RxStorageIntanceLevelDB } from './leveldb/instance' +import { LevelDBInternal } from './leveldb/internal' +import { wrappedKeyEncryptionStorage } from '@pluto-encrypted/encryption' -export type * from './leveldb/types'; +export type * from './leveldb/types' - -export type { MangoQuerySelectorAndIndex, CRDTSchemaOptions, RxQueryPlanKey, PrimaryKey, StringKeys, TopLevelProperty, MangoQuerySortDirection } from "rxdb/dist/types/types"; +export type { MangoQuerySelectorAndIndex, CRDTSchemaOptions, RxQueryPlanKey, PrimaryKey, StringKeys, TopLevelProperty, MangoQuerySortDirection } from 'rxdb/dist/types/types' export type { DefaultPreparedQuery, RxJsonSchema, FilledMangoQuery, CompressionMode, RxQueryPlan, MangoQuery, MangoQueryNoLimit, MangoQuerySortPart } from 'rxdb' export type { Level } from 'level' -export const RX_STORAGE_NAME_LEVELDB = 'leveldb'; +export const RX_STORAGE_NAME_LEVELDB = 'leveldb' -async function preloadData(constructorProps: LevelDBInternalConstructor) { - try { - const internalStorage = new LevelDBInternal(constructorProps); - await internalStorage.getDocuments([]); - return internalStorage - } catch (err) { - throw err - } +async function preloadData (constructorProps: LevelDBInternalConstructor) { + const internalStorage = new LevelDBInternal(constructorProps) + await internalStorage.getDocuments([]) + return internalStorage } -let internalInstance: Map> = new Map() - -function getRxStorageLevel(settings: LevelDBSettings): RxStorageLevelDBType { - const instance: RxStorageLevelDBType = { - name: RX_STORAGE_NAME_LEVELDB, - statics: RxStorageDefaultStatics, - async createStorageInstance(params: RxStorageInstanceCreationParams): Promise, LevelDBSettings, any>> { - if (params.schema.keyCompression) { - throw newRxError('UT5', { args: { databaseName: params.databaseName, collectionName: params.collectionName } }) - } - - const levelDBConstructorProps: LevelDBInternalConstructor = "level" in settings ? - { - level: settings.level, - refCount: 1, - schema: params.schema, - } - : - { - dbPath: settings.dbPath, - refCount: 1, - schema: params.schema, - }; - - const databasePath = "level" in levelDBConstructorProps ? - levelDBConstructorProps.level.location : - levelDBConstructorProps.dbPath; - - const existingInstance = internalInstance.get(databasePath); - - if (!existingInstance) { - internalInstance.set(databasePath, await preloadData(levelDBConstructorProps)) - } else { - existingInstance.refCount++; - internalInstance.set(databasePath, existingInstance) - } - - const rxStorageInstance = new RxStorageIntanceLevelDB( - this, - params.databaseName, - params.collectionName, - params.schema, - internalInstance.get(databasePath)!, - settings - ) - - return rxStorageInstance - } +const internalInstance = new Map>() + +function getRxStorageLevel (settings: LevelDBSettings): RxStorageLevelDBType { + const instance: RxStorageLevelDBType = { + name: RX_STORAGE_NAME_LEVELDB, + statics: RxStorageDefaultStatics, + async createStorageInstance(params: RxStorageInstanceCreationParams): Promise, LevelDBSettings, any>> { + if (params.schema.keyCompression) { + throw newRxError('UT5', { args: { databaseName: params.databaseName, collectionName: params.collectionName } }) + } + + const levelDBConstructorProps: LevelDBInternalConstructor = 'level' in settings + ? { + level: settings.level, + refCount: 1, + schema: params.schema + } + : { + dbPath: settings.dbPath, + refCount: 1, + schema: params.schema + } + + const databasePath = 'level' in levelDBConstructorProps + ? levelDBConstructorProps.level.location + : levelDBConstructorProps.dbPath + + const existingInstance = internalInstance.get(databasePath) + + if (!existingInstance) { + internalInstance.set(databasePath, await preloadData(levelDBConstructorProps)) + } else { + existingInstance.refCount++ + internalInstance.set(databasePath, existingInstance) + } + + const rxStorageInstance = new RxStorageIntanceLevelDB( + this, + params.databaseName, + params.collectionName, + params.schema, + internalInstance.get(databasePath)!, + settings + ) + + return rxStorageInstance } + } - return instance + return instance } - -export function createLevelDBStorage(settings: LevelDBSettings) { - return wrappedKeyEncryptionStorage({ - storage: getRxStorageLevel(settings) - }) +export function createLevelDBStorage (settings: LevelDBSettings) { + return wrappedKeyEncryptionStorage({ + storage: getRxStorageLevel(settings) + }) } diff --git a/packages/leveldb/src/leveldb/instance.ts b/packages/leveldb/src/leveldb/instance.ts index 0e7cc6ba..2a7a62e6 100644 --- a/packages/leveldb/src/leveldb/instance.ts +++ b/packages/leveldb/src/leveldb/instance.ts @@ -1,284 +1,281 @@ - -import { RxStorageInstance, RxStorageDefaultCheckpoint, StringKeys, RxDocumentData, EventBulk, RxStorageChangeEvent, RxJsonSchema, getPrimaryFieldOfPrimaryKey, BulkWriteRow, RxStorageBulkWriteResponse, newRxError, CategorizeBulkWriteRowsOutput, categorizeBulkWriteRows, PROMISE_RESOLVE_VOID, ensureNotFalsy, now, RxDocumentDataById, RxStorageQueryResult, RxStorageCountResult, RxConflictResultionTask, RxConflictResultionTaskSolution, getQueryMatcher, getStartIndexStringFromLowerBound, getStartIndexStringFromUpperBound, MangoQuerySelector, flatClone, getSortComparator } from "rxdb"; +import { type RxStorageInstance, type RxStorageDefaultCheckpoint, type StringKeys, type RxDocumentData, type EventBulk, type RxStorageChangeEvent, type RxJsonSchema, getPrimaryFieldOfPrimaryKey, type BulkWriteRow, type RxStorageBulkWriteResponse, categorizeBulkWriteRows, ensureNotFalsy, now, type RxDocumentDataById, type RxStorageQueryResult, type RxStorageCountResult, type RxConflictResultionTask, getQueryMatcher, getSortComparator } from 'rxdb' import { - Subject, Observable -} from "rxjs"; + Subject, type Observable +} from 'rxjs' -import { LevelDBStorageInternals, LevelDBSettings, RxStorageLevelDBType, LevelDBPreparedQuery } from "./types"; -import { QueryMatcher } from "rxdb/dist/types/types"; -import { fixTxPipe } from "@pluto-encrypted/shared"; +import { type LevelDBStorageInternals, type LevelDBSettings, type RxStorageLevelDBType, type LevelDBPreparedQuery } from './types' +import { type QueryMatcher } from 'rxdb/dist/types/types' +import { fixTxPipe } from '@pluto-encrypted/shared' export class RxStorageIntanceLevelDB implements RxStorageInstance< - RxDocType, - LevelDBStorageInternals, - LevelDBSettings, - RxStorageDefaultCheckpoint> -{ - public readonly primaryPath: StringKeys>; - public conflictResultionTasks$: Subject> = new Subject() - public changes$: Subject>, RxStorageDefaultCheckpoint>> = new Subject() - public closed: boolean = false; - - constructor( - public readonly storage: RxStorageLevelDBType, - public readonly databaseName: string, - public readonly collectionName: string, - public readonly schema: Readonly>>, - public readonly internals: LevelDBStorageInternals, - public readonly options: Readonly, - ) { - this.primaryPath = getPrimaryFieldOfPrimaryKey(this.schema.primaryKey); +RxDocType, +LevelDBStorageInternals, +LevelDBSettings, +RxStorageDefaultCheckpoint> { + public readonly primaryPath: StringKeys> + public conflictResultionTasks$ = new Subject>() + public changes$ = new Subject>, RxStorageDefaultCheckpoint>>() + public closed: boolean = false + + constructor ( + public readonly storage: RxStorageLevelDBType, + public readonly databaseName: string, + public readonly collectionName: string, + public readonly schema: Readonly>>, + public readonly internals: LevelDBStorageInternals, + public readonly options: Readonly + ) { + this.primaryPath = getPrimaryFieldOfPrimaryKey(this.schema.primaryKey) + } + + async bulkWrite ( + documentWrites: Array>, + context: string): Promise> { + const primaryPath = this.primaryPath + const ret: RxStorageBulkWriteResponse = { + success: {}, + error: {} } - - async bulkWrite( - documentWrites: BulkWriteRow[], - context: string): Promise> { - - const primaryPath = this.primaryPath; - const ret: RxStorageBulkWriteResponse = { - success: {}, - error: {} - }; - const documentKeys: string[] = documentWrites.map(writeRow => writeRow.document[this.primaryPath] as any); - const documents = await this.internals.getDocuments(documentKeys) - - const fixed = documentWrites.reduce[]>((fixedDocs, currentWriteDoc) => { - const currentId = currentWriteDoc.document[this.primaryPath] as any; - const previousDocument = currentWriteDoc.previous || this.internals.documents.get(currentId) - if (context === "data-migrator-delete") { - if (previousDocument && previousDocument._rev === currentWriteDoc.document._rev) { - fixedDocs.push(currentWriteDoc) - } - } else { - if (previousDocument && previousDocument._rev !== currentWriteDoc.document._rev) { - currentWriteDoc.previous = previousDocument - } else { - currentWriteDoc.previous = undefined - } - fixedDocs.push(currentWriteDoc) - } - return fixedDocs - }, []); - - const categorized = categorizeBulkWriteRows( - this, - primaryPath as any, - documents, - fixed, - context - ); - ret.error = categorized.errors; - - /** - * Do inserts/updates - */ - const bulkInsertDocs = categorized.bulkInsertDocs; - for (let i = 0; i < bulkInsertDocs.length; ++i) { - const writeRow = bulkInsertDocs[i]!; - const docId = writeRow.document[primaryPath]; - await this.internals.bulkPut([writeRow.document], this.collectionName, this.schema) - ret.success[docId as any] = writeRow.document; + const documentKeys: string[] = documentWrites.map(writeRow => writeRow.document[this.primaryPath] as any) + const documents = await this.internals.getDocuments(documentKeys) + + const fixed = documentWrites.reduce>>((fixedDocs, currentWriteDoc) => { + const currentId = currentWriteDoc.document[this.primaryPath] as any + const previousDocument = currentWriteDoc.previous ?? this.internals.documents.get(currentId) + if (context === 'data-migrator-delete') { + if (previousDocument && previousDocument._rev === currentWriteDoc.document._rev) { + fixedDocs.push(currentWriteDoc) } - - const bulkUpdateDocs = categorized.bulkUpdateDocs; - for (let i = 0; i < bulkUpdateDocs.length; ++i) { - const writeRow = bulkUpdateDocs[i]!; - const docId = writeRow.document[primaryPath]; - await this.internals.bulkPut([writeRow.document], this.collectionName, this.schema) - ret.success[docId as any] = writeRow.document; - } - - if (categorized.eventBulk.events.length > 0) { - const lastState = ensureNotFalsy(categorized.newestRow).document; - categorized.eventBulk.checkpoint = { - id: lastState[primaryPath], - lwt: lastState._meta.lwt - }; - const endTime = now(); - categorized.eventBulk.events.forEach(event => (event as any).endTime = endTime); - this.changes$.next(categorized.eventBulk); - } - - return Promise.resolve(ret); - } - - async findDocumentsById(ids: string[], withDeleted: boolean): Promise> { - const docs: RxDocumentDataById = {}; - for (let docId of ids) { - const document = await this.internals.get(docId); - if (document) { - docs[docId] = document; - } - } - return docs - } - - async query(preparedQuery: LevelDBPreparedQuery): Promise> { - const { queryPlan, query } = preparedQuery; - const selector = query.selector; - const selectorKeys = Object.keys(selector); - const skip = query.skip ? query.skip : 0; - const limit = query.limit ? query.limit : Infinity; - const skipPlusLimit = skip + limit; - let queryMatcher: QueryMatcher> = getQueryMatcher( - this.schema, - query - ); - - - const queryPlanFields: string[] = queryPlan.index; - let indexes: string[] = [] - if (queryPlanFields.length === 1) { - indexes.push(fixTxPipe(queryPlanFields[0]!)) + } else { + if (previousDocument && previousDocument._rev !== currentWriteDoc.document._rev) { + currentWriteDoc.previous = previousDocument } else { - indexes.push(...queryPlanFields.map(field => fixTxPipe(field))) - + currentWriteDoc.previous = undefined } - - const shouldAddCompoundIndexes = this.schema.indexes?.find((index) => { - if (typeof index === "string") { - return indexes.find((index2) => index2 === index) - } else { - return index.find((subIndex) => subIndex === subIndex) - } - }); - - if (shouldAddCompoundIndexes) { - indexes.splice(0, indexes.length) - indexes.push(this.collectionName) - if (typeof shouldAddCompoundIndexes === "string") { - indexes.push(shouldAddCompoundIndexes) - } else { - indexes.push(...shouldAddCompoundIndexes) - } - } else { - indexes.unshift(this.collectionName) - } - - const indexName: string = `[${indexes.join('+')}]`; - const docsWithIndex = await this.internals.getIndex(indexName); - const documents: RxDocumentData[] = await this.internals.bulkGet(docsWithIndex); - let filteredDocuments = documents.filter((document) => { - if (selectorKeys.length <= 0) { - return true - } else { - return queryMatcher(document) - } - }) - - const sortComparator = getSortComparator(this.schema, preparedQuery.query); - filteredDocuments = filteredDocuments.sort(sortComparator); - - filteredDocuments = filteredDocuments.slice(skip, skipPlusLimit); - return { - documents: filteredDocuments - } - // let indexOfLower = (queryPlan.inclusiveStart ? boundGE : boundGT)( - // docsWithIndex, - // { - // indexString: lowerBoundString - // } as any, - // compareDocsWithIndex - // ); - // const indexOfUpper = (queryPlan.inclusiveEnd ? boundLE : boundLT)( - // docsWithIndex, - // { - // indexString: upperBoundString - // } as any, - // compareDocsWithIndex - // ); - - // let rows: RxDocumentData[] = []; - // let done = false; - // while (!done) { - // const currentRow = docsWithIndex[indexOfLower] as any; - // if ( - // !currentRow || - // indexOfLower > indexOfUpper - // ) { - // break; - // } - // const currentDoc = currentRow.doc; - - // if (!queryMatcher || queryMatcher(currentDoc)) { - // rows.push(currentDoc); - // } - - // if ( - // (rows.length >= skipPlusLimit && !mustManuallyResort) || - // indexOfLower >= docsWithIndex.length - // ) { - // done = true; - // } - - // indexOfLower++; - // } - - // if (mustManuallyResort) { - // const sortComparator = getSortComparator(this.schema, preparedQuery.query); - // rows = rows.sort(sortComparator); - // } - - // // apply skip and limit boundaries. - // rows = rows.slice(skip, skipPlusLimit); - // return Promise.resolve({ - // documents: rows - // }) - + fixedDocs.push(currentWriteDoc) + } + return fixedDocs + }, []) + + const categorized = categorizeBulkWriteRows( + this, + primaryPath as any, + documents, + fixed, + context + ) + ret.error = categorized.errors + + /** + * Do inserts/updates + */ + const bulkInsertDocs = categorized.bulkInsertDocs + for (let i = 0; i < bulkInsertDocs.length; ++i) { + const writeRow = bulkInsertDocs[i]! + const docId = writeRow.document[primaryPath] + await this.internals.bulkPut([writeRow.document], this.collectionName, this.schema) + ret.success[docId as any] = writeRow.document } - async count(preparedQuery: LevelDBPreparedQuery): Promise { - const result = await this.query(preparedQuery); - return { - count: result.documents.length, - mode: 'fast' - }; + const bulkUpdateDocs = categorized.bulkUpdateDocs + for (let i = 0; i < bulkUpdateDocs.length; ++i) { + const writeRow = bulkUpdateDocs[i]! + const docId = writeRow.document[primaryPath] + await this.internals.bulkPut([writeRow.document], this.collectionName, this.schema) + ret.success[docId as any] = writeRow.document } - /* istanbul ignore next */ - getAttachmentData(documentId: string, attachmentId: string, digest: string): Promise { - throw new Error("Method not implemented."); + if (categorized.eventBulk.events.length > 0) { + const lastState = ensureNotFalsy(categorized.newestRow).document + categorized.eventBulk.checkpoint = { + id: lastState[primaryPath], + lwt: lastState._meta.lwt + } + const endTime = now() + categorized.eventBulk.events.forEach(event => { + (event as any).endTime = endTime + }) + this.changes$.next(categorized.eventBulk) } - /* istanbul ignore next */ - getChangedDocumentsSince(limit: number, checkpoint?: RxStorageDefaultCheckpoint | undefined): Promise<{ documents: RxDocumentData[]; checkpoint: RxStorageDefaultCheckpoint; }> { - throw new Error("Method not implemented."); - } + return await Promise.resolve(ret) + } - /* istanbul ignore next */ - changeStream(): Observable, RxStorageDefaultCheckpoint>> { - return this.changes$.asObservable(); + async findDocumentsById (ids: string[]): Promise> { + const docs: RxDocumentDataById = {} + for (const docId of ids) { + const document = await this.internals.get(docId) + if (document) { + docs[docId] = document + } } - - async cleanup(): Promise { - await this.internals.clear() - return false; + return docs + } + + async query (preparedQuery: LevelDBPreparedQuery): Promise> { + const { queryPlan, query } = preparedQuery + const selector = query.selector + const selectorKeys = Object.keys(selector) + const skip = query.skip ? query.skip : 0 + const limit = query.limit ? query.limit : Infinity + const skipPlusLimit = skip + limit + const queryMatcher: QueryMatcher> = getQueryMatcher( + this.schema, + query + ) + + const queryPlanFields: string[] = queryPlan.index + const indexes: string[] = [] + if (queryPlanFields.length === 1) { + indexes.push(fixTxPipe(queryPlanFields[0]!)) + } else { + indexes.push(...queryPlanFields.map(field => fixTxPipe(field))) } - /* istanbul ignore next */ - async close(): Promise { - if (this.closed) { - return Promise.resolve() - } - - await this.internals.close() - this.changes$.complete(); - this.closed = true; - this.internals.refCount = this.internals.refCount - 1; + const shouldAddCompoundIndexes = this.schema.indexes?.find((index) => { + if (typeof index === 'string') { + return indexes.find((index2) => index2 === index) + } else { + return index.find((subIndex) => { + return subIndex === index.find((indexValue) => indexValue === subIndex) + }) + } + }) + + if (shouldAddCompoundIndexes) { + indexes.splice(0, indexes.length) + indexes.push(this.collectionName) + if (typeof shouldAddCompoundIndexes === 'string') { + indexes.push(shouldAddCompoundIndexes) + } else { + indexes.push(...shouldAddCompoundIndexes) + } + } else { + indexes.unshift(this.collectionName) } - /* istanbul ignore next */ - async remove(): Promise { - return Promise.resolve() + const indexName: string = `[${indexes.join('+')}]` + const docsWithIndex = await this.internals.getIndex(indexName) + const documents: Array> = await this.internals.bulkGet(docsWithIndex) + let filteredDocuments = documents.filter((document) => { + if (selectorKeys.length <= 0) { + return true + } else { + return queryMatcher(document) + } + }) + + const sortComparator = getSortComparator(this.schema, preparedQuery.query) + filteredDocuments = filteredDocuments.sort(sortComparator) + + filteredDocuments = filteredDocuments.slice(skip, skipPlusLimit) + return { + documents: filteredDocuments } - - conflictResultionTasks(): Observable> { - return this.conflictResultionTasks$.asObservable(); + // let indexOfLower = (queryPlan.inclusiveStart ? boundGE : boundGT)( + // docsWithIndex, + // { + // indexString: lowerBoundString + // } as any, + // compareDocsWithIndex + // ); + // const indexOfUpper = (queryPlan.inclusiveEnd ? boundLE : boundLT)( + // docsWithIndex, + // { + // indexString: upperBoundString + // } as any, + // compareDocsWithIndex + // ); + + // let rows: RxDocumentData[] = []; + // let done = false; + // while (!done) { + // const currentRow = docsWithIndex[indexOfLower] as any; + // if ( + // !currentRow || + // indexOfLower > indexOfUpper + // ) { + // break; + // } + // const currentDoc = currentRow.doc; + + // if (!queryMatcher || queryMatcher(currentDoc)) { + // rows.push(currentDoc); + // } + + // if ( + // (rows.length >= skipPlusLimit && !mustManuallyResort) || + // indexOfLower >= docsWithIndex.length + // ) { + // done = true; + // } + + // indexOfLower++; + // } + + // if (mustManuallyResort) { + // const sortComparator = getSortComparator(this.schema, preparedQuery.query); + // rows = rows.sort(sortComparator); + // } + + // // apply skip and limit boundaries. + // rows = rows.slice(skip, skipPlusLimit); + // return Promise.resolve({ + // documents: rows + // }) + } + + async count (preparedQuery: LevelDBPreparedQuery): Promise { + const result = await this.query(preparedQuery) + return { + count: result.documents.length, + mode: 'fast' } - - /* istanbul ignore next */ - resolveConflictResultionTask(taskSolution: RxConflictResultionTaskSolution): Promise { - return Promise.resolve() + } + + /* istanbul ignore next */ + async getAttachmentData (): Promise { + throw new Error('Method not implemented.') + } + + /* istanbul ignore next */ + async getChangedDocumentsSince (): Promise<{ documents: Array>, checkpoint: RxStorageDefaultCheckpoint }> { + throw new Error('Method not implemented.') + } + + /* istanbul ignore next */ + changeStream (): Observable, RxStorageDefaultCheckpoint>> { + return this.changes$.asObservable() + } + + async cleanup (): Promise { + await this.internals.clear() + return false + } + + /* istanbul ignore next */ + async close (): Promise { + if (this.closed) { + await Promise.resolve(); return } + await this.internals.close() + this.changes$.complete() + this.closed = true + this.internals.refCount = this.internals.refCount - 1 + } + + /* istanbul ignore next */ + async remove (): Promise { + await Promise.resolve() + } + + conflictResultionTasks (): Observable> { + return this.conflictResultionTasks$.asObservable() + } + + /* istanbul ignore next */ + async resolveConflictResultionTask (): Promise { + await Promise.resolve() + } } diff --git a/packages/leveldb/src/leveldb/internal.ts b/packages/leveldb/src/leveldb/internal.ts index 83a53f49..bd417e8d 100644 --- a/packages/leveldb/src/leveldb/internal.ts +++ b/packages/leveldb/src/leveldb/internal.ts @@ -1,240 +1,233 @@ -import { RxDocumentData, RxJsonSchema, getPrimaryFieldOfPrimaryKey } from "rxdb"; -import Level from 'level'; -import pull from 'pull-stream'; -import pullLevel from 'pull-level'; +import { type RxDocumentData, type RxJsonSchema, getPrimaryFieldOfPrimaryKey } from 'rxdb' +import Level from 'level' +import pull from 'pull-stream' +import pullLevel from 'pull-level' import { - LevelDBStorageInternals, - LevelDBInternalConstructor, - LevelDBType -} from "./types"; -import { getPrivateKeyValue, safeIndexList } from "@pluto-encrypted/shared"; + type LevelDBStorageInternals, + type LevelDBInternalConstructor, + type LevelDBType +} from './types' +import { getPrivateKeyValue, safeIndexList } from '@pluto-encrypted/shared' export class LevelDBInternal implements LevelDBStorageInternals { - - public removed = false; - public refCount: number; - private db: LevelDBType; - public documents: Map> - public schema: RxJsonSchema>; - - - static isLevelDBConstructor(_options: LevelDBInternalConstructor): _options is { - level: LevelDBType, - refCount: number, - schema: RxJsonSchema>; - documents?: Map> - } { - return "level" in _options && _options.level !== undefined + public removed = false + public refCount: number + private readonly db: LevelDBType + public documents: Map> + public schema: RxJsonSchema> + + static isLevelDBConstructor(_options: LevelDBInternalConstructor): _options is { + level: LevelDBType + refCount: number + schema: RxJsonSchema> + documents?: Map> + } { + return 'level' in _options && _options.level !== undefined + } + + constructor (private readonly _options: LevelDBInternalConstructor) { + this.refCount = this._options.refCount + this.schema = this._options.schema + this.documents = this._options.documents ?? new Map() + if (LevelDBInternal.isLevelDBConstructor(this._options)) { + this.db = this._options.level + } else { + this.db = Level(this._options.dbPath, { valueEncoding: 'json' }) } - - constructor(private _options: LevelDBInternalConstructor) { - this.refCount = this._options.refCount; - this.schema = this._options.schema; - this.documents = this._options.documents || new Map(); - if (LevelDBInternal.isLevelDBConstructor(this._options)) { - this.db = this._options.level - } else { - this.db = Level(this._options.dbPath, { valueEncoding: 'json' }) - } + } + + async getDocuments (query: string[]): Promise>> { + const docsInDbMap = new Map>() + if (query.length <= 0) { + const db = await this.getInstance() + + await pull( + pullLevel.read(db), + pull.filter(row => !Array.isArray(row.value)), + pull.map(row => { + docsInDbMap.set(row.key, row.value) + return row + }), + pull.collectAsPromise() + ) + + return docsInDbMap } - async getDocuments(query: string[]): Promise>> { - const docsInDbMap = new Map>(); - if (query.length <= 0) { - const db = await this.getInstance() - - await pull( - pullLevel.read(db), - pull.filter(row => !Array.isArray(row.value)), - pull.map(row => { - docsInDbMap.set(row.key, row.value) - return - }), - pull.collectAsPromise() - ) - - return docsInDbMap + const documents = await this.bulkGet(query) + const primaryPath = getPrimaryFieldOfPrimaryKey(this.schema.primaryKey) + documents.forEach((document) => { + docsInDbMap.set(document[primaryPath] as any, document) + this.documents.set(document[primaryPath] as any, document) + }) + return docsInDbMap + } + + async getInstance () { + await this.db.open() + return this.db + } + + async getIndex (key: string): Promise { + const db = await this.getInstance() + return db.get(key) + .then(result => result ? JSON.parse(result) : []) + .catch(err => { + /* istanbul ignore else -- @preserve */ + if (err.message.startsWith('Key not found in database')) { + return [] + } else { + throw err } + }) + } - const documents = await this.bulkGet(query) - const primaryPath = getPrimaryFieldOfPrimaryKey(this.schema.primaryKey); - documents.forEach((document) => { - docsInDbMap.set(document[primaryPath] as any, document) - this.documents.set(document[primaryPath] as any, document) - }) - return docsInDbMap - } - - async getInstance() { - await this.db.open() - return this.db + async bulkGet (keys: string[]): Promise>> { + if (!keys || keys.length <= 0) { + return [] } - async getIndex(key: string): Promise { - const db = await this.getInstance() - return db.get(key) - .then(result => result ? JSON.parse(result) : []) - .catch(err => { - /* istanbul ignore else -- @preserve */ - if (err.message.startsWith('Key not found in database')) { - return [] - } else { - throw err - } - }); - } - - async bulkGet(keys: string[]): Promise[]> { - if (!keys || keys.length <= 0) { - return [] + const db = await this.getInstance() + const keysSet = new Set(keys) + return pull( + pullLevel.read(db), + pull.filter(row => ( + keysSet.has(row.key) && + row.value !== undefined + )), + pull.map(row => JSON.parse(row.value)), + pull.collectAsPromise() + ) + } + + async get (key: string): Promise | null> { + const db = await this.getInstance() + return await db.get(key) + .then(result => result ? JSON.parse(result) : null) + .catch(err => { + /* istanbul ignore else -- @preserve */ + if (err.message.startsWith('Key not found in database')) { + return null + } else { + throw err } + }) + } - const db = await this.getInstance() - const keysSet = new Set(keys) - return pull( - pullLevel.read(db), - pull.filter(row => ( - keysSet.has(row.key) && - row.value !== undefined - )), - pull.map(row => JSON.parse(row.value)), - pull.collectAsPromise() - ) + async set (key: string, data: RxDocumentData) { + if (!key) { + throw new Error('Undefined key') } - - async get(key: string): Promise | null> { - const db = await this.getInstance() - return await db.get(key) - .then(result => result ? JSON.parse(result) : null) - .catch(err => { - /* istanbul ignore else -- @preserve */ - if (err.message.startsWith('Key not found in database')) { - return null - } else { - throw err - } - }) + if (!data) { + throw new Error('Undefined value') } - async set(key: string, data: RxDocumentData) { - if (!key) { - throw new Error("Undefined key") - } - if (!data) { - throw new Error("Undefined value") - } + const db = await this.getInstance() - const db = await this.getInstance() - - return new Promise((resolve, reject) => { - db.put(key, JSON.stringify(data), (err) => { - if (err) { - return reject(err); - } - return resolve() - }) - }) - } - - async setIndex(key: string, ids: string[]) { - if (!key) { - throw new Error("Undefined key") - } - if (!ids) { - throw new Error("Undefined value") + await new Promise((resolve, reject) => { + db.put(key, JSON.stringify(data), (err) => { + if (err) { + reject(err); return } - - const db = await this.getInstance() - return new Promise((resolve, reject) => { - db.put(key, JSON.stringify(ids), (err) => { - if (err) { - return reject(err); - } - return resolve() - }) - }) + resolve() + }) + }) + } + + async setIndex (key: string, ids: string[]) { + if (!key) { + throw new Error('Undefined key') } - - async delete(key: string) { - if (!key) { - throw new Error("Undefined key") - } - - const db = await this.getInstance() - return new Promise((resolve, reject) => { - db.del(key, (err) => { - if (err) { - return reject(err); - } - return resolve() - }) - }) + if (!ids) { + throw new Error('Undefined value') } - async updateIndex(key: string, id: string) { - if (!id) { - throw new Error("Undefined id") + const db = await this.getInstance() + await new Promise((resolve, reject) => { + db.put(key, JSON.stringify(ids), (err) => { + if (err) { + reject(err); return } - if (!key) { - throw new Error("Undefined key") - } - const existingIndex = await this.getIndex(key); - const newIndexes = Array.from(new Set([...existingIndex, id])); - await this.setIndex(key, newIndexes); + resolve() + }) + }) + } + + async delete (key: string) { + if (!key) { + throw new Error('Undefined key') } - async removeFromIndex(key: string, id: string) { - if (!id) { - throw new Error("Undefined id") - } - if (!key) { - throw new Error("Undefined key") + const db = await this.getInstance() + await new Promise((resolve, reject) => { + db.del(key, (err) => { + if (err) { + reject(err); return } - const existingIndex = await this.getIndex(key); - await this.setIndex(key, existingIndex.filter((vId) => vId !== id)); + resolve() + }) + }) + } + + async updateIndex (key: string, id: string) { + if (!id) { + throw new Error('Undefined id') } - - async clear() { - const db = await this.getInstance() - return await db.clear() + if (!key) { + throw new Error('Undefined key') } - - async close() { - return this.db.close() + const existingIndex = await this.getIndex(key) + const newIndexes = Array.from(new Set([...existingIndex, id])) + await this.setIndex(key, newIndexes) + } + + async removeFromIndex (key: string, id: string) { + if (!id) { + throw new Error('Undefined id') } - - - - async bulkPut(items: RxDocumentData[], collectionName: string, schema: Readonly>>) { - - const primaryKeyKey = typeof schema.primaryKey === "string" ? schema.primaryKey : schema.primaryKey.key; - const saferIndexList = safeIndexList(schema); - - for (let item of items) { - - const shouldDelete = item._deleted; - const id = getPrivateKeyValue(item, schema) - if (shouldDelete) { - for (let requiredIndexes of saferIndexList) { - const requiredIndex = `[${collectionName}+${requiredIndexes.join("+")}]` - await this.removeFromIndex(requiredIndex, id) - } - await this.removeFromIndex(`[${collectionName}+${primaryKeyKey}]`, id) - await this.removeFromIndex('[all]', id) - await this.delete(id) - this.documents.delete(id) - } else { - for (let requiredIndexes of saferIndexList) { - const requiredIndex = `[${collectionName}+${requiredIndexes.join("+")}]` - await this.updateIndex(requiredIndex, id) - } - await this.updateIndex(`[${collectionName}+${primaryKeyKey}]`, id) - await this.updateIndex('[all]', id) - await this.set(id, item); - this.documents.set(id, item) - } + if (!key) { + throw new Error('Undefined key') + } + const existingIndex = await this.getIndex(key) + await this.setIndex(key, existingIndex.filter((vId) => vId !== id)) + } + + async clear () { + const db = await this.getInstance() + return db.clear() + } + + async close () { + return this.db.close() + } + + async bulkPut (items: Array>, collectionName: string, schema: Readonly>>) { + const primaryKeyKey = typeof schema.primaryKey === 'string' ? schema.primaryKey : schema.primaryKey.key + const saferIndexList = safeIndexList(schema) + + for (const item of items) { + const shouldDelete = item._deleted + const id = getPrivateKeyValue(item, schema) + if (shouldDelete) { + for (const requiredIndexes of saferIndexList) { + const requiredIndex = `[${collectionName}+${requiredIndexes.join('+')}]` + await this.removeFromIndex(requiredIndex, id) } + await this.removeFromIndex(`[${collectionName}+${primaryKeyKey}]`, id) + await this.removeFromIndex('[all]', id) + await this.delete(id) + this.documents.delete(id) + } else { + for (const requiredIndexes of saferIndexList) { + const requiredIndex = `[${collectionName}+${requiredIndexes.join('+')}]` + await this.updateIndex(requiredIndex, id) + } + await this.updateIndex(`[${collectionName}+${primaryKeyKey}]`, id) + await this.updateIndex('[all]', id) + await this.set(id, item) + this.documents.set(id, item) + } } + } } - diff --git a/packages/leveldb/src/leveldb/types.ts b/packages/leveldb/src/leveldb/types.ts index 8065bdce..afc7b967 100644 --- a/packages/leveldb/src/leveldb/types.ts +++ b/packages/leveldb/src/leveldb/types.ts @@ -1,66 +1,64 @@ -import { Level } from "level"; -import { DefaultPreparedQuery, RxDocumentData, RxJsonSchema, RxStorage, } from "rxdb"; +import { type Level } from 'level' +import { type DefaultPreparedQuery, type RxDocumentData, type RxJsonSchema, type RxStorage } from 'rxdb' /** * Index of a table can be a string or a number */ -export type IndexType = string | number; +export type IndexType = string | number /** * LevelDB internal data structure is a Map with an index * and RxDocumentData from RXDB */ -export type LevelDBDataStructure = Map>; +export type LevelDBDataStructure = Map> /** * Data type for index keystorage * I used this to get faster searches based on what RXDB indexes we were * informed */ -export type LevelDBDataIndex = Map; +export type LevelDBDataIndex = Map /** * Query type for LevelDB */ export type LevelDBType = Level -export type LevelDBPreparedQuery = DefaultPreparedQuery; +export type LevelDBPreparedQuery = DefaultPreparedQuery export type LevelDBInternalConstructor = { - dbPath: string, - refCount: number, - schema: RxJsonSchema>; - documents?: Map> + dbPath: string + refCount: number + schema: RxJsonSchema> + documents?: Map> } | { - level: LevelDBType, - refCount: number, - schema: RxJsonSchema>; - documents?: Map> + level: LevelDBType + refCount: number + schema: RxJsonSchema> + documents?: Map> } /** * Main storage interface for LevelDBStorage */ -export type LevelDBStorageInternals = { - getDocuments(query: string[]): Promise>>; - documents: Map>; - removed: boolean; - refCount: number; - schema: RxJsonSchema>; - bulkPut( - items: any, - collectionName: string, - schema: Readonly>>): Promise; - close(): Promise; - clear(): Promise; - get(key: string): Promise | null>; - getIndex(key: string): Promise; - bulkGet(keys: string[]): Promise[]>; - set(key: string, data: RxDocumentData): Promise - setIndex(key: string, ids: string[]): Promise - updateIndex(key: string, id: string): Promise +export interface LevelDBStorageInternals { + getDocuments: (query: string[]) => Promise>> + documents: Map> + removed: boolean + refCount: number + schema: RxJsonSchema> + bulkPut: ( + items: any, + collectionName: string, + schema: Readonly>>) => Promise + close: () => Promise + clear: () => Promise + get: (key: string) => Promise | null> + getIndex: (key: string) => Promise + bulkGet: (keys: string[]) => Promise>> + set: (key: string, data: RxDocumentData) => Promise + setIndex: (key: string, ids: string[]) => Promise + updateIndex: (key: string, id: string) => Promise } export type RxStorageLevelDBType = RxStorage - export type LevelDBSettings = { - dbPath: string, + dbPath: string } | { - level: LevelDBType, + level: LevelDBType } - diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 8936c1a2..3e8886c6 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -3,239 +3,232 @@ * @module shared * @description Shared is used by other dependencies of pluto-encrypted to reduce code duplication. */ -import { MangoQuerySelector, RxDocumentData, BulkWriteRow, RxJsonSchema } from "rxdb"; -export type { RxDocumentMeta, PlainJsonValue, PropertyType, PlainSimpleJsonObject } from "rxdb/dist/types/types"; -export type { MangoQuerySelector, RxAttachmentDataBase, MangoQueryOperators, RxDocumentData, RxAttachmentData } from "rxdb"; - -export type DocWithIndexString = { - id: string; - doc: RxDocumentData; - indexString: string; -}; - -export function compareDocsWithIndex( - a: DocWithIndexString, - b: DocWithIndexString +import { type MangoQuerySelector, type RxDocumentData, type RxJsonSchema } from 'rxdb' +export type { RxDocumentMeta, PlainJsonValue, PropertyType, PlainSimpleJsonObject } from 'rxdb/dist/types/types' +export type { MangoQuerySelector, RxAttachmentDataBase, MangoQueryOperators, RxDocumentData, RxAttachmentData } from 'rxdb' + +export interface DocWithIndexString { + id: string + doc: RxDocumentData + indexString: string +} + +export function compareDocsWithIndex ( + a: DocWithIndexString, + b: DocWithIndexString ): 1 | 0 | -1 { - if (a.indexString < b.indexString) { - return -1; - } else if (a.indexString === b.indexString) { - return 0; - } else { - return 1; - } + if (a.indexString < b.indexString) { + return -1 + } else if (a.indexString === b.indexString) { + return 0 + } else { + return 1 + } } -export function conditionMatches(selector: MangoQuerySelector, key: string, document: RxDocumentData) { - if (key === "$and") { - const matchingSelector = selector[key]! - const matches = Object.values(matchingSelector).every((condition) => { - const [conditionKey] = Object.keys(condition); - const [conditionValue] = Object.values(condition); - if (conditionKey === "$or") { - return conditionValue!.some((orSelector) => { - - return conditionMatches(orSelector, "$or", document) - }) - } else if (conditionKey === "$and") { - return conditionValue!.every((orSelector) => conditionMatches(orSelector, "$or", document)) - } else if (document[conditionKey!] === conditionValue) { - return true; - } - - return false; +export function conditionMatches (selector: MangoQuerySelector, key: string, document: RxDocumentData) { + if (key === '$and') { + const matchingSelector = selector[key]! + const matches = Object.values(matchingSelector).every((condition) => { + const [conditionKey] = Object.keys(condition) + const [conditionValue] = Object.values(condition) + if (conditionKey === '$or') { + return conditionValue!.some((orSelector) => { + return conditionMatches(orSelector, '$or', document) }) - if (matches) { - return true + } else if (conditionKey === '$and') { + return conditionValue!.every((orSelector) => conditionMatches(orSelector, '$or', document)) + } else if (document[conditionKey!] === conditionValue) { + return true + } + + return false + }) + if (matches) { + return true + } + } else if (key === '$or') { + const matchingSelector = Object.keys(selector) + const atLeastOneMatching = matchingSelector.find((conditionKey) => { + if (conditionKey === '$or') { + const matchingOrKey = selector[conditionKey]?.find((orKey) => { + const [orKeyName] = Object.keys(orKey) + const [orKeyValue] = Object.values(orKey) + return document[orKeyName!] === orKeyValue + }) + if (matchingOrKey) { + return true } - } else if (key === "$or") { - const matchingSelector = Object.keys(selector) - const atLeastOneMatching = matchingSelector.find((conditionKey) => { - if (conditionKey === "$or") { - const matchingOrKey = selector[conditionKey]?.find((orKey) => { - const [orKeyName] = Object.keys(orKey) - const [orKeyValue] = Object.values(orKey) - return document[orKeyName!] === orKeyValue - }) - if (matchingOrKey) { - return true; - } - } else if (conditionKey === "$and") { - const matchingAndKey = selector[conditionKey]?.find((andKey) => { - const [andKeyName] = Object.keys(andKey) - const [andKeyValue] = Object.values(andKey) - return document[andKeyName!] === andKeyValue - }); - if (matchingAndKey) { - return true; - } - } else { - const conditionValue = selector[conditionKey] - if (document[conditionKey!] === conditionValue) { - return true; - } - } - return false; + } else if (conditionKey === '$and') { + const matchingAndKey = selector[conditionKey]?.find((andKey) => { + const [andKeyName] = Object.keys(andKey) + const [andKeyValue] = Object.values(andKey) + return document[andKeyName!] === andKeyValue }) - if (atLeastOneMatching) { - return true + if (matchingAndKey) { + return true } - } else { - const selectorValue = selector[key] - if (typeof selectorValue === "object") { - const selectorQueries = Object.keys(selectorValue) - const [value] = Object.values(selector[key]) - for (let selectorQuery of selectorQueries) { - if (selectorQuery === "$eq") { - if (document[key] === value) { - return true - - } - } - } + } else { + const conditionValue = selector[conditionKey] + if (document[conditionKey] === conditionValue) { + return true } + } + return false + }) + if (atLeastOneMatching) { + return true } - return false + } else { + const selectorValue = selector[key] + if (typeof selectorValue === 'object') { + const selectorQueries = Object.keys(selectorValue) + const [value] = Object.values(selector[key]) + for (const selectorQuery of selectorQueries) { + if (selectorQuery === '$eq') { + if (document[key] === value) { + return true + } + } + } + } + } + return false } +type Compare = ((a: T, b: T) => number | null | undefined) -type Compare = ((a: T, b: T) => number | null | undefined); - -function ge(a: T[], y: T, c: Compare, l?: any, h?: any): number { - let i: number = h + 1; - while (l <= h) { - const m = (l + h) >>> 1; - const x: any = a[m]; - const p: any = (c !== undefined) ? c(x, y) : (x - (y as any)); - if (p >= 0) { - i = m; h = m - 1; - } else { - l = m + 1; - } - } - return i; -} - -function gt(a: T[], y: T, c: Compare, l?: any, h?: any): number { - let i = h + 1; - while (l <= h) { - const m = (l + h) >>> 1; - const x = a[m]; - const p: any = (c !== undefined) ? c(x!, y) : ((x as any) - (y as any)); - if (p > 0) { - i = m; h = m - 1; - } else { - l = m + 1; - } +function ge (a: T[], y: T, c: Compare, l?: any, h?: any): number { + let i: number = h + 1 + while (l <= h) { + const m = (l + h) >>> 1 + const x: any = a[m] + const p: any = (c !== undefined) ? c(x, y) : (x - (y as any)) + if (p >= 0) { + i = m; h = m - 1 + } else { + l = m + 1 } - return i; -} - -function lt(a: T[], y: T, c: Compare, l?: any, h?: any): number { - let i = l - 1; - while (l <= h) { - const m = (l + h) >>> 1, x = a[m]; - const p: any = (c !== undefined) ? c(x!, y) : ((x as any) - (y as any)); - if (p < 0) { - i = m; l = m + 1; - } else { - h = m - 1; - } + } + return i +} + +function gt (a: T[], y: T, c: Compare, l?: any, h?: any): number { + let i = h + 1 + while (l <= h) { + const m = (l + h) >>> 1 + const x = a[m] + const p: any = (c !== undefined) ? c(x!, y) : ((x as any) - (y as any)) + if (p > 0) { + i = m; h = m - 1 + } else { + l = m + 1 } - return i; -} - -function le(a: T[], y: T, c: Compare, l?: any, h?: any): number { - let i = l - 1; - while (l <= h) { - const m = (l + h) >>> 1, x = a[m]; - const p: any = (c !== undefined) ? c(x!, y) : ((x as any) - (y as any)); - if (p <= 0) { - i = m; l = m + 1; - } else { - h = m - 1; - } + } + return i +} + +function lt (a: T[], y: T, c: Compare, l?: any, h?: any): number { + let i = l - 1 + while (l <= h) { + const m = (l + h) >>> 1; const x = a[m] + const p: any = (c !== undefined) ? c(x!, y) : ((x as any) - (y as any)) + if (p < 0) { + i = m; l = m + 1 + } else { + h = m - 1 } - return i; + } + return i } -function eq(a: T[], y: T, c: Compare, l?: any, h?: any): number { - while (l <= h) { - const m = (l + h) >>> 1, x = a[m]; - const p: any = (c !== undefined) ? c(x!, y) : ((x as any) - (y as any)); - if (p === 0) { - return m; - } - if (p <= 0) { - l = m + 1; - } else { - h = m - 1; - } +function le (a: T[], y: T, c: Compare, l?: any, h?: any): number { + let i = l - 1 + while (l <= h) { + const m = (l + h) >>> 1; const x = a[m] + const p: any = (c !== undefined) ? c(x!, y) : ((x as any) - (y as any)) + if (p <= 0) { + i = m; l = m + 1 + } else { + h = m - 1 } - return -1; + } + return i } -function norm(a: T[], y: T, c: Compare, l: any, h: any, f: any) { - if (typeof c === 'function') { - return f(a, y, c, (l === undefined) ? 0 : l | 0, (h === undefined) ? a.length - 1 : h | 0); +function eq (a: T[], y: T, c: Compare, l?: any, h?: any): number { + while (l <= h) { + const m = (l + h) >>> 1; const x = a[m] + const p: any = (c !== undefined) ? c(x!, y) : ((x as any) - (y as any)) + if (p === 0) { + return m } - return f(a, y, undefined, (c === undefined) ? 0 : c | 0, (l === undefined) ? a.length - 1 : l | 0); + if (p <= 0) { + l = m + 1 + } else { + h = m - 1 + } + } + return -1 } +function norm (a: T[], y: T, c: Compare, l: any, h: any, f: any) { + if (typeof c === 'function') { + return f(a, y, c, (l === undefined) ? 0 : l | 0, (h === undefined) ? a.length - 1 : h | 0) + } + return f(a, y, undefined, (c === undefined) ? 0 : c | 0, (l === undefined) ? a.length - 1 : l | 0) +} -export function boundGE(a: T[], y: T, c: Compare, l?: any, h?: any) { - return norm(a, y, c, l, h, ge); +export function boundGE (a: T[], y: T, c: Compare, l?: any, h?: any) { + return norm(a, y, c, l, h, ge) } -export function boundGT(a: T[], y: T, c: Compare, l?: any, h?: any) { - return norm(a, y, c, l, h, gt); +export function boundGT (a: T[], y: T, c: Compare, l?: any, h?: any) { + return norm(a, y, c, l, h, gt) } -export function boundLT(a: T[], y: T, c: Compare, l?: any, h?: any) { - return norm(a, y, c, l, h, lt); +export function boundLT (a: T[], y: T, c: Compare, l?: any, h?: any) { + return norm(a, y, c, l, h, lt) } -export function boundLE(a: T[], y: T, c: Compare, l?: any, h?: any) { - return norm(a, y, c, l, h, le); +export function boundLE (a: T[], y: T, c: Compare, l?: any, h?: any) { + return norm(a, y, c, l, h, le) } -export function boundEQ(a: T[], y: T, c: Compare, l?: any, h?: any) { - return norm(a, y, c, l, h, eq); +export function boundEQ (a: T[], y: T, c: Compare, l?: any, h?: any) { + return norm(a, y, c, l, h, eq) } -export function fixTxPipe(str: string): string { - const split = str.split('.'); - if (split.length > 1) { - return split.map(part => fixTxPipe(part)).join('.'); - } - - - return str; +export function fixTxPipe (str: string): string { + const split = str.split('.') + if (split.length > 1) { + return split.map(part => fixTxPipe(part)).join('.') + } + return str } +export function safeIndexList (schema: Readonly>>) { + const primaryKeyKey = typeof schema.primaryKey === 'string' ? schema.primaryKey : schema.primaryKey.key -export function safeIndexList(schema: Readonly>>) { - const primaryKeyKey = typeof schema.primaryKey === "string" ? schema.primaryKey : schema.primaryKey.key; - - const allIndexes: string[][] = []; - for (let requiredIndexes of (schema.indexes || [])) { - const currentIndexes: string[] = [] - if (typeof requiredIndexes === "string") { - currentIndexes.push(requiredIndexes) - } else { - currentIndexes.push(...requiredIndexes) - } - if (!currentIndexes.includes(primaryKeyKey)) { - currentIndexes.unshift(primaryKeyKey) - } - allIndexes.push(currentIndexes) + const allIndexes: string[][] = [] + for (const requiredIndexes of (schema.indexes ?? [])) { + const currentIndexes: string[] = [] + if (typeof requiredIndexes === 'string') { + currentIndexes.push(requiredIndexes) + } else { + currentIndexes.push(...requiredIndexes) + } + if (!currentIndexes.includes(primaryKeyKey)) { + currentIndexes.unshift(primaryKeyKey) } + allIndexes.push(currentIndexes) + } - return allIndexes + return allIndexes } -export function getPrivateKeyValue(document: RxDocumentData, schema: Readonly>>) { - const primaryKeyKey = typeof schema.primaryKey === "string" ? schema.primaryKey : schema.primaryKey.key; - if (!primaryKeyKey) { - throw new Error("Data must have a primaryKey defined of type string or number") - } - const id = document[primaryKeyKey] as string; - return id -} \ No newline at end of file +export function getPrivateKeyValue (document: RxDocumentData, schema: Readonly>>) { + const primaryKeyKey = typeof schema.primaryKey === 'string' ? schema.primaryKey : schema.primaryKey.key + if (!primaryKeyKey) { + throw new Error('Data must have a primaryKey defined of type string or number') + } + const id = document[primaryKeyKey] as string + return id +} diff --git a/packages/test-suite/src/helper/humans-collection.ts b/packages/test-suite/src/helper/humans-collection.ts index 16f46fc9..a472a97d 100644 --- a/packages/test-suite/src/helper/humans-collection.ts +++ b/packages/test-suite/src/helper/humans-collection.ts @@ -1,492 +1,485 @@ -import clone from 'clone'; -import * as schemas from './schemas'; -import * as schemaObjects from './schema-objects'; +import clone from 'clone' +import * as schemas from './schemas' +import * as schemaObjects from './schema-objects' +import { type HumanDocumentType } from './schemas' +import { type RxStorage, type RxCollection, createRxDatabase, randomCouchString, type RxJsonSchema, type RxDatabase, type MigrationStrategies, type RxAttachmentCreator } from 'rxdb' -import { HumanDocumentType } from './schemas'; -import { config } from 'process'; -import { RxStorage, RxCollection, createRxDatabase, randomCouchString, RxJsonSchema, RxDatabase, MigrationStrategies, RxAttachmentCreator } from 'rxdb'; - -export async function create( - size: number = 20, - collectionName: string = 'human', - multiInstance: boolean = true, - eventReduce: boolean = true, - storage: RxStorage +export async function create ( + size: number = 20, + collectionName: string = 'human', + multiInstance: boolean = true, + eventReduce: boolean = true, + storage: RxStorage ): Promise> { - const db = await createRxDatabase<{ human: RxCollection; }>({ - name: randomCouchString(10), - storage, - multiInstance, - eventReduce, - ignoreDuplicate: true, - localDocuments: true - }); - - const collections = await db.addCollections({ - [collectionName]: { - schema: schemas.human, - localDocuments: true - } - }); - - if (!collections[collectionName]) { - throw new Error("Collection does not exist") + const db = await createRxDatabase<{ human: RxCollection }>({ + name: randomCouchString(10), + storage, + multiInstance, + eventReduce, + ignoreDuplicate: true, + localDocuments: true + }) + + const collections = await db.addCollections({ + [collectionName]: { + schema: schemas.human, + localDocuments: true } + }) - // insert data - if (size > 0) { - const docsData = new Array(size) - .fill(0) - .map(() => schemaObjects.human()); + if (!collections[collectionName]) { + throw new Error('Collection does not exist') + } - await collections[collectionName]!.bulkInsert(docsData); - } - return collections[collectionName]!; + // insert data + if (size > 0) { + const docsData = new Array(size) + .fill(0) + .map(() => schemaObjects.human()) + + await collections[collectionName]!.bulkInsert(docsData) + } + return collections[collectionName]! } -export async function createBySchema( - schema: RxJsonSchema, - name = 'human', - storage: RxStorage +export async function createBySchema ( + schema: RxJsonSchema, + name = 'human', + storage: RxStorage ): Promise> { - const db = await createRxDatabase<{ [prop: string]: RxCollection; }>({ - name: randomCouchString(10), - storage, - multiInstance: true, - eventReduce: true, - ignoreDuplicate: true - }); - - const collections = await db.addCollections({ - [name]: { - schema - } - }); - - if (!collections[name]) { - throw new Error("Collection does not exist") + const db = await createRxDatabase>>({ + name: randomCouchString(10), + storage, + multiInstance: true, + eventReduce: true, + ignoreDuplicate: true + }) + + const collections = await db.addCollections({ + [name]: { + schema } + }) + + if (!collections[name]) { + throw new Error('Collection does not exist') + } - return collections[name]!; + return collections[name]! } -export async function createAttachments( - size = 20, - name = 'human', - multiInstance = true, - storage: RxStorage +export async function createAttachments ( + size = 20, + name = 'human', + multiInstance = true, + storage: RxStorage ): Promise> { - if (!name) { - name = 'human'; + if (!name) { + name = 'human' + } + const db = await createRxDatabase>>({ + name: randomCouchString(10), + storage, + multiInstance, + eventReduce: true, + ignoreDuplicate: true + }) + + const schemaJson = clone(schemas.human) + schemaJson.attachments = {} + + const collections = await db.addCollections({ + [name]: { + schema: schemaJson } - const db = await createRxDatabase<{ [prop: string]: RxCollection; }>({ - name: randomCouchString(10), - storage, - multiInstance, - eventReduce: true, - ignoreDuplicate: true - }); - - const schemaJson = clone(schemas.human); - schemaJson.attachments = {}; - - const collections = await db.addCollections({ - [name]: { - schema: schemaJson - } - }); + }) - if (!collections[name]) { - throw new Error("Collection does not exist") - } + if (!collections[name]) { + throw new Error('Collection does not exist') + } - // insert data - if (size > 0) { - const docsData = new Array(size) - .fill(0) - .map(() => schemaObjects.human()); + // insert data + if (size > 0) { + const docsData = new Array(size) + .fill(0) + .map(() => schemaObjects.human()) - await collections[name]!.bulkInsert(docsData); - } + await collections[name]!.bulkInsert(docsData) + } - return collections[name]!; + return collections[name]! } -export async function createNoCompression( - size = 20, - name = 'human', - storage: RxStorage +export async function createNoCompression ( + size = 20, + name = 'human', + storage: RxStorage ): Promise> { - const db = await createRxDatabase<{ [prop: string]: RxCollection; }>({ - name: randomCouchString(10), - storage, - eventReduce: true, - ignoreDuplicate: true - }); - const schemaJSON = clone(schemas.human); - schemaJSON.keyCompression = false; - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - [name]: { - schema: schemaJSON - } - }); - - if (!collections[name]) { - throw new Error("Collection does not exist") + const db = await createRxDatabase>>({ + name: randomCouchString(10), + storage, + eventReduce: true, + ignoreDuplicate: true + }) + const schemaJSON = clone(schemas.human) + schemaJSON.keyCompression = false + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + [name]: { + schema: schemaJSON } + }) - // insert data - if (size > 0) { - const docsData = new Array(size) - .fill(0) - .map(() => schemaObjects.human()); - await collections[name]!.bulkInsert(docsData); - } + if (!collections[name]) { + throw new Error('Collection does not exist') + } + + // insert data + if (size > 0) { + const docsData = new Array(size) + .fill(0) + .map(() => schemaObjects.human()) + await collections[name]!.bulkInsert(docsData) + } - return collections[name]!; + return collections[name]! } -export async function createAgeIndex( - amount = 20, - storage: RxStorage +export async function createAgeIndex ( + amount = 20, + storage: RxStorage ): Promise> { - const db = await createRxDatabase<{ humana: RxCollection; }>({ - name: randomCouchString(10), - storage, - eventReduce: true, - ignoreDuplicate: true - }); - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - humana: { - schema: schemas.humanAgeIndex - } - }); - - // insert data - if (amount > 0) { - const docsData = new Array(amount) - .fill(0) - .map(() => schemaObjects.human()); - await collections.humana.bulkInsert(docsData); + const db = await createRxDatabase<{ humana: RxCollection }>({ + name: randomCouchString(10), + storage, + eventReduce: true, + ignoreDuplicate: true + }) + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + humana: { + schema: schemas.humanAgeIndex } + }) + + // insert data + if (amount > 0) { + const docsData = new Array(amount) + .fill(0) + .map(() => schemaObjects.human()) + await collections.humana.bulkInsert(docsData) + } - return collections.humana; + return collections.humana } -export async function multipleOnSameDB( - size = 10, - storage: RxStorage +export async function multipleOnSameDB ( + size = 10, + storage: RxStorage ): Promise<{ db: RxDatabase<{ - human: RxCollection; - human2: RxCollection; - }>; - collection: RxCollection; - collection2: RxCollection; -}> { - const db = await createRxDatabase<{ - human: RxCollection; - human2: RxCollection; - }>({ - name: randomCouchString(10), - storage, - eventReduce: true, - ignoreDuplicate: true - }); - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - human: { - schema: schemas.human - }, - human2: { - schema: schemas.human - } - }); - - // insert data - if (size > 0) { - const docsData = new Array(size) - .fill(0) - .map(() => schemaObjects.human()); - await collections.human.bulkInsert(docsData); - - const docsData2 = new Array(size) - .fill(0) - .map(() => schemaObjects.human()); - await collections.human2.bulkInsert(docsData2); + human: RxCollection + human2: RxCollection + }> + collection: RxCollection + collection2: RxCollection + }> { + const db = await createRxDatabase<{ + human: RxCollection + human2: RxCollection + }>({ + name: randomCouchString(10), + storage, + eventReduce: true, + ignoreDuplicate: true + }) + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + human: { + schema: schemas.human + }, + human2: { + schema: schemas.human } - - return { - db, - collection: collections.human, - collection2: collections.human2 - }; + }) + + // insert data + if (size > 0) { + const docsData = new Array(size) + .fill(0) + .map(() => schemaObjects.human()) + await collections.human.bulkInsert(docsData) + + const docsData2 = new Array(size) + .fill(0) + .map(() => schemaObjects.human()) + await collections.human2.bulkInsert(docsData2) + } + + return { + db, + collection: collections.human, + collection2: collections.human2 + } } -export async function createNested( - amount = 5, - storage: RxStorage +export async function createNested ( + amount = 5, + storage: RxStorage ): Promise> { - const db = await createRxDatabase<{ nestedhuman: RxCollection; }>({ - name: randomCouchString(10), - storage, - eventReduce: true, - ignoreDuplicate: true - }); - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - nestedhuman: { - schema: schemas.nestedHuman - } - }); - - // insert data - if (amount > 0) { - const docsData = new Array(amount) - .fill(0) - .map(() => schemaObjects.nestedHuman()); - await collections.nestedhuman.bulkInsert(docsData); + const db = await createRxDatabase<{ nestedhuman: RxCollection }>({ + name: randomCouchString(10), + storage, + eventReduce: true, + ignoreDuplicate: true + }) + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + nestedhuman: { + schema: schemas.nestedHuman } + }) - return collections.nestedhuman; + // insert data + if (amount > 0) { + const docsData = new Array(amount) + .fill(0) + .map(() => schemaObjects.nestedHuman()) + await collections.nestedhuman.bulkInsert(docsData) + } + + return collections.nestedhuman } -export async function createDeepNested( - amount = 5, - storage: RxStorage +export async function createDeepNested ( + amount = 5, + storage: RxStorage ): Promise> { - const db = await createRxDatabase<{ nestedhuman: RxCollection; }>({ - name: randomCouchString(10), - storage, - eventReduce: true, - }); - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - nestedhuman: { - schema: schemas.deepNestedHuman - } - }); - - // insert data - if (amount > 0) { - const docsData = new Array(amount) - .fill(0) - .map(() => schemaObjects.deepNestedHuman()); - await collections.nestedhuman.bulkInsert(docsData); + const db = await createRxDatabase<{ nestedhuman: RxCollection }>({ + name: randomCouchString(10), + storage, + eventReduce: true + }) + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + nestedhuman: { + schema: schemas.deepNestedHuman } + }) + + // insert data + if (amount > 0) { + const docsData = new Array(amount) + .fill(0) + .map(() => schemaObjects.deepNestedHuman()) + await collections.nestedhuman.bulkInsert(docsData) + } - return collections.nestedhuman; + return collections.nestedhuman } -export async function createPrimary( - amount = 10, - name = randomCouchString(10), - storage: RxStorage +export async function createPrimary ( + amount = 10, + name = randomCouchString(10), + storage: RxStorage ): Promise> { - - const db = await createRxDatabase<{ human: RxCollection; }>({ - name, - storage, - multiInstance: true, - eventReduce: true, - ignoreDuplicate: true - }); - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - human: { - schema: schemas.primaryHuman - } - }); - - // insert data - if (amount > 0) { - const docsData = new Array(amount) - .fill(0) - .map(() => schemaObjects.simpleHuman()); - await collections.human.bulkInsert(docsData); + const db = await createRxDatabase<{ human: RxCollection }>({ + name, + storage, + multiInstance: true, + eventReduce: true, + ignoreDuplicate: true + }) + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + human: { + schema: schemas.primaryHuman } + }) - return collections.human; + // insert data + if (amount > 0) { + const docsData = new Array(amount) + .fill(0) + .map(() => schemaObjects.simpleHuman()) + await collections.human.bulkInsert(docsData) + } + + return collections.human } -export async function createHumanWithTimestamp( - amount = 0, - databaseName = randomCouchString(10), - multiInstance = true, - storage: RxStorage +export async function createHumanWithTimestamp ( + amount = 0, + databaseName = randomCouchString(10), + multiInstance = true, + storage: RxStorage ): Promise> { - - const db = await createRxDatabase<{ humans: RxCollection; }>({ - name: databaseName, - storage, - multiInstance, - eventReduce: true, - ignoreDuplicate: true - }); - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - humans: { - schema: schemas.humanWithTimestamp - } - }); - - // insert data - if (amount > 0) { - const docsData = new Array(amount) - .fill(0) - .map(() => schemaObjects.humanWithTimestamp()); - await collections.humans.bulkInsert(docsData); + const db = await createRxDatabase<{ humans: RxCollection }>({ + name: databaseName, + storage, + multiInstance, + eventReduce: true, + ignoreDuplicate: true + }) + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + humans: { + schema: schemas.humanWithTimestamp } + }) - return collections.humans; + // insert data + if (amount > 0) { + const docsData = new Array(amount) + .fill(0) + .map(() => schemaObjects.humanWithTimestamp()) + await collections.humans.bulkInsert(docsData) + } + + return collections.humans } -export async function createMigrationCollection( - storage: RxStorage, - amount = 0, - addMigrationStrategies: MigrationStrategies = {}, - name = randomCouchString(10), - autoMigrate = false, - attachment?: RxAttachmentCreator, +export async function createMigrationCollection ( + storage: RxStorage, + amount = 0, + addMigrationStrategies: MigrationStrategies = {}, + name = randomCouchString(10), + autoMigrate = false, + attachment?: RxAttachmentCreator ): Promise> { - - const migrationStrategies: any = Object.assign( - { - 1: (doc: any) => doc, - 2: (doc: any) => doc, - 3: (doc: any) => doc - }, - addMigrationStrategies - ); - - - const colName = 'human'; - const db = await createRxDatabase<{ human: RxCollection; }>({ - name, - storage, - eventReduce: true, - ignoreDuplicate: true - }); - const cols = await db.addCollections({ - [colName]: { - schema: attachment !== undefined ? { ...schemas.simpleHuman, attachments: {} } : schemas.simpleHuman, - autoMigrate: false - } - }); - - await Promise.all( - new Array(amount) - .fill(0) - .map(() => cols[colName].insert(schemaObjects.simpleHumanAge()).then(doc => { - if (attachment !== undefined) { - return doc.putAttachment(attachment, true); - } - })) - ); - await db.destroy(); - - const db2 = await createRxDatabase<{ human: RxCollection; }>({ - name, - storage, - eventReduce: true, - ignoreDuplicate: true - }); - const cols2 = await db2.addCollections({ - [colName]: { - schema: attachment !== undefined ? { ...schemas.simpleHumanV3, attachments: {} } : schemas.simpleHumanV3, - autoMigrate, - migrationStrategies + const migrationStrategies: any = Object.assign( + { + 1: (doc: any) => doc, + 2: (doc: any) => doc, + 3: (doc: any) => doc + }, + addMigrationStrategies + ) + + const colName = 'human' + const db = await createRxDatabase<{ human: RxCollection }>({ + name, + storage, + eventReduce: true, + ignoreDuplicate: true + }) + const cols = await db.addCollections({ + [colName]: { + schema: attachment !== undefined ? { ...schemas.simpleHuman, attachments: {} } : schemas.simpleHuman, + autoMigrate: false + } + }) + + await Promise.all( + new Array(amount) + .fill(0) + .map(async () => await cols[colName].insert(schemaObjects.simpleHumanAge()).then(doc => { + if (attachment !== undefined) { + return doc.putAttachment(attachment, true) } - }); + })) + ) + await db.destroy() + + const db2 = await createRxDatabase<{ human: RxCollection }>({ + name, + storage, + eventReduce: true, + ignoreDuplicate: true + }) + const cols2 = await db2.addCollections({ + [colName]: { + schema: attachment !== undefined ? { ...schemas.simpleHumanV3, attachments: {} } : schemas.simpleHumanV3, + autoMigrate, + migrationStrategies + } + }) - return cols2[colName]; + return cols2[colName] } -export async function createRelated( - storage: RxStorage, - name = randomCouchString(10) +export async function createRelated ( + storage: RxStorage, + name = randomCouchString(10) ): Promise> { - const db = await createRxDatabase<{ human: RxCollection; }>({ - name, - storage, - multiInstance: true, - eventReduce: true, - ignoreDuplicate: true - }); - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - human: { - schema: schemas.refHuman - } - }); + const db = await createRxDatabase<{ human: RxCollection }>({ + name, + storage, + multiInstance: true, + eventReduce: true, + ignoreDuplicate: true + }) + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + human: { + schema: schemas.refHuman + } + }) - const doc1 = schemaObjects.refHuman(); - const doc2 = schemaObjects.refHuman(doc1.name); - doc1.bestFriend = doc2.name; // cross-relation + const doc1 = schemaObjects.refHuman() + const doc2 = schemaObjects.refHuman(doc1.name) + doc1.bestFriend = doc2.name // cross-relation - await collections.human.insert(doc1); - await collections.human.insert(doc2); + await collections.human.insert(doc1) + await collections.human.insert(doc2) - return collections.human; + return collections.human } -export async function createRelatedNested( - storage: RxStorage, - name = randomCouchString(10) +export async function createRelatedNested ( + storage: RxStorage, + name = randomCouchString(10) ): Promise> { + const db = await createRxDatabase<{ human: RxCollection }>({ + name, + storage, + multiInstance: true, + eventReduce: true, + ignoreDuplicate: true + }) + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + human: { + schema: schemas.refHumanNested + } + }) - const db = await createRxDatabase<{ human: RxCollection; }>({ - name, - storage, - multiInstance: true, - eventReduce: true, - ignoreDuplicate: true - }); - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - human: { - schema: schemas.refHumanNested - } - }); - - const doc1 = schemaObjects.refHumanNested(); - const doc2 = schemaObjects.refHumanNested(doc1.name); - doc1.foo.bestFriend = doc2.name; // cross-relation + const doc1 = schemaObjects.refHumanNested() + const doc2 = schemaObjects.refHumanNested(doc1.name) + doc1.foo.bestFriend = doc2.name // cross-relation - await collections.human.insert(doc1); - await collections.human.insert(doc2); + await collections.human.insert(doc1) + await collections.human.insert(doc2) - return collections.human; + return collections.human } -export async function createIdAndAgeIndex( - storage: RxStorage, - amount = 20 +export async function createIdAndAgeIndex ( + storage: RxStorage, + amount = 20 ): Promise> { - const db = await createRxDatabase<{ humana: RxCollection; }>({ - name: randomCouchString(10), - storage, - eventReduce: true, - ignoreDuplicate: true - }); - // setTimeout(() => db.destroy(), dbLifetime); - const collections = await db.addCollections({ - humana: { - schema: schemas.humanIdAndAgeIndex - } - }); - - // insert data - if (amount > 0) { - const docsData = new Array(amount) - .fill(0) - .map(() => schemaObjects.humanWithIdAndAgeIndexDocumentType()); - await collections.humana.bulkInsert(docsData); + const db = await createRxDatabase<{ humana: RxCollection }>({ + name: randomCouchString(10), + storage, + eventReduce: true, + ignoreDuplicate: true + }) + // setTimeout(() => db.destroy(), dbLifetime); + const collections = await db.addCollections({ + humana: { + schema: schemas.humanIdAndAgeIndex } + }) + + // insert data + if (amount > 0) { + const docsData = new Array(amount) + .fill(0) + .map(() => schemaObjects.humanWithIdAndAgeIndexDocumentType()) + await collections.humana.bulkInsert(docsData) + } - return collections.humana; + return collections.humana } diff --git a/packages/test-suite/src/helper/index.ts b/packages/test-suite/src/helper/index.ts index 84152735..d0cc391d 100644 --- a/packages/test-suite/src/helper/index.ts +++ b/packages/test-suite/src/helper/index.ts @@ -1,171 +1,170 @@ -import { clone, randomString } from "async-test-util"; -import { MangoQuery, RxDocumentData, RxJsonSchema, RxStorage, RxStorageInstance, createRxDatabase, deepFreeze, ensureNotFalsy, fillWithDefaultSettings, getPrimaryFieldOfPrimaryKey, getQueryMatcher, getQueryPlan, getSortComparator, lastOfArray, newRxError, normalizeMangoQuery, now, randomCouchString } from "rxdb"; -import { describe, it, beforeEach, afterEach } from 'vitest'; -export type RandomDoc = { - id: string; - equal: string; - random: string; - increment: number; -}; -export type TestSuite = { - describe: typeof describe, - it: typeof it, - beforeEach: typeof beforeEach, - afterEach: typeof afterEach +import { clone, randomString } from 'async-test-util' +import { type MangoQuery, type RxDocumentData, type RxJsonSchema, type RxStorage, type RxStorageInstance, createRxDatabase, deepFreeze, ensureNotFalsy, fillWithDefaultSettings, getPrimaryFieldOfPrimaryKey, getQueryMatcher, getQueryPlan, getSortComparator, lastOfArray, newRxError, normalizeMangoQuery, now, randomCouchString } from 'rxdb' +import { type describe, type it, type beforeEach, type afterEach } from 'vitest' +export interface RandomDoc { + id: string + equal: string + random: string + increment: number } -export type NestedDoc = { - id: string; - nes: { - ted: string; - }; -}; -export type TestDocType = { key: string; value: string; }; -export type OptionalValueTestDoc = { key: string; value?: string; }; +export interface TestSuite { + describe: typeof describe + it: typeof it + beforeEach: typeof beforeEach + afterEach: typeof afterEach +} +export interface NestedDoc { + id: string + nes: { + ted: string + } +} +export interface TestDocType { key: string, value: string } +export interface OptionalValueTestDoc { key: string, value?: string } /** * Some storages had problems with umlauts and other special chars. * So we add these to all test strings. * TODO add emojis */ -export const TEST_DATA_CHARSET = '0987654321ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzäöüÖÄßÜ[]{}\''; -export const TEST_DATA_CHARSET_LAST_SORTED = ensureNotFalsy(lastOfArray(TEST_DATA_CHARSET.split('').sort())); +export const TEST_DATA_CHARSET = '0987654321ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzäöüÖÄßÜ[]{}\'' +export const TEST_DATA_CHARSET_LAST_SORTED = ensureNotFalsy(lastOfArray(TEST_DATA_CHARSET.split('').sort())) // const someEmojis = '😊💩👵🍌'; -export function randomStringWithSpecialChars(length: number) { - return randomString(length, TEST_DATA_CHARSET); +export function randomStringWithSpecialChars (length: number) { + return randomString(length, TEST_DATA_CHARSET) } /** * @returns a format of the query that can be used with the storage * when calling RxStorageInstance().query() */ -export function prepareQuery(schema, mutateableQuery) { - if (!mutateableQuery.sort) { - throw newRxError('SNH', { - query: mutateableQuery - }); - } +export function prepareQuery (schema, mutateableQuery) { + if (!mutateableQuery.sort) { + throw newRxError('SNH', { + query: mutateableQuery + }) + } - /** + /** * Store the query plan together with the * prepared query to save performance. */ - var queryPlan = getQueryPlan(schema, mutateableQuery); - return { - query: mutateableQuery, - queryPlan - }; + const queryPlan = getQueryPlan(schema, mutateableQuery) + return { + query: mutateableQuery, + queryPlan + } } -export function getNestedDocSchema() { - const schema: RxJsonSchema> = fillWithDefaultSettings({ - version: 0, - primaryKey: 'id', +export function getNestedDocSchema () { + const schema: RxJsonSchema> = fillWithDefaultSettings({ + version: 0, + primaryKey: 'id', + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 100 + }, + nes: { type: 'object', properties: { - id: { - type: 'string', - maxLength: 100 - }, - nes: { - type: 'object', - properties: { - ted: { - type: 'string', - maxLength: 100 - } - }, - required: [ - 'ted' - ] - } + ted: { + type: 'string', + maxLength: 100 + } }, - indexes: [ - ['nes.ted', 'id'] - ], required: [ - 'id', - 'nes' + 'ted' ] - }); - return schema; + } + }, + indexes: [ + ['nes.ted', 'id'] + ], + required: [ + 'id', + 'nes' + ] + }) + return schema } -export function getWriteData( - ownParams: Partial> = {} +export function getWriteData ( + ownParams: Partial> = {} ): RxDocumentData { - return Object.assign( - { - key: randomString(10), - value: 'barfoo', - _deleted: false, - _attachments: {}, - _meta: { - lwt: now() - }, - _rev: EXAMPLE_REVISION_1 - }, - ownParams - ); + return Object.assign( + { + key: randomString(10), + value: 'barfoo', + _deleted: false, + _attachments: {}, + _meta: { + lwt: now() + }, + _rev: EXAMPLE_REVISION_1 + }, + ownParams + ) } -export function getTestDataSchema(): RxJsonSchema> { - return fillWithDefaultSettings({ - version: 0, - type: 'object', - primaryKey: 'key', - properties: { - key: { - type: 'string', - maxLength: 100 - }, - value: { - type: 'string', - maxLength: 100 - } - }, - required: [ - 'key', - 'value' - ], - indexes: [ - 'value' - ] - }); +export function getTestDataSchema (): RxJsonSchema> { + return fillWithDefaultSettings({ + version: 0, + type: 'object', + primaryKey: 'key', + properties: { + key: { + type: 'string', + maxLength: 100 + }, + value: { + type: 'string', + maxLength: 100 + } + }, + required: [ + 'key', + 'value' + ], + indexes: [ + 'value' + ] + }) } -export const EXAMPLE_REVISION_1 = '1-12080c42d471e3d2625e49dcca3b8e1a'; -export const EXAMPLE_REVISION_2 = '2-22080c42d471e3d2625e49dcca3b8e2b'; -export const EXAMPLE_REVISION_3 = '3-32080c42d471e3d2625e49dcca3b8e3c'; -export const EXAMPLE_REVISION_4 = '4-42080c42d471e3d2625e49dcca3b8e3c'; -export const testContext = 'rx-storage-implementations.test.ts'; -export const testQueryContext = 'rx-storage-query-correctness.test.ts'; - - -export type RxTestStorage = { - // TODO remove name here, it can be read out already via getStorage().name - readonly name: string; - readonly getStorage: (encrypted?: boolean) => RxStorage; - /** +export const EXAMPLE_REVISION_1 = '1-12080c42d471e3d2625e49dcca3b8e1a' +export const EXAMPLE_REVISION_2 = '2-22080c42d471e3d2625e49dcca3b8e2b' +export const EXAMPLE_REVISION_3 = '3-32080c42d471e3d2625e49dcca3b8e3c' +export const EXAMPLE_REVISION_4 = '4-42080c42d471e3d2625e49dcca3b8e3c' +export const testContext = 'rx-storage-implementations.test.ts' +export const testQueryContext = 'rx-storage-query-correctness.test.ts' + +export interface RxTestStorage { + // TODO remove name here, it can be read out already via getStorage().name + readonly name: string + readonly getStorage: (encrypted?: boolean) => RxStorage + /** * Returns a storage that is used in performance tests. * For example in a browser it should return the storage with an IndexedDB based adapter, * while in node.js it must use the filesystem. */ - readonly getPerformanceStorage: (encrypted?: boolean) => { - storage: RxStorage; - /** + readonly getPerformanceStorage: (encrypted?: boolean) => { + storage: RxStorage + /** * A description that describes the storage and setting. * For example 'dexie-native'. */ - description: string; - }; - /** + description: string + } + /** * True if the storage is able to * keep data after an instance is closed and opened again. */ - readonly hasPersistence: boolean; - readonly hasMultiInstance: boolean; - readonly hasAttachments: boolean; - readonly hasBooleanIndexSupport: boolean; - /** + readonly hasPersistence: boolean + readonly hasMultiInstance: boolean + readonly hasAttachments: boolean + readonly hasBooleanIndexSupport: boolean + /** * To make it possible to test alternative encryption plugins, * you can specify hasEncryption to signal * the test runner that the given storage already contains an @@ -175,188 +174,176 @@ export type RxTestStorage = { * hasEncryption must contain a function that is able * to create a new password. */ - readonly hasEncryption?: () => Promise; -}; - - + readonly hasEncryption?: () => Promise +} -export type TestCorrectQueriesInput = { - notRunIfTrue?: () => boolean; - testTitle: string; - schema: RxJsonSchema; - data: RxDocType[]; - queries: ({ - info: string; - query: MangoQuery; - expectedResultDocIds: string[]; - /** +export interface TestCorrectQueriesInput { + notRunIfTrue?: () => boolean + testTitle: string + schema: RxJsonSchema + data: RxDocType[] + queries: Array<{ + info: string + query: MangoQuery + expectedResultDocIds: string[] + /** * If this is set, we expect the output * of the RxDB query planner to have * set selectorSatisfiedByIndex as the given value. */ - selectorSatisfiedByIndex?: boolean; - } | undefined)[]; -}; + selectorSatisfiedByIndex?: boolean + } | undefined> +} -export function withIndexes( - schema: RxJsonSchema, - indexes: string[][] +export function withIndexes ( + schema: RxJsonSchema, + indexes: string[][] ): RxJsonSchema { - schema = clone(schema); - schema.indexes = indexes; - return schema; + schema = clone(schema) + schema.indexes = indexes + return schema } - -export function testCorrectQueries( - suite: TestSuite, - testStorage: RxTestStorage, - input: TestCorrectQueriesInput +export function testCorrectQueries ( + suite: TestSuite, + testStorage: RxTestStorage, + input: TestCorrectQueriesInput ) { - const { it, describe, beforeEach, afterEach } = suite - let storage: RxStorage; - let storageInstance: RxStorageInstance + const { it, describe, beforeEach, afterEach } = suite + let storage: RxStorage + let storageInstance: RxStorageInstance - describe(`Testing - ${input.testTitle}`, () => { - beforeEach(async () => { - storage = await testStorage.getStorage() - }) + describe(`Testing - ${input.testTitle}`, () => { + beforeEach(async () => { + storage = await testStorage.getStorage() + }) - afterEach(async () => { - if (storageInstance) { - await storageInstance.cleanup(Infinity); - await storageInstance.close() - } - }) + afterEach(async () => { + if (storageInstance) { + await storageInstance.cleanup(Infinity) + await storageInstance.close() + } + }) + if (input.notRunIfTrue && input.notRunIfTrue()) { + return + } - if (input.notRunIfTrue && input.notRunIfTrue()) { - return; + it(input.testTitle, async ({ expect }) => { + const schema = fillWithDefaultSettings(clone(input.schema)) + const primaryPath = getPrimaryFieldOfPrimaryKey(schema.primaryKey) + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema, + options: {}, + multiInstance: false, + devMode: true + }) + await storageInstance.cleanup(Infinity) + + const rawDocsData = input.data.map(row => { + const writeData = Object.assign( + {}, + row, + { + _deleted: false, + _attachments: {}, + _meta: { + lwt: now() + }, + _rev: EXAMPLE_REVISION_1 + } + ) + return writeData + }) + + await storageInstance.bulkWrite( + rawDocsData.map(document => ({ document })), + testQueryContext + ) + + const database = await createRxDatabase({ + name: randomCouchString(10), + storage, + allowSlowCount: true + }) + const collections = await database.addCollections({ + test: { + schema: input.schema } + }) + const collection = collections.test + await collection.bulkInsert(input.data) + for (const queryData of input.queries) { + if (!queryData) { + continue + } - it(input.testTitle, async ({ expect }) => { - const schema = fillWithDefaultSettings(clone(input.schema)); - const primaryPath = getPrimaryFieldOfPrimaryKey(schema.primaryKey); - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema, - options: {}, - multiInstance: false, - devMode: true - }); - await storageInstance.cleanup(Infinity); - - const rawDocsData = input.data.map(row => { - const writeData = Object.assign( - {}, - row, - { - _deleted: false, - _attachments: {}, - _meta: { - lwt: now() - }, - _rev: EXAMPLE_REVISION_1 - } - ); - return writeData; - }); - - await storageInstance.bulkWrite( - rawDocsData.map(document => ({ document })), - testQueryContext - ); - - const database = await createRxDatabase({ - name: randomCouchString(10), - storage, - allowSlowCount: true - }); - const collections = await database.addCollections({ - test: { - schema: input.schema - } - }); - const collection = collections.test; - await collection.bulkInsert(input.data); - - for (const queryData of input.queries) { - if (!queryData) { - continue; - } - - const queryForStorage = clone(queryData.query) as MangoQuery; - if (!queryForStorage.selector) { - queryForStorage.selector = {}; - } - // (queryForStorage.selector as any)._deleted = false; - // if (queryForStorage.index) { - // (queryForStorage.index as any).unshift('_deleted'); - // } - const normalizedQuery = deepFreeze(normalizeMangoQuery(schema, queryForStorage)); - const skip = normalizedQuery.skip ? normalizedQuery.skip : 0; - const limit = normalizedQuery.limit ? normalizedQuery.limit : Infinity; - const skipPlusLimit = skip + limit; - - const preparedQuery = prepareQuery( - schema, - normalizedQuery - ); - - // Test output of RxStorageStatics - const queryMatcher = getQueryMatcher(schema, normalizedQuery); - const sortComparator = getSortComparator(schema, normalizedQuery); - const staticsResult = rawDocsData.slice(0) - .filter(d => queryMatcher(d)) - .sort(sortComparator) - .slice(skip, skipPlusLimit); - const resultStaticsIds = staticsResult.map(d => (d as any)[primaryPath]); - - expect(resultStaticsIds, 'expectedResultDocIds does not match').toStrictEqual(queryData.expectedResultDocIds) - - - - // Test correct selectorSatisfiedByIndex - // This selectorSatisfiedByIndex and getQueryPlan is completely broken - // We will rely on the right storage instance implementations to mitigate this - // if (typeof queryData.selectorSatisfiedByIndex !== 'undefined') { - // const queryPlan = getQueryPlan(schema, normalizedQuery); - // expect(queryPlan.selectorSatisfiedByIndex).toBe(queryData.selectorSatisfiedByIndex); - // } - - // Test output of RxStorageInstance.query(); - const resultFromStorage = await storageInstance.query(preparedQuery); - const resultIds = resultFromStorage.documents.map(d => (d as any)[primaryPath]); - - expect(resultIds).toStrictEqual(queryData.expectedResultDocIds) - - - // Test output of RxCollection.find() - const rxQuery = collection.find(queryData.query); - const resultFromCollection = await rxQuery.exec(); - const resultFromCollectionIds = resultFromCollection.map(d => d.primary); - - expect(resultFromCollectionIds).toStrictEqual(queryData.expectedResultDocIds) - - - // Test output of .count() - if ( - !queryData.query.limit && + const queryForStorage = clone(queryData.query) as MangoQuery + if (!queryForStorage.selector) { + queryForStorage.selector = {} + } + // (queryForStorage.selector as any)._deleted = false; + // if (queryForStorage.index) { + // (queryForStorage.index as any).unshift('_deleted'); + // } + const normalizedQuery = deepFreeze(normalizeMangoQuery(schema, queryForStorage)) + const skip = normalizedQuery.skip ? normalizedQuery.skip : 0 + const limit = normalizedQuery.limit ? normalizedQuery.limit : Infinity + const skipPlusLimit = skip + limit + + const preparedQuery = prepareQuery( + schema, + normalizedQuery + ) + + // Test output of RxStorageStatics + const queryMatcher = getQueryMatcher(schema, normalizedQuery) + const sortComparator = getSortComparator(schema, normalizedQuery) + const staticsResult = rawDocsData.slice(0) + .filter(d => queryMatcher(d)) + .sort(sortComparator) + .slice(skip, skipPlusLimit) + const resultStaticsIds = staticsResult.map(d => (d as any)[primaryPath]) + + expect(resultStaticsIds, 'expectedResultDocIds does not match').toStrictEqual(queryData.expectedResultDocIds) + + // Test correct selectorSatisfiedByIndex + // This selectorSatisfiedByIndex and getQueryPlan is completely broken + // We will rely on the right storage instance implementations to mitigate this + // if (typeof queryData.selectorSatisfiedByIndex !== 'undefined') { + // const queryPlan = getQueryPlan(schema, normalizedQuery); + // expect(queryPlan.selectorSatisfiedByIndex).toBe(queryData.selectorSatisfiedByIndex); + // } + + // Test output of RxStorageInstance.query(); + const resultFromStorage = await storageInstance.query(preparedQuery) + const resultIds = resultFromStorage.documents.map(d => (d as any)[primaryPath]) + + expect(resultIds).toStrictEqual(queryData.expectedResultDocIds) + + // Test output of RxCollection.find() + const rxQuery = collection.find(queryData.query) + const resultFromCollection = await rxQuery.exec() + const resultFromCollectionIds = resultFromCollection.map(d => d.primary) + + expect(resultFromCollectionIds).toStrictEqual(queryData.expectedResultDocIds) + + // Test output of .count() + if ( + !queryData.query.limit && !queryData.query.skip - ) { - const countResult = await storageInstance.count(preparedQuery); - expect(countResult.count).toStrictEqual(queryData.expectedResultDocIds.length) - - } - } - - await storageInstance.cleanup(Infinity) - await database.remove() - - }); + ) { + const countResult = await storageInstance.count(preparedQuery) + expect(countResult.count).toStrictEqual(queryData.expectedResultDocIds.length) + } + } + await storageInstance.cleanup(Infinity) + await database.remove() }) + }) } diff --git a/packages/test-suite/src/helper/schema-objects.ts b/packages/test-suite/src/helper/schema-objects.ts index f59026ae..e99b411b 100644 --- a/packages/test-suite/src/helper/schema-objects.ts +++ b/packages/test-suite/src/helper/schema-objects.ts @@ -2,453 +2,451 @@ * this file contains objects which match the schemas in schemas.js */ -import { faker } from '@faker-js/faker'; -import { ensureNotFalsy, lastOfArray } from 'rxdb'; +import { faker } from '@faker-js/faker' +import { ensureNotFalsy, lastOfArray } from 'rxdb' import { - randomNumber, - randomString -} from 'async-test-util'; -import { HumanDocumentType } from './schemas'; -import * as schemas from './schemas'; + randomNumber, + randomString +} from 'async-test-util' +import { type HumanDocumentType } from './schemas' +import * as schemas from './schemas' /** * Some storages had problems with umlauts and other special chars. * So we add these to all test strings. * TODO add emojis */ -export const TEST_DATA_CHARSET = '0987654321ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzäöüÖÄßÜ[]{}\''; -export const TEST_DATA_CHARSET_LAST_SORTED = ensureNotFalsy(lastOfArray(TEST_DATA_CHARSET.split('').sort())); +export const TEST_DATA_CHARSET = '0987654321ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzäöüÖÄßÜ[]{}\'' +export const TEST_DATA_CHARSET_LAST_SORTED = ensureNotFalsy(lastOfArray(TEST_DATA_CHARSET.split('').sort())) // const someEmojis = '😊💩👵🍌'; -export function randomStringWithSpecialChars(length: number) { - return randomString(length, TEST_DATA_CHARSET); +export function randomStringWithSpecialChars (length: number) { + return randomString(length, TEST_DATA_CHARSET) } - export interface SimpleHumanDocumentType { - passportId: string; - firstName: string; - lastName: string; + passportId: string + firstName: string + lastName: string } -export function human( - passportId: string = randomStringWithSpecialChars(12), - age: number = randomNumber(10, 50), - firstName: string = faker.person.firstName() +export function human ( + passportId: string = randomStringWithSpecialChars(12), + age: number = randomNumber(10, 50), + firstName: string = faker.person.firstName() ): HumanDocumentType { - return { - passportId: passportId, - firstName, - lastName: faker.person.lastName(), - age - }; + return { + passportId, + firstName, + lastName: faker.person.lastName(), + age + } } -export function simpleHuman(): SimpleHumanDocumentType { - return { - passportId: randomStringWithSpecialChars(12), - firstName: faker.person.firstName(), - lastName: faker.person.lastName() - }; +export function simpleHuman (): SimpleHumanDocumentType { + return { + passportId: randomStringWithSpecialChars(12), + firstName: faker.person.firstName(), + lastName: faker.person.lastName() + } } export interface SimpleHumanV3DocumentType { - passportId: string; - age: number; - oneOptional?: string; -} -export function simpleHumanV3(partial: Partial = {}): SimpleHumanV3DocumentType { - const defaultObj = { - passportId: randomStringWithSpecialChars(12), - age: randomNumber(10, 50) - }; - return Object.assign( - defaultObj, - partial - ); + passportId: string + age: number + oneOptional?: string +} +export function simpleHumanV3 (partial: Partial = {}): SimpleHumanV3DocumentType { + const defaultObj = { + passportId: randomStringWithSpecialChars(12), + age: randomNumber(10, 50) + } + return Object.assign( + defaultObj, + partial + ) } export interface SimpleHumanAgeDocumentType { - passportId: string; - age: string; -} -export function simpleHumanAge(partial: Partial = {}): SimpleHumanAgeDocumentType { - const defaultObj = { - passportId: randomStringWithSpecialChars(12), - age: randomNumber(10, 50) + '' - }; - return Object.assign( - defaultObj, - partial - ); + passportId: string + age: string +} +export function simpleHumanAge (partial: Partial = {}): SimpleHumanAgeDocumentType { + const defaultObj = { + passportId: randomStringWithSpecialChars(12), + age: randomNumber(10, 50) + '' + } + return Object.assign( + defaultObj, + partial + ) } export interface HumanWithSubOtherDocumentType { - passportId: string; + passportId: string + other: { + age: number + } +} +export function humanWithSubOther (): HumanWithSubOtherDocumentType { + return { + passportId: randomStringWithSpecialChars(12), other: { - age: number; - }; -} -export function humanWithSubOther(): HumanWithSubOtherDocumentType { - return { - passportId: randomStringWithSpecialChars(12), - other: { - age: randomNumber(10, 50) - } - }; + age: randomNumber(10, 50) + } + } } export interface NoIndexHumanDocumentType { - firstName: string; - lastName: string; + firstName: string + lastName: string } -export function NoIndexHuman(): NoIndexHumanDocumentType { - return { - firstName: faker.person.firstName(), - lastName: faker.person.lastName() - }; +export function NoIndexHuman (): NoIndexHumanDocumentType { + return { + firstName: faker.person.firstName(), + lastName: faker.person.lastName() + } } export interface NestedHumanDocumentType { - passportId: string; - firstName: string; + passportId: string + firstName: string + mainSkill: { + name: string + level: number + } +} +export function nestedHuman (partial: Partial = {}): NestedHumanDocumentType { + const defaultObj = { + passportId: randomStringWithSpecialChars(12), + firstName: faker.person.firstName(), mainSkill: { - name: string; - level: number; - }; -} -export function nestedHuman(partial: Partial = {}): NestedHumanDocumentType { - const defaultObj = { - passportId: randomStringWithSpecialChars(12), - firstName: faker.person.firstName(), - mainSkill: { - name: randomStringWithSpecialChars(6), - level: 5 - } - }; - return Object.assign( - defaultObj, - partial - ); + name: randomStringWithSpecialChars(6), + level: 5 + } + } + return Object.assign( + defaultObj, + partial + ) } export interface DeepNestedHumanDocumentType { - passportId: string; + passportId: string + mainSkill: { + name: string + attack: { + good: boolean + count: number + } + } +} +export function deepNestedHuman (): DeepNestedHumanDocumentType { + return { + passportId: randomStringWithSpecialChars(12), mainSkill: { - name: string; - attack: { - good: boolean; - count: number; - }; - }; -} -export function deepNestedHuman(): DeepNestedHumanDocumentType { - return { - passportId: randomStringWithSpecialChars(12), - mainSkill: { - name: randomStringWithSpecialChars(6), - attack: { - good: false, - count: 5 - } - } - }; + name: randomStringWithSpecialChars(6), + attack: { + good: false, + count: 5 + } + } + } } export interface BigHumanDocumentType { - passportId: string; - dnaHash: string; - firstName: string; - lastName: string; - age: number; -} -export function bigHumanDocumentType(): BigHumanDocumentType { - return { - passportId: randomStringWithSpecialChars(12), - dnaHash: randomStringWithSpecialChars(12), - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - age: randomNumber(10, 50) - }; + passportId: string + dnaHash: string + firstName: string + lastName: string + age: number +} +export function bigHumanDocumentType (): BigHumanDocumentType { + return { + passportId: randomStringWithSpecialChars(12), + dnaHash: randomStringWithSpecialChars(12), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: randomNumber(10, 50) + } } export interface HeroArrayDocumentType { - name: string; - skills: { - name: string; - damage: number; - }[]; -} -export function heroArray(): HeroArrayDocumentType { - return { + name: string + skills: Array<{ + name: string + damage: number + }> +} +export function heroArray (): HeroArrayDocumentType { + return { + name: randomStringWithSpecialChars(6), + skills: new Array(3).fill(0).map(() => { + return { name: randomStringWithSpecialChars(6), - skills: new Array(3).fill(0).map(() => { - return { - name: randomStringWithSpecialChars(6), - damage: randomNumber(10, 50) - }; - }) - }; + damage: randomNumber(10, 50) + } + }) + } } export interface SimpleHeroArrayDocumentType { - name: string; - skills: string[]; -} -export function simpleHeroArray(partial: Partial = {}): SimpleHeroArrayDocumentType { - const defaultObj = { - name: randomStringWithSpecialChars(6), - skills: new Array(3).fill(0).map(() => randomStringWithSpecialChars(6)) - }; - return Object.assign( - defaultObj, - partial - ); + name: string + skills: string[] +} +export function simpleHeroArray (partial: Partial = {}): SimpleHeroArrayDocumentType { + const defaultObj = { + name: randomStringWithSpecialChars(6), + skills: new Array(3).fill(0).map(() => randomStringWithSpecialChars(6)) + } + return Object.assign( + defaultObj, + partial + ) } export interface EncryptedHumanDocumentType { - passportId: string; - firstName: string; - secret: string; + passportId: string + firstName: string + secret: string } -export function encryptedHuman(secret = randomStringWithSpecialChars(12)): EncryptedHumanDocumentType { - return { - passportId: randomStringWithSpecialChars(12), - firstName: faker.person.firstName(), - secret - }; +export function encryptedHuman (secret = randomStringWithSpecialChars(12)): EncryptedHumanDocumentType { + return { + passportId: randomStringWithSpecialChars(12), + firstName: faker.person.firstName(), + secret + } } export interface EncryptedObjectHumanDocumentType { - passportId: string; - firstName: string; + passportId: string + firstName: string + secret: { + name: string + subname: string + } +} +export function encryptedObjectHuman (): EncryptedObjectHumanDocumentType { + return { + passportId: randomStringWithSpecialChars(12), + firstName: faker.person.firstName(), secret: { - name: string; - subname: string; - }; -} -export function encryptedObjectHuman(): EncryptedObjectHumanDocumentType { - return { - passportId: randomStringWithSpecialChars(12), - firstName: faker.person.firstName(), - secret: { - name: randomStringWithSpecialChars(12), - subname: randomStringWithSpecialChars(12) - } - }; + name: randomStringWithSpecialChars(12), + subname: randomStringWithSpecialChars(12) + } + } } export interface EncryptedDeepHumanDocumentType { - passportId: string; - firstName: string; - firstLevelPassword: string; + passportId: string + firstName: string + firstLevelPassword: string + secretData: { + pw: string + } + deepSecret: { + darkhole: { + pw: string + } + } + nestedSecret: { + darkhole: { + pw: string + } + } +} +export function encryptedDeepHumanDocumentType (): EncryptedDeepHumanDocumentType { + return { + passportId: randomStringWithSpecialChars(12), + firstName: faker.person.firstName(), + firstLevelPassword: randomStringWithSpecialChars(12), secretData: { - pw: string; - }; + pw: randomStringWithSpecialChars(12) + }, deepSecret: { - darkhole: { - pw: string; - }; - }; + darkhole: { + pw: randomStringWithSpecialChars(12) + } + }, nestedSecret: { - darkhole: { - pw: string; - }; - }; -} -export function encryptedDeepHumanDocumentType(): EncryptedDeepHumanDocumentType { - return { - passportId: randomStringWithSpecialChars(12), - firstName: faker.person.firstName(), - firstLevelPassword: randomStringWithSpecialChars(12), - secretData: { - pw: randomStringWithSpecialChars(12) - }, - deepSecret: { - darkhole: { - pw: randomStringWithSpecialChars(12) - } - }, - nestedSecret: { - darkhole: { - pw: randomStringWithSpecialChars(12) - } - } - }; + darkhole: { + pw: randomStringWithSpecialChars(12) + } + } + } } export interface CompoundIndexDocumentType { - passportId: string; - passportCountry: string; - age: number; + passportId: string + passportCountry: string + age: number } -export function compoundIndex(): CompoundIndexDocumentType { - return { - passportId: randomStringWithSpecialChars(12), - passportCountry: randomStringWithSpecialChars(12), - age: randomNumber(10, 50) - }; +export function compoundIndex (): CompoundIndexDocumentType { + return { + passportId: randomStringWithSpecialChars(12), + passportCountry: randomStringWithSpecialChars(12), + age: randomNumber(10, 50) + } } export interface CompoundIndexNoStringDocumentType { - passportId: string; - passportCountry: { [prop: string]: string; }; - age: number; + passportId: string + passportCountry: Record + age: number } -export function compoundIndexNoString(): CompoundIndexNoStringDocumentType { - return { - passportId: randomStringWithSpecialChars(12), - passportCountry: { [randomStringWithSpecialChars(12)]: randomStringWithSpecialChars(12) }, - age: randomNumber(10, 50) - }; +export function compoundIndexNoString (): CompoundIndexNoStringDocumentType { + return { + passportId: randomStringWithSpecialChars(12), + passportCountry: { [randomStringWithSpecialChars(12)]: randomStringWithSpecialChars(12) }, + age: randomNumber(10, 50) + } } export interface NostringIndexDocumentType { - passportId: {}; - firstName: string; + passportId: {} + firstName: string } -export function nostringIndex(): NostringIndexDocumentType { - return { - passportId: {}, - firstName: faker.person.firstName() - }; +export function nostringIndex (): NostringIndexDocumentType { + return { + passportId: {}, + firstName: faker.person.firstName() + } } export interface RefHumanDocumentType { - name: string; - bestFriend: string; + name: string + bestFriend: string } -export function refHuman(bestFriend?: string): RefHumanDocumentType { - return { - name: randomStringWithSpecialChars(12), - bestFriend - } as any; +export function refHuman (bestFriend?: string): RefHumanDocumentType { + return { + name: randomStringWithSpecialChars(12), + bestFriend + } as any } export interface RefHumanNestedDocumentType { - name: string; + name: string + foo: { + bestFriend: string + } +} +export function refHumanNested (bestFriend?: string): RefHumanNestedDocumentType { + return { + name: randomStringWithSpecialChars(12), foo: { - bestFriend: string; - }; -} -export function refHumanNested(bestFriend?: string): RefHumanNestedDocumentType { - return { - name: randomStringWithSpecialChars(12), - foo: { - bestFriend - } as any - }; + bestFriend + } as any + } } export interface HumanWithTimestampDocumentType { - id: string; - name: string; - age: number; - updatedAt: number; - deletedAt?: number; -} -export function humanWithTimestamp(givenData: Partial = {}): HumanWithTimestampDocumentType { - let ret = { - id: randomStringWithSpecialChars(12), - name: faker.person.firstName(), - age: randomNumber(1, 100), - // use some time in the past week - updatedAt: Date.now() - }; - ret = Object.assign({}, ret, givenData); - return ret; + id: string + name: string + age: number + updatedAt: number + deletedAt?: number +} +export function humanWithTimestamp (givenData: Partial = {}): HumanWithTimestampDocumentType { + let ret = { + id: randomStringWithSpecialChars(12), + name: faker.person.firstName(), + age: randomNumber(1, 100), + // use some time in the past week + updatedAt: Date.now() + } + ret = Object.assign({}, ret, givenData) + return ret } export interface AverageSchemaDocumentType { - id: string; - var1: string; - var2: number; - deep: { - deep1: string; - deep2: string; - deeper: { - deepNr: number; - }; - }; - list: { - deep1: string; - deep2: string; - }[]; + id: string + var1: string + var2: number + deep: { + deep1: string + deep2: string + deeper: { + deepNr: number + } + } + list: Array<{ + deep1: string + deep2: string + }> } - -const averageSchemaForFieldLength = schemas.averageSchema() as any; -export function averageSchema( - partial: Partial = {} +const averageSchemaForFieldLength = schemas.averageSchema() as any +export function averageSchema ( + partial: Partial = {} ): AverageSchemaDocumentType { - return Object.assign( - {}, - { - id: randomStringWithSpecialChars(ensureNotFalsy(averageSchemaForFieldLength.properties.id.maxLength)), - var1: randomStringWithSpecialChars(ensureNotFalsy(averageSchemaForFieldLength.properties.var1.maxLength)), - var2: randomNumber(100, ensureNotFalsy(averageSchemaForFieldLength.properties.var2.maximum)), - deep: { - deep1: randomStringWithSpecialChars(ensureNotFalsy(averageSchemaForFieldLength.properties.deep.properties.deep1.maxLength)), - deep2: randomStringWithSpecialChars(ensureNotFalsy(averageSchemaForFieldLength.properties.deep.properties.deep2.maxLength)), - deeper: { - deepNr: randomNumber(0, 10) - } - }, - list: new Array(5).fill(0).map(() => ({ - deep1: randomStringWithSpecialChars(5), - deep2: randomStringWithSpecialChars(8) - })) - }, - partial - ); + return Object.assign( + {}, + { + id: randomStringWithSpecialChars(ensureNotFalsy(averageSchemaForFieldLength.properties.id.maxLength)), + var1: randomStringWithSpecialChars(ensureNotFalsy(averageSchemaForFieldLength.properties.var1.maxLength)), + var2: randomNumber(100, ensureNotFalsy(averageSchemaForFieldLength.properties.var2.maximum)), + deep: { + deep1: randomStringWithSpecialChars(ensureNotFalsy(averageSchemaForFieldLength.properties.deep.properties.deep1.maxLength)), + deep2: randomStringWithSpecialChars(ensureNotFalsy(averageSchemaForFieldLength.properties.deep.properties.deep2.maxLength)), + deeper: { + deepNr: randomNumber(0, 10) + } + }, + list: new Array(5).fill(0).map(() => ({ + deep1: randomStringWithSpecialChars(5), + deep2: randomStringWithSpecialChars(8) + })) + }, + partial + ) } export interface PointDocumentType { - id: string; - x: number; - y: number; + id: string + x: number + y: number } -export function point(): PointDocumentType { - return { - id: randomStringWithSpecialChars(12), - x: faker.number.int(), - y: faker.number.int() - }; +export function point (): PointDocumentType { + return { + id: randomStringWithSpecialChars(12), + x: faker.number.int(), + y: faker.number.int() + } } export interface HumanWithIdAndAgeIndexDocumentType { - id: string; - name: string; - age: number; + id: string + name: string + age: number } -export function humanWithIdAndAgeIndexDocumentType( - age: number = randomNumber(1, 100) +export function humanWithIdAndAgeIndexDocumentType ( + age: number = randomNumber(1, 100) ): HumanWithIdAndAgeIndexDocumentType { - return { - id: randomStringWithSpecialChars(12), - name: faker.person.firstName(), - age - }; + return { + id: randomStringWithSpecialChars(12), + name: faker.person.firstName(), + age + } } -export type HumanWithCompositePrimary = { - // optional because it might be created by RxDB and not known before - id?: string; - firstName: string; - lastName: string; +export interface HumanWithCompositePrimary { + // optional because it might be created by RxDB and not known before + id?: string + firstName: string + lastName: string + info: { + age: number + } +} +export function humanWithCompositePrimary (partial: Partial = {}): HumanWithCompositePrimary { + const defaultObj = { + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), info: { - age: number; - }; -}; -export function humanWithCompositePrimary(partial: Partial = {}): HumanWithCompositePrimary { - const defaultObj = { - firstName: faker.person.firstName(), - lastName: faker.person.lastName(), - info: { - age: randomNumber(10, 50) - } - }; - return Object.assign( - defaultObj, - partial - ); + age: randomNumber(10, 50) + } + } + return Object.assign( + defaultObj, + partial + ) } diff --git a/packages/test-suite/src/helper/schemas.ts b/packages/test-suite/src/helper/schemas.ts index 76bb68cb..f3bbe0bb 100644 --- a/packages/test-suite/src/helper/schemas.ts +++ b/packages/test-suite/src/helper/schemas.ts @@ -1,1282 +1,1277 @@ -import AsyncTestUtil from 'async-test-util'; -import { ExtractDocumentTypeFromTypedRxJsonSchema, RxJsonSchema, flatClone, overwritable, toTypedRxJsonSchema } from 'rxdb'; +import AsyncTestUtil from 'async-test-util' +import { type ExtractDocumentTypeFromTypedRxJsonSchema, type RxJsonSchema, flatClone, overwritable, toTypedRxJsonSchema } from 'rxdb' import { - SimpleHumanV3DocumentType, - HumanWithSubOtherDocumentType, - NestedHumanDocumentType, - DeepNestedHumanDocumentType, - EncryptedHumanDocumentType, - EncryptedObjectHumanDocumentType, - EncryptedDeepHumanDocumentType, - CompoundIndexDocumentType, - CompoundIndexNoStringDocumentType, - HeroArrayDocumentType, - SimpleHeroArrayDocumentType, - RefHumanDocumentType, - RefHumanNestedDocumentType, - AverageSchemaDocumentType, - PointDocumentType, - HumanWithTimestampDocumentType, - BigHumanDocumentType, - NostringIndexDocumentType, - NoIndexHumanDocumentType, - HumanWithCompositePrimary -} from './schema-objects'; - + type SimpleHumanV3DocumentType, + type HumanWithSubOtherDocumentType, + type NestedHumanDocumentType, + type DeepNestedHumanDocumentType, + type EncryptedHumanDocumentType, + type EncryptedObjectHumanDocumentType, + type EncryptedDeepHumanDocumentType, + type CompoundIndexDocumentType, + type CompoundIndexNoStringDocumentType, + type HeroArrayDocumentType, + type SimpleHeroArrayDocumentType, + type RefHumanDocumentType, + type RefHumanNestedDocumentType, + type AverageSchemaDocumentType, + type PointDocumentType, + type HumanWithTimestampDocumentType, + type BigHumanDocumentType, + type NostringIndexDocumentType, + type NoIndexHumanDocumentType, + type HumanWithCompositePrimary +} from './schema-objects' export const humanSchemaLiteral = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - description: 'describes a human being', - version: 0, - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string', - maxLength: 100 - }, - lastName: { - type: 'string' - }, - age: { - description: 'age in years', - type: 'integer', - minimum: 0, - maximum: 150, - multipleOf: 1 - } + title: 'human schema', + description: 'describes a human being', + version: 0, + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 }, - required: ['firstName', 'lastName', 'passportId', 'age'], - indexes: ['firstName'] -} as const); -const humanSchemaTyped = toTypedRxJsonSchema(humanSchemaLiteral); -export type HumanDocumentType = ExtractDocumentTypeFromTypedRxJsonSchema; -export const human: RxJsonSchema = humanSchemaLiteral; - + firstName: { + type: 'string', + maxLength: 100 + }, + lastName: { + type: 'string' + }, + age: { + description: 'age in years', + type: 'integer', + minimum: 0, + maximum: 150, + multipleOf: 1 + } + }, + required: ['firstName', 'lastName', 'passportId', 'age'], + indexes: ['firstName'] +} as const) +const humanSchemaTyped = toTypedRxJsonSchema(humanSchemaLiteral) +export type HumanDocumentType = ExtractDocumentTypeFromTypedRxJsonSchema +export const human: RxJsonSchema = humanSchemaLiteral export const humanDefault: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - version: 0, - description: 'describes a human being', - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string', - maxLength: 100 - }, - lastName: { - type: 'string', - maxLength: 100 - }, - age: { - description: 'age in years', - type: 'integer', - minimum: 0, - maximum: 150, - default: 20 - } + title: 'human schema', + version: 0, + description: 'describes a human being', + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + firstName: { + type: 'string', + maxLength: 100 }, - indexes: [], - required: ['passportId'] -}); + lastName: { + type: 'string', + maxLength: 100 + }, + age: { + description: 'age in years', + type: 'integer', + minimum: 0, + maximum: 150, + default: 20 + } + }, + indexes: [], + required: ['passportId'] +}) export const humanFinal: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema with age set final', - version: 0, - keyCompression: false, - type: 'object', - primaryKey: 'passportId', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string' - }, - lastName: { - type: 'string' - }, - age: { - type: 'integer', - minimum: 0, - maximum: 150, - final: true - } + title: 'human schema with age set final', + version: 0, + keyCompression: false, + type: 'object', + primaryKey: 'passportId', + properties: { + passportId: { + type: 'string', + maxLength: 100 }, - required: [ - 'passportId' - ] -}); + firstName: { + type: 'string' + }, + lastName: { + type: 'string' + }, + age: { + type: 'integer', + minimum: 0, + maximum: 150, + final: true + } + }, + required: [ + 'passportId' + ] +}) export const simpleHuman: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - version: 0, - keyCompression: false, - description: 'describes a simple human being', - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - age: { - type: 'string', - maxLength: 100 - }, - oneOptional: { - type: 'string' - } + title: 'human schema', + version: 0, + keyCompression: false, + description: 'describes a simple human being', + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 }, - indexes: ['age'], - required: ['passportId', 'age'] -}); + age: { + type: 'string', + maxLength: 100 + }, + oneOptional: { + type: 'string' + } + }, + indexes: ['age'], + required: ['passportId', 'age'] +}) export const simpleHumanV3: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - version: 3, - keyCompression: false, - description: 'describes a simple human being', - type: 'object', - primaryKey: 'passportId', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - age: { - type: 'number', - minimum: 0, - maximum: 1000, - multipleOf: 1 - }, - oneOptional: { - type: 'string' - } + title: 'human schema', + version: 3, + keyCompression: false, + description: 'describes a simple human being', + type: 'object', + primaryKey: 'passportId', + properties: { + passportId: { + type: 'string', + maxLength: 100 }, - indexes: ['age'], - required: ['passportId', 'age'] -}); + age: { + type: 'number', + minimum: 0, + maximum: 1000, + multipleOf: 1 + }, + oneOptional: { + type: 'string' + } + }, + indexes: ['age'], + required: ['passportId', 'age'] +}) export const humanAgeIndex: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - version: 0, - keyCompression: false, - description: 'describes a human being', - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string' - }, - lastName: { - type: 'string' - }, - age: { - description: 'Age in years', - type: 'integer', - minimum: 0, - maximum: 150, - multipleOf: 1 - } + title: 'human schema', + version: 0, + keyCompression: false, + description: 'describes a human being', + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 }, - required: ['firstName', 'lastName', 'age'], - indexes: ['age'] -}); + firstName: { + type: 'string' + }, + lastName: { + type: 'string' + }, + age: { + description: 'Age in years', + type: 'integer', + minimum: 0, + maximum: 150, + multipleOf: 1 + } + }, + required: ['firstName', 'lastName', 'age'], + indexes: ['age'] +}) export const humanSubIndex: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - version: 0, - description: 'describes a human being where other.age is index', - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - other: { - type: 'object', - properties: { - age: { - description: 'Age in years', - type: 'integer', - minimum: 0, - maximum: 150, - multipleOf: 1 - } - } - } + title: 'human schema', + version: 0, + description: 'describes a human being where other.age is index', + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 }, - required: [ - 'passportId' - ], - indexes: ['other.age'] -}); + other: { + type: 'object', + properties: { + age: { + description: 'Age in years', + type: 'integer', + minimum: 0, + maximum: 150, + multipleOf: 1 + } + } + } + }, + required: [ + 'passportId' + ], + indexes: ['other.age'] +}) /** * each field is an index, * use this to slow down inserts in tests */ export const humanWithAllIndex: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - description: 'describes a human being', - version: 0, - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string', - maxLength: 100 - }, - lastName: { - type: 'string', - maxLength: 100 - }, - age: { - description: 'age in years', - type: 'integer', - minimum: 0, - maximum: 150, - multipleOf: 1 - } + title: 'human schema', + description: 'describes a human being', + version: 0, + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + firstName: { + type: 'string', + maxLength: 100 + }, + lastName: { + type: 'string', + maxLength: 100 }, - indexes: ['firstName', 'lastName', 'age'], - required: ['firstName', 'lastName'] -}); + age: { + description: 'age in years', + type: 'integer', + minimum: 0, + maximum: 150, + multipleOf: 1 + } + }, + indexes: ['firstName', 'lastName', 'age'], + required: ['firstName', 'lastName'] +}) export const nestedHuman: RxJsonSchema = { - title: 'human nested', - version: 0, - description: 'describes a human being with a nested field', - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string', - maxLength: 100 - }, - mainSkill: { - type: 'object', - properties: { - name: { - type: 'string', - maxLength: 10 - }, - level: { - type: 'number', - minimum: 0, - maximum: 10, - multipleOf: 1 - } - }, - required: ['name', 'level'], - additionalProperties: false - } + title: 'human nested', + version: 0, + description: 'describes a human being with a nested field', + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 }, - required: ['firstName'], - indexes: [] -}; + firstName: { + type: 'string', + maxLength: 100 + }, + mainSkill: { + type: 'object', + properties: { + name: { + type: 'string', + maxLength: 10 + }, + level: { + type: 'number', + minimum: 0, + maximum: 10, + multipleOf: 1 + } + }, + required: ['name', 'level'], + additionalProperties: false + } + }, + required: ['firstName'], + indexes: [] +} export const deepNestedHuman: RxJsonSchema = { - title: 'deep human nested', - version: 0, - keyCompression: false, - description: 'describes a human being with a nested field', - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 + title: 'deep human nested', + version: 0, + keyCompression: false, + description: 'describes a human being with a nested field', + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + mainSkill: { + type: 'object', + properties: { + name: { + type: 'string' }, - mainSkill: { - type: 'object', - properties: { - name: { - type: 'string' - }, - attack: { - type: 'object', - properties: { - good: { - type: 'boolean' - }, - count: { - type: 'number' - } - } - } + attack: { + type: 'object', + properties: { + good: { + type: 'boolean' }, - required: ['name'] + count: { + type: 'number' + } + } } - }, - indexes: [], - required: ['mainSkill'] -}; + }, + required: ['name'] + } + }, + indexes: [], + required: ['mainSkill'] +} export const noIndexHuman: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - version: 0, - description: 'this schema has no index', - keyCompression: false, - primaryKey: 'firstName', - type: 'object', - properties: { - firstName: { - type: 'string', - maxLength: 100 - }, - lastName: { - type: 'string' - } + title: 'human schema', + version: 0, + description: 'this schema has no index', + keyCompression: false, + primaryKey: 'firstName', + type: 'object', + properties: { + firstName: { + type: 'string', + maxLength: 100 }, - required: ['lastName'] -}); + lastName: { + type: 'string' + } + }, + required: ['lastName'] +}) export const noStringIndex: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - description: 'the index has no type:string', - version: 0, - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'object', - maxLength: 100 - }, - firstName: { - type: 'string' - } + description: 'the index has no type:string', + version: 0, + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'object', + maxLength: 100 }, - required: ['firstName', 'passportId'], - indexes: [] -}); - + firstName: { + type: 'string' + } + }, + required: ['firstName', 'passportId'], + indexes: [] +}) export const bigHuman: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - version: 0, - description: 'describes a human being with 2 indexes', - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - dnaHash: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string', - maxLength: 100 - }, - lastName: { - type: 'string' - }, - age: { - description: 'Age in years', - type: 'integer', - minimum: 0 - } + title: 'human schema', + version: 0, + description: 'describes a human being with 2 indexes', + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + dnaHash: { + type: 'string', + maxLength: 100 }, - required: ['firstName', 'lastName'], - indexes: ['firstName', 'dnaHash'] -}); + firstName: { + type: 'string', + maxLength: 100 + }, + lastName: { + type: 'string' + }, + age: { + description: 'Age in years', + type: 'integer', + minimum: 0 + } + }, + required: ['firstName', 'lastName'], + indexes: ['firstName', 'dnaHash'] +}) export const encryptedHuman: RxJsonSchema = { - title: 'human encrypted', - version: 0, - description: 'uses an encrypted field', - primaryKey: 'passportId', - type: 'object', - keyCompression: false, - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string' - }, - secret: { - type: 'string', - maxLength: 100 - } + title: 'human encrypted', + version: 0, + description: 'uses an encrypted field', + primaryKey: 'passportId', + type: 'object', + keyCompression: false, + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + firstName: { + type: 'string' }, - indexes: [], - required: ['firstName', 'secret'], - encrypted: ['secret'] -}; + secret: { + type: 'string', + maxLength: 100 + } + }, + indexes: [], + required: ['firstName', 'secret'], + encrypted: ['secret'] +} export const encryptedObjectHuman: RxJsonSchema = { - title: 'human encrypted', - version: 0, - keyCompression: false, - description: 'uses an encrypted field', - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string' + title: 'human encrypted', + version: 0, + keyCompression: false, + description: 'uses an encrypted field', + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + firstName: { + type: 'string' + }, + secret: { + type: 'object', + properties: { + name: { + type: 'string' }, - secret: { - type: 'object', - properties: { - name: { - type: 'string' - }, - subname: { - type: 'string' - } - } + subname: { + type: 'string' } - }, - indexes: [], - required: ['firstName', 'secret'], - encrypted: ['secret'] -}; + } + } + }, + indexes: [], + required: ['firstName', 'secret'], + encrypted: ['secret'] +} export const encryptedDeepHuman: RxJsonSchema = { - title: 'human encrypted', - version: 0, - keyCompression: false, - description: 'uses an encrypted field', - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string' - }, - firstLevelPassword: { - type: 'string', - }, - secretData: { - type: 'object', - properties: { - pw: { - type: 'string' - } - } - }, - deepSecret: { - type: 'object', - properties: { - darkhole: { - type: 'object', - properties: { - pw: { - type: 'string' - } - } - } - } - }, - nestedSecret: { - type: 'object', - properties: { - darkhole: { - type: 'object', - properties: { - pw: { - type: 'string' - } - } - } - } + title: 'human encrypted', + version: 0, + keyCompression: false, + description: 'uses an encrypted field', + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + firstName: { + type: 'string' + }, + firstLevelPassword: { + type: 'string' + }, + secretData: { + type: 'object', + properties: { + pw: { + type: 'string' } - + } }, - indexes: [], - required: ['firstName', 'secretData'], - encrypted: [ - 'firstLevelPassword', - 'secretData', - 'deepSecret.darkhole.pw', - 'nestedSecret.darkhole.pw' - ] -}; - -export const notExistingIndex: RxJsonSchema<{ passportId: string; address: { street: string; }; }> = { - title: 'index', - version: 0, - description: 'this schema has a specified index which does not exists', - primaryKey: 'passportId', - type: 'object', - keyCompression: false, - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - address: { - type: 'object', - properties: { - street: { type: 'string' } + deepSecret: { + type: 'object', + properties: { + darkhole: { + type: 'object', + properties: { + pw: { + type: 'string' } + } } + } }, - required: [ - 'passportId' - ], - indexes: ['address.apartment'] -}; - -export const compoundIndex: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'compound index', - version: 0, - description: 'this schema has a compoundIndex', - primaryKey: 'passportId', - type: 'object', - keyCompression: false, - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - passportCountry: { - type: 'string', - maxLength: 100 - }, - age: { - type: 'integer', - minimum: 0, - maximum: 150, - multipleOf: 1 + nestedSecret: { + type: 'object', + properties: { + darkhole: { + type: 'object', + properties: { + pw: { + type: 'string' + } + } } - }, - required: [ - 'passportId' - ], - indexes: [ - ['age', 'passportCountry'] - ] -}); + } + } -export const compoundIndexNoString: RxJsonSchema = { - title: 'compound index', - version: 0, - description: 'this schema has a compoundIndex', - primaryKey: 'passportId', - keyCompression: false, - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - passportCountry: { - type: 'object' - }, - age: { - type: 'integer' - } - }, - indexes: [ - [10, 'passportCountry'] - ] -} as RxJsonSchema; + }, + indexes: [], + required: ['firstName', 'secretData'], + encrypted: [ + 'firstLevelPassword', + 'secretData', + 'deepSecret.darkhole.pw', + 'nestedSecret.darkhole.pw' + ] +} -export const empty: RxJsonSchema = { - title: 'empty schema', - version: 0, - type: 'object', - primaryKey: 'id', - properties: { - id: { - type: 'string', - maxLength: 100 - } +export const notExistingIndex: RxJsonSchema<{ passportId: string, address: { street: string } }> = { + title: 'index', + version: 0, + description: 'this schema has a specified index which does not exists', + primaryKey: 'passportId', + type: 'object', + keyCompression: false, + properties: { + passportId: { + type: 'string', + maxLength: 100 }, - required: ['id'] -}; + address: { + type: 'object', + properties: { + street: { type: 'string' } + } + } + }, + required: [ + 'passportId' + ], + indexes: ['address.apartment'] +} -export const heroArray: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'hero schema', - version: 0, - keyCompression: false, - description: 'describes a hero with an array-field', - primaryKey: 'name', - type: 'object', - properties: { - name: { - type: 'string', - maxLength: 100 - }, - skills: { - type: 'array', - maxItems: 5, - uniqueItems: true, - items: { - type: 'object', - properties: { - name: { - type: 'string' - }, - damage: { - type: 'number' - } - } - } - } +export const compoundIndex: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ + title: 'compound index', + version: 0, + description: 'this schema has a compoundIndex', + primaryKey: 'passportId', + type: 'object', + keyCompression: false, + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + passportCountry: { + type: 'string', + maxLength: 100 }, - required: [ - 'name' - ] -}); + age: { + type: 'integer', + minimum: 0, + maximum: 150, + multipleOf: 1 + } + }, + required: [ + 'passportId' + ], + indexes: [ + ['age', 'passportCountry'] + ] +}) -export const simpleArrayHero: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'hero schema', - version: 0, - description: 'describes a hero with a string-array-field', - keyCompression: false, - primaryKey: 'name', - type: 'object', - properties: { - name: { - type: 'string', - maxLength: 100 - }, - skills: { - type: 'array', - maxItems: 5, - uniqueItems: true, - items: { - type: 'string', - } +export const compoundIndexNoString: RxJsonSchema = { + title: 'compound index', + version: 0, + description: 'this schema has a compoundIndex', + primaryKey: 'passportId', + keyCompression: false, + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + passportCountry: { + type: 'object' + }, + age: { + type: 'integer' + } + }, + indexes: [ + [10, 'passportCountry'] + ] +} as RxJsonSchema + +export const empty: RxJsonSchema = { + title: 'empty schema', + version: 0, + type: 'object', + primaryKey: 'id', + properties: { + id: { + type: 'string', + maxLength: 100 + } + }, + required: ['id'] +} + +export const heroArray: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ + title: 'hero schema', + version: 0, + keyCompression: false, + description: 'describes a hero with an array-field', + primaryKey: 'name', + type: 'object', + properties: { + name: { + type: 'string', + maxLength: 100 + }, + skills: { + type: 'array', + maxItems: 5, + uniqueItems: true, + items: { + type: 'object', + properties: { + name: { + type: 'string' + }, + damage: { + type: 'number' + } } + } + } + }, + required: [ + 'name' + ] +}) + +export const simpleArrayHero: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ + title: 'hero schema', + version: 0, + description: 'describes a hero with a string-array-field', + keyCompression: false, + primaryKey: 'name', + type: 'object', + properties: { + name: { + type: 'string', + maxLength: 100 }, - required: [ - 'name' - ] -}); + skills: { + type: 'array', + maxItems: 5, + uniqueItems: true, + items: { + type: 'string' + } + } + }, + required: [ + 'name' + ] +}) export const primaryHumanLiteral = overwritable.deepFreezeWhenDevMode({ - title: 'human schema with primary', - version: 0, - description: 'describes a human being with passportID as primary', - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - minLength: 4, - maxLength: 100 - }, - firstName: { - type: 'string', - maxLength: 100 - }, - lastName: { - type: 'string', - maxLength: 500 - }, - age: { - type: 'integer', - minimum: 0, - maximum: 150, - multipleOf: 1 - } + title: 'human schema with primary', + version: 0, + description: 'describes a human being with passportID as primary', + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + minLength: 4, + maxLength: 100 + }, + firstName: { + type: 'string', + maxLength: 100 + }, + lastName: { + type: 'string', + maxLength: 500 }, - required: ['passportId', 'firstName', 'lastName'] -} as const); -const primaryHumanTypedSchema = toTypedRxJsonSchema(primaryHumanLiteral); -export type PrimaryHumanDocType = ExtractDocumentTypeFromTypedRxJsonSchema; -export const primaryHuman: RxJsonSchema = primaryHumanLiteral; + age: { + type: 'integer', + minimum: 0, + maximum: 150, + multipleOf: 1 + } + }, + required: ['passportId', 'firstName', 'lastName'] +} as const) +const primaryHumanTypedSchema = toTypedRxJsonSchema(primaryHumanLiteral) +export type PrimaryHumanDocType = ExtractDocumentTypeFromTypedRxJsonSchema +export const primaryHuman: RxJsonSchema = primaryHumanLiteral export const humanNormalizeSchema1Literal = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - version: 0, - keyCompression: false, - description: 'describes a human being', - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - minLength: 4, - maxLength: 100 - }, - age: { - description: 'age in years', - type: 'integer', - minimum: 0, - maximum: 150, - multipleOf: 1 - } + title: 'human schema', + version: 0, + keyCompression: false, + description: 'describes a human being', + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + minLength: 4, + maxLength: 100 }, - required: ['age', 'passportId'] -} as const); -const humanNormalizeSchema1Typed = toTypedRxJsonSchema(humanNormalizeSchema1Literal); -export type AgeHumanDocumentType = ExtractDocumentTypeFromTypedRxJsonSchema; -export const humanNormalizeSchema1: RxJsonSchema = humanNormalizeSchema1Literal; + age: { + description: 'age in years', + type: 'integer', + minimum: 0, + maximum: 150, + multipleOf: 1 + } + }, + required: ['age', 'passportId'] +} as const) +const humanNormalizeSchema1Typed = toTypedRxJsonSchema(humanNormalizeSchema1Literal) +export type AgeHumanDocumentType = ExtractDocumentTypeFromTypedRxJsonSchema +export const humanNormalizeSchema1: RxJsonSchema = humanNormalizeSchema1Literal export const humanNormalizeSchema2: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - version: 0, - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - minLength: 4, - maxLength: 100 - }, - age: { - minimum: 0, - type: 'integer', - description: 'age in years', - maximum: 150, - multipleOf: 1 - } + title: 'human schema', + version: 0, + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + minLength: 4, + maxLength: 100 }, - description: 'describes a human being', - required: ['age', 'passportId'] -}); + age: { + minimum: 0, + type: 'integer', + description: 'age in years', + maximum: 150, + multipleOf: 1 + } + }, + description: 'describes a human being', + required: ['age', 'passportId'] +}) export const refHuman: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human related to other human', - version: 0, - keyCompression: false, - primaryKey: 'name', - type: 'object', - properties: { - name: { - type: 'string', - maxLength: 100 - }, - bestFriend: { - ref: 'human', - type: 'string' - } + title: 'human related to other human', + version: 0, + keyCompression: false, + primaryKey: 'name', + type: 'object', + properties: { + name: { + type: 'string', + maxLength: 100 }, - required: [ - 'name' - ] -}); + bestFriend: { + ref: 'human', + type: 'string' + } + }, + required: [ + 'name' + ] +}) export const humanCompositePrimary: RxJsonSchema = { - title: 'human schema', - description: 'describes a human being', - version: 0, - keyCompression: false, - primaryKey: { - key: 'id', - fields: [ - 'firstName', - 'info.age' - ], - separator: '|' + title: 'human schema', + description: 'describes a human being', + version: 0, + keyCompression: false, + primaryKey: { + key: 'id', + fields: [ + 'firstName', + 'info.age' + ], + separator: '|' + }, + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 100 }, - type: 'object', - properties: { - id: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string', - maxLength: 100 - }, - lastName: { - type: 'string' - }, - info: { - type: 'object', - properties: { - age: { - description: 'age in years', - type: 'integer', - minimum: 0, - maximum: 150 - } - }, - required: ['age'] - } + firstName: { + type: 'string', + maxLength: 100 }, - required: [ - 'id', - 'firstName', - 'lastName', - 'info' - ], - indexes: ['firstName'] -}; + lastName: { + type: 'string' + }, + info: { + type: 'object', + properties: { + age: { + description: 'age in years', + type: 'integer', + minimum: 0, + maximum: 150 + } + }, + required: ['age'] + } + }, + required: [ + 'id', + 'firstName', + 'lastName', + 'info' + ], + indexes: ['firstName'] +} export const humanCompositePrimarySchemaLiteral = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - description: 'describes a human being', - version: 0, - keyCompression: false, - primaryKey: { - key: 'id', - fields: [ - 'firstName', - 'info.age' - ], - separator: '|' - }, - encrypted: [], - type: 'object', - properties: { - id: { - type: 'string', - maxLength: 100 - }, - firstName: { - type: 'string', - maxLength: 100 - }, - lastName: { - type: 'string' - }, - info: { - type: 'object', - properties: { - age: { - description: 'age in years', - type: 'integer', - minimum: 0, - maximum: 150 - } - }, - required: ['age'] - }, - readonlyProps: { - allOf: [], - anyOf: [], - oneOf: [], - type: [], - dependencies: { - someDep: ['asd'], - }, - items: [], - required: [], - enum: [], + title: 'human schema', + description: 'describes a human being', + version: 0, + keyCompression: false, + primaryKey: { + key: 'id', + fields: [ + 'firstName', + 'info.age' + ], + separator: '|' + }, + encrypted: [], + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 100 + }, + firstName: { + type: 'string', + maxLength: 100 + }, + lastName: { + type: 'string' + }, + info: { + type: 'object', + properties: { + age: { + description: 'age in years', + type: 'integer', + minimum: 0, + maximum: 150 } + }, + required: ['age'] }, - required: [ - 'id', - 'firstName', - 'lastName', - 'info' - ], - indexes: ['firstName'] -} as const); + readonlyProps: { + allOf: [], + anyOf: [], + oneOf: [], + type: [], + dependencies: { + someDep: ['asd'] + }, + items: [], + required: [], + enum: [] + } + }, + required: [ + 'id', + 'firstName', + 'lastName', + 'info' + ], + indexes: ['firstName'] +} as const) -const humanCompositePrimarySchemaTyped = toTypedRxJsonSchema(humanCompositePrimarySchemaLiteral); -export type HumanCompositePrimaryDocType = ExtractDocumentTypeFromTypedRxJsonSchema; +const humanCompositePrimarySchemaTyped = toTypedRxJsonSchema(humanCompositePrimarySchemaLiteral) +export type HumanCompositePrimaryDocType = ExtractDocumentTypeFromTypedRxJsonSchema export const refHumanNested: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human related to other human', - version: 0, - keyCompression: false, - primaryKey: 'name', - type: 'object', - properties: { - name: { - type: 'string', - maxLength: 100 - }, - foo: { - type: 'object', - properties: { - bestFriend: { - ref: 'human', - type: 'string' - } - } - } + title: 'human related to other human', + version: 0, + keyCompression: false, + primaryKey: 'name', + type: 'object', + properties: { + name: { + type: 'string', + maxLength: 100 }, - required: [ - 'name' - ] -}); + foo: { + type: 'object', + properties: { + bestFriend: { + ref: 'human', + type: 'string' + } + } + } + }, + required: [ + 'name' + ] +}) /** * an average schema used in performance-tests */ -export function averageSchema(): RxJsonSchema { - const ret: RxJsonSchema = { - title: 'averageSchema_' + AsyncTestUtil.randomString(5), // randomisation used so hash differs - version: 0, - primaryKey: 'id', +export function averageSchema (): RxJsonSchema { + const ret: RxJsonSchema = { + title: 'averageSchema_' + AsyncTestUtil.randomString(5), // randomisation used so hash differs + version: 0, + primaryKey: 'id', + type: 'object', + keyCompression: false, + properties: { + id: { + type: 'string', + maxLength: 12 + }, + var1: { + type: 'string', + maxLength: 12 + }, + var2: { + type: 'number', + minimum: 0, + maximum: 50000, + multipleOf: 1 + }, + deep: { type: 'object', - keyCompression: false, properties: { - id: { - type: 'string', - maxLength: 12 - }, - var1: { - type: 'string', - maxLength: 12 - }, - var2: { - type: 'number', - minimum: 0, - maximum: 50000, - multipleOf: 1 - }, - deep: { - type: 'object', - properties: { - deep1: { - type: 'string', - maxLength: 10 - }, - deep2: { - type: 'string', - maxLength: 10 - } - } + deep1: { + type: 'string', + maxLength: 10 + }, + deep2: { + type: 'string', + maxLength: 10 + } + } + }, + list: { + type: 'array', + items: { + type: 'object', + properties: { + deep1: { + type: 'string' }, - list: { - type: 'array', - items: { - type: 'object', - properties: { - deep1: { - type: 'string' - }, - deep2: { - type: 'string' - } - } - } + deep2: { + type: 'string' } - }, - required: [ - 'id' - ], - indexes: [ - 'var1', - 'var2', - 'deep.deep1', - // one compound index - [ - 'var2', - 'var1' - ] - ], - sharding: { - shards: 6, - mode: 'collection' + } } - }; - return ret; + } + }, + required: [ + 'id' + ], + indexes: [ + 'var1', + 'var2', + 'deep.deep1', + // one compound index + [ + 'var2', + 'var1' + ] + ], + sharding: { + shards: 6, + mode: 'collection' + } + } + return ret } export const point: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'point schema', - version: 0, - description: 'describes coordinates in 2d space', - primaryKey: 'id', - type: 'object', - properties: { - id: { - type: 'string', - maxLength: 100 - }, - x: { - type: 'number' - }, - y: { - type: 'number' - } + title: 'point schema', + version: 0, + description: 'describes coordinates in 2d space', + primaryKey: 'id', + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 100 }, - required: ['x', 'y'] -}); + x: { + type: 'number' + }, + y: { + type: 'number' + } + }, + required: ['x', 'y'] +}) export const humanMinimal: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - title: 'human schema', - description: 'describes a human being', - version: 0, - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - age: { - type: 'integer' - }, - oneOptional: { - type: 'string' - } + title: 'human schema', + description: 'describes a human being', + version: 0, + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 }, - indexes: [], - required: ['passportId', 'age'] -}); - -export const humanMinimalBroken: RxJsonSchema<{ passportId: string; broken: number; }> = { - title: 'human schema', - description: 'describes a human being', - version: 0, - keyCompression: false, - primaryKey: 'passportId', - type: 'object', - properties: { - passportId: { - type: 'string', - maxLength: 100 - }, - broken: { - type: 'integer' - } + age: { + type: 'integer' }, - indexes: [], - required: ['passportId', 'broken'] -} as unknown as RxJsonSchema; + oneOptional: { + type: 'string' + } + }, + indexes: [], + required: ['passportId', 'age'] +}) +export const humanMinimalBroken: RxJsonSchema<{ passportId: string, broken: number }> = { + title: 'human schema', + description: 'describes a human being', + version: 0, + keyCompression: false, + primaryKey: 'passportId', + type: 'object', + properties: { + passportId: { + type: 'string', + maxLength: 100 + }, + broken: { + type: 'integer' + } + }, + indexes: [], + required: ['passportId', 'broken'] +} as unknown as RxJsonSchema /** * used in the graphql-test * contains timestamp */ export const humanWithTimestamp: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - version: 0, - type: 'object', - primaryKey: 'id', - properties: { - id: { - type: 'string', - maxLength: 100 - }, - name: { - type: 'string', - maxLength: 1000 - }, - age: { - type: 'number' - }, - updatedAt: { - type: 'number', - minimum: 0, - maximum: 10000000000000000, - multipleOf: 1 + version: 0, + type: 'object', + primaryKey: 'id', + properties: { + id: { + type: 'string', + maxLength: 100 + }, + name: { + type: 'string', + maxLength: 1000 + }, + age: { + type: 'number' + }, + updatedAt: { + type: 'number', + minimum: 0, + maximum: 10000000000000000, + multipleOf: 1 - }, - deletedAt: { - type: 'number' - } }, - indexes: ['updatedAt'], - required: ['id', 'name', 'age', 'updatedAt'] -}); + deletedAt: { + type: 'number' + } + }, + indexes: ['updatedAt'], + required: ['id', 'name', 'age', 'updatedAt'] +}) /** * each field is an index, * use this to slow down inserts in tests */ export const humanWithTimestampAllIndex: RxJsonSchema = overwritable.deepFreezeWhenDevMode({ - version: 0, - type: 'object', - primaryKey: 'id', - properties: { - id: { - type: 'string', - maxLength: 100 - }, - name: { - type: 'string', - maxLength: 100 - }, - age: { - type: 'number', - minimum: 0, - maximum: 1500, - multipleOf: 1 - }, - updatedAt: { - type: 'number', - minimum: 0, - maximum: 10000000000000000, - multipleOf: 1 - }, - deletedAt: { - type: 'number' - } + version: 0, + type: 'object', + primaryKey: 'id', + properties: { + id: { + type: 'string', + maxLength: 100 }, - indexes: ['name', 'age', 'updatedAt'], - required: ['id', 'name', 'age', 'updatedAt'] -}); + name: { + type: 'string', + maxLength: 100 + }, + age: { + type: 'number', + minimum: 0, + maximum: 1500, + multipleOf: 1 + }, + updatedAt: { + type: 'number', + minimum: 0, + maximum: 10000000000000000, + multipleOf: 1 + }, + deletedAt: { + type: 'number' + } + }, + indexes: ['name', 'age', 'updatedAt'], + required: ['id', 'name', 'age', 'updatedAt'] +}) export const humanWithSimpleAndCompoundIndexes: RxJsonSchema<{ - id: string; - name: string; - age: number; - createdAt: number; - updatedAt: number; + id: string + name: string + age: number + createdAt: number + updatedAt: number }> = overwritable.deepFreezeWhenDevMode({ - version: 0, - primaryKey: 'id', - type: 'object', - properties: { - id: { - type: 'string', - maxLength: 100 - }, - name: { - type: 'string', - maxLength: 100 - }, - age: { - type: 'number', - minimum: 0, - maximum: 1500, - multipleOf: 1 - }, - createdAt: { - type: 'number', - minimum: 0, - maximum: 10000000000000000, - multipleOf: 1 - }, - updatedAt: { - type: 'number', - minimum: 0, - maximum: 10000000000000000, - multipleOf: 1 - } + version: 0, + primaryKey: 'id', + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 100 }, - indexes: [ - ['name', 'id'], - ['age', 'id'], - ['createdAt', 'updatedAt', 'id'] - ], - required: ['id', 'name', 'age', 'updatedAt'] -}); + name: { + type: 'string', + maxLength: 100 + }, + age: { + type: 'number', + minimum: 0, + maximum: 1500, + multipleOf: 1 + }, + createdAt: { + type: 'number', + minimum: 0, + maximum: 10000000000000000, + multipleOf: 1 + }, + updatedAt: { + type: 'number', + minimum: 0, + maximum: 10000000000000000, + multipleOf: 1 + } + }, + indexes: [ + ['name', 'id'], + ['age', 'id'], + ['createdAt', 'updatedAt', 'id'] + ], + required: ['id', 'name', 'age', 'updatedAt'] +}) -export const humanWithDeepNestedIndexes: RxJsonSchema<{ id: string; name: string; job: any; }> = overwritable.deepFreezeWhenDevMode({ - version: 0, - primaryKey: 'id', - type: 'object', - properties: { - id: { - type: 'string', - maxLength: 100 - }, +export const humanWithDeepNestedIndexes: RxJsonSchema<{ id: string, name: string, job: any }> = overwritable.deepFreezeWhenDevMode({ + version: 0, + primaryKey: 'id', + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 100 + }, + name: { + type: 'string', + maxLength: 100 + }, + job: { + type: 'object', + properties: { name: { - type: 'string', - maxLength: 100 - }, - job: { - type: 'object', - properties: { - name: { + type: 'string', + maxLength: 100 + }, + manager: { + type: 'object', + properties: { + fullName: { + type: 'string', + maxLength: 100 + }, + previousJobs: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string', maxLength: 100 - }, - manager: { - type: 'object', - properties: { - fullName: { - type: 'string', - maxLength: 100 - }, - previousJobs: { - type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string', - maxLength: 100 - } - } - } - } - } + } } + } } + } } - }, - required: [ - 'id' - ], - indexes: [ - 'name', - 'job.name', - 'job.manager.fullName' - ] -}); + } + } + }, + required: [ + 'id' + ], + indexes: [ + 'name', + 'job.name', + 'job.manager.fullName' + ] +}) -export const humanIdAndAgeIndex: RxJsonSchema<{ id: string; name: string; age: number; }> = overwritable.deepFreezeWhenDevMode({ - version: 0, - description: 'uses a compound index with id as lowest level', - primaryKey: 'id', - type: 'object', - properties: { - id: { - type: 'string', - maxLength: 100 - }, - name: { - type: 'string' - }, - age: { - description: 'Age in years', - type: 'integer', - minimum: 0, - maximum: 150, - multipleOf: 1 - } +export const humanIdAndAgeIndex: RxJsonSchema<{ id: string, name: string, age: number }> = overwritable.deepFreezeWhenDevMode({ + version: 0, + description: 'uses a compound index with id as lowest level', + primaryKey: 'id', + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 100 }, - required: ['id', 'name', 'age'], - indexes: [ - ['age', 'id'] - ] -}); - + name: { + type: 'string' + }, + age: { + description: 'Age in years', + type: 'integer', + minimum: 0, + maximum: 150, + multipleOf: 1 + } + }, + required: ['id', 'name', 'age'], + indexes: [ + ['age', 'id'] + ] +}) -export function enableKeyCompression( - schema: RxJsonSchema +export function enableKeyCompression ( + schema: RxJsonSchema ): RxJsonSchema { - const ret = flatClone(schema); - ret.keyCompression = true; - return ret; + const ret = flatClone(schema) + ret.keyCompression = true + return ret } diff --git a/packages/test-suite/src/index.ts b/packages/test-suite/src/index.ts index 093a36c2..d24daca1 100644 --- a/packages/test-suite/src/index.ts +++ b/packages/test-suite/src/index.ts @@ -11,2580 +11,2549 @@ // simpleHumanV3, // SimpleHumanV3DocumentType // } from '../helper/schema-objects.ts'; -import { FilledMangoQuery, RxDocumentData, RxDocumentWriteData, RxJsonSchema, RxSchema, RxStorage, RxStorageBulkWriteResponse, RxStorageInstance, RxStorageInstanceCreationParams, clone, createRevision, ensureNotFalsy, fillWithDefaultSettings, flatCloneDocWithMeta, getPseudoSchemaForVersion, getQueryMatcher, getSortComparator, newRxError, now, parseRevision, randomCouchString, shuffleArray } from "rxdb"; -import { EXAMPLE_REVISION_1, EXAMPLE_REVISION_2, EXAMPLE_REVISION_3, EXAMPLE_REVISION_4, NestedDoc, OptionalValueTestDoc, RandomDoc, RxTestStorage, TestDocType, TestSuite, getNestedDocSchema, getTestDataSchema, getWriteData, prepareQuery, testContext, testCorrectQueries, withIndexes } from "./helper"; -import * as schemas from './helper/schemas'; -import { HeroArrayDocumentType, NestedHumanDocumentType, SimpleHumanV3DocumentType, human, nestedHuman, simpleHumanV3 } from "./helper/schema-objects"; -import { HumanDocumentType } from "./helper/schemas"; -import { randomString } from "async-test-util"; - -let storage: RxStorage; -let storageInstance: RxStorageInstance; +import { type FilledMangoQuery, type RxDocumentData, type RxDocumentWriteData, type RxJsonSchema, type RxStorage, type RxStorageBulkWriteResponse, type RxStorageInstance, type RxStorageInstanceCreationParams, clone, createRevision, ensureNotFalsy, fillWithDefaultSettings, flatCloneDocWithMeta, getPseudoSchemaForVersion, getQueryMatcher, getSortComparator, newRxError, now, parseRevision, randomCouchString, shuffleArray } from 'rxdb' +import { EXAMPLE_REVISION_1, EXAMPLE_REVISION_2, EXAMPLE_REVISION_3, EXAMPLE_REVISION_4, type NestedDoc, type OptionalValueTestDoc, type RandomDoc, type RxTestStorage, type TestDocType, type TestSuite, getNestedDocSchema, getTestDataSchema, getWriteData, prepareQuery, testContext, testCorrectQueries, withIndexes } from './helper' +import * as schemas from './helper/schemas' +import { type HeroArrayDocumentType, type NestedHumanDocumentType, type SimpleHumanV3DocumentType, human, nestedHuman, simpleHumanV3 } from './helper/schema-objects' +import { type HumanDocumentType } from './helper/schemas' +import { randomString } from 'async-test-util' + +let storage: RxStorage +let storageInstance: RxStorageInstance + +export function runTestSuite (suite: TestSuite, testStorage: RxTestStorage): void { + const { describe, it, beforeEach, afterEach } = suite + describe('RxStorageInstance', () => { + beforeEach(async () => { + storage = await testStorage.getStorage() + }) + afterEach(async () => { + if (storageInstance) { + await storageInstance.cleanup(Infinity) + } + }) -export function runTestSuite(suite: TestSuite, testStorage: RxTestStorage) { - const { describe, it, beforeEach, afterEach } = suite - describe('RxStorageInstance', () => { + describe('creation', () => { + it('open many instances on the same database name', async () => { + const databaseName = randomCouchString(12) + const amount = 20 + + for (let i = 0; i < amount; i++) { + const storageInstance = await testStorage.getStorage().createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName, + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true, + password: randomCouchString(24) + }) + + await storageInstance.cleanup(Infinity) + await storageInstance.close() + } + }) + + it('open and close', async ({ expect }) => { + const collectionName = randomCouchString(12) + const databaseName = randomCouchString(12) + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName, + collectionName, + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true, + password: randomCouchString(24) + }) + expect(storageInstance.collectionName).toBe(collectionName) + expect(storageInstance.databaseName).toBe(databaseName) + }) + + /** + * This test ensures that people do not accidentally set + * keyCompression: true in the schema but then forget to use + * the key-compression RxStorage wrapper. + */ + it('must throw if keyCompression is set but no key-compression plugin is used', async ({ expect }) => { + const schema = getPseudoSchemaForVersion(0, 'key') + schema.keyCompression = true + + const params: RxStorageInstanceCreationParams = { + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema, + options: {}, + multiInstance: false, + devMode: true, + password: randomCouchString(24) + } + await expect(async () => await storage.createStorageInstance(params)).rejects.toThrowError(newRxError('UT5', { args: { databaseName: params.databaseName, collectionName: params.collectionName } })) + }) + }) - beforeEach(async () => { - storage = await testStorage.getStorage() + describe('.bulkWrite()', () => { + it('should write the document', async ({ expect }) => { + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) + const pkey = 'foobar' + const docData: RxDocumentWriteData = { + key: 'foobar', + value: 'barfoo1', + _deleted: false, + _meta: { + lwt: now() + }, + _rev: EXAMPLE_REVISION_1, + _attachments: {} + } + const writeResponse = await storageInstance.bulkWrite( + [{ + document: clone(docData) + }], + testContext + ) + + expect(writeResponse.error).toStrictEqual({}) + const first = writeResponse.success[pkey] + expect(docData).toStrictEqual(first) + }) + + it('should error on conflict', async ({ expect }) => { + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true }) + const pkey = 'foobar' + const writeData: RxDocumentWriteData = { + key: pkey, + value: 'barfoo', + _deleted: false, + _attachments: {}, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } - afterEach(async () => { - if (storageInstance) { - await storageInstance.cleanup(Infinity); - } + await storageInstance.bulkWrite( + [{ + document: writeData + }], + testContext + ) + const writeResponse = await storageInstance.bulkWrite( + [{ + document: writeData + }], + testContext + ) + + expect(writeResponse.success).toStrictEqual({}) + expect(writeResponse.error[pkey]).not.toBe(undefined) + const first = writeResponse.error[pkey]! + + expect(first.status).toBe(409) + expect(first.documentId).toBe(pkey) + + /** + * The conflict error state must contain the + * document state in the database. + * This ensures that we can continue resolving the conflict + * without having to pull the document out of the db first. + */ + expect((first as any).documentInDb.value).toBe(writeData.value) + + /** + * The documentInDb must not have any additional attributes. + * Some RxStorage implementations store meta fields + * together with normal document data. + * These fields must never be leaked to 409 conflict errors + */ + expect(Object.keys((first as any).documentInDb).sort()).toStrictEqual(Object.keys(writeData).sort()) + }) + + it('when inserting the same document at the same time, the first call must succeed while the second has a conflict', async ({ expect }) => { + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true }) + const pkey = 'foobar' + const writeData: RxDocumentWriteData = { + key: pkey, + value: 'barfoo', + _deleted: false, + _attachments: {}, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } - describe('creation', () => { + const first = await storageInstance.bulkWrite( + [{ + document: Object.assign({}, writeData, { + value: 'first' + }) + }], + testContext + ) + + const second = await storageInstance.bulkWrite( + [{ + document: Object.assign({}, writeData, { + value: 'second' + }) + }], + testContext + ) + + expect(first.error).toStrictEqual({}) + expect(first.success[pkey]!.value).toBe('first') + + expect(second.error[pkey]!.status).toBe(409) + }) + + it('should not find the deleted document when findDocumentsById(false)', async ({ expect }) => { + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) + const pkey = 'foobar' + // make an insert + const insertData = { + key: pkey, + value: 'barfoo1', + _deleted: false, + _rev: EXAMPLE_REVISION_1, + _attachments: {}, + _meta: { + lwt: now() + } + } + const insertResponse = await storageInstance.bulkWrite( + [{ + document: insertData + }], + testContext + ) + + expect(insertResponse.error).toStrictEqual({}) + const first = insertResponse.success[pkey] + + // make an update + const updateData = Object.assign({}, insertData, { + value: 'barfoo2', + _rev: EXAMPLE_REVISION_2, + _meta: { + lwt: now() + } + }) + const updateResponse = await storageInstance.bulkWrite( + [{ + previous: insertData, + document: updateData + }], + testContext + ) + expect(updateResponse.error).toStrictEqual({}) + + // make the delete + const toDelete = { + previous: updateData, + document: Object.assign({}, first, { + value: 'barfoo_deleted', + _deleted: true, + _rev: EXAMPLE_REVISION_3, + _meta: { + lwt: now() + } + }) + } + const deleteResponse = await storageInstance.bulkWrite( + [toDelete], + testContext + ) + expect(deleteResponse.error).toStrictEqual({}) + + const foundDoc = await storageInstance.findDocumentsById([pkey], false) + + expect(foundDoc).toStrictEqual({}) + }) + + it('should be able to unset a property', async ({ expect }) => { + const schema = getTestDataSchema() + schema.required = ['key'] + + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: schema as any, + options: {}, + multiInstance: false, + devMode: true + }) + const docId = 'foobar' + const insertData: RxDocumentWriteData = { + key: docId, + value: 'barfoo1', + _attachments: {}, + _deleted: false, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } + const writeResponse = await storageInstance.bulkWrite( + [{ + document: insertData + }], + testContext + ) + + expect(writeResponse.success[docId]).not.toBe(undefined) + + const insertResponse = writeResponse.success[docId]! + const insertDataAfterWrite: RxDocumentData = Object.assign( + {}, + insertResponse, + { + _rev: insertResponse._rev + } + ) + + const updateResponse = await storageInstance.bulkWrite( + [{ + previous: insertDataAfterWrite, + document: { + key: docId, + _attachments: {}, + _deleted: false, + _rev: EXAMPLE_REVISION_2, + _meta: { + lwt: now() + } + } + }], + testContext + ) - it('open many instances on the same database name', async ({ expect }) => { - const databaseName = randomCouchString(12); - const amount = 20; + expect(updateResponse.success[docId]).not.toBe(undefined) - for (let i = 0; i < amount; i++) { - const storageInstance = await testStorage.getStorage().createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName, - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true, - password: randomCouchString(24) - }) + const updateResponseDoc = updateResponse.success[docId]! - await storageInstance.cleanup(Infinity) - await storageInstance.close(); - } + delete (updateResponseDoc)._deleted + delete (updateResponseDoc)._rev + delete (updateResponseDoc)._meta - }); - - it('open and close', async ({ expect }) => { - const collectionName = randomCouchString(12); - const databaseName = randomCouchString(12); - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName, - collectionName, - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true, - password: randomCouchString(24) - }); - expect(storageInstance.collectionName).toBe(collectionName) - expect(storageInstance.databaseName).toBe(databaseName) - }); + expect(updateResponseDoc).toStrictEqual({ + key: docId, + _attachments: {} + }) + }) + + it('should be able to do a write where only _meta fields are changed', async ({ expect }) => { + const databaseInstanceToken = randomCouchString(10) + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken, + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) - /** - * This test ensures that people do not accidentally set - * keyCompression: true in the schema but then forget to use - * the key-compression RxStorage wrapper. - */ - it('must throw if keyCompression is set but no key-compression plugin is used', async ({ expect }) => { - const schema = getPseudoSchemaForVersion(0, 'key'); - schema.keyCompression = true; - - const params: RxStorageInstanceCreationParams = { - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema, - options: {}, - multiInstance: false, - devMode: true, - password: randomCouchString(24) - } - await expect(() => storage.createStorageInstance(params)).rejects.toThrowError(newRxError('UT5', { args: { databaseName: params.databaseName, collectionName: params.collectionName } })) - - }); - - }); - - describe('.bulkWrite()', () => { - it('should write the document', async ({ expect }) => { - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - const pkey = 'foobar'; - const docData: RxDocumentWriteData = { - key: 'foobar', - value: 'barfoo1', - _deleted: false, - _meta: { - lwt: now() - }, - _rev: EXAMPLE_REVISION_1, - _attachments: {} - }; - const writeResponse = await storageInstance.bulkWrite( - [{ - document: clone(docData) - }], - testContext - ); - - expect(writeResponse.error).toStrictEqual({}) - const first = writeResponse.success[pkey]; - expect(docData).toStrictEqual(first) - }); - - it('should error on conflict', async ({ expect }) => { - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - const pkey = 'foobar' - const writeData: RxDocumentWriteData = { - key: pkey, - value: 'barfoo', - _deleted: false, - _attachments: {}, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - }; - - - await storageInstance.bulkWrite( - [{ - document: writeData - }], - testContext - ); - const writeResponse = await storageInstance.bulkWrite( - [{ - document: writeData - }], - testContext - ); - - expect(writeResponse.success).toStrictEqual({}) - expect(writeResponse.error[pkey]).not.toBe(undefined) - const first = writeResponse.error[pkey]!; - - expect(first.status).toBe(409) - expect(first.documentId).toBe(pkey) - - /** - * The conflict error state must contain the - * document state in the database. - * This ensures that we can continue resolving the conflict - * without having to pull the document out of the db first. - */ - expect((first as any).documentInDb.value).toBe(writeData.value) - - /** - * The documentInDb must not have any additional attributes. - * Some RxStorage implementations store meta fields - * together with normal document data. - * These fields must never be leaked to 409 conflict errors - */ - expect(Object.keys((first as any).documentInDb).sort()).toStrictEqual(Object.keys(writeData).sort()) - - }); - - it('when inserting the same document at the same time, the first call must succeed while the second has a conflict', async ({ expect }) => { - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - const pkey = 'foobar' - const writeData: RxDocumentWriteData = { - key: pkey, - value: 'barfoo', - _deleted: false, - _attachments: {}, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - }; - - const first = await storageInstance.bulkWrite( - [{ - document: Object.assign({}, writeData, { - value: 'first' - }) - }], - testContext - ) - - const second = await storageInstance.bulkWrite( - [{ - document: Object.assign({}, writeData, { - value: 'second' - }) - }], - testContext - ) - - expect(first.error).toStrictEqual({}) - expect(first.success[pkey]!.value).toBe('first') - - - expect(second.error[pkey]!.status).toBe(409) - - }); - - it('should not find the deleted document when findDocumentsById(false)', async ({ expect }) => { - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - - const pkey = 'foobar' - // make an insert - const insertData = { - key: pkey, - value: 'barfoo1', - _deleted: false, - _rev: EXAMPLE_REVISION_1, - _attachments: {}, - _meta: { - lwt: now() - } - }; - const insertResponse = await storageInstance.bulkWrite( - [{ - document: insertData - }], - testContext - ); - - expect(insertResponse.error).toStrictEqual({}) - const first = insertResponse.success[pkey]; - - - // make an update - const updateData = Object.assign({}, insertData, { - value: 'barfoo2', - _rev: EXAMPLE_REVISION_2, - _meta: { - lwt: now() - } - }); - const updateResponse = await storageInstance.bulkWrite( - [{ - previous: insertData, - document: updateData - }], - testContext - ); - expect(updateResponse.error).toStrictEqual({}) - - // make the delete - const toDelete = { - previous: updateData, - document: Object.assign({}, first, { - value: 'barfoo_deleted', - _deleted: true, - _rev: EXAMPLE_REVISION_3, - _meta: { - lwt: now() - } - }) - } - const deleteResponse = await storageInstance.bulkWrite( - [toDelete], - testContext - ); - expect(deleteResponse.error).toStrictEqual({}) - - const foundDoc = await storageInstance.findDocumentsById([pkey], false); - - expect(foundDoc).toStrictEqual({}) - - }); - - it('should be able to unset a property', async ({ expect }) => { - const schema = getTestDataSchema(); - schema.required = ['key']; - - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: schema as any, - options: {}, - multiInstance: false, - devMode: true - }); - const docId = 'foobar'; - const insertData: RxDocumentWriteData = { - key: docId, - value: 'barfoo1', - _attachments: {}, - _deleted: false, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - }; - const writeResponse = await storageInstance.bulkWrite( - [{ - document: insertData - }], - testContext - ); - - expect(writeResponse.success[docId]).not.toBe(undefined) - - const insertResponse = writeResponse.success[docId]!; - const insertDataAfterWrite: RxDocumentData = Object.assign( - {}, - insertResponse, - { - _rev: insertResponse._rev - } - ); - - const updateResponse = await storageInstance.bulkWrite( - [{ - previous: insertDataAfterWrite, - document: { - key: docId, - _attachments: {}, - _deleted: false, - _rev: EXAMPLE_REVISION_2, - _meta: { - lwt: now() - } - } - }], - testContext - ); - - expect(updateResponse.success[docId]).not.toBe(undefined) - - const updateResponseDoc = updateResponse.success[docId]!; - - delete (updateResponseDoc as any)._deleted; - delete (updateResponseDoc as any)._rev; - delete (updateResponseDoc as any)._meta; - - expect(updateResponseDoc).toStrictEqual({ - key: docId, - _attachments: {} - }) - - }); - - it('should be able to do a write where only _meta fields are changed', async ({ expect }) => { - const databaseInstanceToken = randomCouchString(10); - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken, - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - - const key = 'foobar'; - let docData: RxDocumentData = { - key, - value: 'barfoo1', - _attachments: {}, - _deleted: false, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now(), - foobar: 0 - } - }; - docData._rev = createRevision(databaseInstanceToken); - - const res1 = await storageInstance.bulkWrite( - [{ - document: clone(docData) - }], - testContext - ); - expect(res1.error).toStrictEqual({}) - - // change once - let newDocData: RxDocumentData = clone(docData); - newDocData._meta.foobar = 1; - newDocData._meta.lwt = now(); - newDocData._rev = createRevision(databaseInstanceToken, docData); - - const res2 = await storageInstance.bulkWrite( - [{ - previous: docData, - document: clone(newDocData) - }], - testContext - ); - expect(res2.error).toStrictEqual({}) - docData = newDocData; - - // change again - newDocData = clone(docData); - newDocData._meta.foobar = 2; - newDocData._meta.lwt = now(); - newDocData._rev = createRevision(databaseInstanceToken, docData); - - expect(parseRevision(newDocData._rev).height).toBe(3) - - const res3 = await storageInstance.bulkWrite( - [{ - previous: docData, - document: clone(newDocData) - }], - testContext - ); - expect(res3.error).toStrictEqual({}) - - docData = newDocData; - - const viaStorage = await storageInstance.findDocumentsById([key], true); - const viaStorageDoc = ensureNotFalsy(viaStorage[key]); - expect(parseRevision(viaStorageDoc._rev).height).toBe(3) - }); - it('should be able to create another instance after a write', async ({ expect }) => { - const databaseName = randomCouchString(12); - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName, - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - const docData: RxDocumentWriteData = { - key: 'foobar', - value: 'barfoo1', - _attachments: {}, - _deleted: false, - _rev: EXAMPLE_REVISION_1, - _meta: { + const key = 'foobar' + let docData: RxDocumentData = { + key, + value: 'barfoo1', + _attachments: {}, + _deleted: false, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now(), + foobar: 0 + } + } + docData._rev = createRevision(databaseInstanceToken) + + const res1 = await storageInstance.bulkWrite( + [{ + document: clone(docData) + }], + testContext + ) + expect(res1.error).toStrictEqual({}) + + // change once + let newDocData: RxDocumentData = clone(docData) + newDocData._meta.foobar = 1 + newDocData._meta.lwt = now() + newDocData._rev = createRevision(databaseInstanceToken, docData) + + const res2 = await storageInstance.bulkWrite( + [{ + previous: docData, + document: clone(newDocData) + }], + testContext + ) + expect(res2.error).toStrictEqual({}) + docData = newDocData + + // change again + newDocData = clone(docData) + newDocData._meta.foobar = 2 + newDocData._meta.lwt = now() + newDocData._rev = createRevision(databaseInstanceToken, docData) + + expect(parseRevision(newDocData._rev).height).toBe(3) + + const res3 = await storageInstance.bulkWrite( + [{ + previous: docData, + document: clone(newDocData) + }], + testContext + ) + expect(res3.error).toStrictEqual({}) + + docData = newDocData + + const viaStorage = await storageInstance.findDocumentsById([key], true) + const viaStorageDoc = ensureNotFalsy(viaStorage[key]) + expect(parseRevision(viaStorageDoc._rev).height).toBe(3) + }) + it('should be able to create another instance after a write', async () => { + const databaseName = randomCouchString(12) + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName, + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) + const docData: RxDocumentWriteData = { + key: 'foobar', + value: 'barfoo1', + _attachments: {}, + _deleted: false, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } + await storageInstance.bulkWrite( + [{ + document: clone(docData) + }], + testContext + ) + const storageInstance2 = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName, + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) + await storageInstance2.bulkWrite( + [{ + document: Object.assign( + clone(docData), + { + _rev: EXAMPLE_REVISION_2 + } + ) + }], + testContext + ) + + await Promise.all([ + storageInstance2.cleanup(Infinity).then(async () => { await storageInstance2.close() }) + ]) + }) + + it('should be able to jump more then 1 revision height in a single write operation', async ({ expect }) => { + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) + const pkey = 'foobar' + // insert + const docData: RxDocumentData = { + key: pkey, + value: 'barfoo1', + _deleted: false, + _meta: { + lwt: now() + }, + _rev: EXAMPLE_REVISION_1, + _attachments: {} + } + const insertResponse = await storageInstance.bulkWrite( + [{ + document: clone(docData) + }], + testContext + ) + expect(insertResponse.error).toStrictEqual({}) + + // update + const updated = flatCloneDocWithMeta(docData) + updated.value = 'barfoo2' + updated._meta.lwt = now() + updated._rev = EXAMPLE_REVISION_4 + const updateResponse = await storageInstance.bulkWrite( + [{ + previous: docData, + document: updated + }], + testContext + ) + + expect(updateResponse.error).toStrictEqual({}) + + // find again + const getDocFromDb = await storageInstance.findDocumentsById([docData.key], false) + + expect(getDocFromDb[pkey]).not.toBe(undefined) + const docFromDb = getDocFromDb[pkey]! + + expect(docFromDb._rev).toEqual(EXAMPLE_REVISION_4) + }) + + it('must be able create multiple storage instances on the same database and write documents', async () => { + const collectionsAmount = 3 + const docsAmount = 3 + const databaseName = randomCouchString(10) + const databaseInstanceToken = randomCouchString(10) + + await Promise.all( + new Array(collectionsAmount) + .fill(0) + .map(async () => { + const storageInstance = await storage.createStorageInstance({ + databaseInstanceToken, + databaseName, + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) + await Promise.all( + new Array(docsAmount) + .fill(0) + .map(async (_v, docId) => { + const writeData: RxDocumentWriteData = { + key: `${docId}`, + value: randomCouchString(5), + _rev: EXAMPLE_REVISION_1, + _deleted: false, + _meta: { lwt: now() + }, + _attachments: {} } - }; - await storageInstance.bulkWrite( - [{ - document: clone(docData) - }], - testContext - ); - const storageInstance2 = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName, - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - await storageInstance2.bulkWrite( - [{ - document: Object.assign( - clone(docData), - { - _rev: EXAMPLE_REVISION_2, - } - ) - }], - testContext - ); - - await Promise.all([ - storageInstance2.cleanup(Infinity).then(() => storageInstance2.close()) - ]); - }); - - it('should be able to jump more then 1 revision height in a single write operation', async ({ expect }) => { - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - const pkey = 'foobar'; - // insert - const docData: RxDocumentData = { - key: pkey, - value: 'barfoo1', - _deleted: false, - _meta: { - lwt: now() - }, - _rev: EXAMPLE_REVISION_1, - _attachments: {} - }; - const insertResponse = await storageInstance.bulkWrite( - [{ - document: clone(docData) - }], - testContext - ); - expect(insertResponse.error).toStrictEqual({}) - - // update - const updated = flatCloneDocWithMeta(docData); - updated.value = 'barfoo2'; - updated._meta.lwt = now(); - updated._rev = EXAMPLE_REVISION_4; - const updateResponse = await storageInstance.bulkWrite( - [{ - previous: docData, - document: updated - }], - testContext - ); - - expect(updateResponse.error).toStrictEqual({}) - - - // find again - const getDocFromDb = await storageInstance.findDocumentsById([docData.key], false); - - expect(getDocFromDb[pkey]).not.toBe(undefined) - const docFromDb = getDocFromDb[pkey]!; - - expect(docFromDb._rev).toEqual(EXAMPLE_REVISION_4) - - }); - - it('must be able create multiple storage instances on the same database and write documents', async ({ expect }) => { - const collectionsAmount = 3; - const docsAmount = 3; - const databaseName = randomCouchString(10); - const databaseInstanceToken = randomCouchString(10); - - await Promise.all( - new Array(collectionsAmount) - .fill(0) - .map(async () => { - const storageInstance = await storage.createStorageInstance({ - databaseInstanceToken, - databaseName, - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - await Promise.all( - new Array(docsAmount) - .fill(0) - .map(async (_v, docId) => { - const writeData: RxDocumentWriteData = { - key: `${docId}`, - value: randomCouchString(5), - _rev: EXAMPLE_REVISION_1, - _deleted: false, - _meta: { - lwt: now() - }, - _attachments: {} - }; - await storageInstance.bulkWrite([{ document: writeData }], testContext); - }) - ); - return storageInstance; - }) - ); - - }, { timeout: 15000 }); - - // Some storages had problems storing non-utf-8 chars like "é" - it('write and read with umlauts', async ({ expect }) => { - - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - const umlauts = 'äöüßé'; - const pkey = 'foobar' + umlauts - // insert - const docData: RxDocumentData = { - key: pkey, - value: 'value' + umlauts, - _deleted: false, - _meta: { - lwt: now() - }, - _rev: EXAMPLE_REVISION_1, - _attachments: {} - }; - const insertResponse = await storageInstance.bulkWrite( - [{ - document: clone(docData) - }], - testContext - ); + await storageInstance.bulkWrite([{ document: writeData }], testContext) + }) + ) + return storageInstance + }) + ) + }, { timeout: 15000 }) + + // Some storages had problems storing non-utf-8 chars like "é" + it('write and read with umlauts', async ({ expect }) => { + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) + const umlauts = 'äöüßé' + const pkey = 'foobar' + umlauts + // insert + const docData: RxDocumentData = { + key: pkey, + value: 'value' + umlauts, + _deleted: false, + _meta: { + lwt: now() + }, + _rev: EXAMPLE_REVISION_1, + _attachments: {} + } + const insertResponse = await storageInstance.bulkWrite( + [{ + document: clone(docData) + }], + testContext + ) + + expect(insertResponse.error).toStrictEqual({}) + + // find again + const getDocFromDb = await storageInstance.findDocumentsById([docData.key], false) + + expect(getDocFromDb[pkey]).not.toBe(undefined) + + const docFromDb = getDocFromDb[pkey] + + expect(docFromDb.value).toBe('value' + umlauts) + + const pkey2 = 'foobar2' + umlauts + // store another doc + const docData2: RxDocumentData = { + key: pkey2, + value: 'value2' + umlauts, + _deleted: false, + _meta: { + lwt: now() + }, + _rev: EXAMPLE_REVISION_1, + _attachments: {} + } + await storageInstance.bulkWrite( + [{ + document: clone(docData2) + }], + testContext + ) + const getDocFromDb2 = await storageInstance.findDocumentsById([docData2.key], false) + + expect(getDocFromDb2[pkey2]).not.toBe(undefined) + }) + }) + describe('.getSortComparator()', () => { + it('should sort in the correct order', async ({ expect }) => { + storageInstance = await storage.createStorageInstance<{ + _id: string + age: number + }>({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: fillWithDefaultSettings({ + version: 0, + type: 'object', + primaryKey: '_id', + properties: { + _id: { + type: 'string', + maxLength: 100 + }, + age: { + type: 'number' + } + }, + required: [ + '_id', + 'age' + ] + }), + options: {}, + multiInstance: false, + devMode: true + }) - expect(insertResponse.error).toStrictEqual({}) + const query: FilledMangoQuery = { + selector: {}, + limit: 1000, + sort: [ + { age: 'asc' } + ], + skip: 0 + } + const comparator = getSortComparator( + storageInstance.schema, + query + ) + + const doc1: any = human() + doc1._id = 'aa' + doc1.age = 1 + const doc2: any = human() + doc2._id = 'bb' + doc2.age = 100 + + // should sort in the correct order + expect([doc1, doc2]).toStrictEqual([doc1, doc2].sort(comparator)) + }) + it('should still sort in correct order when docs do not match the selector', async ({ expect }) => { + const storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getTestDataSchema(), + options: {}, + multiInstance: false, + devMode: true + }) - // find again - const getDocFromDb = await storageInstance.findDocumentsById([docData.key], false); + const matchingValue = 'foobar' + const query: FilledMangoQuery = { + selector: { + value: { + $eq: matchingValue + } + }, + sort: [ + { key: 'asc' } + ], + skip: 0 + } - expect(getDocFromDb[pkey]).not.toBe(undefined) + const comparator = getSortComparator( + storageInstance.schema, + query + ) + + const docs: TestDocType[] = [ + { + value: matchingValue, + key: 'aaa' + }, + { + value: 'barfoo', + key: 'bbb' + } + ] + + const result = comparator( + docs[0]!, + docs[1]! + + ) + + expect(result).toStrictEqual(-1) + }) + it('should work with a more complex query', async ({ expect }) => { + const storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getTestDataSchema(), + options: {}, + multiInstance: false, + devMode: true + }) - const docFromDb = getDocFromDb[pkey]; + const matchingValue = 'aaa' + const query: FilledMangoQuery = { + selector: { + $or: [ + { + value: matchingValue, + key: matchingValue + }, + { + value: 'barfoo', + key: 'barfoo' + } + ], + key: matchingValue + }, + sort: [ + { key: 'asc' } + ], + skip: 0 + } - expect(docFromDb.value).toBe('value' + umlauts) + const comparator = getSortComparator( + storageInstance.schema, + query + ) + + const docs: TestDocType[] = [ + { + value: matchingValue, + key: matchingValue + }, + { + value: 'bbb', + key: 'bbb' + } + ] + + const result = comparator( + docs[0]!, + docs[1]! + + ) + + expect(result).toStrictEqual(-1) + }) + }) - const pkey2 = 'foobar2' + umlauts - // store another doc - const docData2: RxDocumentData = { - key: pkey2, - value: 'value2' + umlauts, - _deleted: false, - _meta: { - lwt: now() - }, - _rev: EXAMPLE_REVISION_1, - _attachments: {} - }; - await storageInstance.bulkWrite( - [{ - document: clone(docData2) - }], - testContext - ); - const getDocFromDb2 = await storageInstance.findDocumentsById([docData2.key], false); - - expect(getDocFromDb2[pkey2]).not.toBe(undefined) - - }); - - - }); - - describe('.getSortComparator()', () => { - it('should sort in the correct order', async ({ expect }) => { - storageInstance = await storage.createStorageInstance<{ - _id: string; - age: number; - }>({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: fillWithDefaultSettings({ - version: 0, - type: 'object', - primaryKey: '_id', - properties: { - _id: { - type: 'string', - maxLength: 100 - }, - age: { - type: 'number' - } - }, - required: [ - '_id', - 'age' - ] - }), - options: {}, - multiInstance: false, - devMode: true - }); - - const query: FilledMangoQuery = { - selector: {}, - limit: 1000, - sort: [ - { age: 'asc' } - ], - skip: 0 - }; - const comparator = getSortComparator( - storageInstance.schema, - query - ); - - const doc1: any = human(); - doc1._id = 'aa'; - doc1.age = 1; - const doc2: any = human(); - doc2._id = 'bb'; - doc2.age = 100; - - // should sort in the correct order - expect([doc1, doc2]).toStrictEqual([doc1, doc2].sort(comparator)) - }); - it('should still sort in correct order when docs do not match the selector', async ({ expect }) => { - const storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getTestDataSchema(), - options: {}, - multiInstance: false, - devMode: true - }); - - const matchingValue = 'foobar'; - const query: FilledMangoQuery = { - selector: { - value: { - $eq: matchingValue - } - }, - sort: [ - { key: 'asc' } - ], - skip: 0 - }; - - const comparator = getSortComparator( - storageInstance.schema, - query - ); - - const docs: TestDocType[] = [ - { - value: matchingValue, - key: 'aaa' - }, - { - value: 'barfoo', - key: 'bbb' - } - ]; - - const result = comparator( - docs[0]!, - docs[1]! - - ); - - expect(result).toStrictEqual(-1) - }); - it('should work with a more complex query', async ({ expect }) => { - const storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getTestDataSchema(), - options: {}, - multiInstance: false, - devMode: true - }); - - const matchingValue = 'aaa'; - const query: FilledMangoQuery = { - selector: { - $or: [ - { - value: matchingValue, - key: matchingValue - }, - { - value: 'barfoo', - key: 'barfoo' - } - ], - key: matchingValue - }, - sort: [ - { key: 'asc' } - ], - skip: 0 - }; - - const comparator = getSortComparator( - storageInstance.schema, - query - ); - - const docs: TestDocType[] = [ - { - value: matchingValue, - key: matchingValue - }, - { - value: 'bbb', - key: 'bbb' - } - ]; - - const result = comparator( - docs[0]!, - docs[1]! - - ); - - expect(result).toStrictEqual(-1) - }); - }); - - describe('.getQueryMatcher()', () => { - it('should match the right docs', async ({ expect }) => { - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, '_id' as any), - options: {}, - multiInstance: false, - devMode: true - }); - - const query: FilledMangoQuery = { - selector: { - age: { - $gt: 10, - $ne: 50 - } - }, - sort: [ - { _id: 'asc' } - ], - skip: 0 - }; - - const queryMatcher = getQueryMatcher( - storageInstance.schema, - query - ); - - const doc1: any = human(); - doc1._id = 'aa'; - doc1.age = 1; - const doc2: any = human(); - doc2._id = 'bb'; - doc2.age = 100; - - expect(queryMatcher(doc1)).toStrictEqual(false) - expect(queryMatcher(doc2)).toStrictEqual(true) - }); - it('should match the nested document', ({ expect }) => { - const schema = getNestedDocSchema(); - const query: FilledMangoQuery = { - selector: { - 'nes.ted': { - $eq: 'barfoo' - } - }, - sort: [ - { id: 'asc' } - ], - skip: 0 - }; - - const queryMatcher = getQueryMatcher( - schema, - query - ); - - const notMatchingDoc = { - id: 'foobar', - nes: { - ted: 'xxx' - }, - _deleted: false, - _attachments: {}, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - }; - const matchingDoc = { - id: 'foobar', - nes: { - ted: 'barfoo' - }, - _deleted: false, - _attachments: {}, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - }; - - expect(queryMatcher(notMatchingDoc)).toStrictEqual(false) - expect(queryMatcher(matchingDoc)).toStrictEqual(true) - - }); - }); - - describe('.query()', () => { - it('should find all documents', async ({ expect }) => { - storageInstance = await storage.createStorageInstance<{ key: string; value: string; }>({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion<{ key: string; value: string; }>(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - - const writeData = { - key: 'foobar', - value: 'barfoo', - _deleted: false, - _attachments: {}, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - }; - - await storageInstance.bulkWrite( - [{ - document: writeData - }], - testContext - ); - - - const writeData2 = { - key: 'foobar2', - value: 'barfoo2', - _deleted: false, - _attachments: {}, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - }; - await storageInstance.bulkWrite( - [{ - document: writeData2 - }], - testContext - ); - - const preparedQuery = prepareQuery( - storageInstance.schema, - { - selector: { - _deleted: false - }, - sort: [{ key: 'asc' }], - skip: 0 - } - ); - const allDocs = await storageInstance.query(preparedQuery); - const first = allDocs.documents[0]; - - expect(first).not.toBe(undefined) - expect(first.value).toBe('barfoo') - - }); - it('should sort in the correct order', async ({ expect }) => { - storageInstance = await storage.createStorageInstance<{ key: string; value: string; }>({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getTestDataSchema(), - options: {}, - multiInstance: false, - devMode: true - }); - - await storageInstance.bulkWrite([ - { - document: getWriteData({ value: 'a' }) - }, - { - document: getWriteData({ value: 'b' }) - }, - { - document: getWriteData({ value: 'c' }) - }, - ], testContext); - - const preparedQuery = prepareQuery( - storageInstance.schema, - { - selector: {}, - sort: [ - { value: 'desc' } - ], - skip: 0 - } - ); - const allDocs = await storageInstance.query(preparedQuery); + describe('.getQueryMatcher()', () => { + it('should match the right docs', async ({ expect }) => { + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, '_id' as any), + options: {}, + multiInstance: false, + devMode: true + }) - expect(allDocs.documents.length).toBe(3) - expect(allDocs.documents[0].value).toBe('c') - expect(allDocs.documents[1].value).toBe('b') - expect(allDocs.documents[2].value).toBe('a') + const query: FilledMangoQuery = { + selector: { + age: { + $gt: 10, + $ne: 50 + } + }, + sort: [ + { _id: 'asc' } + ], + skip: 0 + } - }); + const queryMatcher = getQueryMatcher( + storageInstance.schema, + query + ) + + const doc1: any = human() + doc1._id = 'aa' + doc1.age = 1 + const doc2: any = human() + doc2._id = 'bb' + doc2.age = 100 + + expect(queryMatcher(doc1)).toStrictEqual(false) + expect(queryMatcher(doc2)).toStrictEqual(true) + }) + it('should match the nested document', ({ expect }) => { + const schema = getNestedDocSchema() + const query: FilledMangoQuery = { + selector: { + 'nes.ted': { + $eq: 'barfoo' + } + }, + sort: [ + { id: 'asc' } + ], + skip: 0 + } - /** - * For event-reduce to work, - * we must ensure we there is always a deterministic sort order. - */ - it('should have the same deterministic order of .query() and .getSortComparator()', async ({ expect }) => { - const schema: RxJsonSchema> = fillWithDefaultSettings({ - version: 0, - primaryKey: 'id', - type: 'object', - properties: { - id: { - type: 'string', - maxLength: 100 - }, - equal: { - type: 'string', - maxLength: 20, - enum: ['foobar'] - }, - increment: { - type: 'number', - minimum: 0, - maximum: 1000, - multipleOf: 1 - }, - random: { - type: 'string', - maxLength: 100 - } - }, - indexes: [ - /** - * RxDB will always append the primaryKey to an index - * if the primaryKey was not used in the index before. - * This ensures we have a deterministic sorting when querying documents - * from that index. - */ - ['equal', 'id'], - ['increment', 'id'], - ['random', 'id'], - [ - 'equal', - 'increment', - 'id' - ] - ], - required: [ - 'id', - 'equal', - 'increment', - 'random' - ] - }); - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema, - options: {}, - multiInstance: false, - devMode: true - }); - - const docsAmount = 6; - const docData: RxDocumentWriteData[] = new Array(docsAmount) - .fill(0) - .map((_x, idx) => ({ - id: randomString(10), - equal: 'foobar', - random: randomString(10), - increment: idx + 1, - _deleted: false, - _attachments: {}, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - })); - const writeResponse: RxStorageBulkWriteResponse = await storageInstance.bulkWrite( - docData.map(d => ({ document: d })), - testContext - ); - if (Object.keys(writeResponse.error).length > 0) { - throw new Error('could not save'); - } - const docs = Object.values(writeResponse.success); - - async function testQuery(query: FilledMangoQuery): Promise { - const preparedQuery = prepareQuery( - storageInstance.schema, - query - ); - const docsViaQuery = (await storageInstance.query(preparedQuery)).documents; - if (docsViaQuery.length !== docsAmount) { - throw new Error('docs missing'); - } - const sortComparator = getSortComparator( - (storageInstance as any).schema, - query - ); - const docsViaSort = shuffleArray(docs).sort(sortComparator); - expect(docsViaQuery).toStrictEqual(docsViaSort) - } - const queries: FilledMangoQuery[] = [ - { - selector: {}, - sort: [ - { id: 'asc' } - ], - skip: 0 - }, - { - selector: {}, - sort: [ - { equal: 'asc' }, - /** - * RxDB will always append the primaryKey as last sort parameter - * if the primary key is not used in the sorting before. - */ - { id: 'asc' } - ], - skip: 0 - }, - { - selector: {}, - sort: [ - { increment: 'desc' }, - { id: 'asc' } - ], - skip: 0 - }, - { - selector: {}, - sort: [ - { equal: 'asc' }, - { increment: 'desc' }, - { id: 'asc' } - ], - skip: 0 - } - ]; - for (const query of queries) { - await testQuery(query); - } + const queryMatcher = getQueryMatcher( + schema, + query + ) + + const notMatchingDoc = { + id: 'foobar', + nes: { + ted: 'xxx' + }, + _deleted: false, + _attachments: {}, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } + const matchingDoc = { + id: 'foobar', + nes: { + ted: 'barfoo' + }, + _deleted: false, + _attachments: {}, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } - }); - it('should be able to search over a nested object', async ({ expect }) => { - const schema = getNestedDocSchema(); - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema, - options: {}, - multiInstance: false, - devMode: true - }); - const insertResult = await storageInstance.bulkWrite([ - { - document: { - id: 'foobar', - nes: { - ted: 'barfoo' - }, - _deleted: false, - _attachments: {}, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - } - } - ], testContext); - - expect(insertResult.error).toStrictEqual({}) - - const preparedQuery = prepareQuery( - schema, - { - selector: { - 'nes.ted': { - $eq: 'barfoo' - } - }, - sort: [ - { 'nes.ted': 'asc' }, - { id: 'asc' } - ], - skip: 0 - } - ); + expect(queryMatcher(notMatchingDoc)).toStrictEqual(false) + expect(queryMatcher(matchingDoc)).toStrictEqual(true) + }) + }) - const results = await storageInstance.query(preparedQuery); + describe('.query()', () => { + it('should find all documents', async ({ expect }) => { + storageInstance = await storage.createStorageInstance<{ key: string, value: string }>({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion<{ key: string, value: string }>(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) - expect(results.documents.length).toBe(1) + const writeData = { + key: 'foobar', + value: 'barfoo', + _deleted: false, + _attachments: {}, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } - }); + await storageInstance.bulkWrite( + [{ + document: writeData + }], + testContext + ) + + const writeData2 = { + key: 'foobar2', + value: 'barfoo2', + _deleted: false, + _attachments: {}, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } + await storageInstance.bulkWrite( + [{ + document: writeData2 + }], + testContext + ) + + const preparedQuery = prepareQuery( + storageInstance.schema, + { + selector: { + _deleted: false + }, + sort: [{ key: 'asc' }], + skip: 0 + } + ) + const allDocs = await storageInstance.query(preparedQuery) + const first = allDocs.documents[0] + + expect(first).not.toBe(undefined) + expect(first.value).toBe('barfoo') + }) + it('should sort in the correct order', async ({ expect }) => { + storageInstance = await storage.createStorageInstance<{ key: string, value: string }>({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getTestDataSchema(), + options: {}, + multiInstance: false, + devMode: true + }) + + await storageInstance.bulkWrite([ + { + document: getWriteData({ value: 'a' }) + }, + { + document: getWriteData({ value: 'b' }) + }, + { + document: getWriteData({ value: 'c' }) + } + ], testContext) + + const preparedQuery = prepareQuery( + storageInstance.schema, + { + selector: {}, + sort: [ + { value: 'desc' } + ], + skip: 0 + } + ) + const allDocs = await storageInstance.query(preparedQuery) + + expect(allDocs.documents.length).toBe(3) + expect(allDocs.documents[0].value).toBe('c') + expect(allDocs.documents[1].value).toBe('b') + expect(allDocs.documents[2].value).toBe('a') + }) + + /** + * For event-reduce to work, + * we must ensure we there is always a deterministic sort order. + */ + it('should have the same deterministic order of .query() and .getSortComparator()', async ({ expect }) => { + const schema: RxJsonSchema> = fillWithDefaultSettings({ + version: 0, + primaryKey: 'id', + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 100 + }, + equal: { + type: 'string', + maxLength: 20, + enum: ['foobar'] + }, + increment: { + type: 'number', + minimum: 0, + maximum: 1000, + multipleOf: 1 + }, + random: { + type: 'string', + maxLength: 100 + } + }, + indexes: [ /** - * This failed on some storages when there are more - * documents then the batchSize of the RxStorage - */ - it('querying many documents should work', async ({ expect }) => { - const schema = getTestDataSchema(); - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema, - options: {}, - multiInstance: false, - devMode: true - }); - - const amount = 100; - - await storageInstance.bulkWrite( - new Array(amount) - .fill(0) - .map((_v, idx) => ({ - document: getWriteData({ - key: idx.toString().padStart(5, '0') + '-' + randomString(10), - value: idx + '' - }) - })), - testContext - ); - - const preparedQuery = prepareQuery( - schema, - { - selector: {}, - skip: 0, - sort: [ - { key: 'asc' } - ] - } - ); - const results = await storageInstance.query(preparedQuery); - - expect(results.documents.length).toBe(amount) - - }); - }); - - describe('.count()', () => { - it('should count the correct amount', async ({ expect }) => { - const schema = getTestDataSchema(); - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema, - options: {}, - multiInstance: false, - devMode: true - }); - const preparedQueryAll = prepareQuery( - schema, - { - selector: {}, - sort: [ - { key: 'asc' } - ], - skip: 0 - } - ); - async function ensureCountIs(nr: number) { - const result = await storageInstance.count(preparedQueryAll); - expect(result.count).toBe(nr) - } - await ensureCountIs(0); - - - await storageInstance.bulkWrite([{ document: getWriteData() }], testContext); - await ensureCountIs(1); - - const writeData = getWriteData(); - await storageInstance.bulkWrite([{ document: writeData }], testContext); - await ensureCountIs(2); - }); - }); - describe('.findDocumentsById()', () => { - it('should find the documents', async ({ expect }) => { - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getPseudoSchemaForVersion(0, 'key'), - options: {}, - multiInstance: false, - devMode: true - }); - - const pkey = 'foobar' - const docData = { - key: pkey, - value: 'barfoo', - _deleted: false, - _attachments: {}, - _rev: EXAMPLE_REVISION_1, - _meta: { - lwt: now() - } - }; - await storageInstance.bulkWrite( - [{ - document: docData - }], - testContext - ); + * RxDB will always append the primaryKey to an index + * if the primaryKey was not used in the index before. + * This ensures we have a deterministic sorting when querying documents + * from that index. + */ + ['equal', 'id'], + ['increment', 'id'], + ['random', 'id'], + [ + 'equal', + 'increment', + 'id' + ] + ], + required: [ + 'id', + 'equal', + 'increment', + 'random' + ] + }) + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema, + options: {}, + multiInstance: false, + devMode: true + }) - const found = await storageInstance.findDocumentsById(['foobar'], false); - const foundDoc = found[pkey]; + const docsAmount = 6 + const docData: Array> = new Array(docsAmount) + .fill(0) + .map((_x, idx) => ({ + id: randomString(10), + equal: 'foobar', + random: randomString(10), + increment: idx + 1, + _deleted: false, + _attachments: {}, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + })) + const writeResponse: RxStorageBulkWriteResponse = await storageInstance.bulkWrite( + docData.map(d => ({ document: d })), + testContext + ) + if (Object.keys(writeResponse.error).length > 0) { + throw new Error('could not save') + } + const docs = Object.values(writeResponse.success) + + async function testQuery (query: FilledMangoQuery): Promise { + const preparedQuery = prepareQuery( + storageInstance.schema, + query + ) + const docsViaQuery = (await storageInstance.query(preparedQuery)).documents + if (docsViaQuery.length !== docsAmount) { + throw new Error('docs missing') + } + const sortComparator = getSortComparator( + (storageInstance as any).schema, + query + ) + const docsViaSort = shuffleArray(docs).sort(sortComparator) + expect(docsViaQuery).toStrictEqual(docsViaSort) + } + const queries: Array> = [ + { + selector: {}, + sort: [ + { id: 'asc' } + ], + skip: 0 + }, + { + selector: {}, + sort: [ + { equal: 'asc' }, + /** + * RxDB will always append the primaryKey as last sort parameter + * if the primary key is not used in the sorting before. + */ + { id: 'asc' } + ], + skip: 0 + }, + { + selector: {}, + sort: [ + { increment: 'desc' }, + { id: 'asc' } + ], + skip: 0 + }, + { + selector: {}, + sort: [ + { equal: 'asc' }, + { increment: 'desc' }, + { id: 'asc' } + ], + skip: 0 + } + ] + for (const query of queries) { + await testQuery(query) + } + }) + it('should be able to search over a nested object', async ({ expect }) => { + const schema = getNestedDocSchema() + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema, + options: {}, + multiInstance: false, + devMode: true + }) + const insertResult = await storageInstance.bulkWrite([ + { + document: { + id: 'foobar', + nes: { + ted: 'barfoo' + }, + _deleted: false, + _attachments: {}, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } + } + ], testContext) + + expect(insertResult.error).toStrictEqual({}) + + const preparedQuery = prepareQuery( + schema, + { + selector: { + 'nes.ted': { + $eq: 'barfoo' + } + }, + sort: [ + { 'nes.ted': 'asc' }, + { id: 'asc' } + ], + skip: 0 + } + ) + + const results = await storageInstance.query(preparedQuery) + + expect(results.documents.length).toBe(1) + }) + /** + * This failed on some storages when there are more + * documents then the batchSize of the RxStorage + */ + it('querying many documents should work', async ({ expect }) => { + const schema = getTestDataSchema() + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema, + options: {}, + multiInstance: false, + devMode: true + }) - expect(foundDoc).toStrictEqual(docData) - }); + const amount = 100 + + await storageInstance.bulkWrite( + new Array(amount) + .fill(0) + .map((_v, idx) => ({ + document: getWriteData({ + key: idx.toString().padStart(5, '0') + '-' + randomString(10), + value: idx + '' + }) + })), + testContext + ) + + const preparedQuery = prepareQuery( + schema, + { + selector: {}, + skip: 0, + sort: [ + { key: 'asc' } + ] + } + ) + const results = await storageInstance.query(preparedQuery) - /** - * Some storage implementations ran into some limits - * like SQLite SQLITE_MAX_VARIABLE_NUMBER etc. - * Writing many documents must just work and the storage itself - * has to workaround any problems with that. - */ - it('should be able to insert and fetch many documents', async ({ expect }) => { - storageInstance = await storage.createStorageInstance({ - databaseInstanceToken: randomCouchString(10), - databaseName: randomCouchString(12), - collectionName: randomCouchString(12), - schema: getTestDataSchema(), - options: {}, - multiInstance: false, - devMode: true - }); - - const amount = 5000; - const writeRows = new Array(amount) - .fill(0) - .map(() => ({ document: getWriteData() })); - - // insert - const writeResult = await storageInstance.bulkWrite(writeRows, 'insert-many-' + amount); - expect(writeResult.error).toStrictEqual({}) - - // fetch again - const fetchResult = await storageInstance.findDocumentsById(writeRows.map(r => r.document.key), false); - expect(Object.keys(fetchResult).length).toStrictEqual(amount) - }, { timeout: 50000 }); - }); - - }); - - describe("RxStorageInstance Queries", () => { - testCorrectQueries(suite, testStorage, { - testTitle: '$gt/$gte', - data: [ - human('aa', 10, 'alice'), - human('bb', 20, 'bob'), - /** - * One must have a longer id - * because we had many bugs around how padLeft - * works on custom indexes. - */ - human('cc-looong-id', 30, 'carol'), - human('dd', 40, 'dave'), - human('ee', 50, 'eve') + expect(results.documents.length).toBe(amount) + }) + }) + + describe('.count()', () => { + it('should count the correct amount', async ({ expect }) => { + const schema = getTestDataSchema() + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema, + options: {}, + multiInstance: false, + devMode: true + }) + const preparedQueryAll = prepareQuery( + schema, + { + selector: {}, + sort: [ + { key: 'asc' } ], - schema: withIndexes(schemas.human, [ - ['age'], - ['age', 'firstName'], - ['firstName'], - ['passportId'] - ]), - queries: [ - { - info: 'normal $gt by number', - query: { - selector: { - age: { - $gt: 20 - } - }, - sort: [{ age: 'asc' }] - }, - selectorSatisfiedByIndex: true, - expectedResultDocIds: [ - 'cc-looong-id', - 'dd', - 'ee' - ] - }, + skip: 0 + } + ) + async function ensureCountIs (nr: number): Promise { + const result = await storageInstance.count(preparedQueryAll) + expect(result.count).toBe(nr) + } + await ensureCountIs(0) + + await storageInstance.bulkWrite([{ document: getWriteData() }], testContext) + await ensureCountIs(1) + + const writeData = getWriteData() + await storageInstance.bulkWrite([{ document: writeData }], testContext) + await ensureCountIs(2) + }) + }) + describe('.findDocumentsById()', () => { + it('should find the documents', async ({ expect }) => { + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getPseudoSchemaForVersion(0, 'key'), + options: {}, + multiInstance: false, + devMode: true + }) + + const pkey = 'foobar' + const docData = { + key: pkey, + value: 'barfoo', + _deleted: false, + _attachments: {}, + _rev: EXAMPLE_REVISION_1, + _meta: { + lwt: now() + } + } + await storageInstance.bulkWrite( + [{ + document: docData + }], + testContext + ) + + const found = await storageInstance.findDocumentsById(['foobar'], false) + const foundDoc = found[pkey] + + expect(foundDoc).toStrictEqual(docData) + }) + + /** + * Some storage implementations ran into some limits + * like SQLite SQLITE_MAX_VARIABLE_NUMBER etc. + * Writing many documents must just work and the storage itself + * has to workaround any problems with that. + */ + it('should be able to insert and fetch many documents', async ({ expect }) => { + storageInstance = await storage.createStorageInstance({ + databaseInstanceToken: randomCouchString(10), + databaseName: randomCouchString(12), + collectionName: randomCouchString(12), + schema: getTestDataSchema(), + options: {}, + multiInstance: false, + devMode: true + }) + + const amount = 5000 + const writeRows = new Array(amount) + .fill(0) + .map(() => ({ document: getWriteData() })) + + // insert + const writeResult = await storageInstance.bulkWrite(writeRows, 'insert-many-' + amount) + expect(writeResult.error).toStrictEqual({}) + + // fetch again + const fetchResult = await storageInstance.findDocumentsById(writeRows.map(r => r.document.key), false) + expect(Object.keys(fetchResult).length).toStrictEqual(amount) + }, { timeout: 50000 }) + }) + }) + + describe('RxStorageInstance Queries', () => { + testCorrectQueries(suite, testStorage, { + testTitle: '$gt/$gte', + data: [ + human('aa', 10, 'alice'), + human('bb', 20, 'bob'), + /** + * One must have a longer id + * because we had many bugs around how padLeft + * works on custom indexes. + */ + human('cc-looong-id', 30, 'carol'), + human('dd', 40, 'dave'), + human('ee', 50, 'eve') + ], + schema: withIndexes(schemas.human, [ + ['age'], + ['age', 'firstName'], + ['firstName'], + ['passportId'] + ]), + queries: [ + { + info: 'normal $gt by number', + query: { + selector: { + age: { + $gt: 20 + } + }, + sort: [{ age: 'asc' }] + }, + selectorSatisfiedByIndex: true, + expectedResultDocIds: [ + 'cc-looong-id', + 'dd', + 'ee' + ] + }, + { + info: 'normal $gte', + query: { + selector: { + age: { + $gte: 20 + } + }, + sort: [{ age: 'asc' }] + }, + selectorSatisfiedByIndex: true, + expectedResultDocIds: [ + 'bb', + 'cc-looong-id', + 'dd', + 'ee' + ] + }, + { + info: '$gt on primary key', + query: { + selector: { + passportId: { + $gt: 'dd' + } + }, + sort: [{ passportId: 'asc' }] + }, + selectorSatisfiedByIndex: true, + expectedResultDocIds: [ + 'ee' + ] + }, + { + info: '$gt and $gte on same field', + query: { + selector: { + age: { + $gte: 40, + $gt: 19 + } + }, + sort: [{ age: 'asc' }] + }, + expectedResultDocIds: [ + 'dd', + 'ee' + ] + }, + { + info: 'sort by something that is not in the selector', + query: { + selector: { + age: { + $gt: 20 + } + }, + sort: [{ passportId: 'asc' }] + }, + selectorSatisfiedByIndex: true, + expectedResultDocIds: [ + 'cc-looong-id', + 'dd', + 'ee' + ] + }, + { + info: 'with string comparison', + query: { + selector: { + firstName: { + $gt: 'bob' + } + } + }, + selectorSatisfiedByIndex: true, + expectedResultDocIds: [ + 'cc-looong-id', + 'dd', + 'ee' + ] + }, + { + info: 'compare more then one field', + query: { + selector: { + age: { + $gt: 20 + }, + firstName: { + $gt: 'a' + } + } + }, + selectorSatisfiedByIndex: false, + expectedResultDocIds: [ + 'cc-looong-id', + 'dd', + 'ee' + ] + } + ] + }) + + testCorrectQueries(suite, testStorage, { + testTitle: '$lt/$lte', + data: [ + human('aa', 10, 'alice'), + human('bb', 20, 'bob'), + /** + * One must have a longer id + * because we had many bugs around how padLeft + * works on custom indexes. + */ + human('cc-looong-id', 30, 'carol'), + human('dd', 40, 'dave'), + human('ee', 50, 'eve') + ], + schema: withIndexes(schemas.human, [ + ['age'], + ['age', 'firstName'], + ['firstName'], + ['passportId'] + ]), + queries: [ + { + info: 'normal $lt', + query: { + selector: { + age: { + $lt: 40 + } + }, + sort: [{ age: 'asc' }] + }, + selectorSatisfiedByIndex: true, + expectedResultDocIds: [ + 'aa', + 'bb', + 'cc-looong-id' + ] + }, + { + info: 'normal $lte', + query: { + selector: { + age: { + $lte: 40 + } + }, + sort: [{ age: 'asc' }] + }, + selectorSatisfiedByIndex: true, + expectedResultDocIds: [ + 'aa', + 'bb', + 'cc-looong-id', + 'dd' + ] + }, + { + info: 'sort by something that is not in the selector', + query: { + selector: { + age: { + $lt: 40 + } + }, + sort: [{ passportId: 'asc' }] + }, + selectorSatisfiedByIndex: false, + expectedResultDocIds: [ + 'aa', + 'bb', + 'cc-looong-id' + ] + }, + { + /** + * @link https://github.com/pubkey/rxdb/pull/4751 + */ + info: '$lt on primaryKey', + query: { + selector: { + passportId: { + $lt: 'bb' + } + }, + sort: [{ passportId: 'asc' }] + }, + expectedResultDocIds: [ + 'aa' + ] + }, + { + info: 'compare more then one field', + query: { + selector: { + age: { + $lt: 40 + }, + firstName: { + $lt: 'd' + } + } + }, + selectorSatisfiedByIndex: false, + expectedResultDocIds: [ + 'aa', + 'bb', + 'cc-looong-id' + ] + } + ] + }) + testCorrectQueries(suite, testStorage, { + testTitle: 'nested properties', + data: [ + nestedHuman({ + passportId: 'aaa', + mainSkill: { + level: 6, + name: 'zzz' + } + }), + nestedHuman({ + passportId: 'bbb', + mainSkill: { + level: 4, + name: 'ttt' + } + }), + nestedHuman({ + passportId: 'ccc', + mainSkill: { + level: 3, + name: 'ccc' + } + }) + ], + schema: withIndexes(schemas.nestedHuman, [ + ['mainSkill.level'], + ['mainSkill.name'] + ]), + queries: [ + { + info: 'sort by nested mainSkill.name', + query: { + selector: { + }, + sort: [{ 'mainSkill.name': 'asc' }] + }, + expectedResultDocIds: [ + 'ccc', + 'bbb', + 'aaa' + ] + } + ] + }) + testCorrectQueries(suite, testStorage, { + testTitle: '$or', + data: [ + simpleHumanV3({ + passportId: 'aaa', + oneOptional: 'A' + }), + simpleHumanV3({ + passportId: 'bbb', + oneOptional: 'B' + }), + simpleHumanV3({ + passportId: 'ccc' + }) + ], + schema: withIndexes(schemas.humanMinimal, [ + ]), + queries: [ + { + info: 'match A or B', + query: { + selector: { + $or: [ { - info: 'normal $gte', - query: { - selector: { - age: { - $gte: 20 - } - }, - sort: [{ age: 'asc' }] - }, - selectorSatisfiedByIndex: true, - expectedResultDocIds: [ - 'bb', - 'cc-looong-id', - 'dd', - 'ee' - ] + passportId: 'aaa' }, { - info: '$gt on primary key', - query: { - selector: { - passportId: { - $gt: 'dd' - } - }, - sort: [{ passportId: 'asc' }] - }, - selectorSatisfiedByIndex: true, - expectedResultDocIds: [ - 'ee' - ] - }, + passportId: 'bbb' + } + ] + }, + sort: [{ passportId: 'asc' }] + }, + expectedResultDocIds: [ + 'aaa', + 'bbb' + ] + }, + { + info: 'match with optional field', + query: { + selector: { + passportId: { + $eq: 'ccc' + }, + $or: [ { - info: '$gt and $gte on same field', - query: { - selector: { - age: { - $gte: 40, - $gt: 19, - }, - }, - sort: [{ age: 'asc' }] - }, - expectedResultDocIds: [ - 'dd', - 'ee' - ] + oneOptional: { + $ne: 'foobar1' + } }, { - info: 'sort by something that is not in the selector', - query: { - selector: { - age: { - $gt: 20 - } - }, - sort: [{ passportId: 'asc' }] - }, - selectorSatisfiedByIndex: true, - expectedResultDocIds: [ - 'cc-looong-id', - 'dd', - 'ee' - ] - }, + oneOptional: { + $ne: 'foobar2' + } + } + ] + }, + sort: [{ passportId: 'asc' }] + }, + expectedResultDocIds: [ + 'ccc' + ] + }, + { + info: 'match non on non-existing optional field', + query: { + selector: { + passportId: { + $eq: 'foobar' + }, + $or: [ { - info: 'with string comparison', - query: { - selector: { - firstName: { - $gt: 'bob' - } - } - }, - selectorSatisfiedByIndex: true, - expectedResultDocIds: [ - 'cc-looong-id', - 'dd', - 'ee' - ] + oneOptional: { + $ne: 'foobar1' + } }, { - info: 'compare more then one field', - query: { - selector: { - age: { - $gt: 20 - }, - firstName: { - $gt: 'a' - } - } - }, - selectorSatisfiedByIndex: false, - expectedResultDocIds: [ - 'cc-looong-id', - 'dd', - 'ee' - ] + oneOptional: { + $ne: 'foobar2' + } + } + ] + }, + sort: [{ passportId: 'asc' }] + }, + expectedResultDocIds: [] + } + ] + }) + testCorrectQueries(suite, testStorage, { + testTitle: '$in', + data: [ + human('aa', 10, 'alice'), + human('bb', 20, 'bob'), + human('cc', 30, 'carol'), + human('dd', 40, 'dave'), + human('ee', 50, 'eve') + ], + schema: schemas.human, + queries: [ + { + info: 'get first', + query: { + selector: { + firstName: { + $in: ['alice'] + } + }, + sort: [{ passportId: 'asc' }] + }, + expectedResultDocIds: [ + 'aa' + ] + }, + { + info: 'get by multiple', + query: { + selector: { + firstName: { + $in: ['alice', 'bob'] + } + }, + sort: [{ passportId: 'asc' }] + }, + expectedResultDocIds: [ + 'aa', + 'bb' + ] + }, + { + info: 'get none matching', + query: { + selector: { + firstName: { + $in: ['foobar', 'barfoo'] + } + }, + sort: [{ passportId: 'asc' }] + }, + expectedResultDocIds: [] + }, + { + info: 'get by primary key', + query: { + selector: { + passportId: { + $in: ['aa', 'cc', 'ee'] + } + } + }, + expectedResultDocIds: ['aa', 'cc', 'ee'] + } + ] + }) + testCorrectQueries(suite, testStorage, { + testTitle: '$elemMatch/$size', + data: [ + { + name: 'foo1', + skills: [ + { + name: 'bar1', + damage: 10 + }, + { + name: 'bar2', + damage: 5 + } + ] + }, + { + name: 'foo2', + skills: [ + { + name: 'bar3', + damage: 10 + }, + { + name: 'bar4', + damage: 10 + } + ] + }, + { + name: 'foo3', + skills: [ + { + name: 'bar5', + damage: 5 + } + ] + } + ], + schema: schemas.heroArray, + queries: [ + { + info: '$elemMatch', + query: { + selector: { + skills: { + $elemMatch: { + damage: 5 + } + } + }, + sort: [{ name: 'asc' }] + }, + selectorSatisfiedByIndex: false, + expectedResultDocIds: [ + 'foo1', + 'foo3' + ] + }, + { + info: '$elemMatch with other operator', + query: { + selector: { + name: { + $eq: 'foo3' + }, + skills: { + $elemMatch: { + damage: 5 + } + } + }, + sort: [{ name: 'asc' }] + }, + selectorSatisfiedByIndex: false, + expectedResultDocIds: [ + 'foo3' + ] + }, + { + info: '$size', + query: { + selector: { + skills: { + $size: 1 + } + }, + sort: [{ name: 'asc' }] + }, + selectorSatisfiedByIndex: false, + expectedResultDocIds: [ + 'foo3' + ] + } + ] + }) + + if (testStorage.hasBooleanIndexSupport) { + testCorrectQueries(suite, testStorage, { + testTitle: '$eq operator', + data: [ + { + id: 'zero', + nonPrimaryString: 'zero', + integer: 0, + number: 0, + boolean: false, + null: 'not-null' + }, + { + id: 'one', + nonPrimaryString: 'one', + integer: 1, + number: 1, + boolean: true, + null: null + }, + { + id: 'two', + nonPrimaryString: 'two', + integer: 2, + number: 2, + boolean: false, + null: 'not-null' + } + ], + schema: { + version: 0, + primaryKey: 'id', + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 100 + }, + nonPrimaryString: { + type: 'string' + }, + integer: { + type: 'integer' + }, + number: { + type: 'number' + }, + boolean: { + type: 'boolean' + }, + null: { + type: 'null' + } + }, + indexes: [ + // boolean indexing was broken on some storages + 'boolean' + ], + required: [ + 'id', + 'nonPrimaryString', + 'integer', + 'number', + 'boolean' + ] + }, + queries: [ + { + info: '$eq primary key', + query: { + selector: { + id: { + $eq: 'one' } + }, + sort: [{ id: 'asc' }] + }, + expectedResultDocIds: [ + 'one' ] - }); - - testCorrectQueries(suite, testStorage, { - testTitle: '$lt/$lte', - data: [ - human('aa', 10, 'alice'), - human('bb', 20, 'bob'), - /** - * One must have a longer id - * because we had many bugs around how padLeft - * works on custom indexes. - */ - human('cc-looong-id', 30, 'carol'), - human('dd', 40, 'dave'), - human('ee', 50, 'eve') - ], - schema: withIndexes(schemas.human, [ - ['age'], - ['age', 'firstName'], - ['firstName'], - ['passportId'] - ]), - queries: [ - { - info: 'normal $lt', - query: { - selector: { - age: { - $lt: 40 - } - }, - sort: [{ age: 'asc' }] - }, - selectorSatisfiedByIndex: true, - expectedResultDocIds: [ - 'aa', - 'bb', - 'cc-looong-id' - ] - }, - { - info: 'normal $lte', - query: { - selector: { - age: { - $lte: 40 - } - }, - sort: [{ age: 'asc' }] - }, - selectorSatisfiedByIndex: true, - expectedResultDocIds: [ - 'aa', - 'bb', - 'cc-looong-id', - 'dd' - ] - }, - { - info: 'sort by something that is not in the selector', - query: { - selector: { - age: { - $lt: 40 - } - }, - sort: [{ passportId: 'asc' }] - }, - selectorSatisfiedByIndex: false, - expectedResultDocIds: [ - 'aa', - 'bb', - 'cc-looong-id' - ] - }, - { - /** - * @link https://github.com/pubkey/rxdb/pull/4751 - */ - info: '$lt on primaryKey', - query: { - selector: { - passportId: { - $lt: 'bb' - } - }, - sort: [{ passportId: 'asc' }] - }, - expectedResultDocIds: [ - 'aa' - ] - }, - { - info: 'compare more then one field', - query: { - selector: { - age: { - $lt: 40 - }, - firstName: { - $lt: 'd' - } - } - }, - selectorSatisfiedByIndex: false, - expectedResultDocIds: [ - 'aa', - 'bb', - 'cc-looong-id' - ] + }, + { + info: '$eq non-primary string', + query: { + selector: { + nonPrimaryString: { + $eq: 'one' } + }, + sort: [{ id: 'asc' }] + }, + expectedResultDocIds: [ + 'one' ] - }); - testCorrectQueries(suite, testStorage, { - testTitle: 'nested properties', - data: [ - nestedHuman({ - passportId: 'aaa', - mainSkill: { - level: 6, - name: 'zzz' - } - }), - nestedHuman({ - passportId: 'bbb', - mainSkill: { - level: 4, - name: 'ttt' - } - }), - nestedHuman({ - passportId: 'ccc', - mainSkill: { - level: 3, - name: 'ccc' - } - }) - ], - schema: withIndexes(schemas.nestedHuman, [ - ['mainSkill.level'], - ['mainSkill.name'] - ]), - queries: [ - { - info: 'sort by nested mainSkill.name', - query: { - selector: { - }, - sort: [{ 'mainSkill.name': 'asc' }] - }, - expectedResultDocIds: [ - 'ccc', - 'bbb', - 'aaa' - ] + }, + { + info: '$eq integer', + query: { + selector: { + integer: { + $eq: 1 } + }, + sort: [{ id: 'asc' }] + }, + expectedResultDocIds: [ + 'one' ] - }); - testCorrectQueries(suite, testStorage, { - testTitle: '$or', - data: [ - simpleHumanV3({ - passportId: 'aaa', - oneOptional: 'A' - }), - simpleHumanV3({ - passportId: 'bbb', - oneOptional: 'B' - }), - simpleHumanV3({ - passportId: 'ccc' - }) - ], - schema: withIndexes(schemas.humanMinimal, [ - ]), - queries: [ - { - info: 'match A or B', - query: { - selector: { - $or: [ - { - passportId: 'aaa' - }, - { - passportId: 'bbb' - } - ] - }, - sort: [{ 'passportId': 'asc' }] - }, - expectedResultDocIds: [ - 'aaa', - 'bbb' - ] - }, - { - info: 'match with optional field', - query: { - selector: { - passportId: { - $eq: 'ccc' - }, - $or: [ - { - oneOptional: { - $ne: 'foobar1' - } - }, - { - oneOptional: { - $ne: 'foobar2' - } - } - ] - }, - sort: [{ 'passportId': 'asc' }] - }, - expectedResultDocIds: [ - 'ccc' - ] - }, - { - info: 'match non on non-existing optional field', - query: { - selector: { - passportId: { - $eq: 'foobar' - }, - $or: [ - { - oneOptional: { - $ne: 'foobar1' - } - }, - { - oneOptional: { - $ne: 'foobar2' - } - } - ] - }, - sort: [{ 'passportId': 'asc' }] - }, - expectedResultDocIds: [] + }, + { + info: '$eq number', + query: { + selector: { + number: { + $eq: 1 } + }, + sort: [{ id: 'asc' }] + }, + expectedResultDocIds: [ + 'one' ] - }); - testCorrectQueries(suite, testStorage, { - testTitle: '$in', - data: [ - human('aa', 10, 'alice'), - human('bb', 20, 'bob'), - human('cc', 30, 'carol'), - human('dd', 40, 'dave'), - human('ee', 50, 'eve') - ], - schema: schemas.human, - queries: [ - { - info: 'get first', - query: { - selector: { - firstName: { - $in: ['alice'] - }, - }, - sort: [{ passportId: 'asc' }] - }, - expectedResultDocIds: [ - 'aa' - ] - }, - { - info: 'get by multiple', - query: { - selector: { - firstName: { - $in: ['alice', 'bob'] - }, - }, - sort: [{ passportId: 'asc' }] - }, - expectedResultDocIds: [ - 'aa', - 'bb' - ] - }, - { - info: 'get none matching', - query: { - selector: { - firstName: { - $in: ['foobar', 'barfoo'] - }, - }, - sort: [{ passportId: 'asc' }] - }, - expectedResultDocIds: [] - }, - { - info: 'get by primary key', - query: { - selector: { - passportId: { - $in: ['aa', 'cc', 'ee'] - } - } - }, - expectedResultDocIds: ['aa', 'cc', 'ee'] + }, + { + info: '$eq boolean', + query: { + selector: { + boolean: { + $eq: true } + }, + sort: [{ id: 'asc' }] + }, + expectedResultDocIds: [ + 'one' ] - }); - testCorrectQueries(suite, testStorage, { - testTitle: '$elemMatch/$size', - data: [ - { - name: 'foo1', - skills: [ - { - name: 'bar1', - damage: 10 - }, - { - name: 'bar2', - damage: 5 - }, - ], - }, - { - name: 'foo2', - skills: [ - { - name: 'bar3', - damage: 10 - }, - { - name: 'bar4', - damage: 10 - }, - ], - }, - { - name: 'foo3', - skills: [ - { - name: 'bar5', - damage: 5 - }, - ], + }, + { + info: '$eq null', + query: { + selector: { + null: { + $eq: null } - ], - schema: schemas.heroArray, - queries: [ - { - info: '$elemMatch', - query: { - selector: { - skills: { - $elemMatch: { - damage: 5 - } - }, - }, - sort: [{ name: 'asc' }] - }, - selectorSatisfiedByIndex: false, - expectedResultDocIds: [ - 'foo1', - 'foo3' - ] - }, - { - info: '$elemMatch with other operator', - query: { - selector: { - name: { - $eq: 'foo3' - }, - skills: { - $elemMatch: { - damage: 5 - } - }, - }, - sort: [{ name: 'asc' }] - }, - selectorSatisfiedByIndex: false, - expectedResultDocIds: [ - 'foo3' - ] - }, - { - info: '$size', - query: { - selector: { - skills: { - $size: 1 - }, - }, - sort: [{ name: 'asc' }] - }, - selectorSatisfiedByIndex: false, - expectedResultDocIds: [ - 'foo3' - ] - }, + }, + sort: [{ id: 'asc' }] + }, + expectedResultDocIds: [ + 'one' ] - }); - - if (testStorage.hasBooleanIndexSupport) { - testCorrectQueries(suite, testStorage, { - testTitle: '$eq operator', - data: [ - { - id: 'zero', - nonPrimaryString: 'zero', - integer: 0, - number: 0, - boolean: false, - null: 'not-null' - }, - { - id: 'one', - nonPrimaryString: 'one', - integer: 1, - number: 1, - boolean: true, - null: null - }, - { - id: 'two', - nonPrimaryString: 'two', - integer: 2, - number: 2, - boolean: false, - null: 'not-null' - } - ], - schema: { - version: 0, - primaryKey: 'id', - type: 'object', - properties: { - id: { - type: 'string', - maxLength: 100 - }, - nonPrimaryString: { - type: 'string' - }, - integer: { - type: 'integer' - }, - number: { - type: 'number' - }, - boolean: { - type: 'boolean' - }, - null: { - type: 'null' - } - }, - indexes: [ - // boolean indexing was broken on some storages - 'boolean' - ], - required: [ - 'id', - 'nonPrimaryString', - 'integer', - 'number', - 'boolean' - ], - }, - queries: [ - { - info: '$eq primary key', - query: { - selector: { - id: { - $eq: 'one' - } - }, - sort: [{ id: 'asc' }] - }, - expectedResultDocIds: [ - 'one' - ] - }, - { - info: '$eq non-primary string', - query: { - selector: { - nonPrimaryString: { - $eq: 'one' - } - }, - sort: [{ id: 'asc' }] - }, - expectedResultDocIds: [ - 'one' - ] - }, - { - info: '$eq integer', - query: { - selector: { - integer: { - $eq: 1 - } - }, - sort: [{ id: 'asc' }] - }, - expectedResultDocIds: [ - 'one' - ] - }, - { - info: '$eq number', - query: { - selector: { - number: { - $eq: 1 - } - }, - sort: [{ id: 'asc' }] - }, - expectedResultDocIds: [ - 'one' - ] - }, - { - info: '$eq boolean', - query: { - selector: { - boolean: { - $eq: true - } - }, - sort: [{ id: 'asc' }] - }, - expectedResultDocIds: [ - 'one' - ] - }, - { - info: '$eq null', - query: { - selector: { - null: { - $eq: null - } - }, - sort: [{ id: 'asc' }] - }, - expectedResultDocIds: [ - 'one' - ] - } - ] - }); - } + } + ] + }) + } - /** - * @link https://github.com/pubkey/rxdb/issues/4571 - */ - testCorrectQueries(suite, testStorage, { - testTitle: '$eq operator with composite primary key', - data: [ - { - id: 'one', - key: 'one|1|1', - string: 'one', - number: 1, - integer: 1, - }, - { - id: 'two', - key: 'two|1|1', - string: 'two', - number: 1, - integer: 1, - }, - { - id: 'three', - key: 'one|2|1', - string: 'one', - number: 2, - integer: 1, - }, - ], - schema: { - version: 0, - indexes: ['string', ['number', 'integer']], - primaryKey: { - key: 'key', - fields: ['string', 'number', 'integer'], - separator: '|', - }, - type: 'object', - properties: { - key: { - maxLength: 100, - type: 'string', - }, - id: { - maxLength: 100, - type: 'string', - }, - string: { - maxLength: 50, - type: 'string', - }, - number: { - type: 'number', - minimum: 0, - maximum: 100, - multipleOf: 1, - }, - integer: { - type: 'integer', - minimum: 0, - maximum: 100, - multipleOf: 1, - }, - }, - required: ['id', 'key', 'string', 'number', 'integer'], + /** + * @link https://github.com/pubkey/rxdb/issues/4571 + */ + testCorrectQueries(suite, testStorage, { + testTitle: '$eq operator with composite primary key', + data: [ + { + id: 'one', + key: 'one|1|1', + string: 'one', + number: 1, + integer: 1 + }, + { + id: 'two', + key: 'two|1|1', + string: 'two', + number: 1, + integer: 1 + }, + { + id: 'three', + key: 'one|2|1', + string: 'one', + number: 2, + integer: 1 + } + ], + schema: { + version: 0, + indexes: ['string', ['number', 'integer']], + primaryKey: { + key: 'key', + fields: ['string', 'number', 'integer'], + separator: '|' + }, + type: 'object', + properties: { + key: { + maxLength: 100, + type: 'string' + }, + id: { + maxLength: 100, + type: 'string' + }, + string: { + maxLength: 50, + type: 'string' + }, + number: { + type: 'number', + minimum: 0, + maximum: 100, + multipleOf: 1 + }, + integer: { + type: 'integer', + minimum: 0, + maximum: 100, + multipleOf: 1 + } + }, + required: ['id', 'key', 'string', 'number', 'integer'] + }, + queries: [ + { + info: '$eq primary key 2', + query: { + selector: { + id: { + $eq: 'one' + } }, - queries: [ - { - info: '$eq primary key 2', - query: { - selector: { - id: { - $eq: 'one', - }, - }, - sort: [{ id: 'asc' }], - }, - expectedResultDocIds: ['one|1|1'], - }, - { - info: '$eq by key', - query: { - selector: { - key: { - $eq: 'one|1|1', - }, - }, - sort: [{ id: 'asc' }], - }, - expectedResultDocIds: ['one|1|1'], - }, - { - info: '$eq by composite key fields', - query: { - selector: { - $and: [ - { - string: { - $eq: 'one', - }, - }, - { - number: { - $eq: 1, - }, - integer: { - $eq: 1, - }, - }, - ], - }, - sort: [{ number: 'desc', integer: 'desc' }], - }, - expectedResultDocIds: ['one|1|1'], - }, - ], - }); - /** - * @link https://github.com/pubkey/rxdb/issues/5273 - */ - testCorrectQueries<{ - id: string; - hasHighlights: number; - lastOpenedAt: number; - exists: number; - }>(suite, testStorage, { - testTitle: 'issue: compound index has wrong range', - data: [ - { - id: '1', - exists: 1, - hasHighlights: 1, - lastOpenedAt: 1600000000000 - }, - { - id: '2', - exists: 1, - hasHighlights: 1, - lastOpenedAt: 1700000000000 - } - ], - schema: { - version: 0, - indexes: [ - ['exists', 'hasHighlights', 'lastOpenedAt'] - ], - primaryKey: 'id', - type: 'object', - properties: { - id: { - type: 'string', - maxLength: 1 - }, - hasHighlights: { - type: 'integer', - minimum: 0, - maximum: 1, - multipleOf: 1 - }, - lastOpenedAt: { - type: 'integer', - minimum: 0, - maximum: Number.MAX_SAFE_INTEGER, - multipleOf: 1, - }, - exists: { - type: 'integer', - minimum: 0, - maximum: 1, - multipleOf: 1 - }, - }, - required: ['id', 'hasHighlights', 'lastOpenedAt', 'exists'] + sort: [{ id: 'asc' }] + }, + expectedResultDocIds: ['one|1|1'] + }, + { + info: '$eq by key', + query: { + selector: { + key: { + $eq: 'one|1|1' + } }, - queries: [ - { - info: 'multiple operators', - query: { - selector: { - exists: 1, - lastOpenedAt: { - $gte: 1600000000000, - $lte: 1650000000000 - } - } - }, - selectorSatisfiedByIndex: false, - expectedResultDocIds: ['1'] - }, - { - info: 'multiple operators 2', - query: { - selector: { - exists: 1, - lastOpenedAt: { - $gte: 1600000000000 - } - } - }, - selectorSatisfiedByIndex: false, - expectedResultDocIds: ['1', '2'] - }, - { - info: 'all operators in index', - query: { - selector: { - exists: 1, - hasHighlights: 1, - lastOpenedAt: { - $gte: 1600000000000 - } - } - }, - selectorSatisfiedByIndex: true, - expectedResultDocIds: ['1', '2'] - } - ], - }); - testCorrectQueries(suite, testStorage, { - testTitle: '$type', - data: [ + sort: [{ id: 'asc' }] + }, + expectedResultDocIds: ['one|1|1'] + }, + { + info: '$eq by composite key fields', + query: { + selector: { + $and: [ { - foo: '1', - bar: 'test' + string: { + $eq: 'one' + } }, { - foo: '2', - bar: 2.0 + number: { + $eq: 1 + }, + integer: { + $eq: 1 + } } + ] + }, + sort: [{ number: 'desc', integer: 'desc' }] + }, + expectedResultDocIds: ['one|1|1'] + } + ] + }) + /** + * @link https://github.com/pubkey/rxdb/issues/5273 + */ + testCorrectQueries<{ + id: string + hasHighlights: number + lastOpenedAt: number + exists: number + }>(suite, testStorage, { + testTitle: 'issue: compound index has wrong range', + data: [ + { + id: '1', + exists: 1, + hasHighlights: 1, + lastOpenedAt: 1600000000000 + }, + { + id: '2', + exists: 1, + hasHighlights: 1, + lastOpenedAt: 1700000000000 + } + ], + schema: { + version: 0, + indexes: [ + ['exists', 'hasHighlights', 'lastOpenedAt'] + ], + primaryKey: 'id', + type: 'object', + properties: { + id: { + type: 'string', + maxLength: 1 + }, + hasHighlights: { + type: 'integer', + minimum: 0, + maximum: 1, + multipleOf: 1 + }, + lastOpenedAt: { + type: 'integer', + minimum: 0, + maximum: Number.MAX_SAFE_INTEGER, + multipleOf: 1 + }, + exists: { + type: 'integer', + minimum: 0, + maximum: 1, + multipleOf: 1 + } + }, + required: ['id', 'hasHighlights', 'lastOpenedAt', 'exists'] + }, + queries: [ + { + info: 'multiple operators', + query: { + selector: { + exists: 1, + lastOpenedAt: { + $gte: 1600000000000, + $lte: 1650000000000 + } + } + }, + selectorSatisfiedByIndex: false, + expectedResultDocIds: ['1'] + }, + { + info: 'multiple operators 2', + query: { + selector: { + exists: 1, + lastOpenedAt: { + $gte: 1600000000000 + } + } + }, + selectorSatisfiedByIndex: false, + expectedResultDocIds: ['1', '2'] + }, + { + info: 'all operators in index', + query: { + selector: { + exists: 1, + hasHighlights: 1, + lastOpenedAt: { + $gte: 1600000000000 + } + } + }, + selectorSatisfiedByIndex: true, + expectedResultDocIds: ['1', '2'] + } + ] + }) + testCorrectQueries(suite, testStorage, { + testTitle: '$type', + data: [ + { + foo: '1', + bar: 'test' + }, + { + foo: '2', + bar: 2.0 + } + ], + schema: { + version: 0, + primaryKey: 'foo', + type: 'object', + properties: { + foo: { + type: 'string', + maxLength: 100 + }, + bar: { + oneOf: [ + { + type: 'string' + }, + { + type: 'number' + } + ] + } + }, + required: ['foo', 'bar'] + }, + queries: [ + { + info: '$type string', + query: { + selector: { + bar: { + $type: 'string' + } + }, + sort: [{ foo: 'asc' }] + }, + expectedResultDocIds: [ + '1' + ] + }, + { + info: '$type number', + query: { + selector: { + bar: { + $type: 'number' + } + }, + sort: [{ foo: 'asc' }] + }, + expectedResultDocIds: [ + '2' + ] + } + ] + }) + testCorrectQueries<{ + _id: string + name: string + gender: string + age: number + }>(suite, testStorage, { + testTitle: 'issue: wrong results on complex index', + data: [ + { + _id: 'nogljngyvo', + name: 'cjbovwbzjx', + gender: 'f', + age: 18 + }, + { + _id: 'zmbznyggnu', + name: 'rpjljekeoy', + gender: 'm', + age: 3 + }, + { + _id: 'hauezldqea', + name: 'ckjndqrthh', + gender: 'f', + age: 20 + }, + { + _id: 'utarwoqkav', + name: 'thfubuvqwr', + gender: 'm', + age: 12 + } + ], + schema: { + primaryKey: '_id', + type: 'object', + version: 0, + properties: { + _id: { + type: 'string', + maxLength: 20 + }, + name: { + type: 'string', + maxLength: 20 + }, + gender: { + type: 'string', + enum: ['f', 'm', 'x'], + maxLength: 1 + }, + age: { + type: 'number', + minimum: 0, + maximum: 100, + multipleOf: 1 + } + }, + indexes: [ + [ + 'name', + 'gender', + 'age', + '_id' + ], + [ + 'gender', + 'age', + 'name', + '_id' + ], + [ + 'age', + 'name', + 'gender', + '_id' + ] + ] + }, + queries: [ + { + info: 'complex query on index', + query: { + selector: { + gender: { + $gt: 'x' + }, + name: { + $lt: 'hqybnsozrv' + } + }, + sort: [ + { + gender: 'asc' + }, + { + age: 'asc' + }, + { + _id: 'asc' + } ], - schema: { - version: 0, - primaryKey: 'foo', - type: 'object', - properties: { - foo: { - type: 'string', - maxLength: 100 - }, - bar: { - oneOf: [ - { - type: 'string' - }, - { - type: 'number' - } - ] - }, - }, - required: ['foo', 'bar'], + index: [ + 'name', + 'gender', + 'age', + '_id' + ] + }, + expectedResultDocIds: [] + }, + { + info: 'complex query on end of index', + query: { + selector: { + gender: { + $lt: 'x', + $lte: 'm' + } }, - queries: [ - { - info: '$type string', - query: { - selector: { - bar: { - $type: 'string' - } - }, - sort: [{ foo: 'asc' }] - }, - expectedResultDocIds: [ - '1' - ] - }, - { - info: '$type number', - query: { - selector: { - bar: { - $type: 'number' - } - }, - sort: [{ foo: 'asc' }] - }, - expectedResultDocIds: [ - '2' - ] - }, + sort: [ + { + age: 'asc' + }, + { + name: 'asc' + }, + { + _id: 'asc' + } + + ], + index: [ + 'gender', + 'age', + 'name', + '_id' + ] - }); - testCorrectQueries<{ - _id: string; - name: string; - gender: string; - age: number; - }>(suite, testStorage, { - testTitle: 'issue: wrong results on complex index', - data: [ - { - '_id': 'nogljngyvo', - 'name': 'cjbovwbzjx', - 'gender': 'f', - 'age': 18 - }, - { - '_id': 'zmbznyggnu', - 'name': 'rpjljekeoy', - 'gender': 'm', - 'age': 3 - }, - { - '_id': 'hauezldqea', - 'name': 'ckjndqrthh', - 'gender': 'f', - 'age': 20 - }, - { - '_id': 'utarwoqkav', - 'name': 'thfubuvqwr', - 'gender': 'm', - 'age': 12 - } + }, + expectedResultDocIds: ['zmbznyggnu', 'utarwoqkav', 'nogljngyvo', 'hauezldqea'] + }, + { + info: 'had wrong index string on upper bound', + query: { + selector: { + age: { + $gte: 4, + $lte: 20 + }, + gender: { + $lt: 'm' + } + + }, + sort: [ + { + name: 'asc' + }, + { + _id: 'asc' + } ], - schema: { - primaryKey: '_id', - type: 'object', - version: 0, - properties: { - _id: { - type: 'string', - maxLength: 20 - }, - name: { - type: 'string', - maxLength: 20 - }, - gender: { - type: 'string', - enum: ['f', 'm', 'x'], - maxLength: 1 - }, - age: { - type: 'number', - minimum: 0, - maximum: 100, - multipleOf: 1 - } - }, - indexes: [ - [ - 'name', - 'gender', - 'age', - '_id' - ], - [ - 'gender', - 'age', - 'name', - '_id' - ], - [ - 'age', - 'name', - 'gender', - '_id' - ] - ] + index: [ + 'age', + 'name', + 'gender', + '_id' + ] + }, + expectedResultDocIds: ['nogljngyvo', 'hauezldqea'] + }, + { + info: 'had wrong index string on upper bound for $eq', + query: { + selector: { + age: { + $lte: 12 + }, + gender: { + $lt: 'x', + $eq: 'm' + } }, - queries: [ - { - info: 'complex query on index', - query: { - 'selector': { - 'gender': { - '$gt': 'x' - }, - 'name': { - '$lt': 'hqybnsozrv' - } - }, - 'sort': [ - { - 'gender': 'asc' - }, - { - 'age': 'asc' - }, - { - '_id': 'asc' - } - ], - 'index': [ - 'name', - 'gender', - 'age', - '_id' - ] - }, - expectedResultDocIds: [] - }, - { - info: 'complex query on end of index', - query: { - 'selector': { - 'gender': { - '$lt': 'x', - '$lte': 'm' - }, - }, - 'sort': [ - { - 'age': 'asc' - }, - { - 'name': 'asc' - }, - { - '_id': 'asc' - } - - ], - 'index': [ - 'gender', - 'age', - 'name', - '_id' - - ] - }, - expectedResultDocIds: ['zmbznyggnu', 'utarwoqkav', 'nogljngyvo', 'hauezldqea'] - }, - { - info: 'had wrong index string on upper bound', - query: { - 'selector': { - 'age': { - '$gte': 4, - '$lte': 20 - }, - 'gender': { - '$lt': 'm' - }, - - }, - 'sort': [ - { - 'name': 'asc' - }, - { - '_id': 'asc' - } - ], - 'index': [ - 'age', - 'name', - 'gender', - '_id' - ] - }, - expectedResultDocIds: ['nogljngyvo', 'hauezldqea'] - }, - { - info: 'had wrong index string on upper bound for $eq', - query: { - 'selector': { - 'age': { - '$lte': 12 - }, - 'gender': { - '$lt': 'x', - '$eq': 'm' - }, - }, - 'sort': [ - { - '_id': 'asc' - } - ], - 'index': [ - 'gender', - 'age', - 'name', - '_id' - ] - }, - expectedResultDocIds: ['utarwoqkav', 'zmbznyggnu'] - }, + sort: [ + { + _id: 'asc' + } ], - }); + index: [ + 'gender', + 'age', + 'name', + '_id' + ] + }, + expectedResultDocIds: ['utarwoqkav', 'zmbznyggnu'] + } + ] }) -} \ No newline at end of file + }) +}